certora-cli 8.1.1__tar.gz → 8.3.0__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 (88) hide show
  1. {certora_cli-8.1.1/certora_cli.egg-info → certora_cli-8.3.0}/PKG-INFO +2 -2
  2. certora_cli-8.3.0/README.md +1 -0
  3. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraApp.py +4 -1
  4. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraBuild.py +143 -63
  5. certora_cli-8.3.0/certora_cli/CertoraProver/certoraBuildSui.py +112 -0
  6. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraConfigIO.py +2 -0
  7. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraContext.py +13 -10
  8. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraContextAttributes.py +109 -21
  9. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Mutate/mutateValidate.py +40 -0
  10. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Shared/certoraUtils.py +30 -5
  11. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Shared/certoraValidateFuncs.py +22 -7
  12. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/certoraRun.py +0 -4
  13. {certora_cli-8.1.1 → certora_cli-8.3.0/certora_cli.egg-info}/PKG-INFO +2 -2
  14. certora_cli-8.1.1/certora_jars/Typechecker.jar → certora_cli-8.3.0/certora_jars/ASTExtraction.jar +0 -0
  15. certora_cli-8.3.0/certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -0
  16. certora_cli-8.1.1/certora_jars/ASTExtraction.jar → certora_cli-8.3.0/certora_jars/Typechecker.jar +0 -0
  17. {certora_cli-8.1.1 → certora_cli-8.3.0}/setup.py +2 -2
  18. certora_cli-8.1.1/README.md +0 -1
  19. certora_cli-8.1.1/certora_cli/CertoraProver/certoraBuildSui.py +0 -68
  20. certora_cli-8.1.1/certora_jars/CERTORA-CLI-VERSION-METADATA.json +0 -1
  21. {certora_cli-8.1.1 → certora_cli-8.3.0}/LICENSE +0 -0
  22. {certora_cli-8.1.1 → certora_cli-8.3.0}/MANIFEST.in +0 -0
  23. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_bins/__init__.py +0 -0
  24. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/Compiler/CompilerCollector.py +0 -0
  25. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +0 -0
  26. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorSol.py +0 -0
  27. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorSolBased.py +0 -0
  28. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +0 -0
  29. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +0 -0
  30. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/Compiler/__init__.py +0 -0
  31. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/__init__.py +0 -0
  32. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraBuildCacheManager.py +0 -0
  33. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraBuildDataClasses.py +0 -0
  34. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraBuildRust.py +0 -0
  35. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraCloudIO.py +0 -0
  36. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +0 -0
  37. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraCollectRunMetadata.py +0 -0
  38. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraCompilerParameters.py +0 -0
  39. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraContextClass.py +0 -0
  40. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraContextValidator.py +0 -0
  41. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraContractFuncs.py +0 -0
  42. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraExtensionInfo.py +0 -0
  43. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraJobList.py +0 -0
  44. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraMiniSpecParser.py +0 -0
  45. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraNodeFilters.py +0 -0
  46. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraParseBuildScript.py +0 -0
  47. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraProjectScanner.py +0 -0
  48. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraSourceFinders.py +0 -0
  49. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraType.py +0 -0
  50. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/certoraVerifyGenerator.py +0 -0
  51. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/erc7201.py +0 -0
  52. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/splitRules.py +0 -0
  53. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/CertoraProver/storageExtension.py +0 -0
  54. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/EquivalenceCheck/Eq_default.conf +0 -0
  55. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/EquivalenceCheck/Eq_mc_no_out_template.spec +0 -0
  56. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/EquivalenceCheck/Eq_mc_template.spec +0 -0
  57. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -0
  58. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/EquivalenceCheck/Eq_template.spec +0 -0
  59. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/EquivalenceCheck/__init__.py +0 -0
  60. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/EquivalenceCheck/equivCheck.py +0 -0
  61. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/EquivalenceCheck/sanity.spec +0 -0
  62. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Mutate/__init__.py +0 -0
  63. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Mutate/mutateApp.py +0 -0
  64. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Mutate/mutateAttributes.py +0 -0
  65. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Mutate/mutateConstants.py +0 -0
  66. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Mutate/mutateUtil.py +0 -0
  67. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Shared/ExpectedComparator.py +0 -0
  68. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Shared/__init__.py +0 -0
  69. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Shared/certoraAttrUtil.py +0 -0
  70. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Shared/certoraLogging.py +0 -0
  71. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/Shared/proverCommon.py +0 -0
  72. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/__init__.py +0 -0
  73. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/certoraCVLFormatter.py +0 -0
  74. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/certoraConcord.py +0 -0
  75. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/certoraEVMProver.py +0 -0
  76. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/certoraEqCheck.py +0 -0
  77. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/certoraMutate.py +0 -0
  78. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/certoraRanger.py +0 -0
  79. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/certoraSolanaProver.py +0 -0
  80. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/certoraSorobanProver.py +0 -0
  81. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli/rustMutator.py +0 -0
  82. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli.egg-info/SOURCES.txt +0 -0
  83. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli.egg-info/dependency_links.txt +0 -0
  84. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli.egg-info/entry_points.txt +0 -0
  85. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli.egg-info/requires.txt +0 -0
  86. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_cli.egg-info/top_level.txt +0 -0
  87. {certora_cli-8.1.1 → certora_cli-8.3.0}/certora_jars/__init__.py +0 -0
  88. {certora_cli-8.1.1 → certora_cli-8.3.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: certora-cli
3
- Version: 8.1.1
3
+ Version: 8.3.0
4
4
  Summary: Runner for the Certora Prover
5
5
  Home-page: https://pypi.org/project/certora-cli
6
6
  Author: Certora
@@ -39,4 +39,4 @@ Dynamic: requires-dist
39
39
  Dynamic: requires-python
40
40
  Dynamic: summary
41
41
 
42
- Commit b03ee33. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
42
+ Commit 3c8a0dd. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -0,0 +1 @@
1
+ Commit 3c8a0dd. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -33,6 +33,9 @@ class EvmApp(EvmAppClass):
33
33
  class RustAppClass(CertoraApp):
34
34
  pass
35
35
 
36
+ class MoveAppClass(CertoraApp):
37
+ pass
38
+
36
39
  class SolanaApp(RustAppClass):
37
40
  attr_class = Attrs.SolanaProverAttributes
38
41
 
@@ -45,5 +48,5 @@ class RangerApp(EvmAppClass):
45
48
  class ConcordApp(EvmAppClass):
46
49
  attr_class = Attrs.ConcordAttributes
47
50
 
48
- class SuiApp(EvmAppClass):
51
+ class SuiApp(MoveAppClass):
49
52
  attr_class = Attrs.SuiProverAttributes
@@ -203,11 +203,18 @@ class PresetImmutableReference(ImmutableReference):
203
203
  # this function is Solidity specific.
204
204
  # todo: create certoraBuildUtilsSol.py file, where such solidity specific functions will be.
205
205
  def generate_finder_body(f: Func, internal_id: int, sym: int, compiler_collector: CompilerCollectorSol,
206
- compressed: bool = False) -> Optional[Tuple[List[int], str]]:
206
+ compressed: bool = False,
207
+ should_generate_inlining: bool = True) -> Optional[Tuple[List[int], str]]:
207
208
  if compressed:
