certora-cli 7.20.3__tar.gz → 7.21.1__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 (70) hide show
  1. {certora_cli-7.20.3/certora_cli.egg-info → certora_cli-7.21.1}/PKG-INFO +3 -2
  2. certora_cli-7.21.1/README.md +1 -0
  3. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraBuild.py +21 -16
  4. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraCloudIO.py +1 -1
  5. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraCollectRunMetadata.py +1 -1
  6. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraContext.py +2 -2
  7. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraContextAttributes.py +19 -18
  8. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraContextValidator.py +2 -2
  9. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Mutate/mutateApp.py +167 -40
  10. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Mutate/mutateAttributes.py +10 -2
  11. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Mutate/mutateConstants.py +1 -2
  12. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Mutate/mutateUtil.py +1 -1
  13. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Mutate/mutateValidate.py +4 -2
  14. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Shared/certoraAttrUtil.py +1 -0
  15. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Shared/certoraUtils.py +40 -10
  16. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Shared/certoraValidateFuncs.py +9 -3
  17. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/certoraMutate.py +17 -14
  18. certora_cli-7.21.1/certora_cli/rustMutator.py +286 -0
  19. {certora_cli-7.20.3 → certora_cli-7.21.1/certora_cli.egg-info}/PKG-INFO +3 -2
  20. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli.egg-info/SOURCES.txt +1 -0
  21. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli.egg-info/requires.txt +1 -0
  22. certora_cli-7.21.1/certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -0
  23. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_jars/Typechecker.jar +0 -0
  24. {certora_cli-7.20.3 → certora_cli-7.21.1}/setup.py +3 -3
  25. certora_cli-7.20.3/README.md +0 -1
  26. certora_cli-7.20.3/certora_jars/CERTORA-CLI-VERSION-METADATA.json +0 -1
  27. {certora_cli-7.20.3 → certora_cli-7.21.1}/LICENSE +0 -0
  28. {certora_cli-7.20.3 → certora_cli-7.21.1}/MANIFEST.in +0 -0
  29. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_bins/__init__.py +0 -0
  30. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/Compiler/CompilerCollector.py +0 -0
  31. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/Compiler/CompilerCollectorFactory.py +0 -0
  32. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/Compiler/CompilerCollectorSol.py +0 -0
  33. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/Compiler/CompilerCollectorSolBased.py +0 -0
  34. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/Compiler/CompilerCollectorVy.py +0 -0
  35. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/Compiler/CompilerCollectorYul.py +0 -0
  36. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/Compiler/__init__.py +0 -0
  37. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/__init__.py +0 -0
  38. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraBuildCacheManager.py +0 -0
  39. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraBuildDataClasses.py +0 -0
  40. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraCompilerParameters.py +0 -0
  41. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraConfigIO.py +0 -0
  42. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraContextClass.py +0 -0
  43. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraContractFuncs.py +0 -0
  44. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraExtensionInfo.py +0 -0
  45. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraJobList.py +0 -0
  46. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraMiniSpecParser.py +0 -0
  47. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraNodeFilters.py +0 -0
  48. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraSourceFinders.py +0 -0
  49. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraType.py +0 -0
  50. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EVMVerifier/certoraVerifyGenerator.py +0 -0
  51. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EquivalenceCheck/Eq_default.conf +0 -0
  52. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EquivalenceCheck/Eq_mc_no_out_template.spec +0 -0
  53. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EquivalenceCheck/Eq_mc_template.spec +0 -0
  54. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -0
  55. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EquivalenceCheck/Eq_template.spec +0 -0
  56. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EquivalenceCheck/__init__.py +0 -0
  57. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EquivalenceCheck/equivCheck.py +0 -0
  58. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/EquivalenceCheck/sanity.spec +0 -0
  59. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Mutate/__init__.py +0 -0
  60. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Shared/ExpectedComparator.py +0 -0
  61. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Shared/__init__.py +0 -0
  62. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/Shared/certoraLogging.py +0 -0
  63. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/__init__.py +0 -0
  64. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/certoraEqCheck.py +0 -0
  65. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli/certoraRun.py +0 -0
  66. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli.egg-info/dependency_links.txt +0 -0
  67. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli.egg-info/entry_points.txt +0 -0
  68. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_cli.egg-info/top_level.txt +0 -0
  69. {certora_cli-7.20.3 → certora_cli-7.21.1}/certora_jars/__init__.py +0 -0
  70. {certora_cli-7.20.3 → certora_cli-7.21.1}/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.21.1
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,6 @@ Requires-Dist: tabulate
21
21
  Requires-Dist: tqdm
22
22
  Requires-Dist: StrEnum
23
23
  Requires-Dist: tomli
24
+ Requires-Dist: universalmutator
24
25
 
