Skip to content

Commit 4726218

Browse files
committed
Algorithm to build the cross-chain CFG (xCFG)
1 parent 5c8e10a commit 4726218

File tree

5 files changed

+485
-16
lines changed

5 files changed

+485
-16
lines changed

src/main/java/it/unipr/abi/ABIFunctionSelector.java

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@
3333
* </ul>
3434
* </p>
3535
*/
36-
public class AbiFunctionSelector {
37-
private static final Logger log = LogManager.getLogger(AbiFunctionSelector.class);
36+
public class ABIFunctionSelector {
37+
private static final Logger log = LogManager.getLogger(ABIFunctionSelector.class);
3838

3939
/**
4040
* Computes the function selector for a given function signature. The
@@ -63,12 +63,18 @@ public static String getFunctionSelector(String functionSignature) {
6363
*
6464
* @return A set of pairs where each pair contains the function signature
6565
* and its corresponding selector.
66-
*
67-
* @throws IOException If an error occurs while reading the ABI file.
6866
*/
69-
public static Set<Pair<String, String>> parseAbi(Path abi) throws IOException {
67+
public static Set<Pair<String, String>> parseABI(Path abi) {
7068
Set<Pair<String, String>> functionSet = new HashSet<>();
71-
String abiJson = Files.readString(abi, StandardCharsets.UTF_8);
69+
String abiJson;
70+
71+
try {
72+
abiJson = Files.readString(abi, StandardCharsets.UTF_8);
73+
} catch (IOException e) {
74+
log.error("Unable to read abi json {}: {}", abi.toString(), e.getMessage());
75+
return functionSet;
76+
}
77+
7278
JSONArray abiArray = new JSONArray(abiJson);
7379

7480
for (int i = 0; i < abiArray.length(); i++) {
@@ -88,7 +94,7 @@ public static Set<Pair<String, String>> parseAbi(Path abi) throws IOException {
8894
String functionSelector = getFunctionSelector(signature.toString());
8995
functionSet.add(Pair.of(signature.toString(), functionSelector));
9096

91-
log.debug("{} -> {}", signature, functionSelector);
97+
// log.debug("{} -> {}", signature, functionSelector);
9298
}
9399
}
94100
return functionSet;
@@ -102,13 +108,18 @@ public static Set<Pair<String, String>> parseAbi(Path abi) throws IOException {
102108
* @param functionSet A set of function signature-selector pairs extracted
103109
* from the ABI.
104110
* @param bytecodePath Path to the smart contract's compiled bytecode.
105-
*
106-
* @throws IOException If an error occurs while reading the bytecode file.
107111
*/
108-
public static void verifyFunctionSelectors(Set<Pair<String, String>> functionSet, Path bytecodePath)
109-
throws IOException {
112+
public static void verifyFunctionSelectors(Set<Pair<String, String>> functionSet, Path bytecodePath) {
110113
int counter = 0;
111-
String bytecode = Files.readString(bytecodePath, StandardCharsets.UTF_8);
114+
String bytecode;
115+
116+
try {
117+
bytecode = Files.readString(bytecodePath, StandardCharsets.UTF_8);
118+
} catch (IOException e) {
119+
log.error("Unable to read bytecode {}: {}", bytecodePath.toString(), e.getMessage());
120+
return;
121+
}
122+
112123
for (Pair<String, String> entry : functionSet) {
113124
int occurrences = StringUtils.countMatches(bytecode, entry.getValue());
114125

@@ -118,6 +129,9 @@ public static void verifyFunctionSelectors(Set<Pair<String, String>> functionSet
118129
log.info("Function selector {} ({}) is present {} times in the bytecode.", entry.getValue(),
119130
entry.getKey(), occurrences);
120131
} else {
132+
// TODO (bug) functions with array in parameters are not
133+
// recognised, e.g.,
134+
// `returnVaultAssets(address,address,tuple[],string)`
121135
log.warn("Function selector {} ({}) is NOT present in the bytecode.", entry.getValue(), entry.getKey());
122136
}
123137
}
@@ -130,9 +144,10 @@ public static void main(String[] args) {
130144
Path bytecode = Paths.get("test-cross-chain-analysis", "test-ABI-function-selector", "buggy_1.bytecode");
131145

132146
try {
133-
Set<Pair<String, String>> functionSet = parseAbi(abi);
147+
Set<Pair<String, String>> functionSet = parseABI(abi);
148+
assert functionSet != null;
134149
verifyFunctionSelectors(functionSet, bytecode);
135-
} catch (IOException e) {
150+
} catch (Exception e) {
136151
log.error("Error reading ABI or bytecode file", e);
137152
}
138153
}

src/main/java/it/unipr/cfg/EVMCFG.java

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,20 @@
3333
import java.util.Set;
3434
import java.util.Stack;
3535
import java.util.stream.Collectors;
36+
import org.apache.commons.lang3.tuple.Pair;
37+
import org.apache.logging.log4j.LogManager;
38+
import org.apache.logging.log4j.Logger;
3639

3740
public class EVMCFG extends CFG {
41+
private static final Logger log = LogManager.getLogger(EVMCFG.class);
3842

3943
public Set<Statement> jumpDestsNodes;
4044
public Set<Number> jumpDestsNodesLocations;
4145
public Set<Statement> jumpNodes;
4246
public Set<Statement> pushedJumps;
4347
public Set<Statement> sstores;
4448
public Set<Statement> sha3s;
49+
public Set<Statement> logxs;
4550

4651
public EVMCFG(CodeMemberDescriptor descriptor, Collection<Statement> entrypoints,
4752
NodeList<CFG, Statement, Edge> list) {
@@ -53,7 +58,29 @@ public EVMCFG(CodeMemberDescriptor cfgDesc) {
5358
}
5459

5560
/**
56-
* Returns a set of all the SSTORE statements in the CFG. SSTORE
61+
* Returns a set of all the LOGx statements in the CFG.
62+
*
63+
* @return a set of all the LOGx statements in the CFG
64+
*/
65+
public Set<Statement> getAllLogX() {
66+
if (logxs == null) {
67+
NodeList<CFG, Statement, Edge> cfgNodeList = this.getNodeList();
68+
Set<Statement> logxs = new HashSet<>();
69+
70+
for (Statement statement : cfgNodeList.getNodes()) {
71+
if (statement instanceof Log) {
72+
logxs.add(statement);
73+
}
74+
}
75+
76+
return this.logxs = logxs;
77+
}
78+
79+
return logxs;
80+
}
81+
82+
/**
83+
* Returns a set of all the SSTORE statements in the CFG.
5784
*
5885
* @return a set of all the SSTORE statements in the CFG
5986
*/
@@ -75,7 +102,7 @@ public Set<Statement> getAllSstore() {
75102
}
76103

77104
/**
78-
* Returns a set of all the SHA3 statements in the CFG. SHA3
105+
* Returns a set of all the SHA3 statements in the CFG.
79106
*
80107
* @return a set of all the SHA3 statements in the CFG
81108
*/
@@ -364,4 +391,45 @@ private boolean dfsSequential(Statement start, Statement target, Set<Statement>
364391

365392
return false;
366393
}
394+
395+
/**
396+
* The method performs a depth-first search (DFS) and identifies `Push`
397+
* statements containing function selectors.
398+
*
399+
* @param start The starting statement for the search.
400+
* @param abiFunctionSet A set of function signatures and their
401+
* corresponding function selectors.
402+
*
403+
* @return A set of pairs where each pair consists of a function signature
404+
* (from the ABI) and the corresponding statement in the CFG.
405+
*/
406+
public Set<Pair<String, Statement>> findMatchingStatements(Statement start,
407+
Set<Pair<String, String>> abiFunctionSet) {
408+
Set<Pair<String, Statement>> matchingStatements = new HashSet<>();
409+
Set<Statement> visited = new HashSet<>();
410+
Stack<Statement> stack = new Stack<>();
411+
stack.push(start);
412+
413+
while (!stack.isEmpty()) {
414+
Statement current = stack.pop();
415+
416+
if (!visited.contains(current)) {
417+
visited.add(current);
418+
419+
if (current instanceof Push)
420+
for (Pair<String, String> abiFunction : abiFunctionSet)
421+
if (current.toString().contains(abiFunction.getRight()))
422+
matchingStatements.add(Pair.of(abiFunction.getLeft(), current));
423+
424+
Collection<Edge> outgoingEdges = list.getOutgoingEdges(current);
425+
for (Edge edge : outgoingEdges) {
426+
Statement next = edge.getDestination();
427+
if (!visited.contains(next))
428+
stack.push(next);
429+
}
430+
}
431+
}
432+
433+
return matchingStatements;
434+
}
367435
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package it.unipr.crossChainAnalysis;
2+
3+
import java.io.IOException;
4+
import java.nio.file.DirectoryStream;
5+
import java.nio.file.Files;
6+
import java.nio.file.Path;
7+
import java.util.*;
8+
import org.apache.logging.log4j.LogManager;
9+
import org.apache.logging.log4j.Logger;
10+
11+
public class Bridge implements Iterable<SmartContract> {
12+
private static final Logger log = LogManager.getLogger(Bridge.class);
13+
private final Set<SmartContract> _contracts;
14+
15+
public Bridge() {
16+
this._contracts = new HashSet<>();
17+
}
18+
19+
/**
20+
* Initializes the bridge by mapping and matching smart contracts from the
21+
* ABI and bytecode directories. It creates `SmartContract` objects for each
22+
* contract that has both an ABI and a bytecode file.
23+
*
24+
* @param abiDir the path to the directory containing ABI files
25+
* @param bytecodeDir the path to the directory containing bytecode files
26+
*/
27+
public Bridge(Path abiDir, Path bytecodeDir) {
28+
this();
29+
Map<String, Path> abiFiles = mapFilesByName(abiDir, ".abi.json");
30+
Map<String, Path> bytecodeFiles = mapFilesByName(bytecodeDir, ".bytecode");
31+
32+
for (String name : bytecodeFiles.keySet()) {
33+
if (abiFiles.containsKey(name)) {
34+
_contracts.add(new SmartContract(name, abiFiles.get(name), bytecodeFiles.get(name)));
35+
} else {
36+
log.warn("Cannot find ABI file: {}", name);
37+
}
38+
}
39+
}
40+
41+
/**
42+
* Scans a directory for files with a given extension and maps them by their
43+
* base name. The base name is derived from the file name without its
44+
* extension.
45+
*
46+
* @param directory the directory to scan
47+
* @param extension the file extension to filter by (e.g., ".abi.json",
48+
* ".bytecode")
49+
*
50+
* @return a map where keys are base file names and values are their
51+
* corresponding paths
52+
*/
53+
private Map<String, Path> mapFilesByName(Path directory, String extension) {
54+
Map<String, Path> fileMap = new HashMap<>();
55+
try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory, "*" + extension)) {
56+
for (Path file : stream) {
57+
String fileName = file.getFileName().toString();
58+
String baseName = fileName.substring(0, fileName.indexOf('.'));
59+
fileMap.put(baseName, file);
60+
}
61+
} catch (IOException e) {
62+
log.error("Error reading directory: {}", e.getMessage());
63+
}
64+
return fileMap;
65+
}
66+
67+
@Override
68+
public String toString() {
69+
StringBuilder result = new StringBuilder("Bridge Contracts:\n");
70+
for (SmartContract contract : _contracts) {
71+
result.append(contract).append("\n");
72+
}
73+
return result.toString();
74+
}
75+
76+
@Override
77+
public Iterator<SmartContract> iterator() {
78+
return _contracts.iterator();
79+
}
80+
}

0 commit comments

Comments
 (0)