208
209
  return generate_compressed_finder(
209
210
  f, internal_id, sym, compiler_collector
210
211
  )
212
+ elif not should_generate_inlining:
213
+ # We should not generate inlining as reported in CERT-9399, only when compressed=False.
214
+ # used_symbols is being generated the exact same way as in generate_full_finder
215
+ used_symbols = [i for i in range(len(f.fullArgs))]
216
+ return used_symbols, ''
217
+
211
218
  else:
212
219
  return generate_full_finder(
213
220
  f, internal_id, sym, compiler_collector
@@ -403,8 +410,9 @@ def get_modifier_param_type_name(ind: int, def_node: Dict[str, Any], f: Func) ->
403
410
 
404
411
  def generate_modifier_finder(f: Func, internal_id: int, sym: int,
405
412
  compiler_collector: CompilerCollectorSol, def_node: Dict[str, Any],
406
- compress: bool) -> Optional[Tuple[str, str]]:
407
- compressed = generate_finder_body(f, internal_id, sym, compiler_collector, compressed=compress)
413
+ compress: bool, should_generate_inlining: bool) -> Optional[Tuple[str, str]]:
414
+ compressed = generate_finder_body(f, internal_id, sym, compiler_collector, compressed=compress,
415
+ should_generate_inlining=should_generate_inlining)
408
416
  if compressed is None:
409
417
  return None
410
418
  modifier_name = f"logInternal{internal_id}"
@@ -427,9 +435,10 @@ def generate_modifier_finder(f: Func, internal_id: int, sym: int,
427
435
  return f'{modifier_name}({",".join(arg_strings)})', modifier_body
428
436
 
429
437
 
430
- def generate_inline_finder(f: Func, internal_id: int, sym: int,
431
- compiler_collector: CompilerCollectorSol, should_compress: bool) -> Optional[str]:
432
- finder = generate_finder_body(f, internal_id, sym, compiler_collector, compressed=should_compress)
438
+ def generate_inline_finder(f: Func, internal_id: int, sym: int, compiler_collector: CompilerCollectorSol,
439
+ should_compress: bool, should_generate_inlining: bool) -> Optional[str]:
440
+ finder = generate_finder_body(f, internal_id, sym, compiler_collector, compressed=should_compress,
441
+ should_generate_inlining=should_generate_inlining)
433
442
  if finder is None:
434
443
  return None
435
444
  return finder[1]
@@ -2448,6 +2457,19 @@ class CertoraBuildGenerator:
2448
2457
  # (deprecate this option later)
2449
2458
  should_compress = self.context.function_finder_mode == Vf.FunctionFinderMode.DEFAULT.name
2450
2459
 
2460
+ if self.context.solc_optimize and self.get_solc_via_ir_value(Path(contract_file)) and \
2461
+ any(param_name == "" for param_name in f.paramNames):
2462
+ # As reported in CERT-9399, we should not generate function finder for this edge case
2463
+
2464
+ instrumentation_logger.warning(f"Failed to generate auto finder for {f.name} @ {f.where()}.")
2465
+ instrumentation_logger.warning(
2466
+ "Cannot apply summaries for internal functions with unnamed argument when compiling "
2467
+ "using solc_optimize and solc_via_ir"
2468
+ )
2469
+ should_generate_inlining = False
2470
+ else:
2471
+ should_generate_inlining = True
2472
+
2451
2473
  if len(mods) > 0:
2452
2474
  # we need to add the instrumentation in a modifer because solidity modifiers will (potentially)
2453
2475
  # appear before any instrumentation we add to the literal source body, which will tank the detection
@@ -2459,8 +2481,10 @@ class CertoraBuildGenerator:
2459
2481
  # where in the source such modifiers will go. In order to insert a modifier, we have to have at
2460
2482
  # least one modifier already present, and then insert before the first modifier's location in the
2461
2483
  # source code
2462
- mod_inst = generate_modifier_finder(f, internal_id, function_symbol, sdc.compiler_collector,
2463
- def_node, compress=should_compress)
2484
+ mod_inst = generate_modifier_finder(
2485
+ f, internal_id, function_symbol, sdc.compiler_collector, def_node,
2486
+ compress=should_compress, should_generate_inlining=should_generate_inlining
2487
+ )
2464
2488
  if mod_inst is None:
2465
2489
  instrumentation_logger.debug(f"Modifier generation for {f.name} @ {f.where()} failed")
2466
2490
  return None
@@ -2494,8 +2518,8 @@ class CertoraBuildGenerator:
2494
2518
  per_file_inst[first_mod_offset] = Instrumentation(expected=bytes(modifier_name[0:1], "utf-8"),
2495
2519
  to_ins=modifier_invocation, mut=InsertBefore())
2496
2520
  else:
2497
- finder_res = generate_inline_finder(f, internal_id, function_symbol,
2498
- sdc.compiler_collector, should_compress)
2521
+ finder_res = generate_inline_finder(f, internal_id, function_symbol, sdc.compiler_collector,
2522
+ should_compress, should_generate_inlining)
2499
2523
  if finder_res is None:
2500
2524
  instrumentation_logger.debug(f"Generating auto finder for {f.name} @ {f.where()}"
2501
2525
  f" failed, giving up generation")
@@ -2505,7 +2529,7 @@ class CertoraBuildGenerator:
2505
2529
  mut=InsertAfter())
2506
2530
  return function_finder_by_contract, function_finder_instrumentation
2507
2531
 
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]]]]:
2532
+ def add_internal_func_harnesses(self, contract_file: str, sdc: SDC, spec_calls: List[str]) -> Optional[Tuple[Dict[str, str], Dict[str, Dict[int, Instrumentation]]]]:
2509
2533
  # contract file -> byte offset -> to insert
2510
2534
  harness_function_instrumentation: Dict[str, Dict[int, Instrumentation]] = defaultdict(dict)
2511
2535
  # internal function name -> harness fuction name
@@ -2519,7 +2543,7 @@ class CertoraBuildGenerator:
2519
2543
 
2520
2544
  for c in sdc.contracts:
2521
2545
  for f in c.internal_funcs:
2522
- if f"{sdc.primary_contract}.{f.name}" not in specCalls:
2546
+ if f"{sdc.primary_contract}.{f.name}" not in spec_calls:
2523
2547
  continue
2524
2548
 
2525
2549
  if f.fromLib:
@@ -2669,14 +2693,14 @@ class CertoraBuildGenerator:
2669
2693
  def build(self, certora_verify_generator: CertoraVerifyGenerator) -> None:
2670
2694
  context = self.context
2671
2695
 
2672
- specCalls: List[str] = []
2696
+ spec_calls: List[str] = []
2673
2697
  if context.verify and not context.disallow_internal_function_calls:
2674
2698
  with tempfile.NamedTemporaryFile("r", dir=Util.get_build_dir()) as tmp_file:
2675
2699
  try:
2676
2700
  Ctx.run_local_spec_check(False, context, ["-listCalls", tmp_file.name], print_errors=False)
2677
- specCalls = tmp_file.read().split("\n")
2701
+ spec_calls = tmp_file.read().split("\n")
2678
2702
  except Exception as e:
2679
- instrumentation_logger.warning(f"Failed to get calls from spec\n{e}")
2703
+ instrumentation_logger.debug(f"Failed to get calls from spec\n{e}")
2680
2704
 
2681
2705
  self.context.remappings = []
2682
2706
  for i, build_arg_contract_file in enumerate(sorted(self.input_config.sorted_files)):
@@ -2729,7 +2753,7 @@ class CertoraBuildGenerator:
2729
2753
  # We start by trying to instrument _all_ finders, both autofinders and source finders
2730
2754
  added_finders, all_finders_success, src_finders_gen_success, post_backup_dir = self.finders_compilation_round(
2731
2755
  build_arg_contract_file, i, ignore_patterns, path_for_compiler_collector_file, pre_backup_dir,
2732
- sdc_pre_finders, not context.disable_source_finders, specCalls)
2756
+ sdc_pre_finders, not context.disable_source_finders, spec_calls)
2733
2757
 
2734
2758
  # we could have a case where source finders failed but regular finders succeeded.
2735
2759
  # e.g. if we processed the AST wrong and skipped source finders generation
@@ -2741,7 +2765,7 @@ class CertoraBuildGenerator:
2741
2765
  # let's try just the function autofinders
2742
2766
  added_finders, function_autofinder_success, _, post_backup_dir = self.finders_compilation_round(
2743
2767
  build_arg_contract_file, i, ignore_patterns, path_for_compiler_collector_file, pre_backup_dir,
2744
- sdc_pre_finders, False, specCalls)
2768
+ sdc_pre_finders, False, spec_calls)
2745
2769
 
2746
2770
  if not function_autofinder_success:
2747
2771
  self.auto_finders_failed = True
@@ -2825,7 +2849,7 @@ class CertoraBuildGenerator:
2825
2849
  self.handle_erc7201_annotations()
2826
2850
  self.handle_storage_extension_harnesses()
2827
2851
 
