diff --git a/README.md b/README.md index 6df92bf9a..b21ece771 100644 --- a/README.md +++ b/README.md @@ -88,18 +88,21 @@ This command will initiate the analysis process for the specified smart contract ``` Options: - -a,--address address of an Ethereum smart contract - -b,--benchmark filepath of the benchmark suite (i.e., a list of smart contract addresses) - -C,--cores number of cores to use - -c,--dump-cfg dump the CFG - -d,--dump-analysis dump the analysis (html, dot) - -D,--download-bytecode download the bytecode, without analyzing it - -f,--filepath filepath of an EVM bytecode smart contract - -o,--output output directory path - -q,--stack-size maximal height of stack - -s,--dump-stats dump statistics - -S,--use-live-storage use the live storage in SLOAD - -w,--stack-set-size maximal size of stack sets + -a,--address Address of an Ethereum smart contract. + -b,--benchmark Filepath of the benchmark suite (i.e., a list of smart contract addresses). + -c,--cores Number of cores used in benchmark. + --creation-code Parse bytecode as creation code (instead of runtime code). + --dot Export a dot-notation file. + --download-bytecode Download the bytecode. + --dump-report Dump analysis report. + --dump-stats Dump statistics. + -f,--filepath-bytecode Filepath of the bytecode file. + --html Export a graphic HTML report. + -o,--output Output directory path. + --serialize-inputs Serialize inputs. + --stack-set-size Dimension of stack-set (default: 8). + --stack-size Dimension of stack (default: 32). + --use-live-storage Use the live storage in SLOAD. ``` # 🔍 Abstract Stack Set Domain diff --git a/script-python/journal/run-benchmark.py b/script-python/journal/run-benchmark.py index c0e693264..70606ff17 100644 --- a/script-python/journal/run-benchmark.py +++ b/script-python/journal/run-benchmark.py @@ -14,19 +14,15 @@ #################################### Utility def delete_tmp_files(directory): """ - Deletes files in the specified directory that contain 'opcodes' in their name. + Deletes files in the specified directory that contain 'no-address' in their name. Args: directory (str): The path to the directory from which to delete files. """ - try: - for filename in os.listdir(directory): - if 'opcodes' in filename: - file_path = os.path.join(directory, filename) - os.remove(file_path) - # print(f"Deleted: {file_path}") - except Exception as e: - print(f"An error occurred: {e}") + command = ( + f"rm -rf {directory}/no-address* " + ) + subprocess.run(command, shell=True, check=True) def clean_files(directory_path): """ @@ -122,8 +118,12 @@ def evmlisa(): # print(f"Analysis complete for {result_file}") analysis_ended += 1 pbar.update(1) + print(f"[EVMLISA] Completed {analysis_ended}/{num_files}.") - delete_tmp_files(bytecode_dir) + delete_tmp_files(results_dir) + delete_tmp_files("./execution/results") + clean_files(result_evmlisa_dir) + print(f"[EVMLISA] File cleaned.") #################################### EtherSolve @@ -223,7 +223,6 @@ def count_sstore_occurrences(directory_path): ethersolve_thread.join() print("Finished") - clean_files(result_evmlisa_dir) # TODO print of results - count_sstore_occurrences(result_ethersolve_dir) \ No newline at end of file + # count_sstore_occurrences(result_ethersolve_dir) \ No newline at end of file diff --git a/src/main/java/it/unipr/EVMLiSA.java b/src/main/java/it/unipr/EVMLiSA.java index a2b7e1416..fa7cc0f7e 100644 --- a/src/main/java/it/unipr/EVMLiSA.java +++ b/src/main/java/it/unipr/EVMLiSA.java @@ -78,83 +78,7 @@ public static void main(String[] args) throws Exception { } public void go(String[] args) throws Exception { - Options options = new Options(); - - // String parameters - Option addressOption = new Option("a", "address", true, "address of an Ethereum smart contract"); - addressOption.setRequired(false); - options.addOption(addressOption); - - Option outputOption = new Option("o", "output", true, "output directory path"); - outputOption.setRequired(false); - options.addOption(outputOption); - - Option dumpAnalysisOption = new Option("d", "dump-analysis", true, "dump the analysis (html, dot)"); - dumpAnalysisOption.setRequired(false); - options.addOption(dumpAnalysisOption); - - Option filePathOption = new Option("f", "filepath", true, "filepath of the Etherem smart contract"); - filePathOption.setRequired(false); - options.addOption(filePathOption); - - Option stackSizeOption = new Option("q", "stack-size", true, "dimension of stack"); - stackSizeOption.setRequired(false); - options.addOption(stackSizeOption); - - Option stackSetSizeOption = new Option("w", "stack-set-size", true, "dimension of stack-set"); - stackSetSizeOption.setRequired(false); - options.addOption(stackSetSizeOption); - - Option benchmarkOption = new Option("b", "benchmark", true, "filepath of the benchmark"); - benchmarkOption.setRequired(false); - options.addOption(benchmarkOption); - - Option coresOption = new Option("C", "cores", true, "number of cores used"); - coresOption.setRequired(false); - options.addOption(coresOption); - - // Boolean parameters - Option dumpStatisticsOption = Option.builder("s") - .longOpt("dump-stats") - .desc("dump statistics") - .required(false) - .hasArg(false) - .build(); - - Option dumpCFGOption = Option.builder("c") - .longOpt("dump-cfg") - .desc("dump the CFG") - .required(false) - .hasArg(false) - .build(); - - Option downloadBytecodeOption = Option.builder("D") - .longOpt("download-bytecode") - .desc("download the bytecode") - .required(false) - .hasArg(false) - .build(); - - Option useStorageLiveOption = Option.builder("S") - .longOpt("use-live-storage") - .desc("use the live storage in SLOAD") - .required(false) - .hasArg(false) - .build(); - - Option dumpAnalysisReport = Option.builder("r") - .longOpt("dump-report") - .desc("dump analysis report") - .required(false) - .hasArg(false) - .build(); - - options.addOption(dumpStatisticsOption); - options.addOption(dumpCFGOption); - options.addOption(downloadBytecodeOption); - options.addOption(useStorageLiveOption); - options.addOption(dumpAnalysisReport); - + Options options = getOptions(); CommandLineParser parser = new DefaultParser(); HelpFormatter formatter = new HelpFormatter(); CommandLine cmd = null; @@ -170,17 +94,19 @@ public void go(String[] args) throws Exception { String addressSC = cmd.getOptionValue("address"); String outputDir = cmd.getOptionValue("output"); - boolean dumpCFG = cmd.hasOption("dumpcfg"); - String dumpAnalysis = cmd.getOptionValue("dump-analysis"); + boolean serializeInputs = cmd.hasOption("serialize-inputs"); + boolean dumpHTML = cmd.hasOption("html"); + boolean dumpDot = cmd.hasOption("dot"); boolean dumpStatistics = cmd.hasOption("dump-stats"); boolean downloadBytecode = cmd.hasOption("download-bytecode"); boolean useStorageLive = cmd.hasOption("use-live-storage"); - String filepath = cmd.getOptionValue("filepath"); + String filepath = cmd.getOptionValue("filepath-bytecode"); String stackSize = cmd.getOptionValue("stack-size"); String stackSetSize = cmd.getOptionValue("stack-set-size"); String benchmark = cmd.getOptionValue("benchmark"); String coresOpt = cmd.getOptionValue("cores"); boolean dumpReport = cmd.hasOption("dump-report"); + boolean useCreationCode = cmd.hasOption("creation-code"); // Download bytecode case if (downloadBytecode && benchmark != null) { @@ -222,15 +148,19 @@ public void go(String[] args) throws Exception { if (useStorageLive) EVMAbstractState.setUseStorageLive(); + if (useCreationCode) + EVMFrontend.setUseCreationCode(); // Creating json output notes JSONObject jsonOptions = new JSONObject(); jsonOptions.put("address", addressSC); - jsonOptions.put("dump-CFG", dumpCFG); - jsonOptions.put("dump-analysis", dumpAnalysis); + jsonOptions.put("serialize-inputs", serializeInputs); + jsonOptions.put("dump-html", dumpHTML); + jsonOptions.put("dump-dot", dumpDot); jsonOptions.put("dump-statistics", dumpStatistics); jsonOptions.put("download-bytecode", downloadBytecode); jsonOptions.put("use-storage-live", useStorageLive); + jsonOptions.put("use-creation-code", useCreationCode); jsonOptions.put("filepath", filepath); jsonOptions.put("stack-size", AbstractStack.getStackLimit()); jsonOptions.put("stack-set-size", AbstractStackSet.getStackSetLimit()); @@ -259,6 +189,8 @@ public void go(String[] args) throws Exception { } // Single analysis case + if (addressSC == null) + addressSC = "no-address-" + System.currentTimeMillis();; OUTPUT_DIR += "/" + addressSC; Files.createDirectories(Paths.get(OUTPUT_DIR)); jsonOptions.put("output-directory", OUTPUT_DIR); @@ -269,12 +201,10 @@ public void go(String[] args) throws Exception { STATISTICS_FULLPATH = OUTPUT_DIR + "/" + addressSC + "_STATISTICS" + ".csv"; FAILURE_FULLPATH = OUTPUT_DIR + "/" + addressSC + "_FAILURE" + ".csv"; - String BYTECODE_FULLPATH; + String BYTECODE_FULLPATH = OUTPUT_DIR + "/" + addressSC + ".opcode"; if (filepath == null) { - BYTECODE_FULLPATH = OUTPUT_DIR + "/" + addressSC + ".sol"; EVMFrontend.parseContractFromEtherscan(addressSC, BYTECODE_FULLPATH); } else { - BYTECODE_FULLPATH = filepath + "opcodes"; String bytecode = new String(Files.readAllBytes(Paths.get(filepath))); EVMFrontend.opcodesFromBytecode(bytecode, BYTECODE_FULLPATH); } @@ -285,7 +215,7 @@ public void go(String[] args) throws Exception { long finish; LiSAConfiguration conf = new LiSAConfiguration(); - conf.serializeInputs = dumpCFG; + conf.serializeInputs = serializeInputs; conf.abstractState = new SimpleAbstractState<>(new MonolithicHeap(), new EVMAbstractState(addressSC), new TypeEnvironment<>(new InferredTypes())); conf.jsonOutput = dumpReport; @@ -297,12 +227,10 @@ public void go(String[] args) throws Exception { conf.serializeResults = false; conf.optimize = false; conf.useWideningPoints = false; - if (dumpAnalysis != null) { - if (dumpAnalysis.equals("dot")) - conf.analysisGraphs = GraphType.DOT; - else if (dumpAnalysis.equals("html")) - conf.analysisGraphs = GraphType.HTML_WITH_SUBNODES; - } + if (dumpHTML) + conf.analysisGraphs = GraphType.HTML_WITH_SUBNODES; + else if (dumpDot) + conf.analysisGraphs = GraphType.DOT; try { LiSA lisa = new LiSA(conf); @@ -348,7 +276,7 @@ else if (dumpAnalysis.equals("html")) private MyLogger newAnalysis(String CONTRACT_ADDR, JSONObject jsonOptions) throws Exception { String BYTECODE_FULLPATH = OUTPUT_DIR + "/benchmark/" + CONTRACT_ADDR + "/" + CONTRACT_ADDR - + ".sol"; + + ".opcode"; // Directory setup and bytecode retrieval Files.createDirectories(Paths.get(OUTPUT_DIR + "/" + "benchmark/" + CONTRACT_ADDR)); @@ -871,6 +799,136 @@ private void saveSmartContractsFromEtherscan() throws Exception { log.info("Downloaded {} smart contract.", numberOfAPIEtherscanRequestOnSuccess); } + private Options getOptions() { + Options options = new Options(); + + // String parameters + Option addressOption = Option.builder("a") + .longOpt("address") + .desc("Address of an Ethereum smart contract.") + .required(false) + .hasArg(true) + .build(); + + Option outputOption = Option.builder("o") + .longOpt("output") + .desc("Output directory path.") + .required(false) + .hasArg(true) + .build(); + + Option filePathOption = Option.builder("f") + .longOpt("filepath-bytecode") + .desc("Filepath of the bytecode file.") + .required(false) + .hasArg(true) + .build(); + + Option stackSizeOption = Option.builder() + .longOpt("stack-size") + .desc("Dimension of stack (default: 32).") + .required(false) + .hasArg(true) + .build(); + + Option stackSetSizeOption = Option.builder() + .longOpt("stack-set-size") + .desc("Dimension of stack-set (default: 8).") + .required(false) + .hasArg(true) + .build(); + + Option benchmarkOption = Option.builder("b") + .longOpt("benchmark") + .desc("Filepath of the benchmark.") + .required(false) + .hasArg(true) + .build(); + + Option coresOption = Option.builder("c") + .longOpt("cores") + .desc("Number of cores used in benchmark.") + .required(false) + .hasArg(true) + .build(); + + // Boolean parameters + Option dumpStatisticsOption = Option.builder() + .longOpt("dump-stats") + .desc("Dump statistics.") + .required(false) + .hasArg(false) + .build(); + + Option dumpHtmlOption = Option.builder() + .longOpt("html") + .desc("Export a graphic HTML report.") + .required(false) + .hasArg(false) + .build(); + + Option dumpDotOption = Option.builder() + .longOpt("dot") + .desc("Export a dot-notation file.") + .required(false) + .hasArg(false) + .build(); + + Option serializeInputsOption = Option.builder() + .longOpt("serialize-inputs") + .desc("Serialize inputs.") + .required(false) + .hasArg(false) + .build(); + + Option downloadBytecodeOption = Option.builder() + .longOpt("download-bytecode") + .desc("Download the bytecode.") + .required(false) + .hasArg(false) + .build(); + + Option useStorageLiveOption = Option.builder() + .longOpt("use-live-storage") + .desc("Use the live storage in SLOAD.") + .required(false) + .hasArg(false) + .build(); + + Option dumpAnalysisReport = Option.builder() + .longOpt("dump-report") + .desc("Dump analysis report.") + .required(false) + .hasArg(false) + .build(); + + Option useCreationCodeOption = Option.builder() + .longOpt("creation-code") + .desc("Parse bytecode as creation code (instead of runtime code).") + .required(false) + .hasArg(false) + .build(); + + options.addOption(addressOption); + options.addOption(outputOption); + options.addOption(filePathOption); + options.addOption(stackSizeOption); + options.addOption(stackSetSizeOption); + options.addOption(benchmarkOption); + options.addOption(coresOption); + + options.addOption(dumpStatisticsOption); + options.addOption(serializeInputsOption); + options.addOption(downloadBytecodeOption); + options.addOption(useStorageLiveOption); + options.addOption(dumpAnalysisReport); + options.addOption(useCreationCodeOption); + options.addOption(dumpHtmlOption); + options.addOption(dumpDotOption); + + return options; + } + public static class Converter { long kilo = 1024; long mega = kilo * kilo; diff --git a/src/main/java/it/unipr/analysis/AbstractStackSet.java b/src/main/java/it/unipr/analysis/AbstractStackSet.java index 75386c96d..ee886f1a9 100644 --- a/src/main/java/it/unipr/analysis/AbstractStackSet.java +++ b/src/main/java/it/unipr/analysis/AbstractStackSet.java @@ -9,7 +9,7 @@ public class AbstractStackSet extends SetLattice { - private static int SIZE = 32; + private static int SIZE = 8; private static final AbstractStackSet BOTTOM = new AbstractStackSet(null, false); private static final AbstractStackSet TOP = new AbstractStackSet(Collections.emptySet(), true); diff --git a/src/main/java/it/unipr/frontend/EVMFrontend.java b/src/main/java/it/unipr/frontend/EVMFrontend.java index 7926b1c76..b639b55a6 100644 --- a/src/main/java/it/unipr/frontend/EVMFrontend.java +++ b/src/main/java/it/unipr/frontend/EVMFrontend.java @@ -31,6 +31,12 @@ */ public class EVMFrontend { + private static boolean USE_CREATION_CODE = false; + + static public void setUseCreationCode() { + USE_CREATION_CODE = true; + } + /** * Verifies the syntactic correctness of the smart contract bytecode stored * in {@code filePath} and returns its {@code ProgramContext}. @@ -537,7 +543,10 @@ private static boolean addOpcode(String opcode, Writer writer) throws IOExceptio break; case "fe": writer.write("INVALID\n"); - return false; + if (USE_CREATION_CODE) + break; + else + return false; case "ff": writer.write("SELFDESTRUCT\n"); break;