25
- Commit 7b9a1c7. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
26
+ Commit 03eae31. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -0,0 +1 @@
1
+ Commit 03eae31. 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):
@@ -1692,7 +1697,7 @@ class CertoraBuildGenerator:
1692
1697
  is_vyper = smart_contract_lang == CompilerLangVy()
1693
1698
  sdc_name = f"{Path(build_arg_contract_file).name}_{file_index}"
1694
1699
  compilation_path = self.get_compilation_path(sdc_name)
1695
- self.file_to_sdc_name[Path(build_arg_contract_file).resolve()] = sdc_name
1700
+ self.file_to_sdc_name[Util.abs_norm_path(build_arg_contract_file)] = sdc_name
1696
1701
  # update remappings and collect_cmd:
1697
1702
  if not is_vyper:
1698
1703
  Util.safe_create_dir(compilation_path)
@@ -1714,7 +1719,7 @@ class CertoraBuildGenerator:
1714
1719
  is equivalent to `main_path = self.context.solc_allow_path`, and that's the best we can do in that
1715
1720
  case anyway.
1716
1721
  """
1717
- main_path = (compile_wd / self.context.solc_allow_path).resolve()
1722
+ main_path = (compile_wd / self.context.solc_allow_path).absolute()
1718
1723
  else:
1719
1724
  main_path = self.context.solc_allow_path
1720
1725
  self.context.solc_cmd_allow_paths = [f'"{main_path}"', '.']
@@ -1813,7 +1818,8 @@ class CertoraBuildGenerator:
1813
1818
  contract_file = str(contract_file_obj)
1814
1819
  build_logger.debug(f"Processing dependencies from file {contract_file}")
1815
1820
  # make sure path name is in posix format.
1816
- contract_file_abs = Path(contract_file).resolve().as_posix()
1821
+ contract_file_abs = str(Util.abs_norm_path(contract_file))
1822
+
1817
1823
  # using os.path.relpath because Path.relative_to cannot go up the directory tree (no ..)
1818
1824
  contract_file_rel = os.path.relpath(Path(contract_file_abs), Path.cwd())
1819
1825
 
@@ -1849,7 +1855,7 @@ class CertoraBuildGenerator:
1849
1855
  base_contracts = sorted(self.retrieve_base_contracts_list(build_arg_contract_file, contract_file,
1850
1856
  contract_name), key=lambda x: x[0])
1851
1857
  for c_file, _, _ in base_contracts:
1852
- norm_c_file = Path(c_file).resolve()
1858
+ norm_c_file = Util.abs_norm_path(c_file)
1853
1859
  resolved_to_orig[str(norm_c_file)] = c_file
1854
1860
  if norm_c_file not in seen_link_refs:
1855
1861
  build_logger.debug(f"Adding a base contract link ref {norm_c_file} to worklist")
@@ -1860,7 +1866,7 @@ class CertoraBuildGenerator:
1860
1866
  contract_object = data[CONTRACTS][contract_file][contract_name]
1861
1867
  lib_link_refs = sorted(contract_object["evm"]["deployedBytecode"]["linkReferences"])
1862
1868
  for lib_link_ref in lib_link_refs: # linkReference is a library reference
1863
- norm_link_ref = Path(lib_link_ref).resolve()
1869
+ norm_link_ref = Path(lib_link_ref).absolute()
1864
1870
  resolved_to_orig[str(norm_link_ref)] = lib_link_ref
1865
1871
  if norm_link_ref not in seen_link_refs:
1866
1872
  build_logger.debug(f"Adding library link ref {norm_link_ref} to worklist")
@@ -1879,8 +1885,7 @@ class CertoraBuildGenerator:
1879
1885
  contract_refs = [c_file for c_file in
1880
1886
  data[CONTRACTS].keys() if
1881
1887
  ref in data[CONTRACTS][c_file].keys()]
1882
- contract_files_resolved = sorted([Path(x).resolve() for x in
1883
- contract_refs])
1888
+ contract_files_resolved = sorted([Util.abs_norm_path(x) for x in contract_refs])
1884
1889
 
1885
1890
  # There may be two non unique paths but actually referring to the same absolute path.
1886
1891
  # Normalizing as absolute
@@ -2302,7 +2307,7 @@ class CertoraBuildGenerator:
2302
2307
  f" {c.name} that doesn't have a location")
2303
2308
  return None
2304
2309
 
2305
- instrumentation_path = Util.convert_path_for_solc_import(loc[0])
2310
+ instrumentation_path = str(Util.abs_norm_path(loc[0]))
2306
2311
  if instrumentation_path not in function_finder_instrumentation:
2307
2312
  instrumentation_logger.debug(f"New instrumentation for location {loc[0]}, " +
2308
2313
  f"instrumentation path {instrumentation_path}")
@@ -2879,7 +2884,7 @@ class CertoraBuildGenerator:
2879
2884
  p = Path(path)
2880
2885
  if p.is_absolute():
2881
2886
  return path
2882
- return str(Path.cwd() / p.resolve())
2887
+ return str(Path.cwd() / p.absolute())
2883
2888
 
2884
2889
  @staticmethod
2885
2890
  def merge_dicts_instrumentation(dict1: Dict[str, Dict[int, Instrumentation]],
@@ -3052,7 +3057,7 @@ class CertoraBuildGenerator:
3052
3057
  """
3053
3058
  rel_to_cwd_path = Path(os.path.relpath(path, '.'))
3054
3059
  new_path = Util.get_certora_sources_dir() / self.cwd_rel_in_sources / rel_to_cwd_path
3055
- return str(new_path.resolve())
3060
+ return str(new_path.absolute())
3056
3061
 
3057
3062
  def handle_links(self) -> None:
3058
3063
  # Link processing
@@ -3481,7 +3486,7 @@ def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
3481
3486
  saw_paths_not_in_sources = False
3482
3487
  paths_not_in_sources = set()
3483
3488
  may_store_in_build_cache = True
3484
- absolute_sources_dir = Util.get_certora_sources_dir().resolve()
3489
+ absolute_sources_dir = Util.get_certora_sources_dir().absolute()
3485
3490
  for sdc in certora_build_generator.SDCs.values():
3486
3491
  # the contract files in SDCs are relative to .certora_sources. Which isn't good for us here.
3487
3492
  # Need to be relative to original paths
@@ -847,7 +847,7 @@ class CloudVerification:
847
847
  self.vscode_extension_info_writer.add_field("verification_report_url", self.reportUrl)
848
848
  self.vscode_extension_info_writer.write_file()
849
849
 
850
- if wait_for_results == str(Vf.WaitForResultOptions.NONE):
850
+ if not wait_for_results or wait_for_results == str(Vf.WaitForResultOptions.NONE):
851
851
  job_list.save_data()
852
852
  job_list.save_recent_jobs_to_path()
853
853
  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
@@ -528,7 +528,7 @@ def run_local_spec_check(with_typechecking: bool, context: CertoraContext) -> No
528
528
  "To skip local checks run with the --disable_local_typechecking flag")
529
529
 
530
530
  def set_cargo_name(context: CertoraContext) -> None:
531
- mode = "rb" if platform.system() == 'Darwin' else "r"
531
+ mode = "r" if platform.system().lower() == 'windows' else "rb"
532
532
  with open(context.cargo_file, mode) as f:
533
533
  cargo_data = tomllib.load(f)
534
534
 
@@ -550,7 +550,7 @@ def build_wasm(context: CertoraContext) -> None:
550
550
 
551
551
  def run_wasm_compiler(context: CertoraContext) -> None:
552
552
  env = os.environ.copy()
553
- env['RUSTFLAGS'] = "-C strip=none -C debuginfo=2"
553
+ env['RUSTFLAGS'] = "-C strip=none --emit=llvm-ir -C debuginfo=2"
554
554
  try:
555
555
  context_logger.info("Executing build process...")
556
556
 
@@ -130,6 +130,24 @@ class CommonAttributes(AttrUtil.Attributes):
130
130
  affects_build_cache_key=False,
131
131
  disables_build_cache=False
132
132
  )
133
+ # used by certoraMutate, ignored by certoraRun
134
+ MUTATIONS = AttrUtil.AttributeDefinition(
135
+ arg_type=AttrUtil.AttrArgType.MAP,
136
+ argparse_args={
137
+ 'action': AttrUtil.NotAllowed
138
+ },
139
+ affects_build_cache_key=False,
140
+ disables_build_cache=False
141
+ )
142
+
143
+ RUN_SOURCE = AttrUtil.AttributeDefinition(
144
+ attr_validation_func=Vf.validate_run_source,
145
+ argparse_args={
146
+ 'action': AttrUtil.UniqueStore
147
+ },
148
+ affects_build_cache_key=False,
149
+ disables_build_cache=False
150
+ )
133
151
 
134
152
  class DeprecatedAttributes(AttrUtil.Attributes):
135
153
  AUTO_NONDET_DIFFICULT_INTERNAL_FUNCS = AttrUtil.AttributeDefinition(
@@ -707,15 +725,7 @@ class EvmAttributes(AttrUtil.Attributes):
707
725
  affects_build_cache_key=True,
708
726
  disables_build_cache=True
709
727
  )
