certora-cli-beta-mirror 7.28.0__py3-none-any.whl → 7.29.1__py3-none-any.whl

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 (31) hide show
  1. certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +48 -13
  2. certora_cli/CertoraProver/certoraBuild.py +61 -30
  3. certora_cli/CertoraProver/certoraBuildDataClasses.py +5 -2
  4. certora_cli/CertoraProver/certoraBuildRust.py +77 -55
  5. certora_cli/CertoraProver/certoraCloudIO.py +52 -63
  6. certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +205 -70
  7. certora_cli/CertoraProver/certoraCollectRunMetadata.py +3 -1
  8. certora_cli/CertoraProver/certoraConfigIO.py +14 -15
  9. certora_cli/CertoraProver/certoraContext.py +17 -5
  10. certora_cli/CertoraProver/certoraContextAttributes.py +98 -26
  11. certora_cli/CertoraProver/certoraContextValidator.py +47 -5
  12. certora_cli/CertoraProver/certoraParseBuildScript.py +7 -10
  13. certora_cli/CertoraProver/certoraVerifyGenerator.py +12 -0
  14. certora_cli/CertoraProver/splitRules.py +3 -1
  15. certora_cli/Mutate/mutateApp.py +3 -3
  16. certora_cli/Shared/certoraAttrUtil.py +10 -0
  17. certora_cli/Shared/certoraUtils.py +9 -1
  18. certora_cli/Shared/certoraValidateFuncs.py +7 -0
  19. certora_cli/certoraRanger.py +71 -0
  20. certora_cli/certoraRun.py +13 -15
  21. certora_cli/certoraSolanaProver.py +9 -2
  22. certora_cli/certoraSorobanProver.py +253 -4
  23. certora_cli_beta_mirror-7.29.1.dist-info/LICENSE +15 -0
  24. {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-7.29.1.dist-info}/METADATA +18 -4
  25. {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-7.29.1.dist-info}/RECORD +30 -29
  26. {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-7.29.1.dist-info}/WHEEL +1 -1
  27. {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-7.29.1.dist-info}/entry_points.txt +1 -0
  28. certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
  29. certora_jars/Typechecker.jar +0 -0
  30. certora_cli_beta_mirror-7.28.0.dist-info/LICENSE +0 -22
  31. {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-7.29.1.dist-info}/top_level.txt +0 -0
@@ -556,43 +556,78 @@ class CompilerLangVy(CompilerLang, metaclass=Singleton):
556
556
  else:
557
557
  raise Exception(f"Unexpected ast_node {ast_node}, cannot evaluate constant")
558
558
 
559
+ @staticmethod
560
+ def subscript_node(ast_node: Dict[str, Any]) -> Optional[Dict[str, Any]]:
561
+ ty = ast_node.get("ast_type")
562
+ # pre-vyper 0.4.0, look deeper within
563
+ if ty == "Index":
564
+ nested = ast_node.get("value")
565
+ if nested is None or not isinstance(nested, dict):
566
+ return None
567
+ return nested
568
+ else:
569
+ return ast_node
570
+
571
+ @staticmethod
572
+ def int_subscript(ast_node: Dict[str, Any], named_constants: Dict[str, int]) -> Optional[int]:
573
+ node = CompilerLangVy.subscript_node(ast_node)
574
+ if node is None:
575
+ return None
576
+
577
+ if "id" in node and node["id"] in named_constants:
578
+ return named_constants[node["id"]]
579
+ elif node.get("ast_type") == "Int":
580
+ return node.get("value")
581
+ else:
582
+ return None
583
+
584
+ @staticmethod
585
+ def get_tuple_elements(ast_node: Dict[str, Any]) -> List[Dict[str, Any]]:
586
+ node = CompilerLangVy.subscript_node(ast_node)
587
+ if node is None or node.get("ast_type") != "Tuple":
588
+ raise Exception("Couldn't get tuple elements from ast node")
589
+ return node["elements"]
590
+
559
591
  @staticmethod
560
592
  def extract_type_from_subscript_node(ast_subscript_node: Dict[str, Any],
561
593
  named_constants: Dict[str, int]) -> VyperType:
562
594
  value_id = ast_subscript_node['value'].get('id', None)
563
595
  if value_id == 'String':
564
- max_bytes = ast_subscript_node['slice']['value']['value']
596
+ max_bytes = CompilerLangVy.int_subscript(ast_subscript_node['slice'], named_constants)
597
+ if max_bytes is None:
598
+ raise Exception("Failed to find max length for string type declaration")
565
599
  return CompilerLangVy.VyperTypeString(max_bytes)
566
600
  elif value_id == 'Bytes':
567
- max_bytes = ast_subscript_node['slice']['value']['value']
601
+ max_bytes = CompilerLangVy.int_subscript(ast_subscript_node['slice'], named_constants)
602
+ if max_bytes is None:
603
+ raise Exception("Failed to find max length for bytes type declaration")
568
604
  return CompilerLangVy.VyperTypeBytes(max_bytes)
569
605
  elif value_id == 'DynArray':
570
- elem_type = CompilerLangVy.extract_type_from_type_annotation_node(
571
- ast_subscript_node['slice']['value']['elements'][0], named_constants)
572
- max_elements = CompilerLangVy.extract_constant(ast_subscript_node['slice']['value']['elements'][1],
573
- named_constants)
606
+ tup_elems = CompilerLangVy.get_tuple_elements(ast_subscript_node["slice"])
607
+ elem_type = CompilerLangVy.extract_type_from_type_annotation_node(tup_elems[0], named_constants)
608
+ max_elements = CompilerLangVy.extract_constant(tup_elems[1], named_constants)
574
609
  return CompilerLangVy.VyperTypeDynArray(elem_type, max_elements)
575
610
  elif value_id == 'HashMap':
576
- elements_node = ast_subscript_node['slice']['value']['elements']
611
+ elements_node = CompilerLangVy.get_tuple_elements(ast_subscript_node['slice'])
577
612
  key_type = CompilerLangVy.extract_type_from_type_annotation_node(elements_node[0], named_constants)
578
613
  value_type = CompilerLangVy.extract_type_from_type_annotation_node(elements_node[1], named_constants)
579
614
  return CompilerLangVy.VyperTypeHashMap(key_type, value_type)
580
615
  else: # StaticArray key_type[size]
581
616
  key_type = CompilerLangVy.extract_type_from_type_annotation_node(ast_subscript_node['value'],
582
617
  named_constants)
583
- max_elements_node = ast_subscript_node['slice']['value']
584
- if 'id' in max_elements_node and max_elements_node['id'] in named_constants:
585
- return CompilerLangVy.VyperTypeStaticArray(key_type, named_constants[max_elements_node['id']])
618
+ max_elem_value = CompilerLangVy.int_subscript(ast_subscript_node['slice'], named_constants)
619
+ if max_elem_value is not None:
620
+ return CompilerLangVy.VyperTypeStaticArray(key_type, max_elem_value)
586
621
  else:
587
622
  # this is very specific to curve code which has uint256[CONST/2] static array declaration.
623
+ max_elements_node = CompilerLangVy.subscript_node(ast_subscript_node["slice"])
624
+ if max_elements_node is None:
625
+ raise Exception("Couldn't find subscript node for array type")
588
626
  if 'ast_type' in max_elements_node:
589
627
  if max_elements_node['ast_type'] == 'BinOp' or max_elements_node['ast_type'] in ('Int', 'Name'):
590
628
  # good chance this will succeed
591
629
  static_array_len = CompilerLangVy.extract_constant(max_elements_node, named_constants)
592
630
  return CompilerLangVy.VyperTypeStaticArray(key_type, static_array_len)
593
- elif 'value' in max_elements_node:
594
- return CompilerLangVy.VyperTypeStaticArray(key_type, max_elements_node['value'])
595
-
596
631
  raise Exception(
597
632
  f"Don't know how to deal with vyper static array declaration with length {max_elements_node}")
598
633
 
@@ -411,6 +411,8 @@ def generate_modifier_finder(f: Func, internal_id: int, sym: int,
411
411
  formal_strings = []
412
412
  arg_strings = []
413
413
  for (logged_ty, logged_name) in zip(loggable_types, loggable_names):
414
+ if logged_name == "":
415
+ continue
414
416
  arg_strings.append(logged_name)
415
417
  formal_strings.append(f"{logged_ty} {logged_name}")
416
418
  modifier_body = f"modifier {modifier_name}"
@@ -1198,10 +1200,15 @@ class CertoraBuildGenerator:
1198
1200
  return x[TYPE] == FUNCTION or x[TYPE] == CertoraBuildGenerator.CONSTRUCTOR_STRING
1199
1201
 
1200
1202
  @staticmethod
1201
- def collect_srcmap(data: Dict[str, Any]) -> Any:
1203
+ def collect_srcmap(data: Dict[str, Any]) -> Tuple[str, str]:
1202
1204
  # no source map object in vyper
1203
- return (data["evm"]["deployedBytecode"].get("sourceMap", ""),
1204
- data["evm"]["bytecode"].get("sourceMap", ""))
1205
+ deployed = data["evm"]["deployedBytecode"].get("sourceMap", "")
1206
+ if isinstance(deployed, dict):
1207
+ deployed = deployed.get("pc_pos_map_compressed", "")
1208
+ regular = data["evm"]["bytecode"].get("sourceMap", "")
1209
+ if isinstance(regular, dict):
1210
+ regular = regular.get("pc_pos_map_compressed", "")
1211
+ return deployed, regular
1205
1212
 
1206
1213
  @staticmethod
1207
1214
  def collect_varmap(contract: str, data: Dict[str, Any]) -> Any:
@@ -1516,7 +1523,8 @@ class CertoraBuildGenerator:
1516
1523
  sources_dict = {str(contract_file_posix_abs): {
1517
1524
  "urls": [str(contract_file_posix_abs)]}} # type: Dict[str, Dict[str, Any]]
1518
1525
  output_selection = ["transientStorageLayout", "storageLayout", "abi", "evm.bytecode",
1519
- "evm.deployedBytecode", "evm.methodIdentifiers", "evm.assembly"]
1526
+ "evm.deployedBytecode", "evm.methodIdentifiers", "evm.assembly",
1527
+ "evm.bytecode.functionDebugData"]
1520
1528
  ast_selection = ["id", "ast"]
1521
1529
  elif compiler_collector_lang == CompilerLangVy():
1522
1530
  with open(contract_file_posix_abs) as f:
@@ -1739,6 +1747,10 @@ class CertoraBuildGenerator:
1739
1747
  sdc_name = f"{Path(build_arg_contract_file).name}_{file_index}"
1740
1748
  compilation_path = self.get_compilation_path(sdc_name)
1741
1749
  self.file_to_sdc_name[Util.abs_norm_path(build_arg_contract_file)] = sdc_name
1750
+
1751
+ compiler_collector = self.compiler_coll_factory \
1752
+ .get_compiler_collector(Path(path_for_compiler_collector_file))
1753
+
1742
1754
  # update remappings and collect_cmd:
1743
1755
  if not is_vyper:
1744
1756
  Util.safe_create_dir(compilation_path)
@@ -1786,8 +1798,10 @@ class CertoraBuildGenerator:
1786
1798
  compiler_logger.debug(f"collect_cmd: {collect_cmd}\n")
1787
1799
  else:
1788
1800
  compiler_ver_to_run = get_relevant_compiler(Path(build_arg_contract_file), self.context)
1789
-
1790
- collect_cmd = f'{compiler_ver_to_run} -p "{self.context.solc_allow_path}" -o "{compilation_path}" ' \
1801
+ path_string = ""
1802
+ if compiler_collector.compiler_version[1] < 4:
1803
+ path_string = f' -p "{self.context.solc_allow_path}"'
1804
+ collect_cmd = f'{compiler_ver_to_run}{path_string} -o "{compilation_path}" ' \
1791
1805
  f'--standard-json'
1792
1806
 
1793
1807
  # Make sure compilation artifacts are always deleted
@@ -1800,9 +1814,6 @@ class CertoraBuildGenerator:
1800
1814
  # (we do not try to save a big chain history of changes, just a previous and current)
1801
1815
  self.backup_compiler_outputs(sdc_name, smart_contract_lang, "prev")
1802
1816
 
1803
- compiler_collector = self.compiler_coll_factory \
1804
- .get_compiler_collector(Path(path_for_compiler_collector_file))
1805
-
1806
1817
  # Standard JSON
1807
1818
  remappings = [] if isinstance(compiler_collector, CompilerCollectorYul) else self.context.remappings
1808
1819
  input_for_solc = self.standard_json(Path(file_abs_path), build_arg_contract_file, remappings,
@@ -2246,6 +2257,14 @@ class CertoraBuildGenerator:
2246
2257
  build_arg_contract_file)
2247
2258
  immutables = self.collect_immutables(contract_data, build_arg_contract_file, compiler_lang)
2248
2259
 
2260
+ internal_function_entrypoints = set([])
2261
+
2262
+ if compiler_lang == CompilerLangSol() and "functionDebugData" in contract_data["evm"]["deployedBytecode"]:
2263
+ debug = contract_data["evm"]["deployedBytecode"]["functionDebugData"]
2264
+ for (_, v) in debug.items():
2265
+ if "entryPoint" in v and v["entryPoint"] is not None:
2266
+ internal_function_entrypoints.add(v["entryPoint"])
2267
+
2249
2268
  if self.context.internal_funcs is not None:
2250
2269
  all_internal_functions: Dict[str, Any] = \
2251
2270
  Util.read_json_file(self.context.internal_funcs)
@@ -2296,7 +2315,8 @@ class CertoraBuildGenerator:
2296
2315
  extension_contracts=list(),
2297
2316
  local_assignments={},
2298
2317
  branches={},
2299
- requires={}
2318
+ requires={},
2319
+ internal_starts=list(internal_function_entrypoints)
2300
2320
  )
2301
2321
 
2302
2322
  @staticmethod
@@ -2527,7 +2547,7 @@ class CertoraBuildGenerator:
2527
2547
  sources_from_pre_finder_SDCs |= sdc.all_contract_files
2528
2548
  sources = self.collect_sources(context, certora_verify_generator, sources_from_pre_finder_SDCs)
2529
2549
  try:
2530
- self.cwd_rel_in_sources = build_source_tree(sources, context)
2550
+ build_source_tree(sources, context)
2531
2551
  except Exception as e:
2532
2552
  build_logger.debug(f"build_source_tree failed. Sources: {sources}", exc_info=e)
2533
2553
  raise
@@ -2698,8 +2718,8 @@ class CertoraBuildGenerator:
2698
2718
  return None
2699
2719
  cloned_field = storage_field_info.copy()
2700
2720
  cloned_field["slot"] = str(link_slot)
2701
- # Try to uniquify the name
2702
- cloned_field["label"] = f"certoralink_{ext_instance.name}_{var_name}"
2721
+ # Don't bother trying to uniquify the name
2722
+ cloned_field["label"] = var_name
2703
2723
  return cloned_field
2704
2724
 
2705
2725
  def handle_one_extension(storage_ext: str) -> tuple[Any, str, List[Dict[str, Any]]] :
@@ -2750,23 +2770,34 @@ class CertoraBuildGenerator:
2750
2770
  if target_contract.storage_layout.get("types") is None:
2751
2771
  target_contract.storage_layout["types"] = {}
2752
2772
  target_slots = {storage["slot"] for storage in target_contract.storage_layout["storage"]}
2773
+ target_vars = {storage["label"] for storage in target_contract.storage_layout["storage"]}
2753
2774
  # Keep track of slots we've added, and error if we
2754
2775
  # find two extensions extending the same slot
2755
2776
  added_slots: Dict[str, str] = {}
2777
+ added_vars: Dict[str, str] = {}
2756
2778
  for ext in extensions:
2757
2779
  (new_fields, new_types) = to_add[ext]
2758
2780
 
2759
2781
  for f in new_fields:
2760
- # See if any of the new fields is a slot we've already added
2782
+ # See if any of the new fields is a slot or variable name we've already added
2761
2783
  slot = f["slot"]
2784
+ var = f["label"]
2762
2785
  if slot in added_slots:
2763
2786
  seen = added_slots[slot]
2764
- raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} already added by {seen}")
2787
+ raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} was already added by {seen}")
2788
+
2789
+ if var in added_vars:
2790
+ seen = added_vars[var]
2791
+ raise Util.CertoraUserInputError(f"Var '{var}' added to {target_contract.name} by {ext} was already added by {seen}")
2765
2792
 
2766
2793
  if slot in target_slots:
2767
- raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} already mapped by {target_contract.name}")
2794
+ raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} is already mapped by {target_contract.name}")
2795
+
2796
+ if var in target_vars:
2797
+ raise Util.CertoraUserInputError(f"Var '{var}' added to {target_contract.name} by {ext} is already declared by {target_contract.name}")
2768
2798
 
2769
2799
  added_slots[slot] = ext
2800
+ added_vars[var] = ext
2770
2801
 
2771
2802
  target_contract.storage_layout["storage"].extend(new_fields)
2772
2803
 
@@ -3003,7 +3034,7 @@ class CertoraBuildGenerator:
3003
3034
  for k, v in autofinder_remappings.items():
3004
3035
  self.function_finder_file_remappings[Util.abs_posix_path(k)] = Util.abs_posix_path(v)
3005
3036
  new_sdcs = self.collect_for_file(new_file, i, get_compiler_lang(build_arg_contract_file),
3006
- Util.get_certora_sources_dir() / self.cwd_rel_in_sources,
3037
+ Util.get_certora_sources_dir() / self.context.cwd_rel_in_sources,
3007
3038
  path_for_compiler_collector_file,
3008
3039
  sdc_pre_finder,
3009
3040
  fail_on_compilation_error=False,
@@ -3031,7 +3062,7 @@ class CertoraBuildGenerator:
3031
3062
  contract_path = Util.abs_posix_path_obj(contract_file)
3032
3063
  rel_directory = Path(os.path.relpath(contract_file, '.')).parent
3033
3064
  contract_filename = contract_path.name
3034
- new_path = Util.get_certora_sources_dir() / self.cwd_rel_in_sources / rel_directory / contract_filename
3065
+ new_path = Util.get_certora_sources_dir() / self.context.cwd_rel_in_sources / rel_directory / contract_filename
3035
3066
  new_path.parent.mkdir(parents=True, exist_ok=True)
3036
3067
  return str(new_path)
3037
3068
 
@@ -3041,7 +3072,7 @@ class CertoraBuildGenerator:
3041
3072
  This assumes those paths can be related to cwd.
3042
3073
  """
3043
3074
  rel_to_cwd_path = Path(os.path.relpath(path, '.'))
3044
- new_path = Util.get_certora_sources_dir() / self.cwd_rel_in_sources / rel_to_cwd_path
3075
+ new_path = Util.get_certora_sources_dir() / self.context.cwd_rel_in_sources / rel_to_cwd_path
3045
3076
  return str(new_path.absolute())
3046
3077
 
3047
3078
  def handle_links(self) -> None:
@@ -3449,20 +3480,19 @@ def sources_to_abs(sources: Set[Path]) -> Set[Path]:
3449
3480
  return result
3450
3481
 
3451
3482
 
3452
- def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bool = False) -> Path:
3483
+ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bool = False) -> None:
3453
3484
  """
3454
3485
  Copies files to .certora_sources
3455
- @returns the cwd relative in sources
3456
3486
  """
3457
3487
  sources = sources_to_abs(sources)
3458
- cwd_rel_in_sources, common_path = CertoraBuildGenerator.get_cwd_rel_in_sources(sources)
3488
+ context.cwd_rel_in_sources, context.common_path = CertoraBuildGenerator.get_cwd_rel_in_sources(sources)
3459
3489
 
3460
3490
  for source_path in sources:
3461
3491
  is_dir = source_path.is_dir()
3462
3492
  # copy file to the path of the file from the common root under the sources directory
3463
3493
 
3464
3494
  # make sure directory exists
3465
- target_path = Util.get_certora_sources_dir() / source_path.relative_to(common_path)
3495
+ target_path = Util.get_certora_sources_dir() / source_path.relative_to(context.common_path)
3466
3496
  target_directory = target_path if is_dir else target_path.parent
3467
3497
  try:
3468
3498
  target_directory.mkdir(parents=True, exist_ok=True)
@@ -3491,7 +3521,7 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3491
3521
  raise
3492
3522
 
3493
3523
  # the empty file .cwd is written in the source tree to denote the current working directory
3494
- cwd_file_path = Util.get_certora_sources_dir() / cwd_rel_in_sources / Util.CWD_FILE
3524
+ cwd_file_path = Util.get_certora_sources_dir() / context.cwd_rel_in_sources / Util.CWD_FILE
3495
3525
  cwd_file_path.parent.mkdir(parents=True, exist_ok=True)
3496
3526
  cwd_file_path.touch()
3497
3527
 
@@ -3500,7 +3530,7 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3500
3530
  if rust_proj_dir:
3501
3531
  proj_dir_parent_relative = os.path.relpath(rust_proj_dir, os.getcwd())
3502
3532
  assert Path(rust_proj_dir).is_dir(), f"build_source_tree: not a directory {rust_proj_dir}"
3503
- proj_dir_parent_file = (Util.get_certora_sources_dir() / cwd_rel_in_sources / proj_dir_parent_relative /
3533
+ proj_dir_parent_file = (Util.get_certora_sources_dir() / context.cwd_rel_in_sources / proj_dir_parent_relative /
3504
3534
  Util.PROJECT_DIR_FILE)
3505
3535
  proj_dir_parent_file.parent.mkdir(parents=True, exist_ok=True)
3506
3536
  proj_dir_parent_file.touch()
@@ -3516,9 +3546,6 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3516
3546
  build_logger.debug("Couldn't copy repro conf to certora sources.", exc_info=e)
3517
3547
  raise
3518
3548
 
3519
- return cwd_rel_in_sources
3520
-
3521
-
3522
3549
  def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
3523
3550
  certora_verify_generator: CertoraVerifyGenerator,
3524
3551
  build_cache_enabled: bool) -> CachedFiles:
@@ -3661,8 +3688,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3661
3688
 
3662
3689
  # Start by syntax checking, if we're in the right mode
3663
3690
  if Cv.mode_has_spec_file(context) and not context.build_only and not ignore_spec_syntax_check:
3664
- attr = context.disable_local_typechecking
3665
- if attr:
3691
+ if context.disable_local_typechecking:
3666
3692
  build_logger.warning(
3667
3693
  "Local checks of CVL specification files disabled. It is recommended to enable the checks.")
3668
3694
  else:
@@ -3675,6 +3701,11 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3675
3701
  certora_verify_generator,
3676
3702
  certora_build_cache_manager)
3677
3703
 
3704
+ # avoid running the same test over and over again for each split run, context.split_rules is true only for
3705
+ # the first run and is set to [] for split runs
3706
+ if context.split_rules:
3707
+ Ctx.run_local_spec_check(True, context)
3708
+
3678
3709
  # .certora_verify.json is always constructed even if build cache is enabled
3679
3710
  # Sources construction should only happen when rebuilding
3680
3711
  # Build sources tree
@@ -107,7 +107,8 @@ class ContractInSDC:
107
107
  extension_contracts: List[ContractExtension],
108
108
  local_assignments: Dict[str, UnspecializedSourceFinder],
109
109
  branches: Dict[str, UnspecializedSourceFinder],
110
- requires: Dict[str, UnspecializedSourceFinder]
110
+ requires: Dict[str, UnspecializedSourceFinder],
111
+ internal_starts: List[int]
111
112
  ):
112
113
  self.name = name
113
114
  self.original_file = source_file
@@ -139,6 +140,7 @@ class ContractInSDC:
139
140
  self.original_file_name = Path(source_file).name
140
141
  self.compiler_collector = compiler_collector
141
142
  self.compiler_parameters = compiler_parameters
143
+ self.internal_starts = internal_starts
142
144
 
143
145
  if not self.compiler_collector:
144
146
  compiler_version = ""
@@ -193,7 +195,8 @@ class ContractInSDC:
193
195
  "compilerParameters": None if not self.compiler_parameters else self.compiler_parameters.as_dict(),
194
196
  "sourceBytes": None if self.source_bytes is None else self.source_bytes.as_dict(),
195
197
  "extensionContracts": [e.as_dict() for e in self.extension_contracts],
196
- "localAssignments": {k: v.as_dict() for k, v in self.local_assignments.items()}
198
+ "localAssignments": {k: v.as_dict() for k, v in self.local_assignments.items()},
199
+ "internalFunctionStarts": self.internal_starts
197
200
  }
198
201
  # "sourceHints": {"localAssignments": {k: v.as_dict() for k, v in self.local_assignments.items()},
199
202
  # "branches": {k: v.as_dict() for k, v in self.branches.items()},
@@ -20,51 +20,77 @@ from typing import Set
20
20
 
21
21
  from CertoraProver.certoraBuild import build_source_tree
22
22
  from CertoraProver.certoraContextClass import CertoraContext
23
- from CertoraProver.certoraParseBuildScript import run_script_and_parse_json
23
+ from CertoraProver.certoraParseBuildScript import run_rust_build
24
24
  import CertoraProver.certoraContextAttributes as Attrs
25
25
  from Shared import certoraUtils as Util
26
26
 
27
27
 
28
- def build_rust_app(context: CertoraContext) -> None:
29
- if context.build_script:
30
- run_script_and_parse_json(context)
31
- if not context.rust_executables:
32
- raise Util.CertoraUserInputError("failed to get target executable")
33
-
34
- sources: Set[Path] = set()
35
- collect_files_from_rust_sources(context, sources)
36
-
37
- try:
38
- # Create generators
39
- build_source_tree(sources, context)
40
-
41
- copy_files_to_build_dir(context)
28
+ def set_rust_build_directory(context: CertoraContext) -> None:
29
+ if not context.files:
30
+ build_rust_app(context)
42
31
 
43
- except Exception as e:
44
- raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
45
- else:
46
- if not context.files:
47
- raise Util.CertoraUserInputError("'files' or 'build_script' must be set for Rust projects")
48
- if len(context.files) > 1:
49
- raise Util.CertoraUserInputError("Rust projects must specify exactly one executable in 'files'.")
32
+ copy_files_to_build_dir(context)
50
33
 
51
- try:
52
- Util.get_certora_sources_dir().mkdir(parents=True, exist_ok=True)
53
- shutil.copy(Util.get_last_conf_file(), Util.get_certora_sources_dir() / Util.LAST_CONF_FILE)
54
- except Exception as e:
55
- raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
34
+ sources: Set[Path] = set()
35
+ collect_files_from_rust_sources(context, sources)
56
36
 
57
- context.rust_executables = context.files[0]
37
+ try:
38
+ # Create generators
39
+ build_source_tree(sources, context)
58
40
 
41
+ except Exception as e:
42
+ raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
59
43
 
60
- def add_solana_files(context: CertoraContext, sources: Set[Path]) -> None:
61
- for attr in [Attrs.SolanaProverAttributes.SOLANA_INLINING, Attrs.SolanaProverAttributes.SOLANA_SUMMARIES]:
44
+ def build_rust_app(context: CertoraContext) -> None:
45
+ assert not context.files, "build_rust_app: expecting files to be empty"
46
+ if context.build_script:
47
+ build_command = [context.build_script, '--json', '-l']
48
+ feature_flag = '--cargo_features'
49
+ else: # cargo
50
+ build_command = ["cargo", "certora-sbf", '--json']
51
+ feature_flag = '--features'
52
+ if context.cargo_tools_version:
53
+ build_command.extend(["--tools-version", context.cargo_tools_version])
54
+
55
+ if context.cargo_features is not None:
56
+ build_command.append(feature_flag)
57
+ build_command.extend(context.cargo_features)
58
+
59
+ if context.test == str(Util.TestValue.SOLANA_BUILD_CMD):
60
+ raise Util.TestResultsReady(build_command)
61
+
62
+ run_rust_build(context, build_command)
63
+
64
+ def add_solana_files_from_prover_args(context: CertoraContext) -> None:
65
+ if context.prover_args:
66
+ inlining_file = False
67
+ summaries_file = False
68
+ for prover_arg in context.prover_args:
69
+ for arg in prover_arg.split(' '):
70
+ if inlining_file:
71
+ context.solana_inlining = context.solana_inlining or [Path(arg)]
72
+ inlining_file = False
73
+ if summaries_file:
74
+ context.solana_summaries = context.solana_summaries or [Path(arg)]
75
+ summaries_file = False
76
+
77
+ if arg == '-solanaInlining':
78
+ inlining_file = True
79
+ elif arg == '-solanaSummaries':
80
+ summaries_file = True
81
+
82
+
83
+ def add_solana_files_to_sources(context: CertoraContext, sources: Set[Path]) -> None:
84
+ for attr in [Attrs.SolanaProverAttributes.SOLANA_INLINING,
85
+ Attrs.SolanaProverAttributes.SOLANA_SUMMARIES,
86
+ Attrs.SolanaProverAttributes.BUILD_SCRIPT,
87
+ Attrs.SolanaProverAttributes.FILES]:
62
88
  attr_name = attr.get_conf_key()
63
89
  attr_value = getattr(context, attr_name, None)
64
90
  if not attr_value:
65
91
  continue
66
92
  if isinstance(attr_value, str):
67
- attr_value = [str]
93
+ attr_value = [attr_value]
68
94
  if not isinstance(attr_value, list):
69
95
  raise Util.CertoraUserInputError(f"{attr_value} is not a valid value for {attr_name} {attr_value}. Value "
70
96
  f"must be a string or a llist ")
@@ -72,44 +98,40 @@ def add_solana_files(context: CertoraContext, sources: Set[Path]) -> None:
72
98
  for file_path in file_paths:
73
99
  if not file_path.exists():
74
100
  raise Util.CertoraUserInputError(f"in {attr_name} file {file_path} does not exist")
75
- sources.add(file_path.absolute())
101
+ sources.add(file_path.absolute().resolve())
76
102
 
77
103
 
78
104
  def collect_files_from_rust_sources(context: CertoraContext, sources: Set[Path]) -> None:
79
105
  patterns = ["*.rs", "*.so", "*.wasm", "Cargo.toml", "Cargo.lock", "justfile"]
80
106
  exclude_dirs = [".certora_internal"]
81
107
 
82
- root_directory = Path(context.rust_project_directory)
108
+ if hasattr(context, 'rust_project_directory'):
109
+ project_directory = Path(context.rust_project_directory)
83
110
 
84
- if not root_directory.is_dir():
85
- raise ValueError(f"The given directory '{root_directory}' is not valid.")
111
+ if not project_directory.is_dir():
112
+ raise ValueError(f"The given directory '{project_directory}' is not valid.")
86
113
 
87
- for source in context.rust_sources:
88
- for file in glob.glob(f'{root_directory.joinpath(source)}', recursive=True):
89
- file_path = Path(file)
90
- if any(excluded in file_path.parts for excluded in exclude_dirs):
91
- continue
92
- if file_path.is_file() and any(file_path.match(pattern) for pattern in patterns):
93
- sources.add(file_path)
114
+ for source in context.rust_sources:
115
+ for file in glob.glob(f'{project_directory.joinpath(source)}', recursive=True):
116
+ file_path = Path(file)
117
+ if any(excluded in file_path.parts for excluded in exclude_dirs):
118
+ continue
119
+ if file_path.is_file() and any(file_path.match(pattern) for pattern in patterns):
120
+ sources.add(file_path)
94
121
 
95
- sources.add(Path(context.rust_project_directory).absolute())
96
- if Path(context.build_script).exists():
97
- sources.add(Path(context.build_script).resolve())
122
+ sources.add(project_directory.absolute())
123
+ if context.build_script:
124
+ sources.add(Path(context.build_script).resolve())
98
125
  if getattr(context, 'conf_file', None) and Path(context.conf_file).exists():
99
126
  sources.add(Path(context.conf_file).absolute())
100
- add_solana_files(context, sources)
101
127
 
128
+ add_solana_files_from_prover_args(context)
129
+ add_solana_files_to_sources(context, sources)
102
130
 
103
- def copy_files_to_build_dir(context: CertoraContext) -> None:
104
- rust_executable = Path(context.rust_project_directory) / context.rust_executables
105
- shutil.copyfile(rust_executable, Util.get_build_dir() / rust_executable.name)
106
131
 
107
- additional_files = (getattr(context, 'solana_inlining', None) or []) + \
108
- (getattr(context, 'solana_summaries', None) or [])
109
-
110
- for file in additional_files:
111
- file_path = Path(file).resolve()
112
- shutil.copy(file_path, Util.get_build_dir() / file_path.name)
132
+ def copy_files_to_build_dir(context: CertoraContext) -> None:
133
+ assert context.files, "copy_files_to_build_dir: expecting files to be non-empty"
134
+ shutil.copyfile(context.files[0], Util.get_build_dir() / Path(context.files[0]).name)
113
135
 
114
136
  if rust_logs := getattr(context, 'rust_logs_stdout', None):
115
137
  shutil.copy(Path(rust_logs), Util.get_build_dir() / Path(rust_logs).name)