certora-cli 7.20.3__tar.gz → 7.22.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {certora_cli-7.20.3/certora_cli.egg-info → certora_cli-7.22.2}/PKG-INFO +4 -2
  2. certora_cli-7.22.2/README.md +1 -0
  3. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraBuild.py +113 -98
  4. certora_cli-7.22.2/certora_cli/EVMVerifier/certoraBuildRust.py +78 -0
  5. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraCloudIO.py +82 -55
  6. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraCollectRunMetadata.py +1 -1
  7. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraConfigIO.py +10 -4
  8. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraContext.py +11 -68
  9. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraContextAttributes.py +107 -64
  10. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraContextValidator.py +18 -21
  11. certora_cli-7.22.2/certora_cli/EVMVerifier/certoraParseBuildScript.py +58 -0
  12. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraType.py +1 -0
  13. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Mutate/mutateApp.py +181 -42
  14. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Mutate/mutateAttributes.py +10 -2
  15. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Mutate/mutateConstants.py +1 -2
  16. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Mutate/mutateUtil.py +1 -1
  17. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Mutate/mutateValidate.py +4 -2
  18. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Shared/certoraAttrUtil.py +3 -2
  19. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Shared/certoraUtils.py +51 -20
  20. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Shared/certoraValidateFuncs.py +34 -10
  21. certora_cli-7.22.2/certora_cli/certoraEVMProver.py +21 -0
  22. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/certoraMutate.py +17 -14
  23. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/certoraRun.py +4 -5
  24. certora_cli-7.22.2/certora_cli/certoraSolanaProver.py +152 -0
  25. certora_cli-7.22.2/certora_cli/certoraSorobanProver.py +21 -0
  26. certora_cli-7.22.2/certora_cli/rustMutator.py +401 -0
  27. {certora_cli-7.20.3 → certora_cli-7.22.2/certora_cli.egg-info}/PKG-INFO +4 -2
  28. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli.egg-info/SOURCES.txt +6 -0
  29. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli.egg-info/entry_points.txt +3 -0
  30. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli.egg-info/requires.txt +2 -0
  31. certora_cli-7.22.2/certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -0
  32. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_jars/Typechecker.jar +0 -0
  33. {certora_cli-7.20.3 → certora_cli-7.22.2}/setup.py +7 -4
  34. certora_cli-7.20.3/README.md +0 -1
  35. certora_cli-7.20.3/certora_jars/CERTORA-CLI-VERSION-METADATA.json +0 -1
  36. {certora_cli-7.20.3 → certora_cli-7.22.2}/LICENSE +0 -0
  37. {certora_cli-7.20.3 → certora_cli-7.22.2}/MANIFEST.in +0 -0
  38. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_bins/__init__.py +0 -0
  39. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollector.py +0 -0
  40. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollectorFactory.py +0 -0
  41. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollectorSol.py +0 -0
  42. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollectorSolBased.py +0 -0
  43. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollectorVy.py +0 -0
  44. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollectorYul.py +0 -0
  45. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/__init__.py +0 -0
  46. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/__init__.py +0 -0
  47. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraBuildCacheManager.py +0 -0
  48. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraBuildDataClasses.py +0 -0
  49. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraCompilerParameters.py +0 -0
  50. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraContextClass.py +0 -0
  51. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraContractFuncs.py +0 -0
  52. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraExtensionInfo.py +0 -0
  53. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraJobList.py +0 -0
  54. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraMiniSpecParser.py +0 -0
  55. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraNodeFilters.py +0 -0
  56. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraSourceFinders.py +0 -0
  57. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraVerifyGenerator.py +0 -0
  58. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/Eq_default.conf +0 -0
  59. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/Eq_mc_no_out_template.spec +0 -0
  60. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/Eq_mc_template.spec +0 -0
  61. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -0
  62. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/Eq_template.spec +0 -0
  63. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/__init__.py +0 -0
  64. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/equivCheck.py +0 -0
  65. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/sanity.spec +0 -0
  66. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Mutate/__init__.py +0 -0
  67. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Shared/ExpectedComparator.py +0 -0
  68. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Shared/__init__.py +0 -0
  69. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/Shared/certoraLogging.py +0 -0
  70. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/__init__.py +0 -0
  71. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli/certoraEqCheck.py +0 -0
  72. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli.egg-info/dependency_links.txt +0 -0
  73. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_cli.egg-info/top_level.txt +0 -0
  74. {certora_cli-7.20.3 → certora_cli-7.22.2}/certora_jars/__init__.py +0 -0
  75. {certora_cli-7.20.3 → certora_cli-7.22.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: certora-cli
3
- Version: 7.20.3
3
+ Version: 7.22.2
4
4
  Summary: Runner for the Certora Prover
5
5
  Home-page: https://pypi.org/project/certora-cli
6
6
  Author: Certora
@@ -21,5 +21,7 @@ Requires-Dist: tabulate
21
21
  Requires-Dist: tqdm
22
22
  Requires-Dist: StrEnum
23
23
  Requires-Dist: tomli
24
+ Requires-Dist: universalmutator
25
+ Requires-Dist: jinja2
24
26
 
25
- Commit 7b9a1c7. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
27
+ Commit 31a4e5d. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -0,0 +1 @@
1
+ Commit 31a4e5d. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -515,11 +515,11 @@ class CertoraBuildGenerator:
515
515
  ret = Path(self.function_finder_file_remappings[file])
516
516
  else:
517
517
  ret = Path(file)
518
- abs_sources_dir = Util.get_certora_sources_dir().resolve()
518
+ abs_sources_dir = Util.get_certora_sources_dir().absolute()
519
519
  if abs_sources_dir in ret.parents:
520
520
  # Normalize the file-name to never include the extra `.certora_internal/.../.certora_sources` part
521
521
  # of the path.
522
- ret = ret.relative_to(abs_sources_dir).resolve()
522
+ ret = Util.abs_norm_path(ret.relative_to(abs_sources_dir))
523
523
 
524
524
  # things look nicer if they are rooted in .certora_sources
525
525
  # note that we only have those fully populated after starting to work on internal function finders
@@ -1104,12 +1104,12 @@ class CertoraBuildGenerator:
1104
1104
  function name, inputs, outputs, stateMutability) for each contract
1105
1105
  """
1106
1106
  func_signatures = []
1107
- file_abs_path = Path(self.to_autofinder_file(str(contract_file))).resolve()
1107
+ file_abs_path = Path(self.to_autofinder_file(str(contract_file))).absolute()
1108
1108
  if file_abs_path.suffix == VY:
1109
1109
  smart_contract_lang: CompilerLang = CompilerLangVy()
1110
- sdc_name = self.file_to_sdc_name[Path(contract_file).resolve()]
1110
+ sdc_name = self.file_to_sdc_name[Path(contract_file).absolute()]
1111
1111
  standard_json_data = self.get_standard_json_data(sdc_name, smart_contract_lang)
1112
- abi = standard_json_data[CONTRACTS][str(Path(contract_file).resolve())][contract_name]['abi']
1112
+ abi = standard_json_data[CONTRACTS][str(Path(contract_file).absolute())][contract_name]['abi']
1113
1113
  ast_logger.debug(f"abi is: \n{abi}")
1114
1114
  for f in filter(lambda x: self.is_imported_abi_entry(x), abi):
1115
1115
  func_signature = self.get_full_func_signature(f)
@@ -1347,6 +1347,11 @@ class CertoraBuildGenerator:
1347
1347
  settings_dict["optimizer"] = {"enabled": True}
1348
1348
  if int(self.context.solc_optimize) > 0:
1349
1349
  settings_dict["optimizer"]['runs'] = int(self.context.solc_optimize)
1350
+
1351
+ # if optimizer is true, we should also define settings_dict["optimizer"]["details"]
1352
+ # for both optimize map and optimize
1353
+ optimizer = settings_dict.get("optimizer")
1354
+ if optimizer and isinstance(optimizer, dict) and optimizer.get('enabled'):
1350
1355
  # if we are not disabling finder friendly optimizer specifically, enable it whenever viaIR is also enabled
1351
1356
  if not self.context.strict_solc_optimizer and (
1352
1357
  self.context.solc_via_ir or self.context.solc_experimental_via_ir):
@@ -1572,7 +1577,17 @@ class CertoraBuildGenerator:
1572
1577
  while queue:
1573
1578
  pop = queue.pop(0)
1574
1579
  if isinstance(pop, dict) and node_id_attrb in pop:
1575
- container[int(pop[node_id_attrb])] = pop
1580
+ idAttr = pop[node_id_attrb]
1581
+ if isinstance(idAttr, int):
1582
+ container[int(idAttr)] = pop
1583
+ elif isinstance(idAttr, list) and len(idAttr) == 1 and isinstance(idAttr[0], int):
1584
+ # In the bug reported in https://certora.atlassian.net/browse/CERT-7643 the id field is a list
1585
+ # instead of a single integer - we except that the first element of the list is the actual id
1586
+ # and unpack it.
1587
+ container[int(idAttr[0])] = pop
1588
+ else:
1589
+ raise Exception(f"Unexpected type of attribute `{node_id_attrb}`, was {idAttr}, expected an integer or an int-typed list of length 1")
1590
+
1576
1591
  for key, value in pop.items():
1577
1592
  if (node_type_attrb in pop and
1578
1593
  pop[node_type_attrb] == "InlineAssembly" and key == "externalReferences"):
@@ -1692,7 +1707,7 @@ class CertoraBuildGenerator:
1692
1707
  is_vyper = smart_contract_lang == CompilerLangVy()
1693
1708
  sdc_name = f"{Path(build_arg_contract_file).name}_{file_index}"
1694
1709
  compilation_path = self.get_compilation_path(sdc_name)
1695
- self.file_to_sdc_name[Path(build_arg_contract_file).resolve()] = sdc_name
1710
+ self.file_to_sdc_name[Util.abs_norm_path(build_arg_contract_file)] = sdc_name
1696
1711
  # update remappings and collect_cmd:
1697
1712
  if not is_vyper:
1698
1713
  Util.safe_create_dir(compilation_path)
@@ -1714,7 +1729,7 @@ class CertoraBuildGenerator:
1714
1729
  is equivalent to `main_path = self.context.solc_allow_path`, and that's the best we can do in that
1715
1730
  case anyway.
1716
1731
  """
1717
- main_path = (compile_wd / self.context.solc_allow_path).resolve()
1732
+ main_path = (compile_wd / self.context.solc_allow_path).absolute()
1718
1733
  else:
1719
1734
  main_path = self.context.solc_allow_path
1720
1735
  self.context.solc_cmd_allow_paths = [f'"{main_path}"', '.']
@@ -1813,7 +1828,8 @@ class CertoraBuildGenerator:
1813
1828
  contract_file = str(contract_file_obj)
1814
1829
  build_logger.debug(f"Processing dependencies from file {contract_file}")
1815
1830
  # make sure path name is in posix format.
1816
- contract_file_abs = Path(contract_file).resolve().as_posix()
1831
+ contract_file_abs = str(Util.abs_norm_path(contract_file))
1832
+
1817
1833
  # using os.path.relpath because Path.relative_to cannot go up the directory tree (no ..)
1818
1834
  contract_file_rel = os.path.relpath(Path(contract_file_abs), Path.cwd())
1819
1835
 
@@ -1849,7 +1865,7 @@ class CertoraBuildGenerator:
1849
1865
  base_contracts = sorted(self.retrieve_base_contracts_list(build_arg_contract_file, contract_file,
1850
1866
  contract_name), key=lambda x: x[0])
1851
1867
  for c_file, _, _ in base_contracts:
1852
- norm_c_file = Path(c_file).resolve()
1868
+ norm_c_file = Util.abs_norm_path(c_file)
1853
1869
  resolved_to_orig[str(norm_c_file)] = c_file
1854
1870
  if norm_c_file not in seen_link_refs:
1855
1871
  build_logger.debug(f"Adding a base contract link ref {norm_c_file} to worklist")
@@ -1860,7 +1876,7 @@ class CertoraBuildGenerator:
1860
1876
  contract_object = data[CONTRACTS][contract_file][contract_name]
1861
1877
  lib_link_refs = sorted(contract_object["evm"]["deployedBytecode"]["linkReferences"])
1862
1878
  for lib_link_ref in lib_link_refs: # linkReference is a library reference
1863
- norm_link_ref = Path(lib_link_ref).resolve()
1879
+ norm_link_ref = Path(lib_link_ref).absolute()
1864
1880
  resolved_to_orig[str(norm_link_ref)] = lib_link_ref
1865
1881
  if norm_link_ref not in seen_link_refs:
1866
1882
  build_logger.debug(f"Adding library link ref {norm_link_ref} to worklist")
@@ -1879,8 +1895,7 @@ class CertoraBuildGenerator:
1879
1895
  contract_refs = [c_file for c_file in
1880
1896
  data[CONTRACTS].keys() if
1881
1897
  ref in data[CONTRACTS][c_file].keys()]
1882
- contract_files_resolved = sorted([Path(x).resolve() for x in
1883
- contract_refs])
1898
+ contract_files_resolved = sorted([Util.abs_norm_path(x) for x in contract_refs])
1884
1899
 
1885
1900
  # There may be two non unique paths but actually referring to the same absolute path.
1886
1901
  # Normalizing as absolute
@@ -2302,7 +2317,7 @@ class CertoraBuildGenerator:
2302
2317
  f" {c.name} that doesn't have a location")
2303
2318
  return None
2304
2319
 
2305
- instrumentation_path = Util.convert_path_for_solc_import(loc[0])
2320
+ instrumentation_path = str(Util.abs_norm_path(loc[0]))
2306
2321
  if instrumentation_path not in function_finder_instrumentation:
2307
2322
  instrumentation_logger.debug(f"New instrumentation for location {loc[0]}, " +
2308
2323
  f"instrumentation path {instrumentation_path}")
@@ -2615,7 +2630,7 @@ class CertoraBuildGenerator:
2615
2630
  sources_from_pre_finder_SDCs |= sdc.all_contract_files
2616
2631
  sources = self.collect_sources(context, certora_verify_generator, sources_from_pre_finder_SDCs)
2617
2632
  try:
2618
- self.cwd_rel_in_sources = self.build_source_tree(sources, context)
2633
+ self.cwd_rel_in_sources = build_source_tree(sources, context)
2619
2634
  except Exception as e:
2620
2635
  build_logger.debug(f"build_source_tree failed. Sources: {sources}", exc_info=e)
2621
2636
  raise
@@ -2782,85 +2797,6 @@ class CertoraBuildGenerator:
2782
2797
  common_path = Path(os.path.commonpath(list(sources.union({cwd}))))
2783
2798
  return cwd.relative_to(common_path), common_path
2784
2799
 
2785
- @staticmethod
2786
- def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bool = False) -> Path:
2787
- """
2788
- Copies files to .certora_sources
2789
- @returns the cwd relative in sources
2790
- """
2791
- sources = sources_to_abs(sources)
2792
- cwd_rel_in_sources, common_path = CertoraBuildGenerator.get_cwd_rel_in_sources(sources)
2793
-
2794
- for source_path in sources:
2795
- is_dir = source_path.is_dir()
2796
- # copy file to the path of the file from the common root under the sources directory
2797
-
2798
- # make sure directory exists
2799
- target_path = Util.get_certora_sources_dir() / source_path.relative_to(common_path)
2800
- target_directory = target_path if is_dir else target_path.parent
2801
- try:
2802
- target_directory.mkdir(parents=True, exist_ok=True)
2803
- except OSError as e:
2804
- build_logger.debug(f"Failed to create directory {target_directory}", exc_info=e)
2805
- raise
2806
-
2807
- # copy files. if we got a directory, nothing to do
2808
- if is_dir:
2809
- build_logger.debug(f"Skipping directory {source_path}")
2810
- continue
2811
-
2812
- try:
2813
- if overwrite:
2814
- # expecting target path to exist.
2815
- if target_path.exists():
2816
- build_logger.debug(f"Overwriting {target_path} by copying from {source_path}")
2817
- else:
2818
- build_logger.warning(f"Supposed to overwrite {target_path} by copying from {source_path}" +
2819
- " but it does not exist... this may indicate bad things happen")
2820
- if overwrite or not target_path.exists():
2821
- build_logger.debug(f"Copying {source_path} to {target_path}")
2822
- shutil.copyfile(source_path, target_path)
2823
- except OSError as e:
2824
- build_logger.debug(f"Couldn't copy {source_path} to {target_path}", exc_info=e)
2825
- raise
2826
-
2827
- # the empty file .cwd is written in the source tree to denote the current working directory
2828
- if cwd_rel_in_sources != '.':
2829
- file_path = Util.get_certora_sources_dir() / cwd_rel_in_sources / Util.CWD_FILE
2830
- file_path.parent.mkdir(parents=True, exist_ok=True)
2831
- file_path.touch()
2832
-
2833
- """
2834
- Once the resource files are copied to the source tree, the paths in the value of the 'prover_resource_files'
2835
- attribute are replaced with the relative path of the resource file from the source tree root.
2836
- This way The server can easily find the resource files in the source tree. The path from the source tree is the
2837
- relative path of cwd from source tree root (most cases '.') concatenated with the relative path of the resource
2838
- file from cwd
2839
- """
2840
- if context.prover_resource_files:
2841
- new_value = []
2842
- len_orig = len(context.prover_resource_files)
2843
- for value in context.prover_resource_files:
2844
- label, file_path = value.split(':')
2845
- rel_path = Path(os.path.relpath(file_path, '.'))
2846
- new_value.append(':'.join([label, os.path.normpath(rel_path)]))
2847
- if len_orig != len(new_value):
2848
- raise RuntimeError(f"fail to process prover_resource_files {len_orig} out of {len(new_value)}")
2849
- context.prover_resource_files = new_value
2850
-
2851
- # Copy the repro conf file to the sources as the overall way to reproduce this run easily.
2852
- # The file is being copied in here and not added through collect_sources, because we want
2853
- # to avoid building the whole tree up to the common path for this file. We simply want to copy
2854
- # the repro file from .certora_internal to .certora_sources so it will be uploaded as a run
2855
- # resource.
2856
- try:
2857
- shutil.copy(Util.get_last_conf_file(), Util.get_certora_sources_dir() / Util.LAST_CONF_FILE)
2858
- except OSError as e:
2859
- build_logger.debug("Couldn't copy repro conf to certora sources.", exc_info=e)
2860
- raise
2861
-
2862
- return cwd_rel_in_sources
2863
-
2864
2800
  @staticmethod
2865
2801
  def normalize_path(path: str) -> str:
2866
2802
  """
@@ -2879,7 +2815,7 @@ class CertoraBuildGenerator:
2879
2815
  p = Path(path)
2880
2816
  if p.is_absolute():
2881
2817
  return path
2882
- return str(Path.cwd() / p.resolve())
2818
+ return str(Path.cwd() / p.absolute())
2883
2819
 
2884
2820
  @staticmethod
2885
2821
  def merge_dicts_instrumentation(dict1: Dict[str, Dict[int, Instrumentation]],
@@ -3052,7 +2988,7 @@ class CertoraBuildGenerator:
3052
2988
  """
3053
2989
  rel_to_cwd_path = Path(os.path.relpath(path, '.'))
3054
2990
  new_path = Util.get_certora_sources_dir() / self.cwd_rel_in_sources / rel_to_cwd_path
3055
- return str(new_path.resolve())
2991
+ return str(new_path.absolute())
3056
2992
 
3057
2993
  def handle_links(self) -> None:
3058
2994
  # Link processing
@@ -3457,6 +3393,85 @@ def sources_to_abs(sources: Set[Path]) -> Set[Path]:
3457
3393
  return result
3458
3394
 
3459
3395
 
3396
+ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bool = False) -> Path:
3397
+ """
3398
+ Copies files to .certora_sources
3399
+ @returns the cwd relative in sources
3400
+ """
3401
+ sources = sources_to_abs(sources)
3402
+ cwd_rel_in_sources, common_path = CertoraBuildGenerator.get_cwd_rel_in_sources(sources)
3403
+
3404
+ for source_path in sources:
3405
+ is_dir = source_path.is_dir()
3406
+ # copy file to the path of the file from the common root under the sources directory
3407
+
3408
+ # make sure directory exists
3409
+ target_path = Util.get_certora_sources_dir() / source_path.relative_to(common_path)
3410
+ target_directory = target_path if is_dir else target_path.parent
3411
+ try:
3412
+ target_directory.mkdir(parents=True, exist_ok=True)
3413
+ except OSError as e:
3414
+ build_logger.debug(f"Failed to create directory {target_directory}", exc_info=e)
3415
+ raise
3416
+
3417
+ # copy files. if we got a directory, nothing to do
3418
+ if is_dir:
3419
+ build_logger.debug(f"Skipping directory {source_path}")
3420
+ continue
3421
+
3422
+ try:
3423
+ if overwrite:
3424
+ # expecting target path to exist.
3425
+ if target_path.exists():
3426
+ build_logger.debug(f"Overwriting {target_path} by copying from {source_path}")
3427
+ else:
3428
+ build_logger.warning(f"Supposed to overwrite {target_path} by copying from {source_path}" +
3429
+ " but it does not exist... this may indicate bad things happen")
3430
+ if overwrite or not target_path.exists():
3431
+ build_logger.debug(f"Copying {source_path} to {target_path}")
3432
+ shutil.copyfile(source_path, target_path)
3433
+ except OSError as e:
3434
+ build_logger.debug(f"Couldn't copy {source_path} to {target_path}", exc_info=e)
3435
+ raise
3436
+
3437
+ # the empty file .cwd is written in the source tree to denote the current working directory
3438
+ if cwd_rel_in_sources != '.':
3439
+ file_path = Util.get_certora_sources_dir() / cwd_rel_in_sources / Util.CWD_FILE
3440
+ file_path.parent.mkdir(parents=True, exist_ok=True)
3441
+ file_path.touch()
3442
+
3443
+ """
3444
+ Once the resource files are copied to the source tree, the paths in the value of the 'prover_resource_files'
3445
+ attribute are replaced with the relative path of the resource file from the source tree root.
3446
+ This way The server can easily find the resource files in the source tree. The path from the source tree is the
3447
+ relative path of cwd from source tree root (most cases '.') concatenated with the relative path of the resource
3448
+ file from cwd
3449
+ """
3450
+ if context.prover_resource_files:
3451
+ new_value = []
3452
+ len_orig = len(context.prover_resource_files)
3453
+ for value in context.prover_resource_files:
3454
+ label, file_path = value.split(':')
3455
+ rel_path = Path(os.path.relpath(file_path, '.'))
3456
+ new_value.append(':'.join([label, os.path.normpath(rel_path)]))
3457
+ if len_orig != len(new_value):
3458
+ raise RuntimeError(f"fail to process prover_resource_files {len_orig} out of {len(new_value)}")
3459
+ context.prover_resource_files = new_value
3460
+
3461
+ # Copy the repro conf file to the sources as the overall way to reproduce this run easily.
3462
+ # The file is being copied in here and not added through collect_sources, because we want
3463
+ # to avoid building the whole tree up to the common path for this file. We simply want to copy
3464
+ # the repro file from .certora_internal to .certora_sources so it will be uploaded as a run
3465
+ # resource.
3466
+ try:
3467
+ shutil.copy(Util.get_last_conf_file(), Util.get_certora_sources_dir() / Util.LAST_CONF_FILE)
3468
+ except OSError as e:
3469
+ build_logger.debug("Couldn't copy repro conf to certora sources.", exc_info=e)
3470
+ raise
3471
+
3472
+ return cwd_rel_in_sources
3473
+
3474
+
3460
3475
  def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
3461
3476
  certora_verify_generator: CertoraVerifyGenerator,
3462
3477
  build_cache_enabled: bool) -> CachedFiles:
@@ -3481,7 +3496,7 @@ def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
3481
3496
  saw_paths_not_in_sources = False
3482
3497
  paths_not_in_sources = set()
3483
3498
  may_store_in_build_cache = True
3484
- absolute_sources_dir = Util.get_certora_sources_dir().resolve()
3499
+ absolute_sources_dir = Util.get_certora_sources_dir().absolute()
3485
3500
  for sdc in certora_build_generator.SDCs.values():
3486
3501
  # the contract files in SDCs are relative to .certora_sources. Which isn't good for us here.
3487
3502
  # Need to be relative to original paths
@@ -3620,7 +3635,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3620
3635
  cached_files.all_contract_files)
3621
3636
  try:
3622
3637
  # Copies files, not updating state
3623
- certora_build_generator.build_source_tree(sources, context)
3638
+ build_source_tree(sources, context)
3624
3639
  except Exception as e:
3625
3640
  build_logger.debug("build_source_tree failed", exc_info=e)
3626
3641
 
@@ -0,0 +1,78 @@
1
+ import glob
2
+ import shutil
3
+ from pathlib import Path
4
+ from typing import Set
5
+
6
+ from EVMVerifier.certoraBuild import build_source_tree
7
+ from EVMVerifier.certoraContextClass import CertoraContext
8
+ from EVMVerifier.certoraParseBuildScript import run_script_and_parse_json
9
+ from Shared import certoraUtils as Util
10
+
11
+
12
+ def build_rust_app(context: CertoraContext) -> None:
13
+ if context.build_script:
14
+ run_script_and_parse_json(context)
15
+ if not context.rust_executables:
16
+ raise Util.CertoraUserInputError("failed to get target executable")
17
+
18
+ sources: Set[Path] = set()
19
+ root_directory = Path(context.rust_project_directory)
20
+ collect_files_from_rust_sources(context, sources, root_directory)
21
+
22
+ try:
23
+ # Create generators
24
+ build_source_tree(sources, context)
25
+
26
+ copy_files_to_build_dir(context)
27
+
28
+ except Exception as e:
29
+ raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
30
+ else:
31
+ if not context.files:
32
+ raise Util.CertoraUserInputError("'files' or 'build_script' must be set for Rust projects")
33
+ if len(context.files) > 1:
34
+ raise Util.CertoraUserInputError("Rust projects must specify exactly one executable in 'files'.")
35
+ context.rust_executables = context.files[0]
36
+
37
+
38
+ def collect_files_from_rust_sources(context: CertoraContext, sources: Set[Path], root_directory: Path) -> None:
39
+ patterns = ["*.rs", "*.so", "*.wasm", "Cargo.toml", "Cargo.lock", "justfile"]
40
+ exclude_dirs = [".certora_internal"]
41
+
42
+ if not root_directory.is_dir():
43
+ raise ValueError(f"The given directory {root_directory} is not valid.")
44
+
45
+ for source in context.rust_sources:
46
+ for file in glob.glob(f'{root_directory.joinpath(source)}', recursive=True):
47
+ file_path = Path(file)
48
+ if any(excluded in file_path.parts for excluded in exclude_dirs):
49
+ continue
50
+ if file_path.is_file() and any(file_path.match(pattern) for pattern in patterns):
51
+ sources.add(file_path)
52
+
53
+ if Path(context.build_script).exists():
54
+ sources.add(Path(context.build_script).resolve())
55
+ if context.conf_file and Path(context.conf_file).exists():
56
+ sources.add(Path(context.conf_file).absolute())
57
+
58
+ additional_files = (getattr(context, 'solana_inlining', None) or []) + \
59
+ (getattr(context, 'solana_summaries', None) or [])
60
+ for file in additional_files:
61
+ sources.add(Path(file))
62
+
63
+
64
+ def copy_files_to_build_dir(context: CertoraContext) -> None:
65
+ rust_executable = Path(context.rust_project_directory) / context.rust_executables
66
+ shutil.copyfile(rust_executable, Util.get_build_dir() / rust_executable.name)
67
+
68
+ additional_files = (getattr(context, 'solana_inlining', None) or []) + \
69
+ (getattr(context, 'solana_summaries', None) or [])
70
+
71
+ for file in additional_files:
72
+ file_path = Path(file).resolve()
73
+ shutil.copy(file_path, Util.get_build_dir() / file_path.name)
74
+
75
+ if rust_logs := getattr(context, 'rust_logs_stdout', None):
76
+ shutil.copy(Path(rust_logs), Util.get_build_dir() / Path(rust_logs).name)
77
+ if rust_logs := getattr(context, 'rust_logs_stderr', None):
78
+ shutil.copy(Path(rust_logs), Util.get_build_dir() / Path(rust_logs).name)
@@ -190,25 +190,6 @@ def parse_json(response: Response) -> Dict[str, Any]:
190
190
  return json_response
191
191
 
192
192
 
193
- def compress_files_wasm(zip_file_path: Path, context: CertoraContext) -> bool:
194
- with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zip_obj:
195
- cargo_directory = context.cargo_dir
196
- for path in cargo_directory.rglob('*.rs'):
197
- if cargo_directory / 'target' in path.parents or cargo_directory / '.certora_internal' in path.parents:
198
- continue
199
- zip_obj.write(path, Util.CERTORA_SOURCES / path.relative_to(cargo_directory))
200
- for path in [cargo_directory / 'Cargo.toml', cargo_directory / 'Cargo.lock']:
201
- if path.exists():
202
- zip_obj.write(path, path.relative_to(cargo_directory))
203
- if context.wasm_file.exists():
204
- zip_obj.write(context.wasm_file, context.wasm_file.relative_to(context.wasm_file.parent))
205
-
206
- if zip_file_path.stat().st_size > MAX_FILE_SIZE:
207
- cloud_logger.error(f"{GENERAL_ERR_PREFIX} Max 25MB file size exceeded.")
208
- return False
209
- return True
210
-
211
-
212
193
  def compress_files(zip_file_path: Path, *resource_paths: Path, short_output: bool = False) -> bool:
213
194
  with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zip_obj:
214
195
 
@@ -600,8 +581,12 @@ class CloudVerification:
600
581
  # We need to strip "../" path component from all file paths because
601
582
  # unzip will also do that.
602
583
  solana_jar_settings = []
603
- for file in self.context.files:
604
- solana_jar_settings.append(file.split('../')[-1])
584
+ if hasattr(self.context, 'build_script') and self.context.build_script:
585
+ solana_jar_settings.append(Path(self.context.rust_executables).name)
586
+
587
+ else:
588
+ for file in self.context.files:
589
+ solana_jar_settings.append(file.split('../')[-1])
605
590
 
606
591
  is_file = False
607
592
  for arg in jar_settings:
@@ -616,18 +601,19 @@ class CloudVerification:
616
601
  elif arg == '-solanaSummaries':
617
602
  is_file = True
618
603
  auth_data["jarSettings"] = solana_jar_settings
619
- elif Attrs.is_wasm_app():
604
+ elif Attrs.is_soroban_app():
620
605
  # We need to strip "../" path component from all file paths because
621
606
  # unzip will also do that.
622
- wasm_jar_settings = []
623
- if hasattr(self.context, 'wasm_file') and self.context.wasm_file:
624
- wasm_jar_settings.append(self.context.wasm_file.name) # assuming root is cargo_directory
607
+ soroban_jar_settings = []
608
+ # not needed - should be in files
609
+ if hasattr(self.context, 'build_script') and self.context.build_script:
610
+ soroban_jar_settings.append(Path(self.context.rust_executables).name)
625
611
  else:
626
612
  for file in self.context.files:
627
- wasm_jar_settings.append(file.split('../')[-1])
613
+ soroban_jar_settings.append(file.split('../')[-1])
628
614
  for arg in jar_settings:
629
- wasm_jar_settings.append(arg)
630
- auth_data["jarSettings"] = wasm_jar_settings
615
+ soroban_jar_settings.append(arg)
616
+ auth_data["jarSettings"] = soroban_jar_settings
631
617
  else:
632
618
  auth_data["jarSettings"] = jar_settings
633
619
 
@@ -738,40 +724,81 @@ class CloudVerification:
738
724
  short_output=Ctx.is_minimal_cli_output(self.context))
739
725
  elif Attrs.is_evm_app() and self.context.is_bytecode:
740
726
  # We zip the bytecode jsons and the spec
741
- paths = []
727
+ paths = [Util.get_certora_build_file(), Util.get_certora_verify_file(),
728
+ Util.get_certora_metadata_file()]
729
+ if Util.get_certora_sources_dir().exists():
730
+ paths.append(Util.get_certora_sources_dir())
731
+
742
732
  for bytecode_json in self.context.bytecode_jsons:
743
733
  paths.append(Path(bytecode_json))
744
734
  paths.append(Path(self.context.bytecode_spec))
745
735
  result = compress_files(self.ZipFilePath, *paths,
746
736
  short_output=Ctx.is_minimal_cli_output(self.context))
747
- elif Attrs.is_solana_app():
748
- # We zip the ELF files and the two configuration files
749
- jar_args = Ctx.collect_jar_args(self.context)
750
- paths = []
751
- for file in self.context.files:
752
- paths.append(Path(file))
753
- is_file = False
754
- for arg in jar_args:
755
- if is_file:
756
- paths.append(Path(arg))
757
- is_file = False
737
+ elif Attrs.is_rust_app():
738
+ files_list = [Util.get_certora_metadata_file()]
739
+
740
+ if hasattr(self.context, 'build_script') and self.context.build_script:
741
+ result = compress_files(self.logZipFilePath, Util.get_debug_log_file(),
742
+ short_output=Ctx.is_minimal_cli_output(self.context))
743
+
744
+ if not result:
745
+ return False
746
+ files_list.append(self.logZipFilePath)
747
+ if Util.get_certora_sources_dir().exists():
748
+ files_list.append(Util.get_certora_sources_dir())
749
+
750
+ files_list.append(Util.get_build_dir() / Path(self.context.rust_executables).name)
751
+
752
+ # Create a .RustExecution file to classify zipInput as a rust source code
753
+ rust_execution_file = Util.get_build_dir() / ".RustExecution"
754
+ rust_execution_file.touch(exist_ok=True)
755
+ files_list.append(rust_execution_file)
756
+
757
+ additional_files = (getattr(self.context, 'solana_inlining', None) or []) + \
758
+ (getattr(self.context, 'solana_summaries', None) or [])
759
+ for file in additional_files:
760
+ files_list.append(Util.get_build_dir() / Path(file).name)
761
+
762
+ if attr_file := getattr(self.context, 'rust_logs_stdout', None):
763
+ files_list.append(Util.get_build_dir() / Path(attr_file).name)
764
+ if attr_file := getattr(self.context, 'rust_logs_stderr', None):
765
+ files_list.append(Util.get_build_dir() / Path(attr_file).name)
766
+
767
+ result = compress_files(self.ZipFilePath, *files_list,
768
+ short_output=Ctx.is_minimal_cli_output(self.context))
769
+
770
+ elif Attrs.is_solana_app():
771
+ # We zip the ELF files and the two configuration files
772
+ jar_args = Ctx.collect_jar_args(self.context)
758
773
 
759
- if arg == '-solanaInlining':
760
- is_file = True
761
- elif arg == '-solanaSummaries':
762
- is_file = True
763
- result = compress_files(self.ZipFilePath, *paths,
764
- short_output=Ctx.is_minimal_cli_output(self.context))
765
- elif Attrs.is_wasm_app():
766
- # We zip the wat file
767
- paths = []
768
- if hasattr(self.context, 'wasm_file') and self.context.wasm_file:
769
- result = compress_files_wasm(self.ZipFilePath, self.context)
770
- else:
771
774
  for file in self.context.files:
772
- paths.append(Path(file))
773
- result = compress_files(self.ZipFilePath, *paths,
775
+ files_list.append(Path(file))
776
+ is_file = False
777
+ for arg in jar_args:
778
+ if is_file:
779
+ files_list.append(Path(arg))
780
+ is_file = False
781
+
782
+ if arg == '-solanaInlining':
783
+ is_file = True
784
+ elif arg == '-solanaSummaries':
785
+ is_file = True
786
+ result = compress_files(self.ZipFilePath, *files_list,
774
787
  short_output=Ctx.is_minimal_cli_output(self.context))
788
+
789
+ elif Attrs.is_soroban_app():
790
+ # We zip the wat file
791
+ for file in self.context.files:
792
+ files_list.append(Path(file))
793
+ result = compress_files(self.ZipFilePath, *files_list,
794
+ short_output=Ctx.is_minimal_cli_output(self.context))
795
+ else:
796
+ raise Util.CertoraUserInputError(
797
+ 'When running rust application, context should either have attribute "rust_executables" '
798
+ 'provided by build_script execution, '
799
+ 'or either is_solana_app(), is_soroban_app() should be set to True'
800
+ )
801
+
775
802
  else:
776
803
  # Zip the log file first separately and again with the rest of the files, so it will not be decompressed
777
804
  # on each run in order to save space
@@ -847,7 +874,7 @@ class CloudVerification:
847
874
  self.vscode_extension_info_writer.add_field("verification_report_url", self.reportUrl)
848
875
  self.vscode_extension_info_writer.write_file()
849
876
 
850
- if wait_for_results == str(Vf.WaitForResultOptions.NONE):
877
+ if not wait_for_results or wait_for_results == str(Vf.WaitForResultOptions.NONE):
851
878
  job_list.save_data()
852
879
  job_list.save_recent_jobs_to_path()
853
880
  self.print_output_links()
@@ -149,7 +149,7 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
149
149
  group_id=None)
150
150
 
151
151
  # collect information about current git snapshot
152
- cwd_abs = wd.resolve()
152
+ cwd_abs = wd.absolute()
153
153
 
154
154
  is_git_executable = False
155
155
  git_present_out = None