710
- # used by certoraMutate, ignored by certoraRun
711
- MUTATIONS = AttrUtil.AttributeDefinition(
712
- arg_type=AttrUtil.AttrArgType.MAP,
713
- argparse_args={
714
- 'action': AttrUtil.NotAllowed
715
- },
716
- affects_build_cache_key=False,
717
- disables_build_cache=False
718
- )
728
+
719
729
  DISABLE_INTERNAL_FUNCTION_INSTRUMENTATION = AttrUtil.AttributeDefinition(
720
730
  arg_type=AttrUtil.AttrArgType.BOOLEAN,
721
731
  argparse_args={
@@ -852,15 +862,6 @@ class BackendAttributes(AttrUtil.Attributes):
852
862
  disables_build_cache=False
853
863
  )
854
864
 
855
- RUN_SOURCE = AttrUtil.AttributeDefinition(
856
- attr_validation_func=Vf.validate_run_source,
857
- argparse_args={
858
- 'action': AttrUtil.UniqueStore
859
- },
860
- affects_build_cache_key=False,
861
- disables_build_cache=False
862
- )
863
-
864
865
  LOOP_ITER = AttrUtil.AttributeDefinition(
865
866
  attr_validation_func=Vf.validate_non_negative_integer,
866
867
  jar_flag='-b',
@@ -133,7 +133,7 @@ class CertoraContextValidator:
133
133
  # the absolute one.
134
134
  pass
135
135
 
136
- set_relative_paths(context)
136
+ set_bytecode_relative_paths(context)
137
137
  check_files_input(context.files)
138
138
  check_vyper_flag(context)
139
139
  check_compiler_map_flag(context)
@@ -762,7 +762,7 @@ def to_relative_paths(paths: Union[str, List[str]]) -> Union[str, List[str]]:
762
762
  if isinstance(paths, list):
763
763
  return [os.path.relpath(path, cwd) for path in paths]
764
764
 
765
- def set_relative_paths(context: CertoraContext) -> None:
765
+ def set_bytecode_relative_paths(context: CertoraContext) -> None:
766
766
  for attr in [Attrs.EvmProverAttributes.BYTECODE_JSONS.get_conf_key(),
767
767
  Attrs.EvmProverAttributes.BYTECODE_SPEC.get_conf_key()]:
768
768
  value = getattr(context, attr, None)
@@ -36,6 +36,7 @@ from Mutate import mutateUtil as MutUtil
36
36
  from Mutate import mutateAttributes as MutAttrs
37
37
  import EVMVerifier.certoraContextAttributes as Attrs
38
38
  from EVMVerifier.certoraContextClass import CertoraContext
39
+ from rustMutator import run_universal_mutator
39
40
 
40
41
  class RunTimedout(Exception):
41
42
  pass
@@ -43,7 +44,6 @@ class RunTimedout(Exception):
43
44
 
44
45
  DEFAULT_NUM_MUTANTS = 5
45
46
  ENV_FREE_CHECK = 'envfreeFuncsStaticCheck'
46
- PATCH_SUFFIXES = ['.sol', '.patch']
47
47
 
48
48
  mutation_logger = logging.getLogger("mutation")
49
49
 
@@ -600,7 +600,7 @@ def convert_to_mutation_testing_status(treeview_status: str) -> str:
600
600
  return MutationTestRuleStatus.UNKNOWN.value
601
601
 
602
602
 
603
- def generate_manual_mutant_object(manual_mutant: Path, orig_file: Path) -> Mutant:
603
+ def mutant_object_from_path(manual_mutant: Path, orig_file: Path) -> Mutant:
604
604
  """
605
605
  Generate a Mutant object for a manual mutation.
606
606
 
@@ -699,9 +699,13 @@ class MutateApp:
699
699
  request_timeout: Optional[int]
700
700
  gambit: Union[Dict, List, None]
701
701
  manual_mutants: Optional[Dict]
702
+ universal_mutator: Optional[Dict]
702
703
  prover_context: CertoraContext
703
704
 
704
- def __init__(self, args_list: List[str] = []) -> None:
705
+ def __init__(self, args_list: List[str]) -> None:
706
+ self.mutation_test_id = ''
707
+ self.numb_of_jobs: int = 0
708
+ self.backup_paths: List[Path] = []
705
709
  self.sources_dir: Optional[Path] = None
706
710
  self.with_split_stats_data = False
707
711
  self.manual_mutants_list: List[Mutant] = list()
@@ -710,6 +714,13 @@ class MutateApp:
710
714
  self.read_conf_file()
711
715
  self.server = self.config_server()
712
716
 
717
+ def is_soroban_run(self) -> bool:
718
+ try:
719
+ files = self.prover_context.files
720
+ except AttributeError:
721
+ return False
722
+ return Util.CARGO_FILE in files or any(Path(path).suffix == '.wasm' for path in files)
723
+
713
724
  def config_server(self) -> str:
714
725
  """
715
726
  If given a server, it is taken.
@@ -856,7 +867,58 @@ class MutateApp:
856
867
  raise Util.CertoraUserInputError(f"The mutated file, {file_to_mutate}, does not affect"
857
868
  " verification results. Please remove or replace the mutation")
858
869
 
859
- def submit(self) -> None:
870
+ def restore_backups(self) -> None:
871
+ for backup_path in self.backup_paths:
872
+ Util.restore_backup(backup_path)
873
+ self.backup_paths = []
874
+
875
+ def submit_soroban(self) -> None:
876
+ mutation_logger.info("Generating mutants and submitting...")
877
+ generated_mutants: List[Mutant] = self.get_universal_mutator_mutants()
878
+
879
+ web_utils = WebUtils(SimpleNamespace(**vars(self)))
880
+ # get the mutation test id
881
+ self.numb_of_jobs = len(generated_mutants)
882
+ self.mutation_test_id, collect_presigned_url = self.get_mutation_test_id_request(web_utils,
883
+ self.numb_of_jobs + 1)
884
+ mutation_logger.debug(f"Mutation test id: {self.mutation_test_id}")
885
+ print_separator('PROVER START')
886
+ # run original run. if it fails to compile, nothing to continue with
887
+
888
+ try:
889
+ original_run_result = self.run_certora_prover(self.conf, self.mutation_test_id, msg=MConstants.ORIGINAL)
890
+ except Exception as e:
891
+ raise Util.CertoraUserInputError(f"Orig run: {e}")
892
+ if not original_run_result:
893
+ raise Util.CertoraUserInputError("No Orig results")
894
+ result_link = original_run_result.rule_report_link
895
+ assert result_link, "submit_soroban: Null result_link"
896
+
897
+ if not Vf.Util.is_valid_url(urllib3.util.parse_url(result_link)):
898
+ raise Util.CertoraUserInputError(f"Invalid certoraRun result: {result_link}")
899
+ print_separator('PROVER END')
900
+ # run mutants
901
+ mutant_runs = []
902
+ try:
903
+ self.backup_paths = []
904
+ for mutant in generated_mutants:
905
+ # call certoraRun for each mutant we found
906
+ mutant_runs.append(self.run_mutant_soroban(mutant))
907
+ finally:
908
+ self.restore_backups()
909
+
910
+ # wrap it all up and make the input for the 2nd step: the collector
911
+ assert self.collect_file, "submit_soroban: no collect_file"
912
+ with self.collect_file.open('w+') as collect_file:
913
+ collect_data = {MConstants.ORIGINAL: result_link,
914
+ MConstants.MUTANTS: [dataclasses.asdict(m) for m in mutant_runs]}
915
+ json.dump(collect_data, collect_file, indent=4)
916
+
917
+ self.upload_file_to_cloud_storage(web_utils, collect_presigned_url, collect_data)
918
+ self.print_notification_msg()
919
+ mutation_logger.info(f"... completed submit phase! Now we poll on {self.collect_file}...")
920
+
921
+ def submit_evm(self) -> None:
860
922
  mutation_logger.info("Generating mutants and submitting...")
861
923
 
862
924
  # ensure .certora_internal exists
@@ -875,7 +937,7 @@ class MutateApp:
875
937
  sys.exit(0) # we exit here so we will not continue with collect in case of sync mode
876
938
 
877
939
  if self.manual_mutants:
878
- self.manual_mutants_list = self.parse_manual_mutations()
940
+ self.manual_mutants_list = self.get_manual_mutations_mutants()
879
941
  mutation_logger.debug(f"successfully parsed manual mutants from {self.conf}")
880
942
 
881
943
  # match a generated mutant to a directory where we will apply the diff
@@ -910,7 +972,7 @@ class MutateApp:
910
972
  if certora_run_result:
911
973
  self.sources_dir = certora_run_result.src_dir
912
974
 
913
- assert certora_run_result.rule_report_link, "submit: no certora_run_result.rule_report_link"
975
+ assert certora_run_result.rule_report_link, "submit_evm: no certora_run_result.rule_report_link"
914
976
  result_link = certora_run_result.rule_report_link
915
977
  if self.wait_for_original_run:
916
978
  if wait_for_job(result_link) and download_report_file(result_link, MConstants.SPLIT_STATS_DATA):
@@ -938,7 +1000,7 @@ class MutateApp:
938
1000
  with prover_conf.open('w') as p_conf:
939
1001
  json.dump(prover_conf_content.__dict__, p_conf)
940
1002
  shutil.copy(prover_conf, self.sources_dir)
941
- assert self.orig_run_dir, "submit: orig_run_dir"
1003
+ assert self.orig_run_dir, "submit_evm: orig_run_dir"
942
1004
  src = self.orig_run_dir / MConstants.TARNAME / MConstants.REPORTS / MConstants.SPLIT_STATS_DATA
943
1005
  if src.is_file():
944
1006
  try:
@@ -968,14 +1030,14 @@ class MutateApp:
968
1030
  num_processes_for_mp = None
969
1031
  max_task_per_worker = None
970
1032
 
971
- # For debug call run_mutant in same process
1033
+ # For debug call run_mutant_evm in same process
972
1034
  # runs = [(mutant, trg_dir, prover_conf,
973
1035
  # mutation_test_id, numb_of_jobs) for mutant, trg_dir in all_mutants_with_target_dir]
974
1036
  # mutant_runs = []
975
1037
  # for run in runs:
976
- # mutant_runs.append(self.run_mutant(*run))
1038
+ # mutant_runs.append(self.run_mutant_evm(*run))
977
1039
  with multiprocessing.Pool(processes=num_processes_for_mp, maxtasksperchild=max_task_per_worker) as pool:
978
- mutant_runs = pool.starmap(self.run_mutant,
1040
+ mutant_runs = pool.starmap(self.run_mutant_evm,
979
1041
  [(mutant, trg_dir, prover_conf, mutation_test_id, numb_of_jobs)
980
1042
  for mutant, trg_dir in all_mutants_with_target_dir])
981
1043
 
@@ -986,7 +1048,7 @@ class MutateApp:
986
1048
  mutation_logger.debug([dataclasses.asdict(m) for m in mutant_runs])
987
1049
 
988
1050
  # wrap it all up and make the input for the 2nd step: the collector
989
- assert self.collect_file, "submit: no collect_file"
1051
+ assert self.collect_file, "submit_evm: no collect_file"
990
1052
  with self.collect_file.open('w+') as collect_file:
991
1053
  collect_data = {MConstants.ORIGINAL: result_link,
992
1054
  MConstants.MUTANTS: [dataclasses.asdict(m) for m in mutant_runs]}
@@ -1015,8 +1077,8 @@ class MutateApp:
1015
1077
  if self.with_split_stats_data and os.environ.get("WITH_AUTOCONFING", False) == '1':
1016
1078
  shutil.copy(Path.cwd() / MConstants.SPLIT_STATS_DATA, find_cwd(trg_dir))
1017
1079
 
1018
- def run_mutant(self, mutant: Mutant, trg_dir: Path, orig_conf: Path,
1019
- mutation_test_id: str, numb_of_jobs: int) -> MutantJob:
1080
+ def run_mutant_evm(self, mutant: Mutant, trg_dir: Path, orig_conf: Path,
1081
+ mutation_test_id: str, numb_of_jobs: int) -> MutantJob:
1020
1082
 
1021
1083
  MutUtil.TOTAL_MUTANTS = numb_of_jobs
1022
1084
  with Util.change_working_directory(find_cwd(trg_dir)):
@@ -1033,7 +1095,7 @@ class MutateApp:
1033
1095
  rule_report_link=None
1034
1096
  )
1035
1097
 
1036
- assert certora_run_result, f"run_mutant: no result {mutant.id} "
1098
+ assert certora_run_result, f"run_mutant_evm: no result {mutant.id} "
1037
1099
 
1038
1100
  link = certora_run_result.link
1039
1101
  self.sources_dir = certora_run_result.src_dir
@@ -1046,6 +1108,38 @@ class MutateApp:
1046
1108
  rule_report_link=certora_run_result.rule_report_link
1047
1109
  )
1048
1110
 
1111
+ def run_mutant_soroban(self, mutant: Mutant) -> MutantJob:
1112
+ file_to_mutate = Path(mutant.original_filename)
1113
+ if file_to_mutate not in self.backup_paths:
1114
+ # create a backup copy by adding '.backup' to the end of the file
1115
+ backup_file = Util.create_backup(file_to_mutate)
1116
+ assert backup_file, f"run_mutant_soroban: create_backup for {file_to_mutate} failed"
1117
+ self.backup_paths.append(backup_file)
1118
+ shutil.copy(mutant.filename, file_to_mutate)
1119
+
1120
+ try:
1121
+ certora_run_result = self.run_certora_prover(self.conf, self.mutation_test_id, msg=f"mutant ID: {mutant.id}")
1122
+ except Util.CertoraUserInputError:
1123
+ return MutantJob(
1124
+ gambit_mutant=mutant,
1125
+ success=False,
1126
+ run_directory="",
1127
+ link=None,
1128
+ rule_report_link=None
1129
+ )
1130
+
1131
+ assert certora_run_result, f"run_mutant_soroban: no result {mutant.id} "
1132
+
1133
+ link = certora_run_result.link
1134
+
1135
+ return MutantJob(
1136
+ gambit_mutant=mutant,
1137
+ success=True,
1138
+ run_directory="",
1139
+ link=link,
1140
+ rule_report_link=certora_run_result.rule_report_link
1141
+ )
1142
+
1049
1143
  def get_solc_version(self, path_to_file: Path) -> str:
1050
1144
  solc = getattr(self.prover_context, 'solc', '')
1051
1145
  solc_map = getattr(self.prover_context, 'solc_map', None)
@@ -1125,7 +1219,7 @@ class MutateApp:
1125
1219
  evm_version = getattr(self.prover_context, 'solc_evm_version', None)
1126
1220
  if evm_version:
1127
1221
  shared_attributes['solc_evm_version'] = evm_version
1128
- assert self.gambit, "submit: no gambit"
1222
+ assert self.gambit, "submit_evm: no gambit"
1129
1223
  for gambit_obj in self.gambit:
1130
1224
  if MConstants.NUM_MUTANTS not in gambit_obj:
1131
1225
  gambit_obj[MConstants.NUM_MUTANTS] = DEFAULT_NUM_MUTANTS
@@ -1179,52 +1273,78 @@ class MutateApp:
1179
1273
  mutation_logger.debug("Got mutant information")
1180
1274
  return ret_mutants
1181
1275
 
1182
- @staticmethod
1183
- def add_file_to_mutants(manual_mutants: List[Mutant], mutation_file: Path, file_to_mutate: Path) -> None:
1184
- if mutation_file.suffix in PATCH_SUFFIXES:
1185
- manual_mutant_obj = generate_manual_mutant_object(mutation_file, file_to_mutate)
1186
- manual_mutants.append(manual_mutant_obj)
1276
+ def valid_extensions(self) -> List[str]:
1277
+ if self.is_soroban_run():
1278
+ return ['.rs']
1279
+ else:
1280
+ return ['.sol', '.patch']
1281
+
1282
+ def add_file_to_mutants(self, mutants: List[Mutant], mutation_file: Path, file_to_mutate: Path) -> None:
1283
+
1284
+ if mutation_file.suffix in self.valid_extensions():
1285
+ mutant_obj = mutant_object_from_path(mutation_file, file_to_mutate)
1286
+ mutants.append(mutant_obj)
1187
1287
  else:
1188
1288
  raise Util.CertoraUserInputError(f"Illegal mutant file, {mutation_file.name},"
1189
- " extension must be .sol or .patch")
1289
+ f" extension must be {' or '.join(self.valid_extensions())}")
1190
1290
 
1191
- @staticmethod
1192
- def add_dir_to_mutants(manual_mutants: List[Mutant], mutation_dir: Path, file_to_mutate: Path) -> None:
1193
- mutation_dir_path = Path(mutation_dir).resolve()
1194
- manual_mutants_files = [p for p in mutation_dir_path.glob('*') if p.is_file() and p.suffix in PATCH_SUFFIXES]
1291
+ def add_dir_to_mutants(self, manual_mutants: List[Mutant], mutation_dir: Path, file_to_mutate: Path) -> None:
1292
+ mutation_dir_path = Path(mutation_dir)
1293
+ manual_mutants_files = \
1294
+ [p for p in mutation_dir_path.glob('*') if p.is_file() and p.suffix in self.valid_extensions()]
1195
1295
  if not manual_mutants_files:
1196
1296
  raise Util.CertoraUserInputError(
1197
1297
  f"Could not find any manual mutation files at {mutation_dir}")
1198
1298
  for file in manual_mutants_files:
1199
- MutateApp.add_file_to_mutants(manual_mutants, file, file_to_mutate)
1299
+ self.add_file_to_mutants(manual_mutants, file, file_to_mutate)
1200
1300
 
1201
- def parse_manual_mutations(self) -> List[Mutant]:
1301
+ def get_manual_mutations_mutants(self) -> List[Mutant]:
1202
1302
  """
1203
- Parse manual mutations from a configuration file.
1303
+ Get manual mutations from manual_mutations attribute
1204
1304
 
1205
1305
  Returns:
1206
- - A list of Mutant objects representing the parsed manual mutations.
1306
+ - A list of Mutant objects representing the found manual mutations.
1207
1307
 
1208
1308
  Raises:
1209
- - `FileNotFoundError`: If the mutation configuration file does not exist.
1309
+ - `CertoraUserInputError`: If the mutation configuration file does not exist.
1210
1310
  """
1211
1311
 
1212
1312
  ret_mutants: List[Mutant] = []
1213
- assert self.manual_mutants, "parse_manual_mutations: self.manual_mutants"
1313
+ assert self.manual_mutants, "get_manual_mutations_mutants: self.manual_mutants"
1214
1314
  for mutant in self.manual_mutants:
1215
1315
  file_to_mutate = Path(os.path.normpath(mutant[MConstants.FILE_TO_MUTATE]))
1216
1316
  path_to_orig = Path(file_to_mutate).resolve()
1217
1317
  if not path_to_orig.exists():
1218
1318
  raise Util.CertoraUserInputError(f"Original file '{path_to_orig}' for manual mutations does not exist")
1219
- mutants_location = Path(mutant[MConstants.MUTANTS_LOCATION]).resolve()
1319
+ mutants_location = Path(mutant[MConstants.MUTANTS_LOCATION])
1220
1320
  if mutants_location.is_dir():
1221
- MutateApp.add_dir_to_mutants(ret_mutants, mutants_location, file_to_mutate)
1321
+ self.add_dir_to_mutants(ret_mutants, mutants_location, file_to_mutate)
1222
1322
  elif mutants_location.is_file():
1223
- MutateApp.add_file_to_mutants(ret_mutants, mutants_location, file_to_mutate)
1323
+ self.add_file_to_mutants(ret_mutants, mutants_location, file_to_mutate)
1224
1324
  else:
1225
1325
  raise Util.CertoraUserInputError(f"{mutants_location} not a valid file or directory")
1226
1326
  return ret_mutants
1227
1327
 
1328
+ def get_universal_mutator_mutants(self) -> List[Mutant]:
1329
+ """
1330
+ Get manual mutations from universal_mutator attribute
1331
+
1332
+ Returns:
1333
+ - A list of Mutant objects representing the found manual mutations.
1334
+
1335
+ Raises:
1336
+ - `CertoraUserInputError`: If the mutation configuration file does not exist.
1337
+ """
1338
+
1339
+ ret_mutants: List[Mutant] = []
1340
+ assert self.universal_mutator, "get_universal_mutator_mutants: self.universal_mutator"
1341
+ for mutant in self.universal_mutator:
1342
+ file_to_mutate = Path(os.path.normpath(mutant[MConstants.FILE_TO_MUTATE]))
1343
+ mutants_location = Path(mutant[MConstants.MUTANTS_LOCATION])
1344
+ run_universal_mutator(file_to_mutate, mutants_location)
1345
+ self.add_dir_to_mutants(ret_mutants, mutants_location, file_to_mutate)
1346
+ return ret_mutants
1347
+
1228
1348
  def verify_orig_build(self) -> None:
1229
1349
  if self.orig_run: # if orig run exists we will not try to build again
1230
1350
  return
@@ -1252,7 +1372,8 @@ class MutateApp:
1252
1372
  if self.prover_version:
1253
1373
  certora_args.extend(["--prover_version", self.prover_version])
1254
1374
 
1255
- certora_args.extend(["--disable_local_typechecking"])
1375
+ if not self.is_soroban_run():
1376
+ certora_args.extend(["--disable_local_typechecking"])
1256
1377
  mutation_logger.debug(f"Running the Prover: {certora_args}")
1257
1378
  try:
1258
1379
  certora_run_result = run_certora(certora_args)
@@ -1326,11 +1447,17 @@ class MutateApp:
1326
1447
  all_mutants_results: List[MutantJobWithResults] = []
1327
1448
  for mutant_job in mutants_jobs:
1328
1449
  mutant_link = mutant_job.link
1329
-
1330
- mutant_run_results = self.get_results(mutant_link, fetcher)
1331
- if mutant_run_results is None:
1450
+ if not mutant_link:
1451
+ return None
1452
+ try:
1453
+ mutant_run_results = self.get_results(mutant_link, fetcher)
1454
+ except Util.BadMutationError:
1455
+ # we do not want to abort the mutation testing all together just because Prover failed.
1456
+ # Some mutants may compile but fail during runtime. We just should ignore them in the final report
1457
+ mutant_job.success = False
1458
+ continue
1459
+ if not mutant_run_results:
1332
1460
  return None
1333
-
1334
1461
  mutant_results = MutantJobWithResults(mutant_job, mutant_run_results)
1335
1462
 
1336
1463
  all_mutants_results.append(mutant_results)
@@ -1578,7 +1705,7 @@ class MutateApp:
1578
1705
  raise Util.ImplementationError("Could not get progress object")
1579
1706
  top_level_rules = get_top_level_rules(progress_json)
1580
1707
  if top_level_rules is None:
1581
- raise Util.ImplementationError("Could not get tree view object")
1708
+ raise Util.BadMutationError("Could not get tree view object")
1582
1709
  rule_results = []
1583
1710
 
1584
1711
  for rule in top_level_rules: