Skip to content

Commit 6042daa

Browse files
committed
Adding multiple contracts support!
1 parent 294ba27 commit 6042daa

File tree

14 files changed

+616
-284
lines changed

14 files changed

+616
-284
lines changed

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,23 @@ There are already tools that allow you to debug Ethereum transactions (Solidity)
3636

3737
# Features
3838

39-
* It compiles the provided source code and draws a control flow graph
39+
* It compiles the provided source code and draws a control flow graph (also imported contracts)
4040
* From the provided transaction hash (using `debug_traceTransaction`) it gets the trace of the transaction
4141
* Combines the control flow graph with the execution trace, highlighting the executed instructions in red
42+
* Supports contracts calls. All contracts involved in the transaction can be debugged
4243
* Instructions opcodes can be clicked in the graph, and the Solidity source code in the left panel involving that instruction is highlighted.
4344
* For the selected opcode, The EVM state is shown (stack, memory, storage, gas, gasCost)
4445

4546
# Limitations
4647

4748
For now there are many limitations since this is a very early release of the debugger
4849

49-
* It does not debug the code executed to an external contract call
5050
* The control flow graph is drawn from the static bytecode, so there can be nodes without edges, a symbolic execution would be needed. Maybe added in future releases
5151
* Transactions executed in the runtime bytecode are supported (for example, the constructor execution of a contract cannot be debugged right now)
5252
* You must provide a node URL that supports `debug_traceTransaction`, like Geth or Ganache, therefore, Infura is not supported
53+
* It only supports Solidity for now, but planning to make it more modular to support different languages (if the compiler gives source mappings)
5354
* The interface is quite ugly, but usable
55+
* Not really a limitation, but the editor syntax highlighting is set to Javascript at the moment
5456
* Probably many more
5557

5658
# Build the project

src/main/java/net/nandgr/debugger/Main.java

Lines changed: 94 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@
33
import com.fasterxml.jackson.core.JsonProcessingException;
44
import com.fasterxml.jackson.databind.ObjectMapper;
55
import net.nandgr.debugger.cfg.CFGCreatorDefault;
6+
import net.nandgr.debugger.cfg.ContractObject;
67
import net.nandgr.debugger.cfg.beans.BytecodeChunk;
78
import net.nandgr.debugger.cfg.beans.ContractBytecode;
89
import net.nandgr.debugger.cfg.beans.OpcodeSource;
910
import net.nandgr.debugger.cfg.graphviz.GraphVizCreator;
1011
import net.nandgr.debugger.disassembler.DisassemblerException;
1112
import net.nandgr.debugger.disassembler.LinkedDisassembler;
13+
import net.nandgr.debugger.node.response.json.GetCodeResponse;
1214
import net.nandgr.debugger.report.Report;
1315
import net.nandgr.debugger.report.ReportException;
1416
import net.nandgr.debugger.solc.Solc;
1517
import net.nandgr.debugger.solc.solcjson.Code;
1618
import net.nandgr.debugger.solc.solcjson.Contract;
1719
import net.nandgr.debugger.solc.solcjson.SolcOutput;
18-
import net.nandgr.debugger.trace.TraceService;
19-
import net.nandgr.debugger.trace.response.json.DebugTraceTransactionLog;
20+
import net.nandgr.debugger.node.NodeService;
21+
import net.nandgr.debugger.node.response.json.DebugTraceTransactionLog;
22+
2023
import java.io.File;
2124
import java.io.IOException;
2225
import java.nio.file.Files;
2326
import java.nio.file.Path;
2427
import java.nio.file.Paths;
28+
import java.util.ArrayList;
2529
import java.util.List;
2630
import java.util.Map;
2731

@@ -35,7 +39,7 @@ public static void main(String[] args){
3539
.getLocation()
3640
.getPath())
3741
.getName();
38-
System.out.println("Help: " + execName + " <solidity source file> <node URL> <transaction hash>");
42+
System.out.println("Help: java -jar " + execName + " <solidity source file> <node URL> <transaction hash>");
3943
System.exit(0);
4044
}
4145

