certora-cli-beta-mirror 8.0.0__py3-none-macosx_10_9_universal2.whl → 8.1.1__py3-none-macosx_10_9_universal2.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 (35) hide show
  1. certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +1 -3
  2. certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +3 -0
  3. certora_cli/CertoraProver/certoraApp.py +49 -0
  4. certora_cli/CertoraProver/certoraBuild.py +178 -26
  5. certora_cli/CertoraProver/certoraBuildDataClasses.py +3 -1
  6. certora_cli/CertoraProver/certoraBuildRust.py +1 -1
  7. certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +1 -1
  8. certora_cli/CertoraProver/certoraConfigIO.py +11 -12
  9. certora_cli/CertoraProver/certoraContext.py +61 -54
  10. certora_cli/CertoraProver/certoraContextAttributes.py +9 -0
  11. certora_cli/CertoraProver/certoraContextValidator.py +12 -12
  12. certora_cli/CertoraProver/certoraContractFuncs.py +6 -0
  13. certora_cli/CertoraProver/certoraType.py +10 -1
  14. certora_cli/CertoraProver/certoraVerifyGenerator.py +2 -1
  15. certora_cli/CertoraProver/splitRules.py +18 -17
  16. certora_cli/EquivalenceCheck/equivCheck.py +2 -1
  17. certora_cli/Mutate/mutateApp.py +12 -6
  18. certora_cli/Mutate/mutateValidate.py +2 -2
  19. certora_cli/Shared/certoraUtils.py +70 -7
  20. certora_cli/Shared/proverCommon.py +6 -6
  21. certora_cli/certoraConcord.py +2 -2
  22. certora_cli/certoraEVMProver.py +2 -2
  23. certora_cli/certoraRanger.py +2 -2
  24. certora_cli/certoraRun.py +9 -9
  25. certora_cli/certoraSolanaProver.py +2 -2
  26. certora_cli/certoraSorobanProver.py +2 -3
  27. {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.1.dist-info}/METADATA +3 -2
  28. {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.1.dist-info}/RECORD +35 -34
  29. certora_jars/ASTExtraction.jar +0 -0
  30. certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
  31. certora_jars/Typechecker.jar +0 -0
  32. {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.1.dist-info}/LICENSE +0 -0
  33. {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.1.dist-info}/WHEEL +0 -0
  34. {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.1.dist-info}/entry_points.txt +0 -0
  35. {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.1.dist-info}/top_level.txt +0 -0
@@ -51,10 +51,8 @@ def get_relevant_compiler(contract_file_path: Path, context: CertoraContext) ->
51
51
 
52
52
  if context.compiler_map:
53
53
  match = Ctx.get_map_attribute_value(context, contract_file_path, 'compiler')
54
- assert isinstance(match, str), (f"In the attribute compiler_map, {contract_file_path} expected to be matched "
55
- f"to a string, {match} is of type {type(match)} not a string")
56
54
  if match:
57
- return match
55
+ return str(match)
58
56
  else:
59
57
  raise RuntimeError(f'cannot match compiler to {contract_file_path} from compiler_map')
60
58
 
@@ -104,14 +104,17 @@ class CompilerLangYul(CompilerLangSol, metaclass=Singleton):
104
104
  notpayable=notpayable,
105
105
  fromLib=False,
106
106
  isConstructor=False,
107
+ is_free_func=False,
107
108
  stateMutability=state_mutability,
108
109
  visibility=visibility,
109
110
  implemented=True,
110
111
  overrides=False,
112
+ virtual=False,
111
113
  contractName=contract_name,
112
114
  source_bytes=None,
113
115
  ast_id=None,
114
116
  original_file=None,
117
+ location=None,
115
118
  body_location=None,
116
119
  )
117
120
  funcs.append(func)
@@ -0,0 +1,49 @@
1
+ # The Certora Prover
2
+ # Copyright (C) 2025 Certora Ltd.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, version 3 of the License.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ import CertoraProver.certoraContextAttributes as Attrs
17
+ from Shared import certoraAttrUtil as AttrUtil
18
+
19
+
20
+ from abc import ABC
21
+ from typing import Type
22
+
23
+
24
+ class CertoraApp(ABC):
25
+ attr_class: Type[AttrUtil.Attributes] = Attrs.EvmProverAttributes
26
+
27
+ class EvmAppClass(CertoraApp):
28
+ pass
29
+
30
+ class EvmApp(EvmAppClass):
31
+ pass
32
+
33
+ class RustAppClass(CertoraApp):
34
+ pass
35
+
36
+ class SolanaApp(RustAppClass):
37
+ attr_class = Attrs.SolanaProverAttributes
38
+
39
+ class SorobanApp(RustAppClass):
40
+ attr_class = Attrs.SorobanProverAttributes
41
+
42
+ class RangerApp(EvmAppClass):
43
+ attr_class = Attrs.RangerAttributes
44
+
45
+ class ConcordApp(EvmAppClass):
46
+ attr_class = Attrs.ConcordAttributes
47
+
48
+ class SuiApp(EvmAppClass):
49
+ attr_class = Attrs.SuiProverAttributes
@@ -834,6 +834,7 @@ class CertoraBuildGenerator:
834
834
  body_location = body_node["src"]
835
835
  elif body_node is None and func_def["implemented"]:
836
836
  ast_logger.debug(f"No body for {func_def} but ast claims it is implemented")
837
+ location: Optional[str] = func_def.get("src", None)
837
838
 
838
839
  if original_contract is not None:
839
840
  if method := original_contract.find_method(func_name, solidity_type_args):
@@ -858,14 +859,17 @@ class CertoraBuildGenerator:
858
859
  func_def[STATEMUT] in ["nonpayable", "view", "pure"],
859
860
  c_is_lib,
860
861
  is_constructor,
862
+ func_def.get("kind") == CertoraBuildGenerator.FREEFUNCTION_STRING,
861
863
  func_def[STATEMUT],
862
864
  func_visibility,
863
865
  func_def["implemented"],
864
866
  func_def.get("overrides", None) is not None,
867
+ func_def.get("virtual", False),
865
868
  contract_name,
866
869
  source_bytes,
867
870
  ast_id=func_def.get("id", None),
868
871
  original_file=original_file,
872
+ location=location,
869
873
  body_location=body_location,
870
874
  )
871
875
 
@@ -917,15 +921,18 @@ class CertoraBuildGenerator:
917
921
  notpayable=is_not_payable,
918
922
  fromLib=c_is_lib,
919
923
  isConstructor=False,
924
+ is_free_func=False,
920
925
  stateMutability=state_mutability,
921
926
  implemented=True,
922
927
  overrides=public_state_var.get("overrides", None) is not None,
928
+ virtual=False,
923
929
  # according to Solidity docs, getter functions have external visibility
924
930
  visibility="external",
925
931
  contractName=contract_name,
926
932
  source_bytes=SourceBytes.from_ast_node(public_state_var),
927
933
  ast_id=None,
928
934
  original_file=c_file,
935
+ location=None,
929
936
  body_location=None,
930
937
  )
931
938
  )
@@ -959,6 +966,8 @@ class CertoraBuildGenerator:
959
966
  ret = solc_type == "address"
960
967
  elif isinstance(ct_type, CT.StructType):
961
968
  ret = solc_type == "tuple"
969
+ elif isinstance(ct_type, CT.EnumType):
970
+ ret = solc_type == "uint8"
962
971
  return ret
963
972
 
964
973
  fs = [f for f in fs if all(compareTypes(a.type, i)
@@ -979,7 +988,7 @@ class CertoraBuildGenerator:
979
988
  assert len(f.returns) == len(fabi["outputs"]), \
980
989
  f"function collected for {fabi['name']} has the wrong number of return values"
981
990
  assert all(compareTypes(a.type, i) for a, i in zip(f.returns, fabi["outputs"])), \
982
- f"function collected for {fabi['name']} has the wrong types of return values"
991
+ f"function collected for {fabi['name']} has the wrong types of return values. {[t.type.type_string for t in f.returns]} vs {fabi['outputs']}"
983
992
 
984
993
  verify_collected_all_abi_funcs(
985
994
  [f for f in data["abi"] if f["type"] == "function"],
@@ -1375,7 +1384,9 @@ class CertoraBuildGenerator:
1375
1384
 
1376
1385
  def get_solc_optimize_value(self, contract_file_path: Path) -> Optional[str]:
1377
1386
  match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_optimize')
1378
- assert isinstance(match, (str, type(None))), f"Expected solc_optimize to be string or None, got {type(match)}"
1387
+ assert isinstance(match, (str, int, type(None))), f"Expected solc_optimize to be string, integer , got {type(match)}"
1388
+ if isinstance(match, int):
1389
+ match = str(match)
1379
1390
  return match
1380
1391
 
1381
1392
  def _handle_via_ir(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
@@ -2217,14 +2228,17 @@ class CertoraBuildGenerator:
2217
2228
  notpayable=x.notpayable,
2218
2229
  fromLib=x.fromLib,
2219
2230
  isConstructor=x.isConstructor,
2231
+ is_free_func=False,
2220
2232
  stateMutability=x.stateMutability,
2221
2233
  visibility=x.visibility,
2222
2234
  implemented=x.implemented,
2223
2235
  overrides=x.overrides,
2236
+ virtual=False,
2224
2237
  contractName=x.contractName,
2225
2238
  ast_id=x.ast_id,
2226
2239
  source_bytes=x.source_bytes,
2227
2240
  original_file=x.original_file,
2241
+ location=None,
2228
2242
  body_location=x.body_location,
2229
2243
  ) for x in clfuncs]
2230
2244
  elif compiler_lang == CompilerLangYul():
@@ -2491,6 +2505,114 @@ class CertoraBuildGenerator:
2491
2505
  mut=InsertAfter())
2492
2506
  return function_finder_by_contract, function_finder_instrumentation
2493
2507
 
2508
+ def add_internal_func_harnesses(self, contract_file: str, sdc: SDC, specCalls: List[str]) -> Optional[Tuple[Dict[str, str], Dict[str, Dict[int, Instrumentation]]]]:
2509
+ # contract file -> byte offset -> to insert
2510
+ harness_function_instrumentation: Dict[str, Dict[int, Instrumentation]] = defaultdict(dict)
2511
+ # internal function name -> harness fuction name
2512
+ harness_function_names: Dict[str, str] = {}
2513
+
2514
+ if not isinstance(sdc.compiler_collector, CompilerCollectorSol):
2515
+ raise Exception(f"Encountered a compiler collector that is not solc for file {contract_file}"
2516
+ " when trying to add function autofinders")
2517
+ instrumentation_logger.debug(f"Using {sdc.compiler_collector} compiler to "
2518
+ f"add external function harnesses to contract {sdc.primary_contract}")
2519
+
2520
+ for c in sdc.contracts:
2521
+ for f in c.internal_funcs:
2522
+ if f"{sdc.primary_contract}.{f.name}" not in specCalls:
2523
+ continue
2524
+
2525
+ if f.fromLib:
2526
+ # Even external library functions can't be called directly from spec,
2527
+ # so skip harnessing internal ones.
2528
+ continue
2529
+
2530
+ if f.isConstructor:
2531
+ continue
2532
+
2533
+ if f.is_free_func:
2534
+ # Free functions (declared outside of any contract/library) don't have visibility modifiers
2535
+ continue
2536
+
2537
+ orig_file = f.original_file
2538
+ if not orig_file:
2539
+ instrumentation_logger.debug(f"missing file location for {f.name}")
2540
+ continue
2541
+
2542
+ loc = f.location
2543
+ if not loc:
2544
+ instrumentation_logger.debug(f"Found a function {f.name} in "
2545
+ f"{c.name} that doesn't have a location")
2546
+ continue
2547
+
2548
+ if f.implemented:
2549
+ expected_end_char = b"}"
2550
+ else:
2551
+ # This is a virtual function with no implementation. Declare also a virtual harness so that when
2552
+ # Implementing a harness for the override we can just add the `override` keyword to the harness
2553
+ # function as well without needing to have any extra logic at that point.
2554
+ expected_end_char = b";"
2555
+
2556
+ if len(f.fullArgs) != len(f.paramNames):
2557
+ instrumentation_logger.debug(f"Do not have argument names for {f.name} in"
2558
+ f" {c.name}, giving up internal function harnessing")
2559
+ continue
2560
+
2561
+ if any(ty.location == CT.TypeLocation.STORAGE for ty in f.fullArgs + f.returns):
2562
+ instrumentation_logger.debug(f"Function {f.name} has input arguments with 'storage' location - cannot harness it")
2563
+ continue
2564
+
2565
+ instrumentation_path = str(Util.abs_norm_path(orig_file))
2566
+ per_file_inst = harness_function_instrumentation[instrumentation_path]
2567
+
2568
+ start, size, _ = loc.split(":")
2569
+ body_end_byte = int(start) + int(size) - 1
2570
+
2571
+ def get_local_type_name(ty: CT.TypeInstance) -> str:
2572
+ # Handles imports that use 'as'. E.g. `import {A as B} from "A.sol";`
2573
+ ret = ty.get_source_str()
2574
+ assert orig_file
2575
+ for node in self.asts[sdc.sdc_origin_file][orig_file].values():
2576
+ if node["nodeType"] != "ImportDirective":
2577
+ continue
2578
+ for alias in node["symbolAliases"]:
2579
+ if alias["foreign"]["name"] == ty.get_source_str() and "local" in alias:
2580
+ ret = alias["local"]
2581
+ break
2582
+
2583
+ # Now add the location
2584
+ return ret + (f" {ty.location.value}" if ty.location != CT.TypeLocation.STACK else "")
2585
+
2586
+ harness_name = f"{f.name}_external_harness"
2587
+ harness_string = f" function {harness_name}({', '.join(f'{get_local_type_name(ty)} {n}' for ty, n in zip(f.fullArgs, f.paramNames))}) external"
2588
+
2589
+ if f.stateMutability in ["pure", "view"]:
2590
+ harness_string += f" {f.stateMutability}"
2591
+
2592
+ if f.virtual:
2593
+ harness_string += " virtual"
2594
+
2595
+ if f.overrides:
2596
+ harness_string += " override"
2597
+
2598
+ if f.returns:
2599
+ harness_string += f" returns ({', '.join(get_local_type_name(r) for r in f.returns)})"
2600
+
2601
+ if f.implemented:
2602
+ harness_call = f"{f.name}({', '.join(f.paramNames)})"
2603
+ if f.returns:
2604
+ harness_string += f" {{ return {harness_call}; }}"
2605
+ else:
2606
+ harness_string += f" {{ {harness_call}; }}"
2607
+ else:
2608
+ harness_string += ";"
2609
+
2610
+ per_file_inst[body_end_byte] = Instrumentation(expected=expected_end_char, to_ins=harness_string,
2611
+ mut=InsertAfter())
2612
+ harness_function_names[f.name] = harness_name
2613
+
2614
+ return harness_function_names, harness_function_instrumentation
2615
+
2494
2616
  def cleanup(self) -> None:
2495
2617
  for sdc_name, smart_contract_lang in self.__compiled_artifacts_to_clean:
2496
2618
  self.cleanup_compiler_outputs(sdc_name, smart_contract_lang)
@@ -2546,6 +2668,16 @@ class CertoraBuildGenerator:
2546
2668
 
2547
2669
  def build(self, certora_verify_generator: CertoraVerifyGenerator) -> None:
2548
2670
  context = self.context
2671
+
2672
+ specCalls: List[str] = []
2673
+ if context.verify and not context.disallow_internal_function_calls:
2674
+ with tempfile.NamedTemporaryFile("r", dir=Util.get_build_dir()) as tmp_file:
2675
+ try:
2676
+ Ctx.run_local_spec_check(False, context, ["-listCalls", tmp_file.name], print_errors=False)
2677
+ specCalls = tmp_file.read().split("\n")
2678
+ except Exception as e:
2679
+ instrumentation_logger.warning(f"Failed to get calls from spec\n{e}")
2680
+
2549
2681
  self.context.remappings = []
2550
2682
  for i, build_arg_contract_file in enumerate(sorted(self.input_config.sorted_files)):
2551
2683
  build_logger.debug(f"\nbuilding file {build_arg_contract_file}")
@@ -2597,7 +2729,7 @@ class CertoraBuildGenerator:
2597
2729
  # We start by trying to instrument _all_ finders, both autofinders and source finders
2598
2730
  added_finders, all_finders_success, src_finders_gen_success, post_backup_dir = self.finders_compilation_round(
2599
2731
  build_arg_contract_file, i, ignore_patterns, path_for_compiler_collector_file, pre_backup_dir,
2600
- sdc_pre_finders, not context.disable_source_finders)
2732
+ sdc_pre_finders, not context.disable_source_finders, specCalls)
2601
2733
 
2602
2734
  # we could have a case where source finders failed but regular finders succeeded.
2603
2735
  # e.g. if we processed the AST wrong and skipped source finders generation
@@ -2609,21 +2741,21 @@ class CertoraBuildGenerator:
2609
2741
  # let's try just the function autofinders
2610
2742
  added_finders, function_autofinder_success, _, post_backup_dir = self.finders_compilation_round(
2611
2743
  build_arg_contract_file, i, ignore_patterns, path_for_compiler_collector_file, pre_backup_dir,
2612
- sdc_pre_finders, False)
2744
+ sdc_pre_finders, False, specCalls)
2613
2745
 
2614
2746
  if not function_autofinder_success:
2615
2747
  self.auto_finders_failed = True
2616
2748
 
2617
2749
  if not self.auto_finders_failed or not self.source_finders_failed:
2618
2750
  # setup source_dir. note that post_backup_dir must include the finders in this case
2619
- for _, _, sdc in added_finders:
2751
+ for _, _, _, sdc in added_finders:
2620
2752
  sdc.source_dir = str(post_backup_dir.relative_to(Util.get_certora_sources_dir()))
2621
2753
  sdc.orig_source_dir = str(pre_backup_dir.relative_to(Util.get_certora_sources_dir()))
2622
2754
  else:
2623
2755
  # no point in running autofinders in vyper right now
2624
- added_finders = [({}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders]
2756
+ added_finders = [({}, {}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders]
2625
2757
 
2626
- for added_func_finders, added_source_finders, sdc in added_finders:
2758
+ for added_func_finders, added_source_finders, added_internal_function_harnesses, sdc in added_finders:
2627
2759
  for contract in sdc.contracts:
2628
2760
  all_functions: List[Func] = list()
2629
2761
  for k, v in added_func_finders.items():
@@ -2631,6 +2763,8 @@ class CertoraBuildGenerator:
2631
2763
  contract.function_finders[k] = v
2632
2764
  for source_key, source_value in added_source_finders.items():
2633
2765
  contract.local_assignments[source_key] = source_value
2766
+ if contract.name == sdc.primary_contract:
2767
+ contract.internal_function_harnesses = added_internal_function_harnesses
2634
2768
  all_functions.extend(contract.methods)
2635
2769
  all_functions.extend(contract.internal_funcs)
2636
2770
  functions_unique_by_internal_rep = list() # type: List[Func]
@@ -2954,12 +3088,13 @@ class CertoraBuildGenerator:
2954
3088
  path_for_compiler_collector_file: str,
2955
3089
  pre_backup_dir: Path,
2956
3090
  sdc_pre_finders: List[SDC],
2957
- with_source_finders: bool) -> Tuple[
2958
- List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], SDC]], bool, bool, Path]:
3091
+ with_source_finders: bool,
3092
+ specCalls: List[str]) -> Tuple[
3093
+ List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]], bool, bool, Path]:
2959
3094
  added_finders_to_sdc, finders_compilation_success, source_finders_gen_success = \
2960
3095
  self.instrument_auto_finders(
2961
3096
  build_arg_contract_file, i, sdc_pre_finders,
2962
- path_for_compiler_collector_file, with_source_finders)
3097
+ path_for_compiler_collector_file, with_source_finders, specCalls)
2963
3098
  # successful or not, we backup current .certora_sources for either debuggability, or for availability
2964
3099
  # of sources.
2965
3100
  post_backup_dir = self.get_fresh_backupdir(Util.POST_AUTOFINDER_BACKUP_DIR)
@@ -3031,11 +3166,12 @@ class CertoraBuildGenerator:
3031
3166
  def instrument_auto_finders(self, build_arg_contract_file: str, i: int,
3032
3167
  sdc_pre_finders: List[SDC],
3033
3168
  path_for_compiler_collector_file: str,
3034
- instrument_source_finders: bool) -> Tuple[
3035
- List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], SDC]], bool, bool]:
3169
+ instrument_source_finders: bool,
3170
+ specCalls: List[str]) -> Tuple[
3171
+ List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]], bool, bool]:
3036
3172
 
3037
3173
  # initialization
3038
- ret = [] # type: List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], SDC]]
3174
+ ret: List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]] = []
3039
3175
  instrumentation_logger.debug(f"Instrumenting auto finders in {build_arg_contract_file}")
3040
3176
  # all of the [SDC]s inside [sdc_pre_finders] have the same list of [ContractInSDC]s
3041
3177
  # (generated in the [collect_from_file] function).
@@ -3044,10 +3180,19 @@ class CertoraBuildGenerator:
3044
3180
  if added_function_finders_tuple is None:
3045
3181
  instrumentation_logger.warning(
3046
3182
  f"Computing function finder instrumentation failed for {build_arg_contract_file}")
3047
- return [({}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
3183
+ return [({}, {}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
3048
3184
 
3049
3185
  (added_function_finders, function_instr) = added_function_finders_tuple
3050
3186
 
3187
+ instr = function_instr
3188
+
3189
+ added_internal_function_harnesses: Dict[str, str] = {}
3190
+ if not self.context.disallow_internal_function_calls:
3191
+ added_internal_func_harness_tuple = self.add_internal_func_harnesses(build_arg_contract_file, sdc_pre_finder, specCalls)
3192
+ if added_internal_func_harness_tuple:
3193
+ instr = CertoraBuildGenerator.merge_dicts_instrumentation(function_instr, added_internal_func_harness_tuple[1])
3194
+ added_internal_function_harnesses = added_internal_func_harness_tuple[0]
3195
+
3051
3196
  source_finders_gen_succeeded = False
3052
3197
  if instrument_source_finders:
3053
3198
  try:
@@ -3056,15 +3201,13 @@ class CertoraBuildGenerator:
3056
3201
  (added_source_finders, source_instr) = added_source_finders_tuple
3057
3202
  # Update instr with additional instrumentations. Recall it is a map file -> offset -> instr.
3058
3203
  # Function finders take precedence
3059
- instr = CertoraBuildGenerator.merge_dicts_instrumentation(function_instr, source_instr)
3204
+ instr = CertoraBuildGenerator.merge_dicts_instrumentation(instr, source_instr)
3060
3205
  source_finders_gen_succeeded = True
3061
3206
  except: # noqa: E722
3062
3207
  instrumentation_logger.warning(
3063
3208
  f"Computing source finder instrumentation failed for {build_arg_contract_file}")
3064
- instr = function_instr
3065
3209
  added_source_finders = {}
3066
3210
  else:
3067
- instr = function_instr
3068
3211
  added_source_finders = {}
3069
3212
 
3070
3213
  abs_build_arg_contract_file = Util.abs_posix_path(build_arg_contract_file)
@@ -3084,7 +3227,7 @@ class CertoraBuildGenerator:
3084
3227
  # instrumentation should be keyed only using absolute paths
3085
3228
  instrumentation_logger.warning(f"Already generated autofinder for {new_name}, "
3086
3229
  f"cannot instrument again for {contract_file}")
3087
- return [({}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
3230
+ return [({}, {}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
3088
3231
 
3089
3232
  autofinder_remappings[new_name] = contract_file
3090
3233
 
@@ -3113,7 +3256,7 @@ class CertoraBuildGenerator:
3113
3256
  instrumentation_logger.warning("Skipping source finder generation!")
3114
3257
  else:
3115
3258
  instrumentation_logger.warning("Skipping internal function finder generation!")
3116
- return [({}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
3259
+ return [({}, {}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
3117
3260
  to_skip = to_insert.mut.insert(to_insert.to_ins, to_insert.expected, output)
3118
3261
  if to_skip != 0:
3119
3262
  in_file.read(to_skip)
@@ -3125,7 +3268,7 @@ class CertoraBuildGenerator:
3125
3268
  build_arg_contract_file]
3126
3269
 
3127
3270
  # add generated file to map attributes
3128
- for map_attr in Attrs.get_attribute_class().all_map_attrs():
3271
+ for map_attr in self.context.app.attr_class.all_map_attrs():
3129
3272
  map_attr_value = getattr(self.context, map_attr)
3130
3273
  if map_attr_value and build_arg_contract_file in map_attr_value:
3131
3274
  map_attr_value[new_file] = map_attr_value[build_arg_contract_file]
@@ -3151,7 +3294,7 @@ class CertoraBuildGenerator:
3151
3294
  fail_on_compilation_error=False,
3152
3295
  reroute_main_path=True)
3153
3296
  for new_sdc in new_sdcs:
3154
- ret.append((added_function_finders, added_source_finders, new_sdc))
3297
+ ret.append((added_function_finders, added_source_finders, added_internal_function_harnesses, new_sdc))
3155
3298
 
3156
3299
  except Util.SolcCompilationException as e:
3157
3300
  print(f"Encountered an exception generating autofinder {new_file} ({e}), falling back to original "
@@ -3160,7 +3303,7 @@ class CertoraBuildGenerator:
3160
3303
  f"falling back to the original file {Path(build_arg_contract_file).name}", exc_info=e)
3161
3304
  # clean up mutation
3162
3305
  self.function_finder_file_remappings = {}
3163
- return [({}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders], False, False
3306
+ return [({}, {}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders], False, False
3164
3307
  return ret, True, source_finders_gen_succeeded
3165
3308
 
3166
3309
  def to_autofinder_file(self, contract_file: str) -> str:
@@ -3555,6 +3698,15 @@ class CertoraBuildGenerator:
3555
3698
  add_to_sources(Util.PACKAGE_FILE)
3556
3699
  if Util.REMAPPINGS_FILE.exists():
3557
3700
  add_to_sources(Util.REMAPPINGS_FILE)
3701
+ foundry_toml = Util.find_nearest_foundry_toml()
3702
+ if foundry_toml:
3703
+ # if we find foundry.toml we add it to source tree and, if exists, the remappings.txt file from the
3704
+ # root directory
3705
+ foundry_root = foundry_toml.parent
3706
+ add_to_sources(foundry_root / Util.FOUNDRY_TOML_FILE)
3707
+ remappings_file_in_root = foundry_root / Util.REMAPPINGS_FILE
3708
+ if remappings_file_in_root.exists():
3709
+ add_to_sources(remappings_file_in_root)
3558
3710
  if context.bytecode_jsons:
3559
3711
  for file in context.bytecode_jsons:
3560
3712
  add_to_sources(Path(file))
@@ -3811,11 +3963,11 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3811
3963
 
3812
3964
  # Start by syntax checking, if we're in the right mode
3813
3965
  if Cv.mode_has_spec_file(context) and not context.build_only and not ignore_spec_syntax_check:
3814
- if context.disable_local_typechecking:
3966
+ if Ctx.should_run_local_speck_check(context):
3967
+ Ctx.run_local_spec_check(False, context)
3968
+ else:
3815
3969
  build_logger.warning(
3816
3970
  "Local checks of CVL specification files disabled. It is recommended to enable the checks.")
3817
- else:
3818
- Ctx.run_local_spec_check(False, context)
3819
3971
 
3820
3972
  cache_hit, build_cache_enabled, cached_files = build_from_cache_or_scratch(context,
3821
3973
  certora_build_generator,
@@ -3823,7 +3975,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3823
3975
 
3824
3976
  # avoid running the same test over and over again for each split run, context.split_rules is true only for
3825
3977
  # the first run and is set to [] for split runs
3826
- if context.split_rules:
3978
+ if Ctx.should_run_local_speck_check(context) and context.split_rules:
3827
3979
  Ctx.run_local_spec_check(True, context)
3828
3980
 
3829
3981
  # .certora_verify.json is always constructed even if build cache is enabled
@@ -163,6 +163,7 @@ class ContractInSDC:
163
163
  self.local_assignments = local_assignments
164
164
  self.branches = branches
165
165
  self.requires = requires
166
+ self.internal_function_harnesses: Dict[str, str] = {}
166
167
 
167
168
  def as_dict(self) -> Dict[str, Any]:
168
169
  """
@@ -196,7 +197,8 @@ class ContractInSDC:
196
197
  "sourceBytes": None if self.source_bytes is None else self.source_bytes.as_dict(),
197
198
  "extensionContracts": [e.as_dict() for e in self.extension_contracts],
198
199
  "localAssignments": {k: v.as_dict() for k, v in self.local_assignments.items()},
199
- "internalFunctionStarts": self.internal_starts
200
+ "internalFunctionStarts": self.internal_starts,
201
+ "internalFunctionHarnesses": self.internal_function_harnesses
200
202
  }
201
203
  # "sourceHints": {"localAssignments": {k: v.as_dict() for k, v in self.local_assignments.items()},
202
204
  # "branches": {k: v.as_dict() for k, v in self.branches.items()},
@@ -85,7 +85,7 @@ def build_rust_app(context: CertoraContext) -> None:
85
85
 
86
86
  if context.cargo_features is not None:
87
87
  build_command.append(feature_flag)
88
- build_command.extend(context.cargo_features)
88
+ build_command.append(' '.join(context.cargo_features))
89
89
 
90
90
  if context.test == str(Util.TestValue.SOLANA_BUILD_CMD):
91
91
  raise Util.TestResultsReady(build_command)
@@ -212,7 +212,7 @@ def split_and_sort_arg_list_value(args_list: List[str]) -> List[str]:
212
212
  Assumes each flag starts with '-' and its value follows immediately, if exists.
213
213
  Lines are sorted alphabetically.
214
214
  """
215
- unified_args = ''.join(args_list)
215
+ unified_args = ' '.join(str(arg) for arg in args_list)
216
216
 
217
217
  if not unified_args.strip():
218
218
  return []
@@ -13,14 +13,12 @@
13
13
  # You should have received a copy of the GNU General Public License
14
14
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
15
 
16
- import json5
17
16
  import logging
18
17
  from pathlib import Path
19
- from typing import Dict, Any
20
- from collections import OrderedDict
18
+ from typing import Dict, Any, cast
21
19
 
22
20
  import CertoraProver.certoraContext as Ctx
23
- import CertoraProver.certoraContextAttributes as Attrs
21
+ import CertoraProver.certoraApp as App
24
22
  from CertoraProver.certoraContextClass import CertoraContext
25
23
  from Shared import certoraUtils as Util
26
24
 
@@ -47,11 +45,12 @@ def current_conf_to_file(context: CertoraContext) -> Dict[str, Any]:
47
45
  4. Parsing the .conf file is simpler, as we can ignore the null case
48
46
  """
49
47
  def input_arg_with_value(k: Any, v: Any) -> Any:
50
- all_conf_names = Attrs.get_attribute_class().all_conf_names()
48
+ all_conf_names = context.app.attr_class.all_conf_names()
51
49
  return v is not None and v is not False and k in all_conf_names
52
50
 
53
51
  context_to_save = {k: v for k, v in vars(context).items() if input_arg_with_value(k, v)}
54
- all_conf_names = Attrs.get_attribute_class().all_conf_names()
52
+ context_to_save = cast(Dict[str, Any], Util.convert_str_ints(context_to_save))
53
+ all_conf_names = context.app.attr_class.all_conf_names()
55
54
  context_to_save = dict(sorted(context_to_save.items(), key=lambda x: all_conf_names.index(x[0])))
56
55
  context_to_save.pop('build_dir', None) # build dir should not be saved, each run should define its own build_dir
57
56
  context_to_save.pop('mutation_test_id', None) # mutation_test_id should be recreated for every run
@@ -76,7 +75,7 @@ def read_from_conf_file(context: CertoraContext) -> None:
76
75
 
77
76
  try:
78
77
  with conf_file_path.open() as conf_file:
79
- context.conf_file_attr = json5.load(conf_file, allow_duplicate_keys=False, object_pairs_hook=OrderedDict)
78
+ context.conf_file_attr = Util.read_conf_file(conf_file)
80
79
  try:
81
80
  check_conf_content(context)
82
81
  except Util.CertoraUserInputError as e:
@@ -98,9 +97,9 @@ def handle_override_base_config(context: CertoraContext) -> None:
98
97
 
99
98
  if context.override_base_config:
100
99
  try:
101
- with Path(context.override_base_config).open() as conf_file:
102
- override_base_config_attrs = json5.load(conf_file, allow_duplicate_keys=False,
103
- object_pairs_hook=OrderedDict)
100
+ with (Path(context.override_base_config).open() as conf_file):
101
+ override_base_config_attrs = Util.read_conf_file(conf_file)
102
+
104
103
  context.conf_file_attr = {**override_base_config_attrs, **context.conf_file_attr}
105
104
 
106
105
  if 'override_base_config' in override_base_config_attrs:
@@ -142,10 +141,10 @@ def check_conf_content(context: CertoraContext) -> None:
142
141
 
143
142
  handle_override_base_config(context)
144
143
 
145
- if Attrs.is_evm_app() and not context.files and not context.project_sanity and not context.foundry:
144
+ if Ctx.is_evm_app_class(context) and not context.files and not context.project_sanity and not context.foundry:
146
145
  raise Util.CertoraUserInputError("Mandatory 'files' attribute is missing from the configuration")
147
146
  context.files = context.conf_file_attr.get('files')
148
- if Attrs.is_soroban_app() and not context.files and not context.build_script:
147
+ if context.app == App.SorobanApp and not context.files and not context.build_script:
149
148
  raise Util.CertoraUserInputError("'files' or 'build script' must be set for Soroban runs")
150
149
 
151
150
  context.files = context.conf_file_attr.get('files')