Skip to content

Commit

Permalink
Merge branch 'master' into create-versions-dropdown
Browse files Browse the repository at this point in the history
  • Loading branch information
fergarrui authored Jun 2, 2019
2 parents 2fcac87 + 3ed4a34 commit 582038f
Show file tree
Hide file tree
Showing 14 changed files with 128 additions and 60 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ dist/*
.DS_Store
coverage/*
.vscode/*
contracts/*
18 changes: 18 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM node:8-alpine

RUN apk update && apk upgrade && \
apk add --no-cache bash git python g++ gcc libgcc libstdc++ linux-headers make

RUN npm install --quiet node-gyp -g

WORKDIR /opt/app

COPY package.json ./

RUN npm i -g npm@^6.1.0 && npm install

COPY . .

EXPOSE 9090

CMD [ "npm", "run", "start" ]
37 changes: 9 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ There are already tools that allow you to debug Ethereum transactions (Solidity)

Use release link:

* [v3.5.0](https://github.com/fergarrui/ethereum-graph-debugger/releases/tag/v3.5.0)
* [v3.6.0](https://github.com/fergarrui/ethereum-graph-debugger/releases/tag/v3.6.0)

If you want to use master (it can be more unstable), clone and start the application

Expand All @@ -39,6 +39,14 @@ npm start

Go to localhost:9090

### With docker

`docker-compose up`

Go to localhost:9090

Add contracts to `./contracts` (you will need to create the directory) and empty string at the 'Load contracts from URI' form.

### Use

* Go to localhost:9090
Expand All @@ -53,33 +61,6 @@ Go to localhost:9090
* Scan the directory as described above
* You won't get source code mappings when clicking in operations of the CFG

### [Temporal] - switch solc version

Current `solc` version is `0.5.8`, if you want to use an earlier version, for now it can only be done via API (UI coming soon)

* List supported `solc` versions
* `curl http://localhost:9090/solc/list`
* Output will be like:
```
[
{
"version": "0.5.8",
"commit": "v0.5.8+commit.23d335f2"
},
{
"version": "0.5.7",
"commit": "v0.5.7+commit.6da8b019"
},
{
"version": "0.5.6",
"commit": "v0.5.6+commit.b259423e"
},
(... truncated ...)
```
* Select a commit, for example: `v0.5.6+commit.b259423e`
* Set the version to that commit: `curl -X POST --data '{"version": "v0.5.8+commit.23d335f2"}' http://localhost:9090/solc -H 'Content-Type: application/json'`
* Check what version is loaded: `curl http://localhost:9090/solc`
# Features

* **Now interactive** :star2:: it has a sepparate frontend and API instead of building a static HTML file like in earlier versions
Expand Down
22 changes: 22 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: '3.4'

services:
debugger:
image: ethereum-graph-debugger
build:
context: ./
environment:
- BLOCKCHAIN_HOST=ganache:8545
ports:
- 9090:9090
volumes:
- ./src:/opt/app/src:delegated
- ./contracts:/opt/app/contracts:delegated

ganache:
image: trufflesuite/ganache-cli:v6.1.6
container_name: ganache
restart: always
command: -l 804247552 -h 0.0.0.0 -d
ports:
- 8545:8545
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "ethereum-graph-debugger",
"author": "Fernando Garcia",
"license": "GPL",
"version": "3.5.0",
"version": "3.6.0",
"description": "Ethereum graph debugger",
"main": "dist/run-server.js",
"scripts": {
Expand Down
4 changes: 2 additions & 2 deletions src/api/blockchain/Web3Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ export class Web3Instance implements IWeb3 {
constructor(config: Web3Configuration) {
const isEmpty = Object.values(config).every(x => (!x) || x === '')
if (isEmpty) {
this.web3Instance = new Web3('http://127.0.0.1:8545')
this.web3Instance = new Web3(process.env.BLOCKCHAIN_HOST? `http://${process.env.BLOCKCHAIN_HOST }`:'http://127.0.0.1:8545')
} else {
const protocol = config.blockchainProtocol || 'http'
const url = config.blockchainHost || '127.0.0.1:8545'
const url = config.blockchainHost || process.env.BLOCKCHAIN_HOST || '127.0.0.1:8545'
let blockchainUrl = `${protocol}://`
if (config.blockchainBasicAuthUsername && config.blockchainBasicAuthPassword) {
blockchainUrl += `${config.blockchainBasicAuthUsername}:${config.blockchainBasicAuthPassword}@`
Expand Down
25 changes: 22 additions & 3 deletions src/api/service/controller/ContractController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { logger } from '../../../Logger';
import { RunContractFunctionRequest } from '../request/RunContractFunctionRequest';
import { Web3Configuration } from '../../blockchain/Web3Configuration';
import { DeployContractRequest } from '../request/DeployContractRequest';
import { StringBodyRequest } from '../request/StringBodyRequest';

@Route('contract')
@provideSingleton(ContractController)
Expand All @@ -17,14 +18,14 @@ export class ContractController extends Controller {
super()
}

@Get('abi')
@Post('abi')
async getAbi(
@Query('source') source: string,
@Body() source: StringBodyRequest,
@Query('name') name: string,
@Query('path') path: string
){
try {
const abi = this.contractService.getAbi(name, source, path)
const abi = this.contractService.getAbi(name, source.request, path)
if (!abi) {
throw new Error(`No abi found for contract ${name}`)
}
Expand All @@ -35,6 +36,24 @@ export class ContractController extends Controller {
}
}

@Post('functions')
async getFunctions(
@Body() source: StringBodyRequest,
@Query('name') name: string,
@Query('path') path: string
){
try {
const functions = this.contractService.getFunctions(name, source.request, path)
if (!functions) {
throw new Error(`No abi found for contract ${name}`)
}
return functions
} catch (err) {
logger.error(err)
throw new Error(err.message)
}
}

@Post('deploy')
async deploy(
@Body() deployRequest: DeployContractRequest,
Expand Down
2 changes: 0 additions & 2 deletions src/api/service/controller/ControlFlowGraphController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import { GFCResponse } from '../response/CFGResponse'
import { OperationResponse } from '../response/OperationResponse'
import { logger } from '../../../Logger'
import { StringBodyRequest } from '../request/StringBodyRequest';
const fs = require('fs')

@Route('cfg')
@provideSingleton(ControlFlowGraphController)
Expand All @@ -33,7 +32,6 @@ export class ControlFlowGraphController extends Controller {
throw new Error('Constructor is true but no constructor found in bytecode')
}
const cfg = this.createCFG(contractBlocks, constructor)
await fs.writeFileSync('cfg', cfg)
return this.buildResponse(contractBlocks, constructor, cfg)
} catch (err) {
logger.error(err)
Expand Down
6 changes: 5 additions & 1 deletion src/api/service/controller/FileController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ export class FileController extends Controller {
@Get('{dir}')
async findContractsInDir(@Path() dir: string, @Query('extension') extension: string): Promise<ContractFile[]> {
try {
const contracts = await this.fileService.findContractssWithExtension(dir, extension)
let directory = dir
if (!dir || dir === '' || dir === ' ') {
directory = './contracts'
}
const contracts = await this.fileService.findContractssWithExtension(directory, extension)
if (contracts.length === 0) {
throw new Error(`No contracts found at ${dir} with extension ${extension}`)
}
Expand Down
5 changes: 5 additions & 0 deletions src/api/service/service/ContractService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ export class ContractService {
return contract.abi
}

getFunctions(contractName: string, source: string, path: string): any {
const contract = this.compileContract(contractName, source, path)
return contract.abi.filter(abi => abi.type === 'function' || abi.type === 'constructor')
}

compileContract(contractName: string, source: string, path: string): any {
const compileJson = this.generateCompileObject(contractName, source, path)
const compiledContract = JSON.parse(this.solc.getInstance().compileStandardWrapper(JSON.stringify(compileJson)))
Expand Down
33 changes: 14 additions & 19 deletions src/client/components/Main/Main.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,7 @@ import { connect } from 'react-redux';
import { CSSTransitionGroup } from 'react-transition-group';

import { showLoadingMessage, hideLoadingMessage, showErrorMessage } from '../Store/Actions.js';

<<<<<<< Updated upstream
import baseurl from '../../utils/baseUrl';
=======
import { baseUrl } from '../../utils/baseUrl';
>>>>>>> Stashed changes

import Editor from '../Editor/Editor';
import SideBar from '../SideBar/SideBar';
import Tab from '../Tab/Tab';
Expand Down Expand Up @@ -67,7 +61,7 @@ class Main extends React.Component {
}

getUrl(endPoint, parameters) {
let url = baseurl + endPoint;
let url = baseUrl + endPoint;
const paramKeys = Object.keys(parameters);

if (paramKeys.length) {
Expand All @@ -79,12 +73,13 @@ class Main extends React.Component {

fetchData(url, type, body) {
this.handleRequestPending();

fetch(url, body ? {
body: JSON.stringify({request: body}),
method: 'POST',
headers:{
'Content-Type': 'application/json'
}
body: JSON.stringify({ request: body }),
method: 'POST',
headers:{
'Content-Type': 'application/json'
}
} : {})
.then(res => res.json())
.then(data => {
Expand All @@ -93,7 +88,7 @@ class Main extends React.Component {
: this.handleRequestSuccess(data, type);
})
.catch(err => this.handleRequestFail(err));
}
}

handleRequestPending() {
this.setState({
Expand All @@ -120,7 +115,7 @@ class Main extends React.Component {
});
}

if(type === 'Control Flow Graph') {
if(type === 'Control Flow Graph Constructor' || 'Control Flow Graph Runtime') {
this.setState({
graphResponse: response,
});
Expand Down Expand Up @@ -204,15 +199,14 @@ class Main extends React.Component {
});
}

handleControlFlowGraphClick() {
handleControlFlowGraphClick(isConstructor) {
const { name, path, code } = this.props;

const params = {
name: name.replace('.sol', '').replace('.evm', ''),
path: encodeURIComponent(path),
'constructor': 'false'
'constructor': `${isConstructor}`
}
this.fetchData(this.getUrl('cfg/source', params), 'Control Flow Graph', code);
this.fetchData(this.getUrl('cfg/source', params), `Control Flow Graph ${isConstructor ? 'Constructor' : 'Runtime'}`, code);

document.removeEventListener('click', this.handleOutsideClick);
}
Expand Down Expand Up @@ -307,7 +301,8 @@ class Main extends React.Component {
<SideBar
onDisassemblerClick={() => this.handleDisassemblerClick()}
onTransactionDebuggerClick={() => this.handleTransactionDebuggerClick()}
onControlFlowGraphClick={() => this.handleControlFlowGraphClick()}
onControlFlowGraphRuntimeClick={() => this.handleControlFlowGraphClick(false)}
onControlFlowGraphConstructorClick={() => this.handleControlFlowGraphClick(true)}
onViewStorageClick={() => this.handleViewStorageClick()}
/>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/client/components/TopNavBar/TopNavBar.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ class TopNavBar extends React.Component {
</div>
}
<button className={styles['top-navbar__versions-dropdown__toggler']} onClick={() => this.handleVersionsButtonClick()}>
<span>Solc Version</span>
<span>Change solc version</span>
</button>
<Dropdown active={!!versionsVisible}>
{ fetchRequestStatus === 'success' && versions.length &&
Expand Down
29 changes: 27 additions & 2 deletions src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const models: TsoaRoute.Models = {
"SaveFileRequest": {
"properties": {
"path": { "dataType": "string", "required": true },
"name": { "dataType": "string", "required": true },
"content": { "dataType": "string", "required": true },
},
},
Expand Down Expand Up @@ -322,10 +323,10 @@ export function RegisterRoutes(app: express.Express) {
const promise = controller.getStorage.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, next);
});
app.get('/contract/abi',
app.post('/contract/abi',
function(request: any, response: any, next: any) {
const args = {
source: { "in": "query", "name": "source", "required": true, "dataType": "string" },
source: { "in": "body", "name": "source", "required": true, "ref": "StringBodyRequest" },
name: { "in": "query", "name": "name", "required": true, "dataType": "string" },
path: { "in": "query", "name": "path", "required": true, "dataType": "string" },
};
Expand All @@ -346,6 +347,30 @@ export function RegisterRoutes(app: express.Express) {
const promise = controller.getAbi.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, next);
});
app.post('/contract/functions',
function(request: any, response: any, next: any) {
const args = {
source: { "in": "body", "name": "source", "required": true, "ref": "StringBodyRequest" },
name: { "in": "query", "name": "name", "required": true, "dataType": "string" },
path: { "in": "query", "name": "path", "required": true, "dataType": "string" },
};

let validatedArgs: any[] = [];
try {
validatedArgs = getValidatedArgs(args, request);
} catch (err) {
return next(err);
}

const controller = iocContainer.get<ContractController>(ContractController);
if (typeof controller['setStatus'] === 'function') {
(<any>controller).setStatus(undefined);
}


const promise = controller.getFunctions.apply(controller, validatedArgs as any);
promiseHandler(controller, promise, response, next);
});
app.post('/contract/deploy',
function(request: any, response: any, next: any) {
const args = {
Expand Down

0 comments on commit 582038f

Please sign in to comment.