@@ -50,24 +54,19 @@ public static void main(String[] args){
5054
System.exit(0);
5155
}
5256

53-
TraceService traceService = new TraceService(nodeUrl);
54-
Map<Integer, DebugTraceTransactionLog> traceData = null;
57+
NodeService nodeService = new NodeService(nodeUrl);
58+
59+
Map<String, Map<Integer, DebugTraceTransactionLog>> traceDataResponse = null;
60+
5561
try {
56-
traceData = traceService.getTraceData(txHash);
62+
nodeService.populateTraceDataResponse(txHash);
63+
traceDataResponse = nodeService.getAddressTrace();
5764
} catch (IOException e) {
58-
System.out.println("Failed when getting transaction trace");
5965
e.printStackTrace();
6066
System.exit(0);
6167
}
68+
6269
Path path = Paths.get(solidityFile);
63-
String sourceCode = null;
64-
try {
65-
sourceCode = new String(Files.readAllBytes(path));
66-
} catch (IOException e) {
67-
System.out.println("Failed when reading source code from source file");
68-
e.printStackTrace();
69-
System.exit(0);
70-
}
7170
String fileName = path.getFileName().toString();
7271
String contractName = fileName.substring(0, fileName.lastIndexOf("."));
7372

@@ -80,39 +79,90 @@ public static void main(String[] args){
8079
e.printStackTrace();
8180
System.exit(0);
8281
}
83-
84-
Contract contract = solcOutput.getContracts().get(solidityFile + ":" + contractName);
85-
List<Code> asmCode = contract.getAsm().getData().get("0").getCode();
86-
String code = contract.getBinRuntime();
87-
88-
LinkedDisassembler disassembler = new LinkedDisassembler(code);
89-
List<OpcodeSource> opcodeSources = null;
90-
try {
91-
opcodeSources = disassembler.getOpcodeSources(asmCode);
92-
} catch (DisassemblerException e) {
93-
System.out.println("Failed when disassembling");
94-
e.printStackTrace();
95-
System.exit(0);
82+
Map<String, Contract> solcContracts = solcOutput.getContracts();
83+
84+
List<ContractObject> contracts = new ArrayList<>();
85+
for (Map.Entry<String, Map<Integer, DebugTraceTransactionLog>> stringMapEntry : traceDataResponse.entrySet()) {
86+
String contractAddress = stringMapEntry.getKey();
87+
Map<Integer, DebugTraceTransactionLog> contractTrace = stringMapEntry.getValue();
88+
String contractPath = "";
89+
String cName = "";
90+
if (contractAddress.equals(NodeService.EMPTY_ADDRESS)) {
91+
contractPath = solidityFile;
92+
cName = contractName;
93+
} else {
94+
GetCodeResponse contractCodeFromChain = null;
95+
try {
96+
contractCodeFromChain = nodeService.getContractCode(contractAddress);
97+
for (Map.Entry<String, Contract> stringContractEntry : solcContracts.entrySet()) {
98+
String asmRuntime = LinkedDisassembler.cleanData(stringContractEntry.getValue().getBinRuntime())[0];
99+
String chainRuntime = LinkedDisassembler.cleanData(contractCodeFromChain.getResult())[0];
100+
if (asmRuntime.equals(chainRuntime)) {
101+
System.out.println(contractAddress);
102+
System.out.println(stringContractEntry.getKey());
103+
String key = stringContractEntry.getKey();
104+
String[] split = key.split(":");
105+
contractPath = split[0];
106+
cName = split[1];
107+
break;
108+
}
109+
}
110+
} catch (IOException e) {
111+
e.printStackTrace();
112+
}
113+
114+
}
115+
116+
Contract contract = solcContracts.get(contractPath + ":" + cName);
117+
List<Code> asmCode = contract.getAsm().getData().get("0").getCode();
118+
String code = contract.getBinRuntime();
119+
120+
121+
LinkedDisassembler disassembler = new LinkedDisassembler(code);
122+
List<OpcodeSource> opcodeSources = null;
123+
try {
124+
opcodeSources = disassembler.getOpcodeSources(asmCode);
125+
} catch (DisassemblerException e) {
126+
System.out.println("Failed when disassembling: " + contractAddress + " - " + contractPath);
127+
e.printStackTrace();
128+
System.exit(0);
129+
}
130+
path = Paths.get(contractPath);
131+
Map<Integer, DebugTraceTransactionLog> traceData = traceDataResponse.get(contractAddress);
132+
String sourceCode = null;
133+
try {
134+
sourceCode = new String(Files.readAllBytes(path));
135+
} catch (IOException e) {
136+
System.out.println("Failed when reading source code from source file");
137+
e.printStackTrace();
138+
System.exit(0);
139+
}
140+
141+
String traceMapJson = null;
142+
try {
143+
traceMapJson = objectMapper.writeValueAsString(traceData);
144+
} catch (JsonProcessingException e) {
145+
System.out.println("Failed when mapping trace data");
146+
e.printStackTrace();
147+
System.exit(0);
148+
}
149+
150+
CFGCreatorDefault cfgCreatorDefault = new CFGCreatorDefault();
151+
152+
ContractBytecode contractBytecode = cfgCreatorDefault.createContractBytecode(opcodeSources);
153+
Map<Integer, BytecodeChunk> runtimeChunks = contractBytecode.getRuntime().getChunks();
154+
155+
GraphVizCreator graphVizCreator = new GraphVizCreator(runtimeChunks, traceData, cName);
156+
String graph = graphVizCreator.buildStringGraph();
157+
158+
ContractObject contractObject = new ContractObject(cName, contractPath, traceData, contractAddress, code,
159+
traceMapJson, opcodeSources, sourceCode, graph);
160+
contracts.add(contractObject);
96161
}
97162

98-
CFGCreatorDefault cfgCreatorDefault = new CFGCreatorDefault();
99-
100-
ContractBytecode contractBytecode = cfgCreatorDefault.createContractBytecode(opcodeSources);
101-
Map<Integer, BytecodeChunk> runtimeChunks = contractBytecode.getRuntime().getChunks();
102-
103-
String traceMapJson = null;
104-
try {
105-
traceMapJson = objectMapper.writeValueAsString(traceData);
106-
} catch (JsonProcessingException e) {
107-
System.out.println("Failed when mapping trace data");
108-
e.printStackTrace();
109-
System.exit(0);
110-
}
111163

112-
GraphVizCreator graphVizCreator = new GraphVizCreator(runtimeChunks, traceData);
113-
String graph = graphVizCreator.buildStringGraph();
114164

115-
Report report = new Report(sourceCode, traceMapJson, graph, solidityFile, txHash);
165+
Report report = new Report(contracts, txHash);
116166
String reportName = null;
117167
try {
118168
reportName = report.createReport();
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package net.nandgr.debugger.cfg;
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import net.nandgr.debugger.cfg.beans.OpcodeSource;
6+
import net.nandgr.debugger.node.response.json.DebugTraceTransactionLog;
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
public class ContractObject {
11+
12+
private final String contractName;
13+
private final String filePath;
14+
private final Map<Integer, DebugTraceTransactionLog> trace;
15+
private final String traceMapJson;
16+
private final String address;
17+
private final List<OpcodeSource> opcodes;
18+
private final String sourceCode;
19+
private final String graph;
20+
21+
public ContractObject(String contractName, String filePath, Map<Integer, DebugTraceTransactionLog> trace, String address, String bytecode, String traceMapJson, List<OpcodeSource> opcodes, String sourceCode, String graph) {
22+
this.contractName = contractName;
23+
this.filePath = filePath;
24+
this.trace = trace;
25+
this.address = address;
26+
this.traceMapJson = traceMapJson;
27+
this.opcodes = opcodes;
28+
this.sourceCode = sourceCode;
29+
this.graph = graph;
30+
}
31+
32+
public String getContractName() {
33+
return contractName;
34+
}
35+
36+
public String getFilePath() {
37+
return filePath;
38+
}
39+
40+
public Map<Integer, DebugTraceTransactionLog> getTrace() {
41+
return trace;
42+
}
43+
44+
public String getTraceMapJson() {
45+
return traceMapJson;
46+
}
47+
48+
public String getAddress() {
49+
return address;
50+
}
51+
52+
public String getSourceCode() {
53+
return sourceCode;
54+
}
55+
56+
public List<OpcodeSource> getOpcodes() {
57+
return opcodes;
58+
}
59+
60+
public String getGraph() {
61+
return graph;
62+
}
63+
64+
@Override
65+
public String toString() {
66+
return "ContractObject{" +
67+
"contractName='" + contractName + '\'' +
68+
", filePath='" + filePath + '\'' +
69+
", trace=" + trace +
70+
", traceMapJson='" + traceMapJson + '\'' +
71+
", address='" + address + '\'' +
72+
", opcodes=" + opcodes +
73+
", sourceCode='" + sourceCode + '\'' +
74+
", graph='" + graph + '\'' +
75+
'}';
76+
}
77+
78+
public String toStringJson() throws JsonProcessingException {
79+
ObjectMapper objectMapper = new ObjectMapper();
80+
return objectMapper.writeValueAsString(this);
81+
}
82+
}

src/main/java/net/nandgr/debugger/cfg/graphviz/GraphVizCreator.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,20 @@
22

33
import net.nandgr.debugger.cfg.beans.BytecodeChunk;
44
import net.nandgr.debugger.cfg.beans.OpcodeSource;
5-
import net.nandgr.debugger.trace.response.json.DebugTraceTransactionLog;
5+
import net.nandgr.debugger.node.response.json.DebugTraceTransactionLog;
66
import java.util.Map;
77

88
// Not the most elegant way to create the graph, but it works for now
99
public class GraphVizCreator {
1010

1111
private final Map<Integer, BytecodeChunk> chunks;
1212
private final Map<Integer, DebugTraceTransactionLog> trace;
13+
private final String contractName;
1314

14-
public GraphVizCreator(Map<Integer, BytecodeChunk> chunks, Map<Integer, DebugTraceTransactionLog> trace) {
15+
public GraphVizCreator(Map<Integer, BytecodeChunk> chunks, Map<Integer, DebugTraceTransactionLog> trace, String contractName) {
1516
this.chunks = chunks;
1617
this.trace = trace;
18+
this.contractName = contractName;
1719
}
1820

1921
public String buildStringGraph() {
@@ -43,9 +45,9 @@ private String buildLabel(BytecodeChunk bytecodeChunk) {
4345
StringBuilder sb= new StringBuilder("< <TABLE BORDER=\"0\" CELLBORDER=\"1\" CELLSPACING=\"0\" CELLPADDING=\"4\">").append(System.lineSeparator());
4446
for (OpcodeSource opcodeSource : bytecodeChunk.getOpcodes()) {
4547
String id = opcodeSource.getOffset() + "#" + opcodeSource.getBegin() + "#" + opcodeSource.getEnd();
46-
sb.append("<TR><TD ID=\"").append(id).append("#offset\" HREF=\" \">0x")
48+
sb.append("<TR><TD ID=\"").append(id).append("#offset#").append(contractName).append("\" HREF=\" \">0x")
4749
.append(String.format("%04X", opcodeSource.getOffset()))
48-
.append("</TD><TD ID=\"").append(id).append("#instr\" HREF=\" \">")
50+
.append("</TD><TD ID=\"").append(id).append("#instr#").append(contractName).append("\" HREF=\" \">")
4951
.append(opcodeSource.getOpcode())
5052
.append("</TD>");
5153
if (opcodeSource.getParameter() != null) {

0 commit comments

Comments
 (0)