2828
- def extract_slayout(self, original_file: str, ns_storage: Set[NameSpacedStorage], compiler_version: str) -> NewStorageInfo:
2852
+ def extract_slayout(self, original_file: str, ns_storage: Set[NameSpacedStorage], compiler_version: str, target_file: str) -> NewStorageInfo:
2829
2853
  """
2830
2854
  Given a file containing a contract with namespaced storage, extract the storage information
2831
2855
  corresponding to the namespaced types.
@@ -2869,8 +2893,8 @@ class CertoraBuildGenerator:
2869
2893
  abs_path = Util.abs_posix_path(tmp_file.name)
2870
2894
  self.context.file_to_contract[abs_path] = {harness_name}
2871
2895
 
2872
- try:
2873
- # Compile & fetch the raw storage_layout
2896
+ def attempt_compilation() -> NewStorageInfo:
2897
+ """Helper function to compile and extract layout"""
2874
2898
  compile_idx = storageExtension.get_next_file_index(self.file_to_sdc_name)
2875
2899
  sdcs = self.collect_for_file(tmp_file.name, compile_idx, CompilerLangSol(), Path.cwd(), Util.abs_posix_path(tmp_file.name), None)
2876
2900
  if not sdcs:
@@ -2879,12 +2903,38 @@ class CertoraBuildGenerator:
2879
2903
 
2880
2904
  # Remap each slot according to the ERC-7201 namespace
2881
2905
  remapped_fields = storageExtension.remapped_fields_from_layout(layout, var_to_slot)
2882
-
2883
2906
  return (remapped_fields, layout.get('types', {}))
2884
2907
 
2908
+ # First attempt with original content
2909
+ try:
2910
+ return attempt_compilation()
2885
2911
  except Exception as e:
2886
- build_logger.error(f"Error extracting storage layout for {original_file}: {str(e)}")
2887
- raise
2912
+ if not target_file:
2913
+ build_logger.error(f"Error extracting storage layout for {original_file}: {str(e)}")
2914
+ raise
2915
+
2916
+ # Retry with target file import
2917
+ build_logger.info("First attempt failed, retrying with import of target file")
2918
+
2919
+ # Read current content
2920
+ tmp_file.seek(0)
2921
+ current_content = tmp_file.read()
2922
+
2923
+ # Prepare modified content with target import at the beginning
2924
+ rel_target_path = os.path.relpath(target_file, Path.cwd())
2925
+ modified_content = f'import "{rel_target_path}";\n{current_content}'
2926
+
2927
+ # Write modified content
2928
+ tmp_file.seek(0)
2929
+ tmp_file.truncate()
2930
+ tmp_file.write(modified_content)
2931
+ tmp_file.flush()
2932
+
2933
+ try:
2934
+ return attempt_compilation()
2935
+ except Exception as retry_e:
2936
+ build_logger.error(f"Retry also failed for {original_file}: {str(retry_e)}")
2937
+ raise retry_e
2888
2938
  finally:
2889
2939
  # Delete the key from the context
2890
2940
  self.context.file_to_contract.pop(abs_path, None)
@@ -2933,7 +2983,8 @@ class CertoraBuildGenerator:
2933
2983
 
2934
2984
  # Now that we have all the storage layout information, extract it once
2935
2985
  slayouts[key] = self.extract_slayout(imported_file, ns_storage,
2936
- get_relevant_compiler(Path(target_file), self.context))
2986
+ get_relevant_compiler(Path(target_file), self.context),
2987
+ target_file)
2937
2988
 
2938
2989
  if self.context.test == str(Util.TestValue.STORAGE_EXTENSION_LAYOUT):
2939
2990
  raise Util.TestResultsReady(slayouts)
@@ -3089,12 +3140,12 @@ class CertoraBuildGenerator:
3089
3140
  pre_backup_dir: Path,
3090
3141
  sdc_pre_finders: List[SDC],
3091
3142
  with_source_finders: bool,
3092
- specCalls: List[str]) -> Tuple[
3143
+ spec_calls: List[str]) -> Tuple[
3093
3144
  List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]], bool, bool, Path]:
3094
3145
  added_finders_to_sdc, finders_compilation_success, source_finders_gen_success = \
3095
3146
  self.instrument_auto_finders(
3096
3147
  build_arg_contract_file, i, sdc_pre_finders,
3097
- path_for_compiler_collector_file, with_source_finders, specCalls)
3148
+ path_for_compiler_collector_file, with_source_finders, spec_calls)
3098
3149
  # successful or not, we backup current .certora_sources for either debuggability, or for availability
3099
3150
  # of sources.
3100
3151
  post_backup_dir = self.get_fresh_backupdir(Util.POST_AUTOFINDER_BACKUP_DIR)
@@ -3167,7 +3218,7 @@ class CertoraBuildGenerator:
3167
3218
  sdc_pre_finders: List[SDC],
3168
3219
  path_for_compiler_collector_file: str,
3169
3220
  instrument_source_finders: bool,
3170
- specCalls: List[str]) -> Tuple[
3221
+ spec_calls: List[str]) -> Tuple[
3171
3222
  List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]], bool, bool]:
3172
3223
 
3173
3224
  # initialization
@@ -3188,7 +3239,7 @@ class CertoraBuildGenerator:
3188
3239
 
3189
3240
  added_internal_function_harnesses: Dict[str, str] = {}
3190
3241
  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)
3242
+ added_internal_func_harness_tuple = self.add_internal_func_harnesses(build_arg_contract_file, sdc_pre_finder, spec_calls)
3192
3243
  if added_internal_func_harness_tuple:
3193
3244
  instr = CertoraBuildGenerator.merge_dicts_instrumentation(function_instr, added_internal_func_harness_tuple[1])
3194
3245
  added_internal_function_harnesses = added_internal_func_harness_tuple[0]
@@ -3793,6 +3844,14 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3793
3844
  cwd_file_path.parent.mkdir(parents=True, exist_ok=True)
3794
3845
  cwd_file_path.touch()
3795
3846
 
3847
+ # copy context.forge_remappings to remappings.txt in the source tree, overwriting any existing remappings.txt
3848
+ forge_remappings = getattr(context, 'forge_remappings', None)
3849
+ if forge_remappings:
3850
+ remappings_file_path = Util.get_certora_sources_dir() / context.cwd_rel_in_sources / Util.REMAPPINGS_FILE
3851
+ with remappings_file_path.open("w") as remap_file:
3852
+ for remap in context.forge_remappings:
3853
+ remap_file.write(remap + "\n")
3854
+
3796
3855
  # the empty file .project_directory is written in the source tree to denote the project directory
3797
3856
  rust_proj_dir = getattr(context, 'rust_project_directory', None)
3798
3857
  if rust_proj_dir:
@@ -3887,21 +3946,19 @@ def build_from_scratch(context: CertoraContext,
3887
3946
  def build_from_cache_or_scratch(context: CertoraContext,
3888
3947
  certora_build_generator: CertoraBuildGenerator,
3889
3948
  certora_verify_generator: CertoraVerifyGenerator) \
3890
- -> Tuple[bool, bool, CachedFiles]:
3949
+ -> Tuple[bool, CachedFiles]:
3891
3950
  """
3892
3951
  Builds either from cache (fast path) or from scratch (slow path)
3893
- @returns 1st tuple element whether there was a cache hit or not
3894
- @returns 2nd tuple element whether the build cache is enabled and applicable
3895
- @returns 3rd tuple element the artifacts of the build to potentially be cached
3952
+ @returns 1st tuple element whether the cache should be saved
3953
+ @returns 2nd tuple element the artifacts of the build to potentially be cached
3896
3954
  """
3897
- cache_hit = False
3898
3955
  cached_files: Optional[CachedFiles] = None
3899
3956
 
3900
3957
  if not context.build_cache:
3901
3958
  cached_files = build_from_scratch(context, certora_build_generator,
3902
3959
  certora_verify_generator,
3903
3960
  False)
3904
- return cache_hit, False, cached_files
3961
+ return False, cached_files
3905
3962
 
3906
3963
  build_cache_applicable = CertoraBuildCacheManager.cache_is_applicable(context)
3907
3964
 
@@ -3912,34 +3969,60 @@ def build_from_cache_or_scratch(context: CertoraContext,
3912
3969
  cached_files = build_from_scratch(context, certora_build_generator,
3913
3970
  certora_verify_generator,
3914
3971
  False)
3915
- return cache_hit, False, cached_files
3972
+ return False, cached_files
3916
3973
 
3917
3974
  cached_files = CertoraBuildCacheManager.build_from_cache(context)
3918
- # if no match, will rebuild from scratch
3919
- if cached_files is not None:
3920
- # write to .certora_build.json
3921
- shutil.copyfile(cached_files.certora_build_file, Util.get_certora_build_file())
3922
- # write build_output_props file
3923
- shutil.copyfile(cached_files.build_output_props_file, Util.get_built_output_props_file())
3924
- # write build_cache indicator file
3925
- with open(Util.get_build_cache_indicator_file(), "w+") as indicator_handle:
3926
- json.dump({"build_cache_hit": True}, indicator_handle)
3927
- # write in sources all the additional paths found
3928
- for p in cached_files.path_with_additional_included_files.glob("*"):
3929
- if p.is_dir():
3930
- Util.safe_copy_folder(p,
3931
- Util.get_certora_sources_dir() / p.name,
3932
- shutil.ignore_patterns())
3933
- else:
3934
- shutil.copyfile(p, Util.get_certora_sources_dir() / p.name)
3935
- cache_hit = True
3936
- else:
3937
- # rebuild
3975
+
3976
+ if not cached_files:
3977
+ # No match, rebuild
3938
3978
  cached_files = build_from_scratch(context, certora_build_generator,
3939
3979
  certora_verify_generator,
3940
3980
  True)
3981
+ return True, cached_files
3941
3982
 
3942
- return cache_hit, True, cached_files
3983
+ # Cache hit!
3984
+
3985
+ # write to .certora_build.json
3986
+ # This must happen _before_ searching for new internal function calls - the typechecker needs this file.
3987
+ shutil.copyfile(cached_files.certora_build_file, Util.get_certora_build_file())
3988
+
3989
+ # Check whether there are any new internal function calls in the spec file - if there are then we need to build
3990
+ # from scratch in order to create the relevant harness function.
3991
+ if context.verify and not context.disallow_internal_function_calls:
3992
+ with tempfile.NamedTemporaryFile("r", dir=Util.get_build_dir()) as tmp_file:
3993
+ internal_calls = []
3994
+ try:
3995
+ Ctx.run_local_spec_check(True, context, ["-listCalls", tmp_file.name], print_errors=False)
3996
+ output = tmp_file.read().strip()
3997
+ if output:
3998
+ internal_calls = output.split("\n")
3999
+ except Exception as e:
4000
+ instrumentation_logger.debug(f"Failed to get calls from spec\n{e}")
4001
+
4002
+ if internal_calls:
4003
+ build_logger.info("Found new internal calls in the spec file, need to recompile anyway")
4004
+ # There are new internal calls in the spec, we need to rebuild in order to generate the
4005
+ # external harness functions for them.
4006
+ cached_files = build_from_scratch(context, certora_build_generator,
4007
+ certora_verify_generator,
4008
+ True)
4009
+ return True, cached_files
4010
+
4011
+ # write build_output_props file
4012
+ shutil.copyfile(cached_files.build_output_props_file, Util.get_built_output_props_file())
4013
+ # write build_cache indicator file
4014
+ with open(Util.get_build_cache_indicator_file(), "w+") as indicator_handle:
4015
+ json.dump({"build_cache_hit": True}, indicator_handle)
4016
+ # write in sources all the additional paths found
4017
+ for p in cached_files.path_with_additional_included_files.glob("*"):
4018
+ if p.is_dir():
4019
+ Util.safe_copy_folder(p,
4020
+ Util.get_certora_sources_dir() / p.name,
4021
+ shutil.ignore_patterns())
4022
+ else:
4023
+ shutil.copyfile(p, Util.get_certora_sources_dir() / p.name)
4024
+
4025
+ return False, cached_files
3943
4026
 
3944
4027
 
3945
4028
  def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> None:
@@ -3965,13 +4048,10 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3965
4048
  if Cv.mode_has_spec_file(context) and not context.build_only and not ignore_spec_syntax_check:
3966
4049
  if Ctx.should_run_local_speck_check(context):
3967
4050
  Ctx.run_local_spec_check(False, context)
3968
- else:
3969
- build_logger.warning(
3970
- "Local checks of CVL specification files disabled. It is recommended to enable the checks.")
3971
4051
 
3972
- cache_hit, build_cache_enabled, cached_files = build_from_cache_or_scratch(context,
3973
- certora_build_generator,
3974
- certora_verify_generator)
4052
+ should_save_cache, cached_files = build_from_cache_or_scratch(context,
4053
+ certora_build_generator,
4054
+ certora_verify_generator)
3975
4055
 
3976
4056
  # avoid running the same test over and over again for each split run, context.split_rules is true only for
3977
4057
  # the first run and is set to [] for split runs
@@ -3990,7 +4070,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3990
4070
  build_logger.debug("build_source_tree failed", exc_info=e)
3991
4071
 
3992
4072
  # save in build cache
3993
- if not cache_hit and build_cache_enabled and cached_files.may_store_in_build_cache:
4073
+ if should_save_cache and cached_files.may_store_in_build_cache:
3994
4074
  CertoraBuildCacheManager.save_build_cache(context, cached_files)
3995
4075
 
3996
4076
  certora_verify_generator.update_certora_verify_struct(True)
@@ -0,0 +1,112 @@
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
+ from __future__ import annotations
17
+
18
+ import subprocess
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
23
+ sys.path.insert(0, str(scripts_dir_path))
24
+
25
+ import shutil
26
+ import time
27
+ import logging
28
+ from pathlib import Path
29
+ from typing import Set, Dict
30
+
31
+ from CertoraProver.certoraBuild import build_source_tree
32
+ from CertoraProver.certoraContextClass import CertoraContext
33
+ from Shared import certoraUtils as Util
34
+
35
+
36
+ log = logging.getLogger(__name__)
37
+
38
+
39
+ def build_sui_project(context: CertoraContext, timings: Dict) -> None:
40
+ """
41
+ Compile the Sui artefact and record elapsed time in *timings*.
42
+
43
+ Args:
44
+ context: The CertoraContext object containing the configuration.
45
+ timings: A dictionary to store timing information.
46
+ """
47
+ log.debug("Build Sui target")
48
+ start = time.perf_counter()
49
+ set_sui_build_directory(context)
50
+ timings["buildTime"] = round(time.perf_counter() - start, 4)
51
+ if context.test == str(Util.TestValue.AFTER_BUILD):
52
+ raise Util.TestResultsReady(context)
53
+
54
+
55
+ def set_sui_build_directory(context: CertoraContext) -> None:
56
+ sources: Set[Path] = set()
57
+
58
+ # If no move_path was specified, try to build the package
59
+ if not context.move_path:
60
+ move_toml_file = Util.find_file_in_parents("Move.toml")
61
+ if not move_toml_file:
62
+ raise Util.CertoraUserInputError("Could not find Move.toml, and no move_path was specified.")
63
+ sources.add(move_toml_file.absolute())
64
+ run_sui_build(context, move_toml_file.parent)
65
+
66
+ assert context.move_path, "expecting move_path to be set after build"
67
+ move_dir = Path(context.move_path)
68
+ assert move_dir.exists(), f"Output path '{move_dir}' does not exist"
69
+ assert move_dir.is_dir(), f"Output path '{move_dir}' is not a directory"
70
+
71
+ # Add all source files. We get these from the Sui build output, because it includes dependencies as well, and is
72
+ # available even if we didn't run the build ourselves.
73
+ sources.update(move_dir.rglob("*.move"))
74
+
75
+ # Add conf file if it exists
76
+ if getattr(context, 'conf_file', None) and Path(context.conf_file).exists():
77
+ sources.add(Path(context.conf_file).absolute())
78
+
79
+ # Copy the binary modules and source maps
80
+ shutil.copytree(move_dir,
81
+ Util.get_build_dir() / move_dir.name,
82
+ ignore=shutil.ignore_patterns('*.move'))
83
+
84
+ try:
85
+ # Create generators
86
+ build_source_tree(sources, context)
87
+
88
+ except Exception as e:
89
+ raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
90
+
91
+ def run_sui_build(context: CertoraContext, package_dir: Path) -> None:
92
+ assert not context.move_path, "run_sui_build: expecting move_path to be empty"
93
+
94
+ build_cmd = ["sui", "move", "build", "--test", "--path", str(package_dir)]
95
+
96
+ try:
97
+ build_cmd_text = ' '.join(build_cmd)
98
+ log.info(f"Building by calling `{build_cmd_text}`")
99
+ result = subprocess.run(build_cmd, capture_output=False)
100
+
101
+ # Check if the script executed successfully
102
+ if result.returncode != 0:
103
+ raise Util.CertoraUserInputError(f"Error running `{build_cmd_text}`")
104
+
105
+ context.move_path = str(package_dir / "build")
106
+
107
+ except Util.TestResultsReady as e:
108
+ raise e
109
+ except Util.CertoraUserInputError as e:
110
+ raise e
111
+ except Exception as e:
112
+ raise Util.CertoraUserInputError(f"An unexpected error occurred: {e}")
@@ -111,6 +111,8 @@ def handle_override_base_config(context: CertoraContext) -> None:
111
111
  if hasattr(context, attr):
112
112
  value = getattr(context, attr, False)
113
113
  if not value:
114
+ if attr in context.conf_file_attr and value is False:
115
+ continue # skip override if a boolean attribute was explicitly set to False in the conf file
114
116
  setattr(context, attr, override_base_config_attrs.get(attr))
115
117
  else:
116
118
  raise Util.CertoraUserInputError(f"{attr} appears in the base conf file {context.override_base_config} but is not a known attribute.")
@@ -523,7 +523,15 @@ def __rename_key(context: CertoraContext, old_key: str, new_key: str) -> None:
523
523
 
524
524
 
525
525
  def should_run_local_speck_check(context: CertoraContext) -> bool:
526
- return not (context.disable_local_typechecking or Util.is_ci_or_git_action())
526
+ output = context.compilation_steps_only or not (context.disable_local_typechecking or Util.is_ci_or_git_action())
527
+ if not output:
528
+ if context.disable_local_typechecking:
529
+ context_logger.warning(
530
+ "Local checks of CVL specification files disabled. It is recommended to enable the checks."
531
+ )
532
+ else:
533
+ context_logger.info("Local checks of CVL specification files skipped in CI and will run remotely.")
534
+ return output
527
535
 
528
536
 
529
537
  def run_typechecker(typechecker_name: str, with_typechecking: bool, args: List[str], print_errors: bool) -> None:
@@ -550,18 +558,13 @@ def run_typechecker(typechecker_name: str, with_typechecking: bool, args: List[s
550
558
  context_logger.debug(f"typechecking cmd: {' '.join(cmd_str_list)}")
551
559
 
552
560
  exit_code = Util.run_jar_cmd(cmd_str_list, False,
553
- custom_error_message="Failed to run Certora Prover locally. Please check the errors "
554
- "below for problems in the specifications (.spec files) or the "
555
- "prover_args defined in the .conf file.",
561
+ custom_error_message="Failed to run Certora Prover typechecker locally.\n"
562
+ "Please check the errors below for problems in the specifications"
563
+ " (.spec files) or the prover_args defined in the .conf file.",
556
564
  logger_topic="type_check",
557
565
  print_err=print_errors)
558
566
  if exit_code != 0:
559
- raise Util.CertoraUserInputError(
560
- "CVL syntax or type check failed.\n Please fix the issue. "
561
- "Using --disable_local_typechecking to skip this check is strongly discouraged, "
562
- "as simple syntax errors will only be detected during the cloud run."
563
- "with --disable_local_typechecking is not recommended is not recommended as "
564
- "simple syntax failures will be visible only on the cloud run.")
567
+ raise Util.CertoraUserInputError("CVL syntax or type check failed, please fix the issue.")
565
568
 
566
569
 
567
570
  def run_local_spec_check(with_typechecking: bool, context: CertoraContext, extra_args: List[str] = list(), print_errors: bool = True) -> None: