This commit is contained in:
Chris Segura 2019-08-02 15:20:21 -07:00
Родитель 095220c8db
Коммит 8459dc8f3e
118 изменённых файлов: 42471 добавлений и 2871 удалений

5
.gitignore поставляемый
Просмотреть файл

@ -8,7 +8,7 @@ coverage/
### Node template
package-lock.json
# Logs
### Logs
logs
*.log
npm-debug.log*
@ -31,3 +31,6 @@ jspm_packages/
.vscode-test/
*.vsix
### User template
resources/drizzle/

Просмотреть файл

@ -1,12 +0,0 @@
.vscode/
node_modules/
src/
test/
out/test/
**/*.js.map
.editorconfig
.gitignore
.vscodeignore
package-lock.json
tsconfig.json
tslint.json

Просмотреть файл

@ -8,6 +8,8 @@ out/**
!out/src/mscorlib.js
!out/src/Nethereum.Generators.DuoCode.js
!out/src/extension.js
!out/src/debugger.js
!out/src/web3ProviderResolver.js
src/**
test/**
.editorconfig

Просмотреть файл

@ -4,6 +4,11 @@ All notable changes to the "azureblockchain" extension will be documented in thi
## 0.1.7
- Added contract UI/interaction feature
- Added Solidity debugging feature
## 0.1.6
- Added telemetry reporting capabilities

39840
NOTICE.txt

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -100,4 +100,4 @@ contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additio
## Telemetry
VS Code collects usage data and sends it to Microsoft to help improve our products and services. Read our [privacy statement](https://go.microsoft.com/fwlink/?LinkID=528096&clcid=0x409) to learn more. If you dont wish to send usage data to Microsoft, you can set the `telemetry.enableTelemetry` setting to `false`. Learn more in our [FAQ](https://code.visualstudio.com/docs/supporting/faq#_how-to-disable-telemetry-reporting).
VS Code collects usage data and sends it to Microsoft to help improve our products and services. Read our [privacy statement](https://go.microsoft.com/fwlink/?LinkID=528096&clcid=0x409) to learn more. If you dont wish to send usage data to Microsoft, you can set the `telemetry.enableTelemetry` setting to `false`. Learn more in our [FAQ](https://code.visualstudio.com/docs/supporting/faq#_how-to-disable-telemetry-reporting).

16
drizzleUI/.babelrc Normal file
Просмотреть файл

@ -0,0 +1,16 @@
{
"presets": [
"@babel/preset-env",
"@babel/preset-react"
],
"plugins": [
"@babel/plugin-proposal-class-properties",
"@babel/plugin-proposal-object-rest-spread",
[
"@babel/plugin-transform-runtime",
{
"regenerator": true
}
]
]
}

13
drizzleUI/.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,13 @@
# EditorConfig is awesome: http://EditorConfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true
[**.*]
indent_style = space
indent_size = 2

87
drizzleUI/.eslintrc Normal file
Просмотреть файл

@ -0,0 +1,87 @@
{
"parser": "babel-eslint",
"rules": {
"indent": ["error", 2, { "SwitchCase": 1 }],
"no-param-reassign": "error",
"semi": 2,
"sort-imports": [
2,
{
"ignoreCase": true,
"memberSyntaxSortOrder": ["single", "multiple", "all", "none"]
}
],
"func-names": "off",
"max-len": 0,
"prefer-destructuring": 0,
"arrow-parens": 0,
"import/newline-after-import": 0,
"no-prototype-builtins": 0,
"lines-around-directive": 0,
"no-unsafe-negation": 0,
"import/first": 0,
"import/order": 0,
"import/no-unresolved": 0,
"import/no-webpack-loader-syntax": 0,
"prefer-spread": 0,
"no-underscore-dangle": 0,
"no-template-curly-in-string": 0,
"class-methods-use-this": 0,
"no-confusing-arrow": 0,
"no-mixed-operators": 0,
"no-undef-init": 0,
"no-plusplus": 0,
"linebreak-style": 0,
"import/prefer-default-export": 0,
"no-useless-escape": 0,
"no-loop-func": 0,
"import/no-extraneous-dependencies": 0,
"import/extensions": 0,
"no-lonely-if": 0,
"comma-dangle": 0,
"no-extra-boolean-cast": 0,
"no-multi-assign": 0,
"newline-per-chained-call": 0,
"block-spacing": 0,
"object-property-newline": 0,
"no-useless-rename": 0,
"no-restricted-syntax": 0,
"no-useless-return": 0,
"no-continue": 0,
"no-console": 0,
"function-paren-newline": 0,
"object-curly-newline": 0,
"arrow-body-style": 0,
"react/react-in-jsx-scope": 0,
"react/prop-types": 1,
"react/sort-comp": 2,
"no-await-in-loop": "off",
"react/jsx-no-bind": [
2,
{
"allowArrowFunctions": true,
"allowBind": false
}
]
},
"env": {
"es6": true,
"browser": true,
"jest/globals": true
},
"globals": {
"shallow": true,
"mount": true
},
"settings": {
"react": {
"version": "detect"
}
},
"extends": [
"airbnb/base",
"plugin:react/recommended",
"plugin:jest/recommended"
],
"plugins": ["react", "import", "jest"]
}

7
drizzleUI/.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,7 @@
/node_modules/
/.idea/
/build/
/dist/
package-lock.json
.DS_Store

54
drizzleUI/package.json Normal file
Просмотреть файл

@ -0,0 +1,54 @@
{
"name": "client",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "cross-env NODE_ENV=production webpack --mode production",
"build:dev": "cross-env NODE_ENV=development webpack-dev-server --open",
"build:ext": "cross-env NODE_ENV=production EXT_ENV=true webpack --mode production",
"build:ext:dev": "cross-env NODE_ENV=development EXT_ENV=true webpack --mode development",
"lint": "eslint ./src --ext .js",
"lint:fix": "npm run lint -- --fix"
},
"devDependencies": {
"@babel/core": "^7.5.5",
"@babel/plugin-proposal-class-properties": "^7.5.5",
"@babel/plugin-proposal-object-rest-spread": "^7.5.5",
"@babel/plugin-transform-runtime": "^7.5.5",
"@babel/preset-env": "^7.5.5",
"@babel/preset-react": "^7.0.0",
"@babel/runtime": "^7.5.5",
"babel-eslint": "^10.0.2",
"babel-loader": "^8.0.6",
"cross-env": "^5.2.0",
"css-loader": "^3.1.0",
"eslint": "^6.1.0",
"eslint-config-airbnb": "^17.1.1",
"eslint-loader": "^2.2.1",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jest": "^22.14.0",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.14.3",
"file-loader": "^4.1.0",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"less": "^3.9.0",
"less-loader": "^5.0.0",
"mini-css-extract-plugin": "^0.8.0",
"prop-types": "^15.7.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"style-loader": "^0.23.1",
"url-loader": "^2.1.0",
"webpack": "^4.38.0",
"webpack-cli": "^3.3.6",
"webpack-plugin-replace": "^1.2.0"
},
"dependencies": {
"@material-ui/core": "^4.2.1",
"@material-ui/icons": "^4.2.1",
"drizzle": "^1.4.0",
"drizzle-react": "^1.3.0",
"drizzle-react-components": "^1.4.0"
}
}

166
drizzleUI/src/App.js Normal file
Просмотреть файл

@ -0,0 +1,166 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Container } from '@material-ui/core';
import { DrizzleContext } from 'drizzle-react';
import { IPCService } from './ipcService';
import PropTypes from 'prop-types';
import React from 'react';
import { SingleContractView } from 'views';
import {
ContractSelector,
Message
} from 'components';
import {
createMuiTheme,
MuiThemeProvider
} from '@material-ui/core/styles';
import './app.less';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
updating: false,
contractInstances: [],
selectedContractId: undefined,
theme: createMuiTheme({
palette: {
type: 'light'
}
})
};
this.target = document.querySelector('body');
this.observer = new MutationObserver(this.updateTheme);
this.observer.observe(this.target, { attributes: true, childList: false });
}
componentDidMount() {
IPCService.on('contracts', this.updateContracts);
IPCService.postMessage('documentReady', true);
}
componentDidUpdate() {
const {
updating,
contractInstances,
selectedContractId
} = this.state;
if (
!updating
&& !selectedContractId
&& contractInstances.length !== 0
) {
const contractInstance = contractInstances[0];
this.setContract(contractInstance);
}
}
updateContracts = contractInstances => this.setState({
contractInstances: contractInstances.reverse()
});
onContractChange = (e) => {
const { value } = e.target;
const { contractInstances } = this.state;
this.setContract(contractInstances.find(instance => instance.id === value));
}
setContract = async contractInstance => {
this.setState({ updating: true });
const { drizzle } = this.props;
const { contract, provider } = contractInstance;
drizzle.deleteAllContracts();
if (provider) {
// TODO cannot connect to http (error with subscription)
if (!provider.host.toLowerCase().startsWith('ws://')) {
provider.host = `ws://${provider.host}`;
}
drizzle.web3.setProvider(provider.host);
}
const address = contractInstance.address;
const accounts = await drizzle.web3.eth.getAccounts();
const contractConfig = {
contractName: contract.contractName,
web3Contract: new drizzle.web3.eth.Contract(
contract.abi,
address,
{
from: accounts[0],
data: contract.bytecode,
networks: contract.networks,
isOnline: !!provider
}
)
};
drizzle.addContract(contractConfig);
this.setState({
selectedContractId: contractInstance.id,
updating: false
});
}
updateTheme = () => {
const type = Array
.from(this.target.classList)
.includes('vscode-dark') ? 'dark' : 'light';
if (this.state.theme.palette.type !== type) {
this.setState({
theme: createMuiTheme({ palette: { type } })
});
}
}
render() {
const { contractInstances, selectedContractId, theme } = this.state;
const { drizzle: { contractList }, initialized } = this.props;
if (!initialized) {
return <Container className='message container'>
<Message message='⚙️ Loading dapp...' />
</Container>;
}
if (!contractList || contractList.length === 0) {
return <Container className='message container'>
<Message message='⚠️ No contracts available' />
</Container>;
}
return <MuiThemeProvider theme={theme}>
<Container>
<ContractSelector
selectedContractId={selectedContractId}
contractInstances={contractInstances}
onChange={this.onContractChange}
/>
<SingleContractView />
</Container>
</MuiThemeProvider>;
}
}
const HOC = () => <DrizzleContext.Consumer>
{props => <App {...props} />}
</DrizzleContext.Consumer>;
export default HOC;
App.propTypes = {
drizzle: PropTypes.any,
drizzleState: PropTypes.any,
initialized: PropTypes.bool.isRequired,
};

50
drizzleUI/src/app.less Normal file
Просмотреть файл

@ -0,0 +1,50 @@
*,
*:before,
*:after {
box-sizing: border-box;
}
body,
html {
position: relative;
width: 100%;
height: 100%;
margin: 0;
font-family: sans-serif;
text-align: center;
}
.vscode-dark {
background-color: #292929;
.MuiChip-outlinedSecondary {
color: #fff;
border: 1px solid #fff;
}
}
.vscode-light {
background-color: #fdfdfd;
}
#root {
width: 100%;
height: 100%;
position: relative;
color: #d4d4d4;
.paper {
padding: 30px;
}
.message.container {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
}

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import PropTypes from 'prop-types';
import React from 'react';
import {
Paper,
Typography
} from '@material-ui/core';
class BaseViewComponent extends React.PureComponent {
render() {
const {
header,
children,
className
} = this.props;
return <Paper className={`paper ${className || ''}`}>
<Typography
variant='h5'
component='h3'
align='left'
>
{header}
</Typography>
{children}
</Paper>;
}
}
export default BaseViewComponent;
BaseViewComponent.propTypes = {
header: PropTypes.string.isRequired,
className: PropTypes.string,
children: PropTypes.any
};

Просмотреть файл

@ -0,0 +1,65 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { BaseViewComponent } from 'components';
import PropTypes from 'prop-types';
import React from 'react';
import {
Container,
InputLabel,
MenuItem,
Select
} from '@material-ui/core';
import './contractSelector.less';
class ContractSelector extends React.Component {
renderOption = (contractInstance, index) => {
const {
id,
updateDate
} = contractInstance;
const itemName = (new Date(updateDate)).toGMTString();
return <MenuItem
key={index}
value={id}
>
{itemName}
</MenuItem>;
}
render() {
const {
selectedContractId = '',
contractInstances = [],
onChange
} = this.props;
return <BaseViewComponent
className='selector component'
header='Select a contract version'
>
<Container className='container'>
<InputLabel htmlFor='contract-selector'>
Contract deployment date
</InputLabel>
<Select
id='contract-selector'
value={selectedContractId}
onChange={onChange}
>
{contractInstances.map(this.renderOption)}
</Select>
</Container>
</BaseViewComponent>;
}
}
export default ContractSelector;
ContractSelector.propTypes = {
selectedContractId: PropTypes.string,
contractInstances: PropTypes.array,
onChange: PropTypes.func.isRequired
};

Просмотреть файл

@ -0,0 +1,24 @@
.selector.component {
@media (min-width: 600px) {
margin: 15px 24px 0 24px;
}
@media (min-width: 960px) {
margin: 15px 32px 0 32px;
}
.container {
display: flex;
align-items: center;
padding: 12px;
color: white;
.MuiFormLabel-root {
margin-right: 15px;
}
.MuiSelect-select {
min-width: 180px;
}
}
}

Просмотреть файл

@ -0,0 +1,31 @@
import PropTypes from 'prop-types';
import React from 'react';
import { TextField } from '@material-ui/core';
class EventSection extends React.Component {
render() {
const { events } = this.props;
const eventsNotifications = events
.reverse()
.map(({ event }) => `${event} was called`)
.join('\n');
return <TextField
className='metadata text field'
disabled
multiline
fullWidth
rows={8}
id='contract-address'
label='event:'
value={eventsNotifications}
/>;
}
}
export default EventSection;
EventSection.propTypes = {
events: PropTypes.array.isRequired
};

Просмотреть файл

@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { newContextComponents } from 'drizzle-react-components';
import PropTypes from 'prop-types';
import React from 'react';
import {
Button,
Container,
InputLabel,
MenuItem,
Select
} from '@material-ui/core';
import { InputComponentMapping, UnsupportedInput } from './inputControlsCollection';
import './executionSection.less';
const { ContractForm } = newContextComponents;
class ExecutionSection extends React.Component {
constructor(props) {
super(props);
this.state = {
executedMethod: undefined
};
}
componentDidMount() {
this.setState({ executedMethod: this.props.actions[0] });
}
onChange = (e) => this.setState({
executedMethod: e.target.value
});
renderExecutionSection = ({ inputs, handleInputChange, handleSubmit }) => {
const containsUnsupportedInput = inputs.some(input => !InputComponentMapping[input.type]);
return <Container className='execution-section'>
<Container className='action'>
<InputLabel className='input-label'>Contract Action</InputLabel>
<Select
className='execution-method'
value={this.state.executedMethod}
onChange={this.onChange}
inputProps={{
id: 'action-method',
name: 'action'
}}
>
{this.props.actions.map((action, index) => {
return (
<MenuItem key={index} value={action}>{action}</MenuItem>
);
})}
</Select>
</Container>
<Container className='input-fields'>
{inputs.map((item, index) => {
const InputControl = InputComponentMapping[item.type] || UnsupportedInput;
return <InputControl
key={index}
item={item}
handleInputChange={handleInputChange}
/>;
})}
</Container>
<Button
className='execute-button'
variant='contained'
onClick={handleSubmit}
disabled={containsUnsupportedInput}
>
Execute
</Button>
</Container>;
}
render() {
return <ContractForm
key={this.state.executedMethod}
contract={this.props.contractName}
method={this.state.executedMethod}
drizzle={this.props.drizzle}
render={this.renderExecutionSection}
/>;
}
}
export default ExecutionSection;
ExecutionSection.propTypes = {
actions: PropTypes.any.isRequired,
contractName: PropTypes.string.isRequired,
drizzle: PropTypes.any.isRequired,
};

Просмотреть файл

@ -0,0 +1,48 @@
.execution-section {
&.MuiContainer-root {
padding: 0;
}
.action {
display: flex;
align-items: center;
padding: 0;
.input-label {
padding-left: 0;
flex: initial;
margin-right: 12px;
}
.execution-method {
flex: 1;
}
}
.input-fields {
padding: 0;
.input-field {
margin-top: 16px;
}
.MuiTextField-root {
width: 100%;
}
.boolean-input {
margin-top: 16px;
padding-left: 0px;
}
.unsupported-input {
margin-top: 16px;
padding-left: 0px;
}
}
.execute-button {
margin-top: 24px;
}
}

Просмотреть файл

@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import PropTypes from 'prop-types';
import React from 'react';
import {
Container,
InputLabel,
MenuItem,
Select
} from '@material-ui/core';
class BooleanInput extends React.Component {
constructor(props) {
super(props);
this.state = { value: false };
this.onChange = this.onChange.bind(this);
}
onChange(event) {
this.setState({ value: event.target.value });
this.props.handleInputChange(event);
}
render() {
return <Container className='boolean-input'>
<InputLabel shrink>
{this.props.item.name}
</InputLabel>
<Select
id={this.props.item.name}
name={this.props.item.name}
onChange={this.onChange}
value={this.state.value}
displayEmpty
>
<MenuItem key='false' value={false}>False</MenuItem>
<MenuItem key='true' value={true}>True</MenuItem>
</Select>
</Container>;
}
}
export default BooleanInput;
BooleanInput.propTypes = {
item: PropTypes.any.isRequired,
handleInputChange: PropTypes.any.isRequired,
};

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import BooleanInput from './BooleanInput';
import TextInput from './TextInput';
const InputComponentMapping = {
address: TextInput,
string: TextInput,
uint: TextInput,
bool: BooleanInput,
};
export default InputComponentMapping;

Просмотреть файл

@ -0,0 +1,38 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import PropTypes from 'prop-types';
import React from 'react';
import { TextField } from '@material-ui/core';
class TextInput extends React.Component {
constructor(props) {
super(props);
this.state = { value: '' };
this.onChange = this.onChange.bind(this);
}
onChange(event) {
this.setState({ value: event.target.value });
this.props.handleInputChange(event);
}
render() {
return <TextField
className='input-field'
id={this.props.item.name}
label={`${this.props.item.name} :`}
name={this.props.item.name}
onChange={this.onChange}
value={this.state.value}
/>;
}
}
export default TextInput;
TextInput.propTypes = {
item: PropTypes.any.isRequired,
handleInputChange: PropTypes.any.isRequired,
};

Просмотреть файл

@ -0,0 +1,18 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Container, FormHelperText, FormLabel } from '@material-ui/core';
class UnsupportedInput extends React.Component {
render() {
return <Container className='unsupported-input'>
<FormHelperText>{this.props.item.name}</FormHelperText>
<FormLabel >Unsupported input type</FormLabel>
</Container>;
}
}
export default UnsupportedInput;
UnsupportedInput.propTypes = {
item: PropTypes.any.isRequired,
};

Просмотреть файл

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import BooleanInput from './BooleanInput';
import InputComponentMapping from './InputComponentMapping';
import TextInput from './TextInput';
import UnsupportedInput from './UnsupportedInput';
export {
BooleanInput,
InputComponentMapping,
TextInput,
UnsupportedInput,
};

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import BaseViewComponent from './baseViewComponent/BaseViewComponent';
import ContractSelector from './contractSelector/ContractSelector';
import EventSection from './eventsSection/EventSection';
import ExecutionSection from './executionSection/ExecutionSection';
import Interaction from './interaction/Interaction';
import LabelWithIcon from './labelWithIcon/LabelWithIcon';
import Message from './message/Message';
import Metadata from './metadata/Metadata';
import StateSection from './stateSection/StateSection';
import TextFieldWithTooltip from './textFieldWithTooltip/TextFieldWithTooltip';
export {
BaseViewComponent,
ContractSelector,
EventSection,
ExecutionSection,
Interaction,
LabelWithIcon,
Message,
Metadata,
StateSection,
TextFieldWithTooltip
};

Просмотреть файл

@ -0,0 +1,117 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Container } from '@material-ui/core';
import { deepEqual } from 'helpers';
import { DrizzleContext } from 'drizzle-react';
import PropTypes from 'prop-types';
import React from 'react';
import {
BaseViewComponent,
EventSection,
ExecutionSection,
StateSection
} from 'components';
import './interaction.less';
const getExecutableMethods = (contract) => {
const isExecutableAbiMethod = (abiEl) => (abiEl.type === 'function' && abiEl.constant === false);
const executableAbiMethods = contract.abi.filter(isExecutableAbiMethod).map(el => el.name);
return Object.keys(contract.methods).filter(m => executableAbiMethods.indexOf(m) !== -1);
};
class Interaction extends React.Component {
constructor(props) {
super(props);
this.state = {
methods: undefined,
contractName: undefined,
subscription: undefined,
events: []
};
}
componentDidMount() {
this.componentDidUpdate();
}
componentDidUpdate() {
const contract = this.props.drizzle.contractList[0];
if (!contract) {
return;
}
const { contractName } = contract;
const methods = Object.keys(contract);
const state = {
...this.state,
methods,
contractName
};
if (!deepEqual(this.state, state)) {
if (!!this.state.subscription) {
this.state.subscription.unsubscribe();
}
const subscription = contract.events.allEvents()
.on('data', (event) => this.setState({ events: [...this.state.events, event] }));
this.setState({ ...state, subscription });
}
}
render() {
const { events } = this.state;
return <BaseViewComponent
header='Interaction'
className='interaction component'
>
<Container className='container'>
<Container className='input'>
<ExecutionSection
actions={getExecutableMethods(this.props.drizzle.contractList[0])}
contractName={this.props.drizzle.contractList[0].contractName}
drizzle={this.props.drizzle}
render={this.renderExecutionSection}
/>
</Container>
<Container className='output'>
<StateSection
contract={this.props.drizzle.contractList[0]}
/>
<EventSection
events={events}
/>
</Container>
</Container>
</BaseViewComponent>;
}
}
const HOC = () => <DrizzleContext.Consumer>
{props => <Interaction {...props} />}
</DrizzleContext.Consumer>;
export default HOC;
Interaction.propTypes = {
drizzle: PropTypes.shape({
contractList: PropTypes.arrayOf(PropTypes.shape({
contractName: PropTypes.string.isRequired,
methods: PropTypes.object.isRequired,
events: PropTypes.shape({
allEvents: PropTypes.func.isRequired
}).isRequired,
options: PropTypes.shape({
isOnline: PropTypes.bool.isRequired
}).isRequired
})).isRequired
}).isRequired,
drizzleState: PropTypes.any
};

Просмотреть файл

@ -0,0 +1,21 @@
.interaction.component {
.container {
display: flex;
padding: 0;
text-align: left;
.input {
flex: 1;
margin-left: 12px;
margin-right: 6px;
padding: 0;
}
.output {
flex: 1;
margin-right: 12px;
margin-left: 6px;
padding: 0;
}
}
}

Просмотреть файл

@ -0,0 +1,24 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Tooltip } from '@material-ui/core';
import './labelWithIcon.less';
class LabelWithIcon extends React.Component {
render() {
const { text, icon } = this.props;
return <Tooltip title='Copy' placement='top-end'>
<div className='label-with-icon'>
<span className='text'>{text}</span>
{icon}
</div>
</Tooltip>;
}
}
export default LabelWithIcon;
LabelWithIcon.propTypes = {
text: PropTypes.string.isRequired,
icon: PropTypes.node.isRequired
};

Просмотреть файл

@ -0,0 +1,12 @@
.label-with-icon {
display: flex;
align-items: center;
.text {
margin-right: 10px;
}
.MuiSvgIcon-root {
font-size: 1em;
}
}

Просмотреть файл

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Chip } from '@material-ui/core';
import PropTypes from 'prop-types';
import React from 'react';
class Message extends React.PureComponent {
render() {
const {
className = '',
message
} = this.props;
return <Chip
className={className}
color='secondary'
variant='outlined'
label={message}
/>;
}
}
export default Message;
Message.propTypes = {
className: PropTypes.string,
message: PropTypes.string.isRequired
};

Просмотреть файл

@ -0,0 +1,236 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { DrizzleContext } from 'drizzle-react';
import { FileCopy } from '@material-ui/icons';
import PropTypes from 'prop-types';
import React from 'react';
import {
BaseViewComponent,
LabelWithIcon,
Message,
TextFieldWithTooltip
} from 'components';
import {
Container,
TextField
} from '@material-ui/core';
import { copy, deepEqual } from 'helpers';
import './metadata.less';
class Metadata extends React.Component {
constructor(props) {
super(props);
this.state = {
abi: undefined,
address: undefined,
bytecode: undefined,
events: undefined,
network: undefined,
showBytecodeTooltip: false,
showABITooltip: false,
transaction: {
from: '',
},
};
}
componentDidMount() {
this.componentDidUpdate();
}
componentDidUpdate() {
const contract = this.props.drizzle.contractList[0];
if (!contract) {
return;
}
const abi = JSON.stringify(contract.abi);
const address = contract.address;
const bytecode = contract.options.data;
const events = this.getEvents();
const network = this.getNetwork();
const state = {
...this.state,
abi,
address,
bytecode,
events,
network
};
if (!deepEqual(this.state, state)) {
this.getTransactionInfo(state.network.transactionHash)
.then((transaction) => this.setState({ ...state, transaction }))
.catch(() => this.setState(state));
}
}
copyBytecodeToClipboard = () => {
copy(this.state.bytecode);
this.showBytecodeTooltip();
};
copyABIToClipboard = () => {
copy(this.state.abi);
this.showABITooltip();
}
getEvents = () => {
const { drizzle } = this.props;
const { events } = drizzle.contractList[0];
return Object.keys(events)
.find(key => key.match(/0x(\d|\S)+/g)) || '';
}
getNetwork = () => {
const { drizzle } = this.props;
const contract = drizzle.contractList[0];
const { networks } = contract.options;
const networkKey = Object.keys(networks)
.find((key) => networks[key].address === contract.address);
return Object.assign(networks[networkKey], { id: networkKey });
}
getTransactionInfo = (transactionHash) => {
const { drizzle } = this.props;
const { getTransaction } = drizzle.contractList[0].web3.eth;
return getTransaction(transactionHash);
}
showBytecodeTooltip = () => this.setState({ showBytecodeTooltip: true });
showABITooltip = () => this.setState({ showABITooltip: true });
hideBytecodeTooltip = () => this.setState({ showBytecodeTooltip: false });
hideABITooltip = () => this.setState({ showABITooltip: false });
render() {
const {
abi,
address,
events,
network,
bytecode,
transaction,
showABITooltip,
showBytecodeTooltip
} = this.state;
if (!abi || !bytecode) {
return <BaseViewComponent header='Metadata' className='metadata component'>
<Message message='Contract not loaded (ABI or bytecode not defined)' />
</BaseViewComponent>;
}
const byteCodeLabel = <LabelWithIcon text='Bytecode:' icon={<FileCopy />} />;
const ABILabel = <LabelWithIcon text='ABI:' icon={<FileCopy />} />;
return <BaseViewComponent
header='Metadata'
className='metadata component'
>
<Container className='list'>
<TextField
disabled
fullWidth
id='contract-address'
label='Contract Address:'
value={address}
className='text field'
/>
<TextField
disabled
fullWidth
id='creator-account'
label='Creator Account:'
value={transaction && transaction.from || ''}
className='text field'
/>
<TextField
disabled
fullWidth
id='deployed-location'
label='Deployed Location:'
value={network.id} // TODO: get network name by network id
className='text field'
/>
<TextFieldWithTooltip
open={showBytecodeTooltip}
title='Bytecode copied to clipboard!'
leaveDelay={900}
onClose={this.hideBytecodeTooltip}
disabled
fullWidth
id='bytecode'
label={byteCodeLabel}
onClick={this.copyBytecodeToClipboard}
value={bytecode}
className='text field'
/>
<TextFieldWithTooltip
open={showABITooltip}
title='ABI copied to clipboard!'
leaveDelay={900}
onClose={this.hideABITooltip}
disabled
fullWidth
id='abi'
label={ABILabel}
onClick={this.copyABIToClipboard}
value={abi}
className='text field'
/>
<TextField
disabled
fullWidth
id='tx-history'
label='TX History:'
value={network.transactionHash}
className='text field'
/>
<TextField
disabled
fullWidth
id='events'
label='Events:'
value={events}
className='text field'
/>
</Container>
</BaseViewComponent>;
}
}
const HOC = () => <DrizzleContext.Consumer>
{props => <Metadata {...props} />}
</DrizzleContext.Consumer>;
export default HOC;
Metadata.propTypes = {
drizzle: PropTypes.shape({
contractList: PropTypes.arrayOf(PropTypes.shape({
abi: PropTypes.array.isRequired,
address: PropTypes.string.isRequired,
events: PropTypes.object.isRequired,
options: PropTypes.shape({
data: PropTypes.string.isRequired,
networks: PropTypes.object.isRequired,
}).isRequired,
web3: PropTypes.shape({
eth: PropTypes.shape({
getTransaction: PropTypes.func.isRequired,
}).isRequired,
}).isRequired,
})).isRequired
}).isRequired
};

Просмотреть файл

@ -0,0 +1,12 @@
.metadata.component {
.list {
.text.field {
margin-bottom: 12px;
margin-left: 12px;
}
}
.MuiContainer-root {
padding-left: 0;
}
}

Просмотреть файл

@ -0,0 +1,61 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import ContractProperty from './contractProperty/ContractProperty';
import PropTypes from 'prop-types';
import React from 'react';
class StateSection extends React.Component {
constructor(props) {
super(props);
this.state = {
stateObjects: this.getStateObjects(),
};
}
getStateObjects = () => {
const abi = this.props.contract.abi;
const methods = this.props.contract.methods;
const constants = abi.filter(element => element.constant);
const stateObjects = [];
for (let i = 0; i < constants.length; i++) {
const stateMethod = methods[constants[i].name];
const stateObject = Object.assign(
constants[i],
{ method: stateMethod },
{ value: '' });
stateObjects.push(stateObject);
}
return stateObjects;
}
renderStateObjects() {
return this.state.stateObjects.map((stateObject, index) => {
return (
<ContractProperty
key={index}
property={stateObject}
/>
);
});
}
render() {
return <>
{this.renderStateObjects()}
</>;
}
}
export default StateSection;
StateSection.propTypes = {
contract: PropTypes.shape({
abi: PropTypes.array.isRequired,
methods: PropTypes.object.isRequired,
}).isRequired
};

Просмотреть файл

@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import PropTypes from 'prop-types';
import React from 'react';
import { TextField } from '@material-ui/core';
class ContractProperty extends React.Component {
constructor(props) {
super(props);
this.state = {
property: {
name: '',
value: '',
},
};
}
componentDidMount() {
this.getPropertyInfo();
}
componentDidUpdate() {
this.getPropertyInfo();
}
getPropertyInfo = () => {
this.props.property.method().call().then(result => {
const property = Object.assign({ name: this.props.property.name, value: result });
if (property.name !== this.state.property.name
|| property.value !== this.state.property.value) {
this.setState({ property });
}
});
}
render() {
const { property } = this.state;
return <TextField
className='metadata text field'
disabled
fullWidth
id='contract-address'
label={property.name}
value={property.value}
/>;
}
}
export default ContractProperty;
ContractProperty.propTypes = {
property: PropTypes.object.isRequired,
};

Просмотреть файл

@ -0,0 +1,63 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import PropTypes from 'prop-types';
import React from 'react';
import {
TextField,
Tooltip
} from '@material-ui/core';
class TextFieldWithTooltip extends React.PureComponent {
render() {
const {
id,
open,
title,
leaveDelay,
onClose,
onClick,
disabled = false,
className = '',
fullWidth = false,
label,
value,
placement = 'top'
} = this.props;
return <Tooltip
open={open}
title={title}
leaveDelay={leaveDelay}
placement={placement}
onClose={onClose}
>
<TextField
id={id}
disabled={disabled}
className={className}
fullWidth={fullWidth}
onClick={onClick}
label={label}
value={value}
/>
</Tooltip>;
}
}
export default TextFieldWithTooltip;
TextFieldWithTooltip.propTypes = {
open: PropTypes.bool.isRequired,
title: PropTypes.string.isRequired,
leaveDelay: PropTypes.number.isRequired,
onClose: PropTypes.func.isRequired,
onClick: PropTypes.func.isRequired,
className: PropTypes.string,
disabled: PropTypes.bool,
fullWidth: PropTypes.bool,
id: PropTypes.string,
label: PropTypes.string,
value: PropTypes.any,
placement: PropTypes.string
};

Просмотреть файл

@ -0,0 +1,23 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export const copy = (text) => {
const span = document.createElement('span');
span.textContent = text;
span.style.whiteSpace = 'pre';
document.body.appendChild(span);
const selection = window.getSelection();
const range = document.createRange();
selection.removeAllRanges();
range.selectNode(span);
selection.addRange(range);
document.execCommand('copy');
selection.removeAllRanges();
document.body.removeChild(span);
};

Просмотреть файл

@ -0,0 +1,40 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export const deepEqual = (first, second) => {
if (first === second) return true;
if (
first && second
&& typeof first === 'object' && typeof second === 'object'
) {
if (first.constructor !== second.constructor) return false;
if (Array.isArray(first) && Array.isArray(second)) {
if (first.length !== second.length) return false;
for (let i = 0; i < first.length; i++) {
if (!deepEqual(first[i], second[i])) return false;
}
return true;
}
const firstKeys = Object.keys(first);
const secondKeys = Object.keys(second);
if (firstKeys.length !== secondKeys.length) return false;
for (const key of firstKeys) {
if (!second.hasOwnProperty(key)) return false;
}
for (const key of firstKeys) {
if (!deepEqual(first[key], second[key])) return false;
}
return true;
}
return false;
};

Просмотреть файл

@ -0,0 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { copy } from './copy';
import { deepEqual } from './deepEqual';
export {
copy,
deepEqual
};

12
drizzleUI/src/index.html Normal file
Просмотреть файл

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Client</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

57
drizzleUI/src/index.js Normal file
Просмотреть файл

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import App from './app';
import { Drizzle } from 'drizzle';
import { DrizzleContext } from 'drizzle-react';
import { LocalStorage } from 'polyfills/localStorage';
import { newContextComponents } from 'drizzle-react-components';
import React from 'react';
import { render } from 'react-dom';
const storage = new LocalStorage();
window.ls = new Proxy(storage, {
set: (_, prop, value) => {
if (LocalStorage.prototype.hasOwnProperty(prop)) {
storage[prop] = value;
} else {
storage.setItem(prop, value);
}
return true;
},
get: (_, name) => {
if (LocalStorage.prototype.hasOwnProperty(name)) {
return storage[name];
}
if (storage.values.has(name)) {
return storage.getItem(name);
}
return undefined;
},
});
Drizzle.prototype.deleteAllContracts = function () {
Object.keys(this.contracts)
.forEach(contractName => this.deleteContract(contractName));
};
newContextComponents.ContractForm.prototype.handleInputChange = function (event) {
const value = event.target.type === 'checkbox'
? event.target.checked
: event.target.value;
this.setState({ [event.target.name]: value });
};
const options = { contracts: [] };
const drizzle = new Drizzle(options);
render(
<DrizzleContext.Provider drizzle={drizzle}>
<App />
</DrizzleContext.Provider>,
document.getElementById('root')
);

Просмотреть файл

@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
const getCommandList = commands => commands
.trim()
.split(' ')
.map(command => command.trim());
const execute = (listeners, command, data) => {
if (listeners.has(command)) {
const handlers = listeners.get(command);
handlers.forEach(handler => handler(data));
}
};
const addToListeners = (listeners, commands, handler) => {
const commandsList = getCommandList(commands);
commandsList.forEach(command => {
if (!listeners.has(command)) {
listeners.set(command, []);
}
const handlers = listeners.get(command);
handlers.push(handler);
});
};
const removeFromListeners = (listeners, commands, handler) => {
const commandsList = getCommandList(commands);
commandsList.forEach(command => {
if (listeners.has(command)) {
if (!handler) {
listeners.delete(command);
return;
}
let handlers = listeners.get(command);
handlers = handlers.filter(element => element !== handler);
listeners.set(command, handlers);
}
});
};
const removeAllListers = (listeners, command) => listeners.delete(command);
export const Helpers = {
execute,
addToListeners,
removeFromListeners,
removeAllListers
};

Просмотреть файл

@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Helpers } from './helpers';
// eslint-disable-next-line no-undef
const vscode = acquireVsCodeApi();
const listeners = new Map();
const onceListeners = new Map();
const on = (commands, handler) => Helpers.addToListeners(listeners, commands, handler);
const once = (commands, handler) => Helpers.addToListeners(onceListeners, commands, handler);
const off = (commands, handler) => {
Helpers.removeFromListeners(listeners, commands, handler);
Helpers.removeFromListeners(onceListeners, commands, handler);
};
const postMessage = (command, value) => vscode.postMessage({ command, value });
const messageReceived = (event) => {
const { command, value: data } = event.data;
Helpers.execute(listeners, command, data);
Helpers.execute(onceListeners, command, data);
Helpers.removeAllListers(onceListeners, command);
};
window.addEventListener('message', messageReceived, false);
export const IPCService = {
on,
once,
off,
postMessage
};

Просмотреть файл

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
export class LocalStorage {
constructor() {
this.values = new Map();
}
getItem(key) {
const stringKey = String(key);
if (this.values.has(stringKey)) {
return String(this.values.get(stringKey));
}
return null;
}
setItem(key, value) {
this.values.set(String(key), String(value));
}
removeItem(key) {
this.values.delete(String(key));
}
clear() {
this.values.clear();
}
key(i) {
return Array.from(this.values.keys())[i];
}
get length() {
return this.values.size;
}
}

Просмотреть файл

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import SingleContractView from './singleContractView/SingleContractView';
export {
SingleContractView
};

Просмотреть файл

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Container } from '@material-ui/core';
import { DrizzleContext } from 'drizzle-react';
import PropTypes from 'prop-types';
import React from 'react';
import {
Interaction,
Message,
Metadata,
} from 'components';
import './singleContractView.less';
class SingleContractView extends React.PureComponent {
render() {
const { drizzle } = this.props;
const contract = drizzle.contractList[0];
const { isOnline } = contract.options;
if (!contract) {
return <Message message='No contract'/>;
}
if (!isOnline) {
return <Message className='red-error' message='Connection error' />;
}
return <Container className='single contract view'>
<Interaction/>
<Metadata />
</Container>;
}
}
const HOC = () => <DrizzleContext.Consumer>
{ props => <SingleContractView {...props} /> }
</DrizzleContext.Consumer>;
export default HOC;
SingleContractView.propTypes = {
drizzle: PropTypes.any,
drizzleState: PropTypes.any
};

Просмотреть файл

@ -0,0 +1,18 @@
.single.contract.view {
& > * {
margin-top: 15px;
}
&:last-child {
margin-bottom: 15px;
}
}
.MuiChip-outlinedSecondary.red-error {
background: #f44336;
width: 94.7%;
color: #fff !important;
border-radius: 4px;
border: 1px solid #f44336 !important;
margin-top: 15px;
}

120
drizzleUI/webpack.config.js Normal file
Просмотреть файл

@ -0,0 +1,120 @@
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ReplacePlugin = require('webpack-plugin-replace');
const webpack = require('webpack');
module.exports = () => {
const ENV = process.env.NODE_ENV || 'development';
const EXT = !!process.env.EXT_ENV || false;
const PATH = EXT ? path.join(__dirname, '../resources/drizzle') : path.resolve(__dirname, 'dist');
const PUBLIC_PATH = EXT ? '{{root}}/resources/drizzle' : '/';
const config = {
target: 'web',
entry: {
app: './src/index.js'
},
output: {
path: PATH,
filename: '[name].js',
chunkFilename: '[name].chunk.js',
publicPath: PUBLIC_PATH
},
devServer: {
contentBase: PATH,
historyApiFallback: true
},
performance: {
hints: false
},
stats: {
colors: true,
chunks: false,
modules: false,
},
resolve: {
alias: {
components: path.join(__dirname, './src/components'),
helpers: path.join(__dirname, './src/helpers'),
polyfills: path.join(__dirname, './src/polyfills'),
views: path.join(__dirname, './src/views')
}
},
module: {
rules: [
{
test: [/\.js$/, /\.jsx$/],
loader: 'babel-loader',
exclude: /node_modules/,
},
{
test: /\.less$/,
use: ['style-loader', 'css-loader', 'less-loader']
},
{
test: /\.css$/,
loader: ['style-loader', 'css-loader']
},
{
test: /\.jpe?g$|\.gif$|\.png$|\.ttf$|\.eot$|\.svg$/,
use: 'file-loader?name=[name].[ext]?[hash]'
},
{
test: /\.woff(2)?(\?v=[0-9]\.[0-9]\.[0-9])?$/,
loader: 'url-loader?limit=10000&mimetype=application/fontwoff'
},
{
test: [/\.js$/, /\.jsx$/],
exclude: /node_modules/,
loader: 'eslint-loader'
}
]
},
plugins: [
new HtmlWebpackPlugin({
alwaysWriteToDisk: true,
template: './src/index.html',
inject: 'body',
hash: ENV !== 'development',
chunks: ['app'],
filename: 'index.html'
}),
new MiniCssExtractPlugin({
filename: '[name].css',
allChunks: true,
}),
new ReplacePlugin({
exclude: path.join(__dirname, 'src', 'index.js'),
include: true,
patterns: [{
regex: /localStorage/g,
value: 'ls'
}],
values: {
ls: 'ls'
}
}),
new webpack.optimize.AggressiveMergingPlugin(),
]
};
switch (ENV) {
case 'production':
config.devtool = false;
config.optimization = { minimize: true };
break;
case 'development':
config.watch = !EXT;
config.devtool = 'source-map';
break;
case 'test':
config.devtool = 'source-map';
delete config.entry;
break;
default:
break;
}
return config;
};

Просмотреть файл

@ -5,7 +5,7 @@
"publisher": "AzBlockchain",
"preview": true,
"icon": "images/blockchain-service-logo.png",
"version": "0.1.6",
"version": "0.1.7",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/vscode-azure-blockchain-ethereum"
@ -46,10 +46,12 @@
"onCommand:azureBlockchainService.generateEventPublishingWorkflows",
"onCommand:azureBlockchainService.generateDataPublishingWorkflows",
"onCommand:azureBlockchainService.connectConsortium",
"onCommand:drizzle.generateSmartContractUI",
"onCommand:azureBlockchainService.showSmartContractPage",
"onCommand:azureBlockchainService.copyRPCEndpointAddress",
"onCommand:azureBlockchainService.createConsortium",
"onCommand:azureBlockchainService.disconnectConsortium"
"onCommand:azureBlockchainService.disconnectConsortium",
"onCommand:extension.truffle.debugTransaction",
"onDebug"
],
"contributes": {
"commands": [
@ -118,8 +120,8 @@
"category": "Azure Blockchain"
},
{
"command": "drizzle.generateSmartContractUI",
"title": "Generate Smart Contract UI",
"command": "azureBlockchainService.showSmartContractPage",
"title": "Show Smart Contract Interaction Page",
"category": "Azure Blockchain"
},
{
@ -151,13 +153,26 @@
"command": "azureBlockchainService.getPrivateKey",
"title": "Retrieve private key",
"category": "Azure Blockchain"
},
{
"command": "extension.truffle.debugTransaction",
"title": "Debug Transaction",
"category": "Azure Blockchain"
}
],
"breakpoints": [
{
"language": "solidity"
},
{
"language": "sol"
}
],
"menus": {
"commandPalette": [
{
"when": "false",
"command": "drizzle.generateSmartContractUI"
"command": "azureBlockchainService.showSmartContractPage"
},
{
"when": "false",
@ -202,10 +217,6 @@
{
"when": "azureBlockchainService:isWorkspaceOpen",
"command": "truffle.deployContracts"
},
{
"when": "azureBlockchainService:isWorkspaceOpen && false",
"command": "drizzle.generateSmartContractUI"
}
],
"view/title": [
@ -266,8 +277,8 @@
"group": "8_buildContractGroup"
},
{
"when": "resourceLangId == json && false",
"command": "drizzle.generateSmartContractUI",
"when": "resourceLangId == solidity",
"command": "azureBlockchainService.showSmartContractPage",
"group": "8_buildContractGroup"
},
{
@ -318,13 +329,93 @@
"id": "AzureBlockchain",
"name": "Azure Blockchain"
}
],
"debug": [
{
"id": "InstructionView",
"name": "Instructions"
}
]
}
},
"debuggers": [
{
"type": "truffle",
"label": "Truffle Debugger",
"program": "./out/src/debugger.js",
"runtime": "node",
"configurationAttributes": {
"launch": {
"required": [],
"properties": {
"stopOnEntry": {
"type": "boolean",
"description": "Automatically stop after launch.",
"default": true
},
"trace": {
"type": "boolean",
"description": "Enable logging of the Debug Adapter Protocol.",
"default": true
},
"txHash": {
"type": "string",
"description": "Transaction hash to debug",
"default": "0x"
},
"files": {
"type": "string[]",
"description": "Array of file paths of solidity files to debug",
"default": []
},
"workingDirectory": {
"type": "string",
"description": "Directory of truffle box",
"default": "${workspaceFolder}"
},
"providerUrl": {
"type": "string",
"description": "URL of provider",
"default": "http://127.0.0.1:8545"
}
}
}
},
"initialConfigurations": [
{
"type": "truffle",
"request": "launch",
"name": "Debug Transaction with Truffle",
"stopOnEntry": false,
"txHash": "0x",
"files": [],
"workingDirectory": "${workspaceFolder}",
"providerUrl": "http://127.0.0.1:8545"
}
],
"configurationSnippets": [
{
"label": "Truffle Debugger: Launch",
"description": "Runs the Truffle debugger (truffle) and attaches to a TestRPC instance",
"body": {
"type": "truffle",
"request": "launch",
"name": "Debug Transaction with Truffle",
"stopOnEntry": false,
"txHash": "0x",
"files": [],
"workingDirectory": "^\"\\${workspaceFolder}/",
"providerUrl": "http://127.0.0.1:8545"
}
}
],
"variables": {}
}
]
},
"scripts": {
"package": "npx vsce package",
"publish": "npx vsce publish",
"vscode:prepublish": "npm run webpack:prod",
"vscode:prepublish": "npm run preinstall && npm i && npm run webpack:prod && cd ./drizzleUI && npm i && npm run build:ext",
"compile": "tsc -p ./",
"webpack:prod": "webpack --mode production",
"watch": "tsc -watch -p ./",
@ -332,42 +423,48 @@
"tslint": "tslint -t verbose src/**/*.ts test/**/*.ts",
"tslint:fix": "npm run tslint -- --fix",
"version": "tsc -v",
"test": "npm run compile && node ./node_modules/vscode/bin/test"
"test": "npm run compile && node ./node_modules/vscode/bin/test",
"preinstall": "npm run clean:node_modules",
"clean:node_modules": "npx rimraf -- node_modules"
},
"devDependencies": {
"@machinomy/types-ethereumjs-wallet": "0.0.12",
"@machinomy/types-ethereumjs-wallet": "^0.0.12",
"@types/astring": "^1.3.0",
"@types/estree": "^0.0.39",
"@types/fs-extra": "^7.0.0",
"@types/glob": "^7.1.1",
"@types/hdkey": "^0.7.0",
"@types/istanbul": "^0.4.30",
"@types/mocha": "^5.2.7",
"@types/node": "^10.12.21",
"@types/request": "^2.48.1",
"@types/request-promise": "^4.1.44",
"@types/semver": "^6.0.0",
"@types/uuid": "^3.4.4",
"@types/glob": "^7.1.1",
"@types/rewire": "^2.5.28",
"@types/semver": "^6.0.0",
"@types/sinon": "^7.0.11",
"@types/uuid": "^3.4.4",
"copy-webpack-plugin": "^5.0.3",
"decache": "^4.5.1",
"sinon": "^7.3.2",
"husky": "^2.4.0",
"glob": "^7.1.4",
"husky": "^2.4.0",
"istanbul": "^0.4.5",
"lint-staged": "^8.2.0",
"remap-istanbul": "^0.13.0",
"rewire": "^4.0.1",
"sinon": "^7.3.2",
"truffle": "^5.0.14",
"ts-loader": "^6.0.2",
"tslint": "^5.18.0",
"tslint-microsoft-contrib": "^6.2.0",
"typescript": "^3.3.1",
"vscode": "^1.1.33",
"webpack": "^4.30.0",
"webpack-cli": "^3.3.1"
"vscode-debugadapter": "^1.33.0",
"vscode-debugprotocol": "^1.33.0",
"webpack": "^4.35.3",
"webpack-cli": "^3.3.6"
},
"dependencies": {
"abi-decoder": "^2.0.1",
"acorn": "^6.1.1",
"acorn-walk": "^6.1.1",
"astring": "^1.4.0",
@ -383,7 +480,14 @@
"semver": "^6.0.0",
"uuid": "^3.3.2",
"vscode-azureextensionui": "^0.25.1",
"vscode-extension-telemetry": "^0.1.2"
"vscode-extension-telemetry": "^0.1.1",
"truffle-debug-utils": "1.0.18",
"truffle-debugger": "5.0.19",
"truffle-decode-utils": "1.0.14",
"truffle-decoder": "3.0.6",
"truffle-contract": "4.0.24",
"truffle-interface-adapter": "0.2.0",
"truffle-provider": "0.1.11"
},
"extensionDependencies": [
"JuanBlanco.solidity",

Просмотреть файл

@ -1,31 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { window } from 'vscode';
import { Constants } from './Constants';
import { vscodeEnvironment } from './helpers';
import { Telemetry } from './TelemetryClient';
import { ConsortiumView } from './ViewItems';
export namespace AzureBlockchain {
export async function copyRPCEndpointAddress(consortiumNode: ConsortiumView): Promise<void> {
Telemetry.sendEvent('AzureBlockchain.copyRPCEndpointAddress.commandStarted');
const rpcEndpointAddress = await consortiumNode.getRPCAddress();
Telemetry.sendEvent(
'AzureBlockchain.copyRPCEndpointAddress.getRPCAddress',
{ data: Telemetry.obfuscate(rpcEndpointAddress) },
);
await AzureBlockchain.addDataInClipboard(Constants.rpcEndpointAddress, rpcEndpointAddress);
}
export async function addDataInClipboard(typeOfData: string, data?: string | null) {
if (data) {
await vscodeEnvironment.writeToClipboard(data);
window.showInformationMessage(typeOfData + Constants.dataCopied);
}
}
export async function viewGasEstimates(): Promise<void> {
window.showInformationMessage('"View Gas Estimates with Remix" command does not implemented yet.');
}
}

Просмотреть файл

@ -88,6 +88,12 @@ export class Constants {
};
public static webViewPages = {
contractUI: {
path: '',
showOnStartup: 'showOnStartupContractUI',
title: 'Smart Contract UI',
viewType: 'contractUIPage',
},
requirements: {
path: '',
showOnStartup: 'showOnStartupRequirementsPage',
@ -299,12 +305,10 @@ export class Constants {
},
};
public static nodeModulesPath = '';
public static dataCopied = ' copied to clipboard';
public static rpcEndpointAddress = 'RPCEndpointAddress';
public static rpcGanacheMethod = 'net_listening';
public static rpcMethods = {
netListening: 'net_listening',
netVersion: 'net_version',
};
public static ganacheCommandStrings = {
cannotStartServer: 'Cannot start ganache server',
@ -335,6 +339,7 @@ export class Constants {
BuildContractsBeforeGenerating: 'Please build contracts before generating',
BuildContractsDirIsEmpty: Constants.getMessageContractsBuildDirectoryIsEmpty,
BuildContractsDirIsNotExist: Constants.getMessageContractsBuildDirectoryIsNotExist,
CompiledContractIsMissing: 'Compiled contract is missing for solidity file.',
DirectoryIsNotEmpty: 'Directory is not empty. Open another one?',
GetMessageChildAlreadyConnected: Constants.getMessageChildAlreadyConnected,
GitIsNotInstalled: 'Git is not installed',
@ -344,6 +349,7 @@ export class Constants {
LoadConsortiumTreeFailed: 'Load consortium tree has failed.',
MnemonicFileHaveNoText: 'Mnemonic file have no text',
NetworkAlreadyExist: Constants.getMessageNetworkAlreadyExist,
NetworkNotFound: Constants.getMessageNetworkNotFound,
NewProjectCreationFailed: 'Command createProject has failed.',
NoSubscriptionFound: 'No subscription found.',
NoSubscriptionFoundClick: 'No subscription found, click an Azure account ' +
@ -362,6 +368,8 @@ export class Constants {
cancelButton: 'Cancel',
consortiumDoesNotHaveMemberWithUrl: 'Consortium does not have member with url',
consortiumNameValidating: 'Consortium name validating...',
contractNotDeployed: 'Contract not deployed yet.',
deployButton: 'Deploy',
detailsButton: 'Details',
generatedLogicApp: 'Generated the logic app!',
invalidRequiredVersion: 'Required app is not installed or has an old version.',
@ -370,6 +378,7 @@ export class Constants {
newProjectCreationStarted: 'New project creation is started',
openButton: 'Open',
privateKeyWasCopiedToClipboard: 'Private key was copied to clipboard',
rpcEndpointCopiedToClipboard: 'RPCEndpointAddress copied to clipboard',
seeDetailsRequirementsPage: 'Please see details on the Requirements Page',
};
@ -404,9 +413,9 @@ export class Constants {
public static initialize(context: ExtensionContext) {
this.extensionContext = context;
this.temporaryDirectory = context.storagePath ? context.storagePath : os.tmpdir();
this.webViewPages.contractUI.path = context.asAbsolutePath(path.join('resources', 'drizzle', 'index.html'));
this.webViewPages.welcome.path = context.asAbsolutePath(path.join('resources', 'welcome', 'index.html'));
this.webViewPages.requirements.path = context.asAbsolutePath(path.join('resources', 'welcome', 'prereqs.html'));
this.nodeModulesPath = context.asAbsolutePath('node_modules');
this.icons.blockchainService = {
dark: context.asAbsolutePath(path.join('resources/dark', 'blockchainService.svg')),
@ -452,6 +461,10 @@ export class Constants {
return `Network with name "${networkName}" already existed in truffle-config.js`;
}
private static getMessageNetworkNotFound(networkName: string): string {
return `Network with name "${networkName}" not found in truffle-config.js`;
}
private static getMessageVariableShouldBeDefined(variable: string): string {
return `${variable} should be defined`;
}

Просмотреть файл

@ -1,5 +1,6 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { ChildProcess, spawn } from 'child_process';
import { OutputChannel, window } from 'vscode';
import { Constants, RequiredApps } from '../Constants';

Просмотреть файл

@ -1,10 +1,15 @@
import * as rp from 'request-promise';
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { Constants } from '../Constants';
import { HttpService } from '../services';
import { Telemetry } from '../TelemetryClient';
export async function isGanacheServer(port: number | string): Promise<boolean> {
try {
const response = await sendRPCRequest(port, Constants.rpcGanacheMethod);
const response = await HttpService.sendRPCRequest(
`http://${Constants.localhost}:${port}`,
Constants.rpcMethods.netListening);
return response && !!response.result || false;
} catch (error) {
Telemetry.sendException(error);
@ -28,21 +33,3 @@ export async function waitGanacheStarted(port: number | string, maxRetries: numb
};
await retry(0);
}
export async function sendRPCRequest(
port: number | string,
methodName: string,
): Promise<{ result?: any } | undefined> {
return rp.post(
`http://${Constants.localhost}:${port}`,
{
body: {
jsonrpc: '2.0',
method: methodName,
params: [],
},
json: true,
})
.then((result) => result)
.catch(() => undefined);
}

Просмотреть файл

@ -1,20 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import * as path from 'path';
import * as vscode from 'vscode';
import { vscodeEnvironment } from './helpers';
export class UiServer {
public static launchWebview() {
const workspacePath = vscode.workspace.workspaceFolders ? vscode.workspace.workspaceFolders[0].uri.fsPath : '';
const htmlPath = path.join(workspacePath, 'drizzle', 'index.html');
vscodeEnvironment.openExternal(vscode.Uri.parse(`file://${htmlPath}`));
}
public static async startServer(): Promise<void> {
this.launchWebview();
}
}

Просмотреть файл

@ -1,10 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
import { UiServer } from '../UiServer';
import { commands, ExtensionContext, Uri, window } from 'vscode';
import { Constants } from '../Constants';
import { CancellationEvent } from '../Models';
import { ContractUI } from '../pages/ContractUI';
import { ContractDB, ContractService } from '../services';
import { Telemetry } from '../TelemetryClient';
export namespace ContractCommands {
export async function generateSmartContractUI(): Promise<void> {
return UiServer.startServer();
export async function showSmartContractPage(context: ExtensionContext, uri: Uri): Promise<void> {
Telemetry.sendEvent('ContractCommands.showSmartContract.commandStarted');
const contractName = ContractService.getContractNameBySolidityFile(uri.fsPath);
let contractHistory = await ContractDB.getContractInstances(contractName);
// First attempt
if (!contractHistory.length) {
Telemetry.sendEvent('ContractCommands.showSmartContract.needUserAction');
const result = await window.showWarningMessage(
Constants.informationMessage.contractNotDeployed,
Constants.informationMessage.deployButton,
Constants.informationMessage.cancelButton,
);
if (result === Constants.informationMessage.deployButton) {
Telemetry.sendEvent('ContractCommands.showSmartContract.deployContracts');
await commands.executeCommand('truffle.deployContracts');
} else {
Telemetry.sendEvent('ContractCommands.showSmartContract.userCancellation');
throw new CancellationEvent();
}
contractHistory = await ContractDB.getContractInstances(contractName);
}
// Second attempt after deploy
if (!contractHistory.length) {
const error = new Error(Constants.errorMessageStrings.CompiledContractIsMissing);
Telemetry.sendException(error);
throw error;
}
const contractPage = new ContractUI(context, contractName);
await contractPage.show();
Telemetry.sendEvent('ContractCommands.showSmartContract.commandFinished');
}
}

Просмотреть файл

@ -0,0 +1,94 @@
import {
debug,
DebugConfiguration,
QuickPickItem,
window,
workspace,
} from 'vscode';
import * as path from 'path';
import { DEBUG_TYPE } from '../debugAdapter/constants/debugAdapter';
import { DebugNetwork } from '../debugAdapter/debugNetwork';
import { TransactionProvider } from '../debugAdapter/transaction/transactionProvider';
import { Web3Wrapper } from '../debugAdapter/web3Wrapper';
export async function startSolidityDebugger() {
const workingDirectory = getWorkingDirectory();
if (!workingDirectory) {
return;
}
const debugNetwork = new DebugNetwork(workingDirectory);
await debugNetwork.load();
const contractBuildDir = debugNetwork.getTruffleConfiguration()!.contracts_build_directory;
const debugNetworkOptions = debugNetwork.getNetwork()!.options;
const web3 = new Web3Wrapper(debugNetworkOptions);
const providerUrl = web3.getProviderUrl();
if (debugNetwork.isLocalNetwork()) {
// if local network then provide last transactions to choose
const transactionProvider = new TransactionProvider(web3, contractBuildDir);
const quickPickItems = await getQuickPickItems(transactionProvider);
const quickPick = window.createQuickPick();
quickPick.placeholder = 'Enter the transaction hash to debug';
quickPick.ignoreFocusOut = true;
const onTransactionSelected = (selection: QuickPickItem[]) => {
if (selection[0]) {
const txHash = selection[0].label;
const config = generateDebugAdapterConfig(txHash, workingDirectory, providerUrl);
debug.startDebugging(undefined, config);
}
quickPick.hide();
};
quickPick.items = quickPickItems;
quickPick.onDidChangeSelection(onTransactionSelected);
quickPick.onDidHide(() => quickPick.dispose());
quickPick.show();
} else {
// if remote network then require txHash
const placeHolder = 'Type the transaction hash you want to debug (0x...)';
const txHash = await window.showInputBox({ placeHolder });
if (txHash) {
const config = generateDebugAdapterConfig(txHash, workingDirectory, providerUrl);
debug.startDebugging(undefined, config);
}
}
}
async function getQuickPickItems(txProvider: TransactionProvider) {
const txHashes = await txProvider.getLastTransactionHashes();
const txInfos = await txProvider.getTransactionsInfo(txHashes);
return txInfos.map((txInfo) => {
const description = generateDescription(txInfo.contractName, txInfo.methodName);
return { alwaysShow: true, label: txInfo.hash, description } as QuickPickItem;
});
}
function getWorkingDirectory() {
if (typeof workspace.workspaceFolders === 'undefined' || workspace.workspaceFolders.length === 0) {
return '';
}
return workspace.workspaceFolders[0].uri.fsPath;
}
function generateDebugAdapterConfig(txHash: string, workingDirectory: string, providerUrl: string)
: DebugConfiguration {
return {
files: [],
name: 'Debug Transactions',
providerUrl,
request: 'launch',
txHash,
type: DEBUG_TYPE,
workingDirectory,
} as DebugConfiguration;
}
// Migration.json, setComplete => Migration.setComplete()
function generateDescription(contractName?: string, methodName?: string) {
const contractNameWithoutExt = path.basename(contractName || '', '.json');
return `${contractNameWithoutExt}.${methodName}()`;
}

Просмотреть файл

@ -27,8 +27,10 @@ import {
MainNetworkConsortium,
} from '../Models';
import { Output } from '../Output';
import { ContractDB } from '../services';
import { Telemetry } from '../TelemetryClient';
import { ConsortiumTreeManager } from '../treeService/ConsortiumTreeManager';
import { ConsortiumView } from '../ViewItems';
import { ConsortiumCommands } from './ConsortiumCommands';
interface IDeployDestination {
@ -93,22 +95,27 @@ export namespace TruffleCommands {
placeHolder: Constants.placeholders.selectDeployDestination,
},
);
Telemetry.sendEvent('TruffleCommands.deployContracts.selectedDestination',
{
cid: Telemetry.obfuscate((command.consortiumId || '').toString()),
url: Telemetry.obfuscate((command.description || '').toString()),
});
// this code should be below showQuickPick because it takes time and it affects on responsiveness
if (!await required.checkAppsSilent(RequiredApps.truffle)) {
Telemetry.sendEvent('TruffleCommands.deployContracts.installTruffle');
await required.installTruffle(required.Scope.locally);
}
if (await required.isHdWalletProviderRequired()
&& !(await required.checkHdWalletProviderVersion())) {
Telemetry.sendEvent('TruffleCommands.deployContracts.installTruffleHdWalletProvider');
await required.installTruffleHdWalletProvider();
}
await command.cmd();
Telemetry.sendEvent('TruffleCommands.deployContracts.commandFinished');
}
@ -128,15 +135,15 @@ export namespace TruffleCommands {
Telemetry.sendEvent('TruffleCommands.writeBytecodeToBuffer.commandFinished');
}
export async function acquireCompiledContractUri(uri: Uri): Promise<Uri> {
if (path.extname(uri.fsPath) === Constants.contractExtension.json) {
Telemetry.sendEvent('TruffleCommands.acquireCompiledContractUri.jsonExtension');
return uri;
} else {
const error = new Error(Constants.errorMessageStrings.InvalidContract);
Telemetry.sendException(error);
throw error;
}
export async function writeRPCEndpointAddressToBuffer(consortiumNode: ConsortiumView): Promise<void> {
Telemetry.sendEvent('TruffleCommands.writeRPCEndpointAddressToBuffer.commandStarted');
const rpcEndpointAddress = await consortiumNode.getRPCAddress();
Telemetry.sendEvent('TruffleCommands.writeRPCEndpointAddressToBuffer.getRPCAddress',
{ data: Telemetry.obfuscate(rpcEndpointAddress) },
);
await vscodeEnvironment.writeToClipboard(rpcEndpointAddress);
window.showInformationMessage(Constants.informationMessage.rpcEndpointCopiedToClipboard);
}
export async function getPrivateKeyFromMnemonic(): Promise<void> {
@ -312,6 +319,8 @@ async function deployToNetwork(networkName: string, truffleConfigPath: string):
'npx',
RequiredApps.truffle, 'migrate', '--reset', '--network', networkName,
);
await ContractDB.updateContracts();
});
}
@ -336,8 +345,19 @@ async function deployToMainNetwork(networkName: string, truffleConfigPath: strin
await deployToNetwork(networkName, truffleConfigPath);
}
async function acquireCompiledContractUri(uri: Uri): Promise<Uri> {
if (path.extname(uri.fsPath) !== Constants.contractExtension.json) {
const error = new Error(Constants.errorMessageStrings.InvalidContract);
Telemetry.sendException(error);
throw error;
}
Telemetry.sendEvent('TruffleCommands.acquireCompiledContractUri.jsonExtension');
return uri;
}
async function readCompiledContract(uri: Uri): Promise<any> {
const contractUri = await TruffleCommands.acquireCompiledContractUri(uri);
const contractUri = await acquireCompiledContractUri(uri);
const data = fs.readFileSync(contractUri.fsPath, null);
return JSON.parse(data.toString());

Просмотреть файл

@ -0,0 +1,56 @@
import * as cp from 'child_process';
import * as os from 'os';
// The same implementation as in helpers/truffleConfig.ts
// The difference is that all code which uses 'vscode' module is removed.
// TODO: think how to reuse code
export interface ICommandResult {
code: number;
cmdOutput: string;
cmdOutputIncludingStderr: string;
}
export async function executeCommand(
workingDirectory: string | undefined,
commands: string,
...args: string[]
): Promise<string> {
const result: ICommandResult = await tryExecuteCommand(workingDirectory, commands, ...args);
if (result.code !== 0) {
throw new Error('Error while executin command: ' + commands.concat(' ', ...args.join(' ')));
}
return result.cmdOutput;
}
async function tryExecuteCommand(workingDirectory: string | undefined, commands: string, ...args: string[])
: Promise<ICommandResult> {
return new Promise((resolve: (res: any) => void, reject: (error: Error) => void): void => {
let cmdOutput: string = '';
let cmdOutputIncludingStderr: string = '';
const options: cp.SpawnOptions = { cwd: workingDirectory || os.tmpdir(), shell: true };
const childProcess: cp.ChildProcess = cp.spawn(commands, args, options);
childProcess.stdout.on('data', (data: string | Buffer) => {
data = data.toString();
cmdOutput = cmdOutput.concat(data);
cmdOutputIncludingStderr = cmdOutputIncludingStderr.concat(data);
});
childProcess.stderr.on('data', (data: string | Buffer) => {
data = data.toString();
cmdOutputIncludingStderr = cmdOutputIncludingStderr.concat(data);
});
childProcess.on('error', reject);
childProcess.on('close', (code: number) => {
resolve({
cmdOutput,
cmdOutputIncludingStderr,
code,
});
});
});
}

Просмотреть файл

@ -0,0 +1,39 @@
import {
DebugAdapterDescriptor,
DebugAdapterDescriptorFactory,
DebugAdapterExecutable,
DebugAdapterServer,
DebugSession,
ProviderResult,
} from 'vscode';
import * as Net from 'net';
import { SolidityDebugSession } from '../debugSession';
export default class TruffleDebugAdapterDescriptorFactory implements DebugAdapterDescriptorFactory {
private _server?: Net.Server;
public createDebugAdapterDescriptor(
_session: DebugSession,
_executable: DebugAdapterExecutable | undefined): ProviderResult<DebugAdapterDescriptor> {
if (!this._server) {
// start listening on a random port
this._server = Net.createServer((socket) => {
const debugSession = new SolidityDebugSession();
debugSession.setRunAsServer(true);
debugSession.start(socket as NodeJS.ReadableStream, socket);
}).listen(0);
}
// make VS Code connect to debug server
const address: any = this._server.address();
return new DebugAdapterServer(address.port);
}
public dispose() {
if (this._server) {
this._server.close();
}
}
}

Просмотреть файл

@ -0,0 +1,20 @@
import {
CancellationToken,
DebugConfiguration,
DebugConfigurationProvider,
ProviderResult,
WorkspaceFolder,
} from 'vscode';
export default class TruffleDebuggerConfigurationProvider implements DebugConfigurationProvider {
/**
* Massage a debug configuration just before a debug session is being launched,
* e.g. add all missing attributes to the debug configuration.
*/
public resolveDebugConfiguration(
_folder: WorkspaceFolder | undefined,
config: DebugConfiguration,
_token?: CancellationToken): ProviderResult<DebugConfiguration> {
return config;
}
}

Просмотреть файл

@ -0,0 +1,22 @@
import { debug, ExtensionContext } from 'vscode';
import { DEBUG_TYPE, EMBED_DEBUG_ADAPTER } from '../constants/debugAdapter';
import DebugAdapterTrackerFactory from '../debugAdapterTracker/debugAdapterTrackerFactory';
import DebugAdapterDescriptorFactory from './debugAdapterDescriptorFactory';
import DebuggerConfigurationProvider from './debugConfigurationProvider';
export class DebuggerConfiguration {
public static initialize(context: ExtensionContext) {
const debugConfiProvider = new DebuggerConfigurationProvider();
context.subscriptions.push(debug.registerDebugConfigurationProvider(DEBUG_TYPE, debugConfiProvider));
if (EMBED_DEBUG_ADAPTER) {
const factory = new DebugAdapterDescriptorFactory();
context.subscriptions.push(debug.registerDebugAdapterDescriptorFactory(DEBUG_TYPE, factory));
context.subscriptions.push(factory);
}
const debugAdapterTrackerFactory = new DebugAdapterTrackerFactory();
const trackerFactory = debug.registerDebugAdapterTrackerFactory(DEBUG_TYPE, debugAdapterTrackerFactory);
context.subscriptions.push(trackerFactory);
}
}

Просмотреть файл

@ -0,0 +1,622 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// The same implementation as in helpers/truffleConfig.ts
// The difference is that all code which uses 'vscode' module is removed.
// TODO: think how to reuse code
import * as acorn from 'acorn';
// @ts-ignore
import * as walk from 'acorn-walk';
import { generate } from 'astring';
import * as bip39 from 'bip39';
import * as crypto from 'crypto';
import * as ESTree from 'estree';
import * as fs from 'fs-extra';
import * as path from 'path';
export namespace ConfigurationReader {
const notAllowedSymbols = new RegExp(
/`|~|!|@|#|\$|%|\^|&|\*|\(|\)|\+|-|=|\[|{|]|}|\||\\|'|<|,|>|\?|\/|""|;|:|"|№|\s/g,
);
interface IFound {
node: ESTree.Node;
state: string;
}
export interface IProvider {
mnemonic?: string;
raw?: string;
url?: string;
}
export interface INetworkOption {
/**
* Ethereum public network
* if "*" - match any network
*/
network_id: string | number;
port?: number;
host?: string;
/**
* You will need this enabled to use the confirmations listener
* or to hear Events using .on or .once. Default is false.
*/
websockets?: boolean;
/**
* Gas limit used for deploys. Default is 4712388.
*/
gas?: number;
/**
* Gas price used for deploys. Default is 100000000000 (100 Shannon).
*/
gasPrice?: number;
/**
* From address used during migrations. Defaults to the first available account provided by your Ethereum client.
*/
from?: string;
/**
* Function that returns a web3 provider instance.
* web3 provider instance Truffle should use to talk to the Ethereum network.
* if specified, host and port are ignored.
* Default web3 provider using host and port options: new Web3.providers.HttpProvider("http://<host>:<port>")
*/
provider?: IProvider;
/**
* true if you don't want to test run the migration locally before the actual migration (default is false)
*/
skipDryRun?: boolean;
/**
* if a transaction is not mined, keep waiting for this number of blocks (default is 50)
*/
timeoutBlocks?: number;
/**
* This identifier needs just for out extension.
*/
consortium_id?: number;
}
export interface INetwork {
name: string;
options: INetworkOption;
}
export interface ISolCompiler {
/**
* A version or constraint - Ex. "^0.5.0" . Can also be set to "native" to use a native solc
*/
version: string;
/**
* Use a version obtained through docker
*/
docker?: boolean;
settings?: {
optimizer?: {
enabled: boolean,
/**
* Optimize for how many times you intend to run the code
*/
runs: number,
},
/**
* Default: "byzantium"
*/
evmVersion?: string,
};
}
export interface IExternalCompiler {
command: string;
workingDirectory: string;
targets: object[];
}
export interface IConfiguration {
contracts_directory: string;
contracts_build_directory: string;
migrations_directory: string;
networks?: INetwork[];
compilers?: {
solc?: ISolCompiler;
external?: IExternalCompiler;
};
}
export function generateMnemonic(): string {
return bip39.entropyToMnemonic(crypto.randomBytes(16).toString('hex'));
}
/**
* looking for truffle config named in old style
* and rename it to truffle-config.js
*/
export function checkTruffleConfigNaming(workspaceRoot: string): void {
// old-style of truffle config naming
if (fs.pathExistsSync(path.join(workspaceRoot, 'truffle.js'))) {
fs.renameSync(
path.join(workspaceRoot, 'truffle.js'),
path.join(workspaceRoot, 'truffle-config.js'),
);
}
}
export class TruffleConfig {
private readonly ast: ESTree.Node;
constructor(private readonly filePath: string) {
const file = fs.readFileSync(this.filePath, 'utf8');
this.ast = acorn.parse(file, {
allowHashBang: true,
allowReserved: true,
sourceType: 'module',
}) as ESTree.Node;
}
public getAST(): ESTree.Node {
return this.ast;
}
public writeAST(): void {
return fs.writeFileSync(this.filePath, generate(this.ast, { comments: true }));
}
public getNetworks(): INetwork[] {
const moduleExports = getModuleExportsObjectExpression(this.ast);
if (moduleExports) {
const networksNode = findProperty(moduleExports, 'networks');
if (networksNode && networksNode.value.type === 'ObjectExpression') {
return astToNetworks(networksNode.value);
}
}
return [];
}
public setNetworks(network: INetwork): void {
const moduleExports = getModuleExportsObjectExpression(this.ast);
if (moduleExports) {
let networksNode = findProperty(moduleExports, 'networks');
if (!networksNode) {
networksNode = generateProperty('networks', generateObjectExpression());
moduleExports.properties.push(networksNode);
}
if (networksNode.value.type === 'ObjectExpression') {
const isExist = findProperty(networksNode.value, network.name);
if (isExist) {
throw new Error('Network already exists');
} else {
const networkNode = generateProperty(network.name, generateObjectExpression());
networkNode.value = networkOptionsToAst(network);
networksNode.value.properties.push(networkNode);
}
}
}
this.writeAST();
}
public importPackage(variableName: string, packageName: string): void {
const packageRequired: IFound = walk.findNodeAt(this.ast, null, null, isVarDeclaration(variableName));
if (!packageRequired) {
const declaration = generateVariableDeclaration(variableName, 'require', packageName);
(this.ast as ESTree.Program).body.unshift(declaration);
this.writeAST();
}
}
public getConfiguration(): IConfiguration {
const moduleExports = getModuleExportsObjectExpression(this.ast);
if (moduleExports) {
return astToConfiguration(moduleExports);
}
return getDefaultConfiguration();
}
public isHdWalletProviderDeclared(): boolean {
try {
const moduleExports = walk.findNodeAt(this.ast, null, null, isHdWalletProviderDeclaration);
return !!moduleExports;
} catch (error) {
// ignore
}
return false;
}
}
function isHdWalletProviderDeclaration(nodeType: string, node: ESTree.Node): boolean {
if (nodeType !== 'NewExpression') {
return false;
}
node = node as ESTree.NewExpression;
node = node.callee as ESTree.Identifier;
return node.name === 'HDWalletProvider';
}
function getModuleExportsObjectExpression(ast: ESTree.Node): ESTree.ObjectExpression | void {
const moduleExports: IFound = walk.findNodeAt(ast, null, null, isModuleExportsExpression);
if (moduleExports && moduleExports.node.type === 'ExpressionStatement') {
const rightExpression = (moduleExports.node.expression as ESTree.AssignmentExpression).right;
if (rightExpression.type === 'ObjectExpression') {
return rightExpression;
}
}
}
function getDefaultConfiguration(): IConfiguration {
return {
contracts_build_directory: path.join('./', 'build', 'contracts'),
contracts_directory: path.join('./', 'contracts'),
migrations_directory: path.join('./', 'migrations'),
};
}
function isModuleExportsExpression(nodeType: string, node: ESTree.Node): boolean {
if (nodeType !== 'ExpressionStatement') {
return false;
}
node = node as ESTree.ExpressionStatement;
if (
node.expression.type === 'AssignmentExpression' &&
node.expression.left.type === 'MemberExpression' &&
node.expression.left.object.type === 'Identifier' &&
node.expression.left.object.name === 'module'
) {
if (
(node.expression.left.property.type === 'Identifier' && node.expression.left.property.name === 'exports') ||
(node.expression.left.property.type === 'Literal' && node.expression.left.property.value === 'module')
) {
return true;
}
}
return false;
}
function isHDWalletProvider(nodeType: string, node: ESTree.Node): boolean {
if (nodeType === 'NewExpression') {
node = node as ESTree.NewExpression;
if (node.callee.type === 'Identifier' && node.callee.name === 'HDWalletProvider') {
return true;
}
}
return false;
}
function isVarDeclaration(varName: string): (nodeType: string, node: ESTree.Node) => boolean {
return (nodeType: string, node: ESTree.Node) => {
if (nodeType === 'VariableDeclaration') {
node = node as ESTree.VariableDeclaration;
if (node.declarations[0].type === 'VariableDeclarator'
&& (node.declarations[0].id as ESTree.Identifier).name === varName) {
return true;
}
}
return false;
};
}
function findProperty(node: ESTree.Node, propertyName: string): ESTree.Property | void {
if (node.type === 'ObjectExpression') {
node = node as ESTree.ObjectExpression;
return node.properties.find((property: ESTree.Property) => {
return (property.key.type === 'Identifier' && property.key.name === propertyName) ||
(property.key.type === 'Literal' && `${property.key.value}` === propertyName);
});
}
}
function astToNetworkOptions(node: ESTree.ObjectExpression): INetworkOption {
const options: INetworkOption = {
network_id: '*',
};
const id = findProperty(node, 'network_id');
if (id && id.value.type === 'Literal' &&
(typeof id.value.value === 'string' || typeof id.value.value === 'number')) {
options.network_id = id.value.value;
}
const port = findProperty(node, 'port');
if (port && port.value.type === 'Literal' && typeof port.value.value === 'number') {
options.port = port.value.value;
}
const host = findProperty(node, 'host');
if (host && host.value.type === 'Literal' && typeof host.value.value === 'string') {
options.host = host.value.value;
}
const websockets = findProperty(node, 'websockets');
if (websockets && websockets.value.type === 'Literal' && typeof websockets.value.value === 'boolean') {
options.websockets = websockets.value.value;
}
const gas = findProperty(node, 'gas');
if (gas && gas.value.type === 'Literal' && typeof gas.value.value === 'number') {
options.gas = gas.value.value;
}
const gasPrice = findProperty(node, 'gasPrice');
if (gasPrice && gasPrice.value.type === 'Literal' && typeof gasPrice.value.value === 'number') {
options.gasPrice = gasPrice.value.value;
}
const from = findProperty(node, 'from');
if (from && from.value.type === 'Literal' && typeof from.value.value === 'string') {
options.from = from.value.value;
}
const skipDryRun = findProperty(node, 'skipDryRun');
if (skipDryRun && skipDryRun.value.type === 'Literal' && typeof skipDryRun.value.value === 'boolean') {
options.skipDryRun = skipDryRun.value.value;
}
const timeoutBlocks = findProperty(node, 'timeoutBlocks');
if (timeoutBlocks && timeoutBlocks.value.type === 'Literal' && typeof timeoutBlocks.value.value === 'number') {
options.timeoutBlocks = timeoutBlocks.value.value;
}
const provider = findProperty(node, 'provider');
if (provider && provider.value.type === 'FunctionExpression') {
const hdWalletProvider: IFound = walk.findNodeAt(provider, null, null, isHDWalletProvider);
if (hdWalletProvider && hdWalletProvider.node.type === 'NewExpression') {
options.provider = astToHDWalletProvider(hdWalletProvider.node);
}
}
if (provider && provider.value.type === 'NewExpression') {
options.provider = astToHDWalletProvider(provider.value);
}
const consortiumId = findProperty(node, 'consortium_id');
if (consortiumId && consortiumId.value.type === 'Literal' && typeof consortiumId.value.value === 'number') {
options.consortium_id = consortiumId.value.value;
}
return options;
}
function networkOptionsToAst(network: INetwork): ESTree.ObjectExpression {
const obj: ESTree.ObjectExpression = {
properties: [],
type: 'ObjectExpression',
};
const options = network.options;
if (options.network_id !== undefined) {
obj.properties.push(generateProperty('network_id', generateLiteral(options.network_id)));
}
if (options.port !== undefined) {
obj.properties.push(generateProperty('port', generateLiteral(options.port)));
}
if (options.host !== undefined) {
obj.properties.push(generateProperty('host', generateLiteral(options.host)));
}
if (options.websockets !== undefined) {
obj.properties.push(generateProperty('websockets', generateLiteral(options.websockets)));
}
if (options.gas !== undefined) {
obj.properties.push(generateProperty('gas', generateLiteral(options.gas)));
}
if (options.gasPrice !== undefined) {
obj.properties.push(generateProperty('gasPrice', generateLiteral(options.gasPrice)));
}
if (options.from !== undefined) {
obj.properties.push(generateProperty('from', generateLiteral(options.from)));
}
if (options.skipDryRun !== undefined) {
obj.properties.push(generateProperty('skipDryRun', generateLiteral(options.skipDryRun)));
}
if (options.timeoutBlocks !== undefined) {
obj.properties.push(generateProperty('timeoutBlocks', generateLiteral(options.timeoutBlocks)));
}
if (options.provider !== undefined) {
obj.properties.push(generateProperty('provider', hdWalletProviderToAst(options.provider)));
}
if (options.consortium_id !== undefined) {
obj.properties.push(generateProperty('consortium_id', generateLiteral(options.consortium_id)));
}
return obj;
}
function astToHDWalletProvider(node: ESTree.NewExpression): IProvider {
const provider: IProvider = {
raw: generate(node),
};
const mnemonicNode = node.arguments[0];
if (mnemonicNode && mnemonicNode.type === 'Literal') {
provider.mnemonic = '' + mnemonicNode.value;
}
const urlNode = node.arguments[1];
if (urlNode && urlNode.type === 'Literal') {
provider.url = '' + urlNode.value;
}
if (urlNode && urlNode.type !== 'Literal') {
provider.url = generate(urlNode);
}
return provider;
}
function hdWalletProviderToAst(provider: IProvider): ESTree.NewExpression {
return {
arguments: [
generateFsReadExpression('fs.readFileSync', (provider.mnemonic || '').replace(/\\/g, '\\\\')),
generateLiteral(provider.url || ''),
],
callee: {
name: 'HDWalletProvider',
type: 'Identifier',
},
type: 'NewExpression',
};
}
function astToConfiguration(node: ESTree.ObjectExpression): IConfiguration {
const configuration = getDefaultConfiguration();
const contractsDir = findProperty(node, 'contracts_directory');
if (contractsDir && contractsDir.value.type === 'Literal' &&
typeof contractsDir.value.value === 'string' && contractsDir.value.value) {
configuration.contracts_directory = contractsDir.value.value;
}
const contractsBuildDir = findProperty(node, 'contracts_build_directory');
if (contractsBuildDir && contractsBuildDir.value.type === 'Literal' &&
typeof contractsBuildDir.value.value === 'string' && contractsBuildDir.value.value) {
configuration.contracts_build_directory = contractsBuildDir.value.value;
}
const migrationsDir = findProperty(node, 'migrations_directory');
if (migrationsDir && migrationsDir.value.type === 'Literal' &&
typeof migrationsDir.value.value === 'string' && migrationsDir.value.value) {
configuration.migrations_directory = migrationsDir.value.value;
}
const networks = findProperty(node, 'networks');
if (networks && networks.value.type === 'ObjectExpression') {
configuration.networks = astToNetworks(networks.value);
}
// TODO: compilers
return configuration;
}
function astToNetworks(node: ESTree.ObjectExpression): INetwork[] {
const networks: INetwork[] = [];
node.properties.forEach((property: ESTree.Property) => {
if (property.key.type === 'Identifier') {
networks.push({
name: property.key.name,
options: astToNetworkOptions(property.value as ESTree.ObjectExpression),
});
}
if (property.key.type === 'Literal') {
networks.push({
name: '' + property.key.value,
options: astToNetworkOptions(property.value as ESTree.ObjectExpression),
});
}
});
return networks;
}
function generateProperty(name: string, value: ESTree.Expression): ESTree.Property {
notAllowedSymbols.lastIndex = 0;
const isLiteral = notAllowedSymbols.test(name);
return {
computed: false,
key: isLiteral ? generateLiteral(name) : generateIdentifier(name),
kind: 'init',
method: false,
shorthand: false,
type: 'Property',
value,
};
}
function generateObjectExpression(): ESTree.ObjectExpression {
return {
properties: [],
type: 'ObjectExpression',
};
}
function generateIdentifier(name: string): ESTree.Identifier {
return {
name,
type: 'Identifier',
};
}
function generateLiteral(value: string | number | boolean | null): ESTree.Literal {
return {
raw: JSON.stringify(value),
type: 'Literal',
value,
};
}
function generateFsReadExpression(operator: string, args: string): ESTree.CallExpression {
const call = {
arguments: [
{
raw: `\'${args}\'`,
type: 'Literal',
value: `${args}`,
},
{
raw: "\'utf-8\'",
type: 'Literal',
value: 'utf-8',
},
],
callee: {
name: operator,
type: 'Identifier',
},
type: 'CallExpression',
};
return call as ESTree.CallExpression;
}
function generateVariableDeclaration(varName: string, loader: string, loaderArg: string): ESTree.VariableDeclaration {
const declaration = {
declarations: [
{
id: {
name: varName,
type: 'Identifier',
},
init: {
arguments: [
{
raw: `\'${loaderArg}\'`,
type: 'Literal',
value: `${loaderArg}`,
},
],
callee: {
name: loader,
type: 'Identifier',
},
type: 'CallExpression',
},
type: 'VariableDeclarator',
},
],
kind: 'const',
type: 'VariableDeclaration',
};
return declaration as ESTree.VariableDeclaration;
}
}

Просмотреть файл

@ -0,0 +1,2 @@
export const CONTRACT_JSON_EXTENSION = '.json';
export const CONTRACT_JSON_ENCODING = 'utf-8';

Просмотреть файл

@ -0,0 +1,39 @@
export const EVENT_TYPES = {
breakpointValidated: 'breakpointValidated',
end: 'end',
launched: 'launched',
stopOnBreakpoint: 'stopOnBreakpoint',
stopOnEntry: 'stopOnEntry',
stopOnException: 'stopOnException',
stopOnStepIn: 'stopOnStepIn',
stopOnStepOut: 'stopOnStepOut',
stopOnStepOver: 'stopOnStepOver',
stopped: 'stopped',
};
export const EVENT_REASONS = {
breakpoint: 'breakpoint',
changed: 'changed',
entry: 'entry',
exception: 'exception',
stepIn: 'stepin',
stepOut: 'stepout',
stepOver: 'step',
};
// we don't support multiple threads, so we can use a hardcoded ID for the default thread
export const MAIN_THREAD = {
id: 1,
name: 'thread 1',
};
export const EVALUATE_REQUEST_TYPES = {
hover: 'hover',
watch: 'watch',
};
export const DEBUG_TYPE = 'truffle';
export const EMBED_DEBUG_ADAPTER = !!(typeof(IS_BUNDLE_TIME) === 'undefined');
export const ERROR_MESSAGE_ID = 1;

Просмотреть файл

@ -0,0 +1,2 @@
export const GET_INSTRUCTIONS = 'requestInstructions';
export const GET_CURRENT_INSTRUCTION = 'requestCurrentInstruction';

Просмотреть файл

@ -0,0 +1,3 @@
export const TRANSACTION_NUMBER_TO_SHOW = 5;
export const TRANSACTION_DEFAULT_METHOD_NAME = 'constructor';
export const LAST_BLOCK_QUERY = 'latest';

Просмотреть файл

@ -0,0 +1,2 @@
export const TRUFFLE_CONFIG_NAME = 'truffle-config.js';
export const TRUFFLE_CONFIG_DEBUG_NETWORK_TYPE = 'development';

Просмотреть файл

@ -0,0 +1,14 @@
const VARIABLE_REFERANCE = {
all: 1,
dynamicVariables: 2,
};
export const SCOPES = {
all: {
name: 'All',
ref: VARIABLE_REFERANCE.all,
},
dynamicVariables: {
ref: VARIABLE_REFERANCE.dynamicVariables,
},
};

Просмотреть файл

@ -0,0 +1,70 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import { CONTRACT_JSON_ENCODING } from '../constants/contractJson';
import { IContractJsonModel } from '../models/IContractJsonModel';
export class ContractJsonsProvider {
private contractBuildDirectory: string;
private contractJsonEncoding: string;
private _cachedContractJsons: { [fileName: string]: IContractJsonModel } | undefined;
constructor(contractBuildDirectory: string, contractJsonEncoding = CONTRACT_JSON_ENCODING) {
this.contractBuildDirectory = contractBuildDirectory;
this.contractJsonEncoding = contractJsonEncoding;
}
public isDirectoryExist(): Promise<boolean> {
return new Promise(((accept: any, reject: any) => {
fs.pathExists(this.contractBuildDirectory, (err: any, isExist) => {
if (!err) {
accept(isExist);
} else {
reject(`Error while reading ${this.contractBuildDirectory}`);
}
});
}).bind(this));
}
public async getJsonsContents(): Promise<{ [fileName: string]: IContractJsonModel }> {
if (!this._cachedContractJsons) {
const isDirectoryExist = await this.isDirectoryExist();
if (!isDirectoryExist) {
this._cachedContractJsons = {};
} else {
const dir = this.contractBuildDirectory;
const response = new Promise<{ [fileName: string]: IContractJsonModel }>((accept) => {
fs.readdir(dir, async (error: any, files: any[]) => {
if (error) {
throw new Error(`Error occured while reading directory ${dir}`);
}
const result: { [fileName: string]: IContractJsonModel } = {};
for (const file of files) {
const fullPath = path.join(dir, file);
const content = await this.readFile(fullPath, this.contractJsonEncoding);
try {
result[file] = JSON.parse(content) as IContractJsonModel;
} catch (e) {
throw new Error(`Error while parsing ${fullPath}. Invalid json file.`);
}
}
accept(result);
});
});
this._cachedContractJsons = await response;
}
}
return this._cachedContractJsons;
}
private readFile(filePath: string, encoding: string): Promise<string> {
return new Promise((accept) => {
fs.readFile(filePath, encoding, (error: any, content: string) => {
if (error) {
throw new Error(`Error occured while reading file ${filePath}. ${error}`);
}
accept(content);
});
});
}
}

Просмотреть файл

@ -0,0 +1,45 @@
import { DebugNetwork } from '../debugNetwork';
import { IContractModel } from '../models/IContractModel';
import { Web3Wrapper } from '../web3Wrapper';
import { ContractJsonsProvider } from './contractJsonsProvider';
export async function prepareContracts(workingDirectory: any) {
// TODO, the same code in the debuggerCommands.ts, do refactoring
const debugNetwork = new DebugNetwork(workingDirectory);
await debugNetwork.load();
const contractBuildDir = debugNetwork.getTruffleConfiguration()!.contracts_build_directory;
const debugNetworkOptions = debugNetwork.getNetwork()!.options;
const web3 = new Web3Wrapper(debugNetworkOptions);
const provider = await web3.getProvider();
const debugNetworkId = await web3.getNetworkId();
const contracts: IContractModel[] = [];
const contractBuildsProvider = new ContractJsonsProvider(contractBuildDir);
const contractJsons = await contractBuildsProvider.getJsonsContents();
Object.keys(contractJsons).forEach((fileName) => {
const contractJson = contractJsons[fileName];
const contractModel = {
address: contractJson.networks
&& contractJson.networks[debugNetworkId]
&& contractJson.networks[debugNetworkId].address,
binary: contractJson.bytecode,
deployedBinary: contractJson.deployedBytecode,
...contractJson,
} as IContractModel;
contracts.push(contractModel);
});
return {
contracts,
provider,
};
}
export function filterContractsWithAddress(contracts: IContractModel[]): IContractModel[] {
return contracts.filter((c) => c.address);
}
// Base contracts are not deployed as particular contract that's why they don't have address
export function filterBaseContracts(contracts: IContractModel[]): IContractModel[] {
return contracts.filter((c) => !c.address);
}

Просмотреть файл

@ -0,0 +1,55 @@
import { DebugAdapterTracker, DebugSession, window } from 'vscode';
import InstructionView from '../instructionsView/instructionView';
import { EVENT_TYPES } from '../constants/debugAdapter';
import { GET_CURRENT_INSTRUCTION, GET_INSTRUCTIONS } from '../constants/debugSessionCommands';
import { IInstruction } from '../models/IInstruction';
export default class SolidityDebugAdapterTracker implements DebugAdapterTracker {
private session: DebugSession;
private instructionView: InstructionView;
constructor(session: DebugSession) {
this.session = session;
this.instructionView = new InstructionView();
}
public onDidSendMessage(message: any): void {
if (message.success === false) {
window.showErrorMessage('Error occured in debug mode: ' + message.body.error.format);
return;
}
switch (message.event) {
case EVENT_TYPES.launched: // init instructions after launch
this.requestForInstructions();
return;
case EVENT_TYPES.stopped: // get current instruction on every stop event
this.requestForCurrentInstruction();
return;
}
switch (message.command) {
case GET_INSTRUCTIONS:
this.updateInstructionView(message.body.instructions);
return;
case GET_CURRENT_INSTRUCTION:
this.revealInstruction(message.body.currentInstruction);
return;
}
}
private requestForInstructions() {
this.session.customRequest(GET_INSTRUCTIONS);
}
private requestForCurrentInstruction() {
this.session.customRequest(GET_CURRENT_INSTRUCTION);
}
private updateInstructionView(instructions: IInstruction[]) {
this.instructionView.update(instructions);
}
private revealInstruction(instruction: IInstruction) {
this.instructionView.revealInstruction(instruction);
}
}

Просмотреть файл

@ -0,0 +1,13 @@
import {
DebugAdapterTracker,
DebugAdapterTrackerFactory,
DebugSession,
ProviderResult,
} from 'vscode';
import SolidityDebugAdapterTracker from './debugAdapterTracker';
export default class SolidityDebugAdapterTrackerFactory implements DebugAdapterTrackerFactory {
public createDebugAdapterTracker(session: DebugSession): ProviderResult<DebugAdapterTracker> {
return new SolidityDebugAdapterTracker(session);
}
}

Просмотреть файл

@ -0,0 +1,95 @@
import * as path from 'path';
import { executeCommand } from './cmdCommandExecutor';
import { ConfigurationReader } from './configurationReader';
import { TRUFFLE_CONFIG_DEBUG_NETWORK_TYPE, TRUFFLE_CONFIG_NAME } from './constants/truffleConfig';
export class DebugNetwork {
public workingDirectory: string;
private _basedConfig: ConfigurationReader.TruffleConfig;
private _truffleConfiguration: ConfigurationReader.IConfiguration | undefined;
private _networkForDebug: ConfigurationReader.INetwork | undefined;
constructor(truffleConfigDirectory: string) {
this._basedConfig =
new ConfigurationReader.TruffleConfig(path.join(truffleConfigDirectory, TRUFFLE_CONFIG_NAME));
this.workingDirectory = truffleConfigDirectory;
}
public async load(): Promise<void> {
this._truffleConfiguration = this.loadConfiguration();
this._networkForDebug = await this.loadNetworkForDebug();
}
public getTruffleConfiguration() {
return this._truffleConfiguration;
}
public getNetwork() {
return this._networkForDebug;
}
// Port and host are defined
public isLocalNetwork() {
if (!this._networkForDebug || !this._networkForDebug.options) {
throw new Error('Network is not defined. Try to call this.load()');
}
const options = this._networkForDebug.options;
return !!(options.host && options.port);
}
private loadConfiguration(): ConfigurationReader.IConfiguration {
const configuration = this._basedConfig.getConfiguration();
configuration.contracts_build_directory =
path.join(this.workingDirectory, configuration.contracts_build_directory);
configuration.contracts_directory =
path.join(this.workingDirectory, configuration.contracts_directory);
configuration.migrations_directory =
path.join(this.workingDirectory, configuration.migrations_directory);
return configuration;
}
private async loadNetworkForDebug(): Promise<ConfigurationReader.INetwork> {
const networks = this._basedConfig.getNetworks();
const networkForDebug = networks
.find((n) => n.name === TRUFFLE_CONFIG_DEBUG_NETWORK_TYPE);
if (!this.isNetworkForDebugValid(networkForDebug)) {
const provider = await this.getProviderByResolvingConfig(TRUFFLE_CONFIG_DEBUG_NETWORK_TYPE);
if (provider.url) {
networkForDebug!.options.provider = {
url: provider.url,
};
} else {
throw new Error(`Truffle config is not properly defined.
Please create ${TRUFFLE_CONFIG_DEBUG_NETWORK_TYPE} network,
set host+port, or hdwallet/http/websocket provider.`);
}
}
return networkForDebug!;
}
private isNetworkForDebugValid(networkForDebug: ConfigurationReader.INetwork | undefined): boolean {
if (!networkForDebug || !networkForDebug.options) {
throw new Error(`No ${TRUFFLE_CONFIG_DEBUG_NETWORK_TYPE} network in the truffle config`);
}
if (networkForDebug.options.host && networkForDebug.options.port) {
return true;
}
// truffle-config helper can read only hdwallet provider
const isHdWalletProvider = !!networkForDebug.options.provider;
if (isHdWalletProvider) {
return true;
}
return false;
}
private async getProviderByResolvingConfig(network: string) {
// use truffle exec web3ProviderResolver.js to solve http- or websocket- web3 provider
const truffleConfigReaderPath = path.join(__dirname, 'web3ProviderResolver.js');
const args = [ 'truffle', 'exec', truffleConfigReaderPath, '--network', network ];
const result = await executeCommand(this.workingDirectory, 'npx', ...args);
const providerJson = result.split('provider%=')[1];
return JSON.parse(providerJson);
}
}

Просмотреть файл

@ -0,0 +1,287 @@
import { basename } from 'path';
import {
Breakpoint, BreakpointEvent, InitializedEvent, logger, Logger, LoggingDebugSession,
Source, StackFrame, StoppedEvent, TerminatedEvent, Thread,
} from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import {
ERROR_MESSAGE_ID,
EVALUATE_REQUEST_TYPES,
EVENT_REASONS,
EVENT_TYPES,
MAIN_THREAD,
} from './constants/debugAdapter';
import {
GET_CURRENT_INSTRUCTION,
GET_INSTRUCTIONS,
} from './constants/debugSessionCommands';
import { DebuggerTypes } from './models/debuggerTypes';
import RuntimeInterface from './runtimeInterface';
import VariablesHandler from './variablesHandler';
export class SolidityDebugSession extends LoggingDebugSession {
private _runtime: RuntimeInterface;
private _variablesHandler: VariablesHandler;
public constructor() {
super('debugAdapter.txt');
// this debugger uses zero-based lines and columns
this.setDebuggerLinesStartAt1(false);
this.setDebuggerColumnsStartAt1(false);
this._runtime = new RuntimeInterface();
this._variablesHandler = new VariablesHandler(this._runtime);
this._runtime.on(EVENT_TYPES.stopOnEntry, () => {
this.sendEvent(new StoppedEvent(EVENT_REASONS.entry, MAIN_THREAD.id));
});
this._runtime.on(EVENT_TYPES.stopOnStepOver, () => {
this.sendEvent(new StoppedEvent(EVENT_REASONS.stepOver, MAIN_THREAD.id));
});
this._runtime.on(EVENT_TYPES.stopOnStepIn, () => {
this.sendEvent(new StoppedEvent(EVENT_REASONS.stepIn, MAIN_THREAD.id));
});
this._runtime.on(EVENT_TYPES.stopOnStepOut, () => {
this.sendEvent(new StoppedEvent(EVENT_REASONS.stepOut, MAIN_THREAD.id));
});
this._runtime.on(EVENT_TYPES.stopOnBreakpoint, () => {
this.sendEvent(new StoppedEvent(EVENT_REASONS.breakpoint, MAIN_THREAD.id));
});
this._runtime.on(EVENT_TYPES.stopOnException, () => {
this.sendEvent(new StoppedEvent(EVENT_REASONS.exception, MAIN_THREAD.id));
});
this._runtime.on(EVENT_TYPES.breakpointValidated, (bp: DebuggerTypes.IBreakpoint) => {
this.sendEvent(
new BreakpointEvent(EVENT_REASONS.changed, { verified: true, id: bp.id } as DebugProtocol.Breakpoint));
});
this._runtime.on(EVENT_TYPES.end, () => {
this.sendEvent(new TerminatedEvent());
});
}
/**
* The 'initialize' request is the first request called by the frontend
* to interrogate the features the debug adapter provides.
*/
protected initializeRequest(response: DebugProtocol.InitializeResponse,
/* args: DebugProtocol.InitializeRequestArguments */): void {
// build and return the capabilities of this debug adapter:
response.body = response.body || {};
// TODO: enable it if needed. (if the adapter implements the configurationDoneRequest.)
response.body.supportsConfigurationDoneRequest = false;
response.body.supportsEvaluateForHovers = true;
// TODO: think about implementation of stepback
// protected stepBackRequest(response: DebugProtocol.StepBackResponse, args: DebugProtocol.StepBackArguments)
response.body.supportsStepBack = false;
response.body.supportsSetVariable = false;
response.body.supportsSetExpression = false;
this.sendResponse(response);
// since this debug adapter can accept configuration requests like 'setBreakpoint' at any time,
// we request them early by sending an 'initializeRequest' to the frontend.
// The frontend will end the configuration sequence by calling 'configurationDone' request.
this.sendEvent(new InitializedEvent());
}
protected async launchRequest(response: DebugProtocol.LaunchResponse,
args: DebuggerTypes.ILaunchRequestArguments): Promise<void> {
await this.sendErrorIfFailed(response, async () => {
// make sure to 'Stop' the buffered logging if 'trace' is not set
// logger.setup enable logs in client
logger.setup(args.trace ? Logger.LogLevel.Verbose : Logger.LogLevel.Stop, false);
this.sendResponse(response);
// start the program in the runtime
await this._runtime.attach(args.txHash, args.workingDirectory);
await this._runtime.processInitialBreakPoints();
// Events order is important
this.sendEvent(new DebuggerTypes.LaunchedEvent());
this.sendEvent(new StoppedEvent(EVENT_REASONS.breakpoint, MAIN_THREAD.id));
});
}
protected async disconnectRequest(response: DebugProtocol.DisconnectResponse,
/* args: DebugProtocol.DisconnectArguments */) {
this.sendResponse(response);
}
protected async setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse,
args: DebugProtocol.SetBreakpointsArguments): Promise<void> {
await this.sendErrorIfFailed(response, async () => {
const path = args.source.path as string;
const clientLines = args.lines || [];
// set and verify breakpoint locations
const actualBreakpoints: DebugProtocol.Breakpoint[] = [];
await this._runtime.clearBreakpoints();
for (const clientLine of clientLines) {
const debuggerBreakpoint =
await this._runtime.setBreakpoint(path, this.convertClientLineToDebugger(clientLine));
if (debuggerBreakpoint) {
const bp = new Breakpoint(true,
this.convertDebuggerLineToClient(debuggerBreakpoint.line)) as DebugProtocol.Breakpoint;
bp.id = debuggerBreakpoint.id;
actualBreakpoints.push(bp);
}
}
// setBreakPointsRequest is hit before runtime debugger is attached
if (!this._runtime.isDebuggerAttached) {
const debuggerLines = clientLines.map((l: number) => this.convertClientLineToDebugger(l));
this._runtime.storeInitialBreakPoints(path, debuggerLines);
}
// send back the actual breakpoint positions
response.body = {
breakpoints: actualBreakpoints,
};
this.sendResponse(response);
});
}
protected threadsRequest(response: DebugProtocol.ThreadsResponse): void {
// return a default thread
response.body = {
threads: [ new Thread(MAIN_THREAD.id, MAIN_THREAD.name) ],
};
this.sendResponse(response);
}
protected async stackTraceRequest(response: DebugProtocol.StackTraceResponse,
/* args: DebugProtocol.StackTraceArguments */): Promise<void> {
await this.sendErrorIfFailed(response, async () => {
let callStack: any[] = [];
callStack = this._runtime.callStack();
if (callStack !== null) {
const stackFrames = callStack.reverse().map((c: any) => {
return new StackFrame(0, c.isCurrent ? 'Current Call' : 'Previous Call', this.createSource(c.file),
this.convertDebuggerLineToClient(c.line), c.column);
});
response.body = {
stackFrames,
totalFrames: 1,
};
} else {
response.body = {
stackFrames: [],
totalFrames: 0,
};
}
this.sendResponse(response);
});
}
protected scopesRequest(response: DebugProtocol.ScopesResponse,
/* args: DebugProtocol.ScopesArguments */): void {
response.body = {
scopes: this._variablesHandler.getScopes(),
};
this.sendResponse(response);
}
protected async variablesRequest(response: DebugProtocol.VariablesResponse,
args: DebugProtocol.VariablesArguments): Promise<void> {
const variables =
await this._variablesHandler.getVariableAttributesByVariableRef(args.variablesReference);
response.body = {
variables,
};
this.sendResponse(response);
}
protected async continueRequest(response: DebugProtocol.ContinueResponse,
/* args: DebugProtocol.ContinueArguments */): Promise<void> {
await this.sendErrorIfFailed(response, async () => {
await this._runtime.continue();
this.sendResponse(response);
});
}
protected reverseContinueRequest(response: DebugProtocol.ReverseContinueResponse,
/* args: DebugProtocol.ReverseContinueArguments */): void {
this._runtime.continueReverse();
this.sendResponse(response);
}
protected async nextRequest(response: DebugProtocol.NextResponse,
/* args: DebugProtocol.NextArguments */): Promise<void> {
await this.sendErrorIfFailed(response, async () => {
await this._runtime.stepNext();
this.sendResponse(response);
});
}
protected async stepInRequest(response: DebugProtocol.StepInResponse,
/* args: DebugProtocol.StepInArguments */): Promise<void> {
await this.sendErrorIfFailed(response, async () => {
await this._runtime.stepIn();
this.sendResponse(response);
});
}
protected async stepOutRequest(response: DebugProtocol.StepOutResponse,
/* args: DebugProtocol.StepOutArguments */): Promise<void> {
await this.sendErrorIfFailed(response, async () => {
await this._runtime.stepOut();
this.sendResponse(response);
});
}
protected async evaluateRequest(response: DebugProtocol.EvaluateResponse,
args: DebugProtocol.EvaluateArguments): Promise<void> {
await this.sendErrorIfFailed(response, async () => {
if (args.context === EVALUATE_REQUEST_TYPES.watch
|| args.context === EVALUATE_REQUEST_TYPES.hover
|| args.context === undefined) {
const { result, variablesReference } =
await this._variablesHandler.evaluateExpression(args.expression);
response.body = { result, variablesReference };
this.sendResponse(response);
} else {
response.body = { result: '', variablesReference: -1 };
this.sendResponse(response);
}
});
}
// is invoked via debugAdaterTrackerFactory
protected async customRequest(command: string, response: DebugProtocol.Response/*, args: any*/): Promise<void> {
await this.sendErrorIfFailed(response, async () => {
switch (command) {
case GET_INSTRUCTIONS:
response.body = {
instructions: this._runtime.getInstructionSteps(),
};
break;
case GET_CURRENT_INSTRUCTION:
response.body = {
currentInstruction: this._runtime.getCurrentInstructionStep(),
};
break;
}
this.sendResponse(response);
});
}
private createSource(filePath: string): Source {
return new Source(basename(filePath),
this.convertDebuggerPathToClient(filePath), undefined, undefined, null);
}
private async sendErrorIfFailed(response: DebugProtocol.Response, fn: () => {}) {
try {
await fn();
} catch (e) {
this.sendErrorResponse(response, { id: ERROR_MESSAGE_ID, format: e.message }, '', null, undefined);
}
}
}

Просмотреть файл

@ -0,0 +1,13 @@
// from "D:/test/test1.sol" -> "d:\test\test1.sol"
export function toWindowsPath(filePath: string): string {
const res = filePath.replace(/\//g, '\\');
return res[0].toLowerCase() + res.substr(1);
}
export function groupBy(array: any[], key: any) {
const a = array.reduce((rv, x) => {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
return a;
}

Просмотреть файл

@ -0,0 +1,52 @@
import { IInstruction } from '../models/IInstruction';
import InstructionTreeNode from './instructionTreeNode';
export default class InstructionDataManager {
private instructionObject: any = {};
public load(instructions: IInstruction[]) {
this.instructionObject = this.mapInstructionsArrayToObject(instructions);
}
public getChidren(element?: InstructionTreeNode): string[] {
if (!element) {
return Object.keys(this.instructionObject).map((k) => k);
}
const item = this.getItemByPath(element.getPaths());
if (item === undefined) {
return [];
}
return Object.keys(item);
}
public getItem(element: InstructionTreeNode): any {
return this.getItemByPath(element.getPaths());
}
public getItemParent(element: InstructionTreeNode): any {
return this.getItemByPath(element.getParentPaths());
}
// Map from array to object in order to used in view
// [{pc:1,op: ''},{pc:2,op:''}] ==> { 1:{pc:1,op:''}, 2:{pc:2,op:''}}
private mapInstructionsArrayToObject(steps: IInstruction[]): any {
const res: any = {};
steps.forEach((s) => {
res[s.pc] = s;
});
return res;
}
private getItemByPath(paths: string[]): any {
if (paths.length === 0) {
return void 0;
}
let item = this.instructionObject;
paths.forEach((key) => {
item = item[key];
});
return item;
}
}

Просмотреть файл

@ -0,0 +1,57 @@
import { Event, EventEmitter, ProviderResult, TreeDataProvider, TreeItem, TreeItemCollapsibleState } from 'vscode';
import { IInstruction } from '../models/IInstruction';
import InstructionDataManager from './instructionDataManager';
import InstructionTreeNode from './instructionTreeNode';
export default class InstructionDataProvider implements TreeDataProvider<InstructionTreeNode> {
public _onDidChangeTreeData: EventEmitter<InstructionTreeNode> = new EventEmitter<InstructionTreeNode>();
public readonly onDidChangeTreeData: Event<InstructionTreeNode> = this._onDidChangeTreeData.event;
private instructionDataManager: InstructionDataManager;
constructor(instructionDataManager: InstructionDataManager) {
this.instructionDataManager = instructionDataManager;
}
public refresh(newInstructions: IInstruction[]) {
this.instructionDataManager.load(newInstructions);
this._onDidChangeTreeData.fire();
}
public getChildren(element?: InstructionTreeNode): ProviderResult<InstructionTreeNode[]> {
const items = this.instructionDataManager.getChidren(element);
return items.map((e) => new InstructionTreeNode(e, element));
}
public getTreeItem(element: InstructionTreeNode): TreeItem {
const item = this.instructionDataManager.getItem(element);
const isSpecificObjectValueType = this.isSpecificObjectValueType(item);
const collapsibleState = isSpecificObjectValueType
? TreeItemCollapsibleState.Collapsed
: TreeItemCollapsibleState.None;
return {
collapsibleState,
id: element.getId(),
label: this.generateTreeItemLabel(element.getKey(), item, isSpecificObjectValueType),
};
}
public getParent(element: InstructionTreeNode): ProviderResult<InstructionTreeNode> {
return element.getParent();
}
private generateTreeItemLabel(treeItemKey: string, treeItemValue: any, isSpecificObjectValueType: boolean) {
return isSpecificObjectValueType
? treeItemKey
: `${treeItemKey}: ${JSON.stringify(treeItemValue)}`;
}
// TODO: refactroign - same method in variablesHandler
private isSpecificObjectValueType(item: any) {
return !Array.isArray(item)
&& item !== null
&& item !== undefined
&& typeof(item) === 'object';
}
}

Просмотреть файл

@ -0,0 +1,45 @@
import { IInstruction } from '../models/IInstruction';
export default class InstructionTreeNode {
private key: string;
private path: string;
private parent?: InstructionTreeNode;
constructor(key?: string, parent?: InstructionTreeNode, instructionData?: IInstruction) {
if (!key && (!instructionData || (!instructionData.pc && instructionData.pc !== 0))) {
throw new Error('Incorrect input params');
}
const nodeKey = instructionData
? instructionData.pc.toString()
: (key || '');
this.key = nodeKey;
this.parent = parent;
this.path = this.generatePath(nodeKey, parent);
}
public getId(): string {
return this.path;
}
public getKey(): string {
return this.key;
}
public getParent(): InstructionTreeNode | undefined {
return this.parent;
}
public getParentPaths(): string[] {
return this.path.split(/\//).slice(1, this.path.length - 1); // not take first empty element and last
}
public getPaths(): string[] {
return this.path.split(/\//).slice(1); // not take first empty element
}
private generatePath(key: string, parent?: InstructionTreeNode) {
const parentPath = parent ? parent.path : '';
return `${parentPath}/${key}`;
}
}

Просмотреть файл

@ -0,0 +1,29 @@
import { TreeView, window } from 'vscode';
import { IInstruction } from '../models/IInstruction';
import InstructionDataManager from './instructionDataManager';
import InstructionDataProvider from './instructionDataProvider';
import InstructionTreeNode from './instructionTreeNode';
const INSTRUCTION_VIEW_ID = 'InstructionView';
export default class InstructionView {
private view: TreeView<InstructionTreeNode>;
private dataProvider: InstructionDataProvider;
constructor() {
const dataManager = new InstructionDataManager();
this.dataProvider = new InstructionDataProvider(dataManager);
this.view = window.createTreeView(INSTRUCTION_VIEW_ID,
{ treeDataProvider: this.dataProvider, showCollapseAll: true });
}
public update(newInstructions: IInstruction[]) {
this.dataProvider.refresh(newInstructions);
}
public revealInstruction(instruction: IInstruction) {
if (this.view.visible) {
const treeNode = new InstructionTreeNode(undefined, undefined, instruction);
this.view.reveal(treeNode, { focus: true, select: true, expand: true });
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
export interface IContractJsonModel {
abi: [];
ast: any;
bytecode: string;
compiler: any;
contractName: string;
deployedBytecode: string;
deployedSourceMap: string;
source: string;
sourceMap: string;
sourcePath: string;
networks: {
[networkId: string]: {
address: string;
};
};
}

Просмотреть файл

@ -0,0 +1,13 @@
export interface IContractModel {
address?: string;
abi: [];
ast: any;
binary: string;
compiler: any;
contractName: string;
deployedBinary: string;
deployedSourceMap: string;
source: string;
sourceMap: string;
sourcePath: string;
}

Просмотреть файл

@ -0,0 +1,4 @@
export interface IExpressionEval {
result: string;
variablesReference: number;
}

Просмотреть файл

@ -0,0 +1,4 @@
export interface IInstruction {
pc: number;
op: string;
}

Просмотреть файл

@ -0,0 +1,4 @@
export interface ITransactionInputData {
methodName: string;
params: any[];
}

Просмотреть файл

@ -0,0 +1,5 @@
export interface ITransactionResponse {
hash: string;
methodName: string;
contractName: string;
}

Просмотреть файл

@ -0,0 +1,35 @@
import { DebugProtocol } from 'vscode-debugprotocol';
import { EVENT_TYPES } from '../constants/debugAdapter';
export namespace DebuggerTypes {
export interface IBreakpoint {
id: number;
sourceId: number;
line: number;
}
export interface IFrame {
file: string;
line: number;
column: number;
}
// The interface should always match schema in the package.json.
export interface ILaunchRequestArguments extends DebugProtocol.LaunchRequestArguments {
// Automatically stop target after launch. If not specified, target does not stop.
stopOnEntry?: boolean;
// enable logging the Debug Adapter Protocol
trace?: boolean;
host?: string;
txHash: string;
files: string[];
workingDirectory: string;
providerUrl: string;
}
export class LaunchedEvent implements DebugProtocol.Event {
public event: string = EVENT_TYPES.launched;
public seq: number = 1000;
public type: string = 'event';
}
}

Просмотреть файл

@ -0,0 +1,36 @@
import * as path from 'path';
const nativeModules = [
{
jsPath: '/scrypt.js/js.js',
nodePath: '/scrypt.js/node.js',
},
{
jsPath: '/web3-eth-accounts/node_modules/scrypt.js/js.js',
nodePath: '/web3-eth-accounts/node_modules/scrypt.js/node.js',
},
];
// Override .node implementation by pure js implementation
// IMPORTANT:
// it doesn't work in 100% of cases and may stop working in future versions of node (this is not public api)
function overrideNativeModule(_module: { jsPath: string, nodePath: string }) {
try {
const absolutePathToNodeModule = path.join(__dirname, '../../../../node_modules' + _module.nodePath);
const absolutePathToJsModule = path.join(__dirname, '../../../../node_modules' + _module.jsPath);
require('module')._cache[absolutePathToNodeModule] = require(absolutePathToJsModule);
} catch (e) {
// ignore since there may be no files in node_modules
}
}
export default function overrideNativeModules() {
// IS_BUNDLE_TIME is initialized in webpack.config
// skip this process when bundle time goes since webpack already overrides by using resolve.alias
if (typeof(IS_BUNDLE_TIME) === 'undefined') {
nativeModules.forEach((m) => {
overrideNativeModule(m);
});
}
}

Просмотреть файл

@ -0,0 +1,228 @@
import { EventEmitter } from 'events';
import * as os from 'os';
import * as truffleDebugUtils from 'truffle-debug-utils';
import * as truffleDebugger from 'truffle-debugger';
import {
filterBaseContracts,
filterContractsWithAddress,
prepareContracts,
} from './contracts/contractsPrepareHelpers';
import { toWindowsPath } from './helpers';
import { DebuggerTypes } from './models/debuggerTypes';
import { IContractModel } from './models/IContractModel';
import { IInstruction } from './models/IInstruction';
const WIN32 = 'win32';
// this is a flag for the case when truffle-debugger lib is updated with custom stacktrace
const CUSTOM_STACK_TRACE_IS_INJECTED = false;
export default class RuntimeInterface extends EventEmitter {
public isDebuggerAttached: boolean;
private _session: truffleDebugger.Session | undefined;
private _selectors: truffleDebugger.Selectors;
private _numBreakpoints: number;
private _osPlatform: string;
private _contractsWithAddress: IContractModel[];
private _baseContracts: IContractModel[];
private _initialBreakPoints: Array<{ path: string, lines: number[] }>;
constructor() {
super();
this._selectors = truffleDebugger.selectors;
this._numBreakpoints = 0;
this._osPlatform = os.platform();
this.isDebuggerAttached = false;
this._initialBreakPoints = [];
this._contractsWithAddress = [];
this._baseContracts = [];
}
public clearBreakpoints(): Promise<void> {
return this._session
? this._session.removeAllBreakpoints()
: Promise.resolve();
}
public storeInitialBreakPoints(path: string, lines: number[]) {
this._initialBreakPoints.push({ path, lines });
}
public async processInitialBreakPoints() {
if (this._initialBreakPoints.length === 0) {
return;
}
for (const initialBreakPoint of this._initialBreakPoints) {
const { path, lines } = initialBreakPoint;
for (const line of lines) {
await this.setBreakpoint(path, line);
}
}
this._initialBreakPoints.length = 0; // remove them since they are no longer needed
}
public async setBreakpoint(path: string, line: number): Promise<DebuggerTypes.IBreakpoint | null> {
if (!this._session) {
return Promise.resolve(null);
}
let breakpoint: DebuggerTypes.IBreakpoint;
// we'll need the debugger-internal ID of this source
const debuggerSources: any = this._session.view(this._selectors.solidity.info.sources);
const matchingSources: any = Object.values(debuggerSources).filter((sourceObject: any) => {
const formattedSourPath = this._osPlatform === WIN32
? toWindowsPath(sourceObject.sourcePath)
: sourceObject.sourcePath;
return formattedSourPath.includes(path);
});
const sourceId = matchingSources[0].id;
breakpoint = {
id: this._numBreakpoints,
line,
sourceId,
};
this._numBreakpoints++;
await this._session.addBreakpoint(breakpoint);
return breakpoint;
}
// Get stack trace (without method name)
public callStack(): any[] {
this.validateSession();
const result: any[] = [];
const callStack = this._session!.view(this._selectors.evm.current.callstack);
const currentLine = this.currentLine();
const defaultLine = { line: 0, column: 0 };
for (const fn of callStack) {
// source is stored for previous call only
if (CUSTOM_STACK_TRACE_IS_INJECTED && fn.source) {
const { file, line, column } = fn.source;
result.push({ file, line, column, isCurrent: false });
} else {
let isCurrent: boolean;
let linesInfo: any;
// if currentLine from baseContract then consider this is current call
const isBaseContract = this._baseContracts.some((c) => c.sourcePath === currentLine.file);
if (isBaseContract) {
isCurrent = true;
linesInfo = currentLine;
} else {
const contract = this._contractsWithAddress
.find((c: any) => c.address === (fn.address || fn.storageAddress));
if (contract === undefined) {
throw new Error(`Coundn\'t find contract by address ${fn.address || fn.storageAddress}`);
}
isCurrent = contract.sourcePath === currentLine.file;
linesInfo = isCurrent ? currentLine : { file: contract.sourcePath, ...defaultLine };
}
result.push({ ...linesInfo, address: fn.address, isCurrent });
}
}
return result;
}
public async variables(/* args?: DebugProtocol.VariablesArguments */): Promise<any> {
if (this._session) {
const variables = await this._session.variables();
return truffleDebugUtils.nativize(variables);
} else {
return Promise.resolve({});
}
}
public async continue(): Promise<void> {
this.validateSession();
await this._session!.continueUntilBreakpoint();
this.processSteping('stopOnBreakpoint');
}
public continueReverse(): void {
this.validateSession();
this.sendEvent('stopOnBreakpoint');
}
public async stepNext(): Promise<void> {
this.validateSession();
await this._session!.stepNext();
this.processSteping('stopOnStepOver');
}
public async stepIn(): Promise<void> {
this.validateSession();
await this._session!.stepInto();
this.processSteping('stopOnStepIn');
}
public async stepOut(): Promise<void> {
this.validateSession();
await this._session!.stepOut();
this.processSteping('stopOnStepOut');
}
public async attach(txHash: string, workingDirectory: string): Promise<void> {
const result: any = await prepareContracts(workingDirectory);
this._contractsWithAddress = filterContractsWithAddress(result.contracts);
this._baseContracts = filterBaseContracts(result.contracts);
const debuggerOptions = {
contracts: result.contracts,
provider: result.provider,
};
const bugger = await truffleDebugger.forTx(txHash, debuggerOptions);
this._session = bugger.connect();
this.isDebuggerAttached = true;
}
public currentLine(): DebuggerTypes.IFrame {
this.validateSession();
const currentLocation = this._session!.view(this._selectors.controller.current.location);
const sourcePath = this._session!.view(this._selectors.solidity.current.source).sourcePath;
if (!sourcePath) {
throw new Error('No source file');
}
return {
column: currentLocation.sourceRange ? currentLocation.sourceRange.lines.start.column : 0,
file: sourcePath,
line: currentLocation.sourceRange ? currentLocation.sourceRange.lines.start.line : 0,
};
}
public getInstructionSteps(): IInstruction[] {
this.validateSession();
const steps: IInstruction[] = this._session!.view(this._selectors.trace.steps);
return steps;
}
public getCurrentInstructionStep(): IInstruction {
this.validateSession();
return this._session!.view(this._selectors.solidity.current.instruction) as IInstruction;
}
public sendEvent(event: string, ...args: any[]) {
setImmediate(() => {
this.emit(event, ...args);
});
}
private processSteping(event: any) {
const isEndOfTransactionTrace = this._session!.view(this._selectors.trace.finished);
if (isEndOfTransactionTrace) {
this.sendEvent('end');
} else {
this.sendEvent(event);
}
}
private validateSession() {
if (!this._session) {
throw new Error('Debug session is undefined');
}
}
}

Просмотреть файл

@ -0,0 +1,17 @@
import * as abiDecoder from 'abi-decoder';
import { TRANSACTION_DEFAULT_METHOD_NAME } from '../constants/transaction';
import { ITransactionInputData } from '../models/ITransactionInputData';
export class TransactionInputDataDecoder {
public addContractAbi(abi: []) {
abiDecoder.addABI(abi);
}
// Use addContractAbi before using decode
public async decode(txInput: string): Promise<ITransactionInputData> {
const decodedInput = abiDecoder.decodeMethod(txInput);
return decodedInput
? { methodName: decodedInput.name, params: decodedInput.params }
: { methodName: TRANSACTION_DEFAULT_METHOD_NAME, params: [] };
}
}

Просмотреть файл

@ -0,0 +1,109 @@
import { TRANSACTION_NUMBER_TO_SHOW } from '../constants/transaction';
import { ContractJsonsProvider } from '../contracts/contractJsonsProvider';
import { groupBy } from '../helpers';
import { IContractJsonModel } from '../models/IContractJsonModel';
import { ITransactionInputData } from '../models/ITransactionInputData';
import { ITransactionResponse } from '../models/ITransactionResponse';
import { Web3Wrapper } from '../web3Wrapper';
import { TransactionInputDataDecoder } from './transactionInputDataDecoder';
export class TransactionProvider {
private _web3: Web3Wrapper;
private _contractBuildsDirectory: string;
private _contractJsonsProvider: ContractJsonsProvider;
private _transactionInputDecoder: TransactionInputDataDecoder;
private _isTransactionInputDecoderReady: boolean;
constructor(web3: Web3Wrapper, contractBuildsDirectory: string) {
this._web3 = web3;
this._contractBuildsDirectory = contractBuildsDirectory;
this._contractJsonsProvider = new ContractJsonsProvider(contractBuildsDirectory);
this._transactionInputDecoder = new TransactionInputDataDecoder();
this._isTransactionInputDecoderReady = false;
}
public async getLastTransactionHashes(take: number = TRANSACTION_NUMBER_TO_SHOW): Promise<string[]> {
const latestBlockNumber = await this._web3.eth.getBlockNumber();
const latestBlock = await this._web3.eth.getBlock(latestBlockNumber);
const txHashes: string[] = [];
let block = latestBlock;
while (txHashes.length <= take && block.number > 0) {
for (let i = 0; i < block.transactions.length && txHashes.length < TRANSACTION_NUMBER_TO_SHOW; i++) {
txHashes.push(block.transactions[i]);
}
block = await this._web3.eth.getBlock(block.number - 1);
}
return txHashes;
}
public async getTransactionsInfo(txHashes: string[]): Promise<ITransactionResponse[]> {
const batchRequest = this._web3.createBatchRequest();
txHashes.forEach((txHash) => {
batchRequest.add(this._web3.eth.getTransaction, txHash);
batchRequest.add(this._web3.eth.getTransactionReceipt, txHash);
});
const result: any[] = await batchRequest.execute();
const hashKey = 'hash';
result.forEach((txI) => (txI[hashKey] = txI[hashKey] || txI.transactionHash)); // fill hash property
const groupsByHash = groupBy(result, hashKey);
const promises = Object.keys(groupsByHash).map((hash) => {
return this.buildTransactionResponse(hash, groupsByHash[hash]);
}, this);
return Promise.all(promises);
}
private async buildTransactionResponse(hash: string, infos: any[]): Promise<ITransactionResponse> {
const infoWithInput = infos.find((txInfo) => (txInfo.input)) || {};
const infoWithAddress = infos.find((txInfo) => (txInfo.to || txInfo.contractAddress)) || {};
const { methodName } = await this.getDecodedTransactionInput(infoWithInput.input);
const contractName = await this.getContractNameByAddress(infoWithAddress.to || infoWithAddress.contractAddress);
return {
contractName,
hash,
methodName,
} as ITransactionResponse;
}
private async getDecodedTransactionInput(input: string): Promise<ITransactionInputData> {
await this.prepareTransactionInputDecoder();
return this._transactionInputDecoder.decode(input);
}
private async getContractNameByAddress(address?: string): Promise<string> {
const contractJsons = await this.getContractJsons();
if (!address) { return ''; }
const currentNetworkId = await this._web3.getNetworkId();
const contractNames = Object.keys(contractJsons);
for (const contractName of contractNames) {
const contractJson = contractJsons[contractName];
const networks = contractJson.networks;
if (networks) {
const network = networks[currentNetworkId];
if (network && network.address
&& network.address.toLowerCase() === address.toLowerCase()) {
return contractName;
}
}
}
return '';
}
private async prepareTransactionInputDecoder(): Promise<void> {
if (this._isTransactionInputDecoderReady) {
return;
}
const contractJsons = await this.getContractJsons();
Object.keys(contractJsons).forEach((file) =>
(this._transactionInputDecoder.addContractAbi(contractJsons[file].abi)));
this._isTransactionInputDecoderReady = true;
}
private async getContractJsons(): Promise<{ [fileName: string]: IContractJsonModel }> {
const filesContents = await this._contractJsonsProvider.getJsonsContents();
if (Object.keys(filesContents).length === 0) {
throw new Error(`No compiled contracts found in ${this._contractBuildsDirectory}`);
}
return filesContents;
}
}

12
src/debugAdapter/types/abi-decoder.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,12 @@
declare module 'abi-decoder' {
interface Decoded {
name: string,
params: any[];
}
function addABI(abi: []): void;
function decodeMethod(input: string): Decoded;
export { addABI, decodeMethod };
}

5
src/debugAdapter/types/truffle-debug-utils.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,5 @@
declare module 'truffle-debug-utils' {
function nativize(variables: any): any;
export { nativize };
}

28
src/debugAdapter/types/truffle-debugger.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,28 @@
declare module 'truffle-debugger' {
interface Selectors {
solidity: any,
evm: any,
controller: any,
trace: any,
}
const selectors: Selectors;
interface Session {
removeAllBreakpoints: () => Promise<void>,
view: (selectors: any) => any,
addBreakpoint: (breakPoint: any) => {},
variables: () => Promise<any>,
continueUntilBreakpoint: () => Promise<void>,
stepNext: () => Promise<void>,
stepInto: () => Promise<void>,
stepOut: () => Promise<void>,
}
interface Debugger {
connect: () => Session,
}
function forTx(txHash: string, debugOptions: any): Promise<Debugger>;
export { selectors, Selectors, forTx, Session };
}

10
src/debugAdapter/types/truffle-provider.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,10 @@
declare module 'truffle-provider' {
interface IProviderOptions {
provider?: any,
host?: string,
port?: number,
websockets?: boolean,
}
function create(networkOptions: IProviderOptions): any;
export { create };
}

50
src/debugAdapter/types/web3.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,50 @@
declare module Web3 {
interface IProvider {
currentProvider: any,
}
interface IBlockResponse {
number: number;
transactions: string[];
}
interface ITransactionResponse {
input: string // encoded methodName and params
}
interface ITransactionReceiptResponse {
from: string;
to?: string;
contractAddress?: string;
}
interface IBatchRequest {
add: (request: any) => void,
execute: () => Promise<any>,
}
interface IRequest {
request: (...args: any[]) => any;
}
interface IWeb3Eth extends IProvider {
net: {
getId: () => Promise<number>,
},
getBlock: any;
getTransaction: any;
getTransactionReceipt: any;
getBlockNumber: any;
currentProvider: any;
}
const providers: {
HttpProvider: new(providerUrl: string) => IProvider,
WebsocketProvider: new(providerUrl: string) => IProvider,
};
}
declare class Web3 {
constructor (provider: Web3.IProvider);
eth: Web3.IWeb3Eth;
BatchRequest: new() => Web3.IBatchRequest;
}
declare module 'web3' {
export = Web3;
}

1
src/debugAdapter/types/webpackVariables.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
declare const IS_BUNDLE_TIME: boolean | undefined;

Просмотреть файл

@ -0,0 +1,116 @@
import { Handles, Scope } from 'vscode-debugadapter';
import { DebugProtocol } from 'vscode-debugprotocol';
import { SCOPES } from './constants/variablesView';
import { IExpressionEval } from './models/IExpressionEval';
import RuntimeInterface from './runtimeInterface';
export default class VariablesHandler {
private _runtime: RuntimeInterface;
private _scopes: Scope[];
private _handlers: Handles<string>;
constructor(runtime: RuntimeInterface) {
this._runtime = runtime;
this._scopes = new Array<Scope>();
this._scopes.push(new Scope(SCOPES.all.name, SCOPES.all.ref, false));
this._handlers = new Handles<string>(SCOPES.dynamicVariables.ref);
}
public getScopes(): Scope[] {
return this._scopes;
}
public getHandlers(): Handles<string> {
return this._handlers;
}
public async getVariableAttributesByVariableRef(variablesReference: number)
: Promise<DebugProtocol.Variable[]> {
let result: DebugProtocol.Variable[] = [];
let variable: any;
let variablePath: string = '';
switch (variablesReference) {
case SCOPES.all.ref:
variable = await this._runtime.variables();
result = this.mapToDebuggableVariables(variablePath, variable);
break;
default: // requesting object variable
variablePath = this._handlers.get(variablesReference);
variable = await this._runtime.variables();
variable = this.getVariableAttriburesByKeyPath(variablePath, variable);
result = this.mapToDebuggableVariables(variablePath, variable);
}
return result;
}
// expression = "attribute"
// expression = "parent.childA.child1"
public async evaluateExpression(expression: string): Promise<IExpressionEval> {
const variablesObj = await this._runtime.variables();
const variable = this.getVariableAttriburesByKeyPath(expression, variablesObj);
const isObjType = this.isSpecificObjectTypeValue(variable, typeof(variable));
const isUndefinedOrNull = (v: any) => (v === undefined || v === null);
return {
result: isObjType
? 'Object'
: isUndefinedOrNull(variable)
? typeof(variable)
: variable.toString(),
variablesReference: isObjType
? this._handlers.create(this.generateVariablesAttrKey('', expression))
: 0,
};
}
private isSpecificObjectTypeValue(value: any, valueType: string) {
return !Array.isArray(value)
&& value !== null
&& value !== undefined
&& valueType === 'object';
}
// replace "." by "/" and generate "path/to/variable"
private generateVariablesAttrKey(variablePath: string, attribute: string): string {
return `${variablePath.replace(/\./g, '/')}/${attribute.replace(/\./g, '/')}`;
}
private getVariableAttriburesByKeyPath(keyPath: string, variable: any): any {
// keyPath = "/parent/childA/child1"
// or keyPath = "parent.childA.child1"
let keys = keyPath.split(/\/|\./);
if (keys[0] === '') {
keys = keys.slice(1);
}
try {
keys.forEach((key) => {
variable = variable[key];
});
} catch (e) {
return undefined;
}
return variable;
}
private mapToDebuggableVariables(variablePath: string, variable: any): DebugProtocol.Variable[] {
const result: DebugProtocol.Variable[] = [];
for (const attr in variable) {
if (variable.hasOwnProperty(attr)) {
const value = variable[attr];
const type = typeof (value);
const isRef = this.isSpecificObjectTypeValue(value, type);
result.push({
name: attr,
type,
value: isRef ? 'Object' : JSON.stringify(value),
variablesReference: isRef
? this._handlers.create(this.generateVariablesAttrKey(variablePath, attr))
: 0,
});
}
}
return result;
}
}

Просмотреть файл

@ -0,0 +1,20 @@
// This is a script for truffle exec command. The purpose is to resolve the provider
// We cann't use truffle-config since it requires specific "npm install" command in user project
// (npm install against the concrete version of electron the VS Code is built on)
module.exports = function(callback) {
var currentProvider = web3.eth.currentProvider;
var providerUrl = '';
var protocol = '';
var constructor = currentProvider.constructor + '';
if (constructor.indexOf('HttpProvider') !== -1) {
protocol = 'http';
providerUrl = currentProvider.host;
}
if (constructor.indexOf('WebsocketProvider') !== -1) {
protocol = 'ws';
providerUrl = currentProvider.connection.url;
}
var result = 'provider%=' + JSON.stringify({ url: providerUrl, protocol: protocol });
console.log(result);
callback();
}

Просмотреть файл

@ -0,0 +1,107 @@
import * as truffleProvider from 'truffle-provider';
import * as web3 from 'web3';
import { TruffleConfiguration } from '../helpers/truffleConfig';
export class Web3Wrapper extends web3 {
private _networkId: number | undefined;
private _options: TruffleConfiguration.INetworkOption;
private _cachedProvider: any;
public constructor(options: TruffleConfiguration.INetworkOption) {
const innerProvider = getWeb3InnerProvider(options);
super(innerProvider);
this._options = options;
}
// Important! It's a truffle provider instance (from truffle-provider library)
public async getProvider() {
if (!this._cachedProvider) {
const web3Provider = this.eth.currentProvider;
const truffleProviderOptions = {
provider: () => {
return web3Provider;
},
};
this._cachedProvider = truffleProvider.create(truffleProviderOptions);
}
return this._cachedProvider;
}
public getProviderUrl() {
return getProviderUrl(this._options);
}
public async getNetworkId(): Promise<number> {
if (!this._networkId) {
this._networkId = await this.eth.net.getId();
this.validateNetworkId(this._networkId);
}
return this._networkId;
}
public createBatchRequest() {
return PromiseBatch(new this.BatchRequest());
}
private async validateNetworkId(networkId: number) {
const declaredNetworkId = this._options.network_id;
if (declaredNetworkId !== '*') {
if (networkId.toString() !== declaredNetworkId.toString()) {
const error =
'The network id specified in the truffle config ' +
`(${networkId}) does not match the one returned by the network ` +
`(${declaredNetworkId}). Ensure that both the network and the ` +
'provider are properly configured.';
throw new Error(error);
}
}
}
}
function PromiseBatch(batch: web3.IBatchRequest) {
const _batch = batch;
const _requests: Array<Promise<any>> = [];
return {
add(method: any, hash: string) {
const promise = new Promise<any>((accept, reject) => {
_batch.add(method.request(hash, (error: any, result: any) => {
if (error) {
reject(error);
}
accept(result);
}));
});
_requests.push(promise);
},
async execute(): Promise<any> {
_batch.execute();
return Promise.all(_requests);
},
};
}
function getProviderUrl(options: TruffleConfiguration.INetworkOption): string {
if (options.host && options.port) {
const protocol = options.websockets ? 'ws' : 'http';
return `${protocol}://${options.host}:${options.port}`;
}
if (options.provider && options.provider.url) {
return options.provider.url;
}
throw new Error('Undefined network options.');
}
function getWeb3InnerProvider(options: TruffleConfiguration.INetworkOption): web3.IProvider {
const providerUrl = getProviderUrl(options);
let provider: web3.IProvider;
if (providerUrl.startsWith('ws')) {
provider = new web3.providers.WebsocketProvider(providerUrl);
} else {
provider = new web3.providers.HttpProvider(providerUrl);
}
return provider;
}

8
src/debugger.ts Normal file
Просмотреть файл

@ -0,0 +1,8 @@
// workaround to load native modules
// (since https://github.com/Microsoft/vscode/issues/658 doesn't work on win10)
import nativeModulesLoader from './debugAdapter/nativeModules/loader';
nativeModulesLoader();
import { SolidityDebugSession } from './debugAdapter/debugSession';
SolidityDebugSession.run(SolidityDebugSession);

Просмотреть файл

@ -1,29 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// workaround to load native modules
// (since https://github.com/Microsoft/vscode/issues/658 doesn't work on win10)
import nativeModulesLoader from './debugAdapter/nativeModules/loader';
nativeModulesLoader();
import { commands, ExtensionContext, Uri, window } from 'vscode';
import { AzureBlockchain } from './AzureBlockchain';
import { ConsortiumCommands } from './commands/ConsortiumCommands';
import { ContractCommands } from './commands/ContractCommands';
import { startSolidityDebugger } from './commands/DebuggerCommands';
import { GanacheCommands } from './commands/GanacheCommands';
import { LogicAppCommands } from './commands/LogicAppCommands';
import { ProjectCommands } from './commands/ProjectCommands';
import { TruffleCommands } from './commands/TruffleCommands';
import { Constants } from './Constants';
import { GanacheService } from './GanacheService/GanacheService';
import { CommandContext, isWorkspaceOpen, required, setCommandContext } from './helpers';
import { MnemonicRepository } from './MnemonicService/MnemonicRepository';
import { CancellationEvent } from './Models';
import { Output } from './Output';
import { RequirementsPage, WelcomePage } from './pages';
import { AdapterType, ContractDB, GanacheService, MnemonicRepository } from './services';
import { Telemetry } from './TelemetryClient';
import { ConsortiumTree } from './treeService/ConsortiumTree';
import { ConsortiumTreeManager } from './treeService/ConsortiumTreeManager';
import { ConsortiumView } from './ViewItems';
import { DebuggerConfiguration } from './debugAdapter/configuration/debuggerConfiguration';
export async function activate(context: ExtensionContext) {
Constants.initialize(context);
MnemonicRepository.initialize(context.globalState);
DebuggerConfiguration.initialize(context);
setCommandContext(CommandContext.Enabled, true);
setCommandContext(CommandContext.IsWorkspaceOpen, isWorkspaceOpen());
@ -34,6 +41,7 @@ export async function activate(context: ExtensionContext) {
const consortiumTree = new ConsortiumTree(consortiumTreeManager);
await welcomePage.checkAndShow();
await ContractDB.initialize(AdapterType.IN_MEMORY);
window.registerTreeDataProvider('AzureBlockchain', consortiumTree);
//#region azureBlockchain extension commands
@ -47,10 +55,6 @@ export async function activate(context: ExtensionContext) {
async (checkShowOnStartup: boolean) => {
return checkShowOnStartup ? requirementsPage.checkAndShow() : requirementsPage.show();
});
const copyRPCEndpointAddress = commands.registerCommand('azureBlockchainService.copyRPCEndpointAddress',
async (viewItem: ConsortiumView) => {
await tryExecute(() => AzureBlockchain.copyRPCEndpointAddress(viewItem));
});
//#endregion
//#region Ganache extension commands
@ -81,6 +85,10 @@ export async function activate(context: ExtensionContext) {
const copyABI = commands.registerCommand('contract.copyABI', async (uri: Uri) => {
await tryExecute(() => TruffleCommands.writeAbiToBuffer(uri));
});
const copyRPCEndpointAddress = commands.registerCommand('azureBlockchainService.copyRPCEndpointAddress',
async (viewItem: ConsortiumView) => {
await tryExecute(() => TruffleCommands.writeRPCEndpointAddressToBuffer(viewItem));
});
const getPrivateKeyFromMnemonic = commands.registerCommand('azureBlockchainService.getPrivateKey', async () => {
await tryExecute(() => TruffleCommands.getPrivateKeyFromMnemonic());
});
@ -99,10 +107,12 @@ export async function activate(context: ExtensionContext) {
});
//#endregion
//#region remix commands
const generateSmartContractUI = commands.registerCommand('drizzle.generateSmartContractUI', async () => {
await tryExecute(() => ContractCommands.generateSmartContractUI());
});
//#region contract commands
const showSmartContractPage = commands.registerCommand(
'azureBlockchainService.showSmartContractPage',
async (contractPath: Uri) => {
await tryExecute(() => ContractCommands.showSmartContractPage(context, contractPath));
});
//#endregion
//#region logic app commands
@ -128,9 +138,12 @@ export async function activate(context: ExtensionContext) {
});
//#endregion
const startDebugger = commands.registerCommand(
'extension.truffle.debugTransaction', startSolidityDebugger);
context.subscriptions.push(showWelcomePage);
context.subscriptions.push(showRequirementsPage);
context.subscriptions.push(generateSmartContractUI);
context.subscriptions.push(showSmartContractPage);
context.subscriptions.push(refresh);
context.subscriptions.push(newSolidityProject);
context.subscriptions.push(buildContracts);
@ -148,6 +161,7 @@ export async function activate(context: ExtensionContext) {
context.subscriptions.push(generateEventPublishingWorkflows);
context.subscriptions.push(generateReportPublishingWorkflows);
context.subscriptions.push(getPrivateKeyFromMnemonic);
context.subscriptions.push(startDebugger);
Telemetry.sendEvent(Constants.telemetryEvents.extensionActivated);
return required.checkAllApps();
@ -158,6 +172,7 @@ export async function deactivate(): Promise<void> {
await Output.dispose();
await Telemetry.dispose();
await GanacheService.dispose();
await ContractDB.dispose();
}
async function tryExecute(func: () => Promise<any>, errorMessage: string | null = null): Promise<void> {

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше