certora-cli-beta-mirror 7.30.0__py3-none-any.whl → 7.31.0__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 (26) hide show
  1. certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +11 -2
  2. certora_cli/CertoraProver/certoraBuild.py +50 -45
  3. certora_cli/CertoraProver/certoraBuildCacheManager.py +15 -16
  4. certora_cli/CertoraProver/certoraBuildRust.py +2 -1
  5. certora_cli/CertoraProver/certoraCloudIO.py +7 -27
  6. certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +62 -51
  7. certora_cli/CertoraProver/certoraConfigIO.py +2 -1
  8. certora_cli/CertoraProver/certoraContext.py +29 -2
  9. certora_cli/CertoraProver/certoraContextAttributes.py +20 -9
  10. certora_cli/CertoraProver/certoraContextValidator.py +39 -61
  11. certora_cli/CertoraProver/certoraParseBuildScript.py +4 -3
  12. certora_cli/EquivalenceCheck/Eq_default.conf +0 -1
  13. certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -1
  14. certora_cli/Mutate/mutateApp.py +3 -1
  15. certora_cli/Shared/certoraUtils.py +21 -19
  16. certora_cli/Shared/certoraValidateFuncs.py +5 -3
  17. certora_cli/Shared/proverCommon.py +2 -0
  18. certora_cli/certoraRun.py +2 -0
  19. {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/METADATA +3 -2
  20. {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/RECORD +26 -26
  21. certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
  22. certora_jars/Typechecker.jar +0 -0
  23. {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/LICENSE +0 -0
  24. {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/WHEEL +0 -0
  25. {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/entry_points.txt +0 -0
  26. {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/top_level.txt +0 -0
@@ -25,8 +25,9 @@ from CertoraProver.Compiler.CompilerCollector import CompilerLang, CompilerColle
25
25
  from CertoraProver.Compiler.CompilerCollectorSol import CompilerCollectorSol, CompilerLangSol
26
26
  from CertoraProver.Compiler.CompilerCollectorYul import CompilerLangYul, CompilerCollectorYul
27
27
  from CertoraProver.Compiler.CompilerCollectorVy import CompilerCollectorVy, CompilerLangVy
28
- from Shared.certoraUtils import match_path_to_mapping_key, remove_file, get_certora_config_dir
28
+ from Shared.certoraUtils import remove_file, get_certora_config_dir
29
29
  from CertoraProver.certoraContextClass import CertoraContext
30
+ import CertoraProver.certoraContext as Ctx
30
31
 
31
32
  # logger for running the Solidity compiler and reporting any errors it emits
32
33
  compiler_logger = logging.getLogger("compiler")
@@ -42,8 +43,16 @@ def get_relevant_compiler(contract_file_path: Path, context: CertoraContext) ->
42
43
 
43
44
  if contract_file_path.is_absolute():
44
45
  contract_file_path = Path(os.path.relpath(contract_file_path, Path.cwd()))
46
+
47
+ # If the contract file is in the sources directory, we want to use the relative path from the "cwd" in the source tree.
48
+ if Util.get_certora_sources_dir() in contract_file_path.parents:
49
+ cwd_in_source = Util.get_certora_sources_dir() / context.cwd_rel_in_sources
50
+ contract_file_path = Path(os.path.relpath(contract_file_path, cwd_in_source))
51
+
45
52
  if context.compiler_map:
46
- match = match_path_to_mapping_key(contract_file_path, context.compiler_map)
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")
47
56
  if match:
48
57
  return match
49
58
  else:
@@ -450,8 +450,12 @@ def convert_pathname_to_posix(json_dict: Dict[str, Any], entry: str, smart_contr
450
450
  if path_obj.is_file():
451
451
  json_dict_posix_paths[path_obj.as_posix()] = json_dict[entry][file_path]
452
452
  else:
453
+ json_dict_str = str(json_dict)
454
+ # protecting against long strings
455
+ if len(json_dict_str) > 200:
456
+ json_dict_str = json_dict_str[:200] + '...'
453
457
  fatal_error(compiler_logger, f"The path of the source file {file_path}"
454
- f"in the standard json file {json_dict} does not exist")
458
+ f"in the standard json file {json_dict_str} does not exist")
455
459
  json_dict[entry] = json_dict_posix_paths
456
460
 
457
461
 
@@ -1361,46 +1365,48 @@ class CertoraBuildGenerator:
1361
1365
 
1362
1366
  return bytecode
1363
1367
 
1364
- def _handle_via_ir(self, contract_file_path: str, settings_dict: Dict[str, Any]) -> None:
1365
- if not self.context.solc_via_ir_map and not self.context.solc_via_ir:
1366
- return
1367
- if self.context.solc_via_ir_map:
1368
- match = Util.match_path_to_mapping_key(Path(contract_file_path), self.context.solc_via_ir_map)
1369
- if match:
1370
- settings_dict["viaIR"] = match
1371
- elif self.context.solc_via_ir:
1368
+ def get_solc_via_ir_value(self, contract_file_path: Path) -> bool:
1369
+ match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_via_ir')
1370
+ assert isinstance(match, (bool, type(None))), f"Expected solc_via_ir to be bool or None, got {type(match)}"
1371
+ return bool(match)
1372
+
1373
+ def get_solc_evm_version_value(self, contract_file_path: Path) -> Optional[str]:
1374
+ match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_evm_version')
1375
+ assert isinstance(match, (str, type(None))), f"Expected solc_evm_version to be string or None, got {type(match)}"
1376
+ return match
1377
+
1378
+ def get_solc_optimize_value(self, contract_file_path: Path) -> Optional[str]:
1379
+ match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_optimize')
1380
+ assert isinstance(match, (str, type(None))), f"Expected solc_optimize to be string or None, got {type(match)}"
1381
+ return match
1382
+
1383
+ def _handle_via_ir(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
1384
+ if self.get_solc_via_ir_value(contract_file_path):
1372
1385
  settings_dict["viaIR"] = True
1373
1386
 
1374
- def _handle_evm_version(self, contract_file_path: str, settings_dict: Dict[str, Any]) -> None:
1375
- if self.context.solc_evm_version_map:
1376
- match = Util.match_path_to_mapping_key(Path(contract_file_path), self.context.solc_evm_version_map)
1377
- if match:
1378
- settings_dict["evmVersion"] = match
1379
- elif self.context.solc_evm_version:
1380
- settings_dict["evmVersion"] = self.context.solc_evm_version
1387
+ def _handle_evm_version(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
1388
+ match = self.get_solc_evm_version_value(contract_file_path)
1389
+ if match:
1390
+ settings_dict["evmVersion"] = match
1381
1391
 
1382
- def _handle_optimize(self, contract_file_path: str, settings_dict: Dict[str, Any],
1392
+ def _handle_optimize(self, contract_file_path: Path, settings_dict: Dict[str, Any],
1383
1393
  compiler_collector: CompilerCollector) -> None:
1384
1394
  """
1385
1395
  @param contract_file_path: the contract that we are working on
1386
1396
  @param settings_dict: data structure for sending to the solc compiler
1387
1397
  """
1388
- if self.context.solc_optimize_map:
1389
- match = Util.match_path_to_mapping_key(Path(contract_file_path), self.context.solc_optimize_map)
1390
- if match and int(match) > 0:
1391
- settings_dict["optimizer"] = {"enabled": True}
1392
- settings_dict["optimizer"]['runs'] = int(match)
1393
- elif self.context.solc_optimize:
1398
+ match = self.get_solc_optimize_value(contract_file_path)
1399
+ if match:
1394
1400
  settings_dict["optimizer"] = {"enabled": True}
1395
- if int(self.context.solc_optimize) > 0:
1396
- settings_dict["optimizer"]['runs'] = int(self.context.solc_optimize)
1401
+ if int(match) > 0:
1402
+ settings_dict["optimizer"]['runs'] = int(match)
1397
1403
 
1398
1404
  # if optimizer is true, we should also define settings_dict["optimizer"]["details"]
1399
1405
  # for both optimize map and optimize
1400
1406
  optimizer = settings_dict.get("optimizer")
1401
1407
  if optimizer and isinstance(optimizer, dict) and optimizer.get('enabled'):
1402
1408
  # if we are not disabling finder friendly optimizer specifically, enable it whenever viaIR is also enabled
1403
- if not self.context.strict_solc_optimizer and self.context.solc_via_ir:
1409
+ if not self.context.strict_solc_optimizer and self.get_solc_via_ir_value(contract_file_path):
1404
1410
  # The default optimizer steps (taken from libsolidity/interface/OptimiserSettings.h) but with the
1405
1411
  # full inliner step removed
1406
1412
  solc0_8_26_to_0_8_30 = ("dhfoDgvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLCTUca[r]LSsTFOtfDnca[r]" +
@@ -1433,6 +1439,8 @@ class CertoraBuildGenerator:
1433
1439
  raise Util.CertoraUserInputError(err_msg)
1434
1440
  elif minor < 6 or (minor == 6 and patch < 7):
1435
1441
  raise Util.CertoraUserInputError(err_msg)
1442
+ elif self.context.yul_optimizer_steps:
1443
+ yul_optimizer_steps = self.context.yul_optimizer_steps
1436
1444
  elif (minor == 6 and patch >= 7) or (minor == 7 and 0 <= patch <= 1):
1437
1445
  yul_optimizer_steps = solc0_6_7_to_0_7_1
1438
1446
  elif minor == 7 and 2 <= patch <= 5:
@@ -1470,7 +1478,7 @@ class CertoraBuildGenerator:
1470
1478
  for opt_pass in self.context.disable_solc_optimizers:
1471
1479
  settings_dict["optimizer"]["details"][opt_pass] = False
1472
1480
 
1473
- def _fill_codegen_related_options(self, contract_file_path: str, settings_dict: Dict[str, Any],
1481
+ def _fill_codegen_related_options(self, contract_file_path: Path, settings_dict: Dict[str, Any],
1474
1482
  compiler_collector: CompilerCollector) -> None:
1475
1483
  """
1476
1484
  Fills options that control how we call solc and affect the bytecode in some way
@@ -1551,7 +1559,7 @@ class CertoraBuildGenerator:
1551
1559
  }
1552
1560
  }
1553
1561
 
1554
- self._fill_codegen_related_options(contract_file_as_provided, settings_dict, compiler_collector)
1562
+ self._fill_codegen_related_options(Path(contract_file_as_provided), settings_dict, compiler_collector)
1555
1563
 
1556
1564
  result_dict = {"language": compiler_collector_lang.name, "sources": sources_dict, "settings": settings_dict}
1557
1565
  # debug_print("Standard json input")
@@ -2286,7 +2294,7 @@ class CertoraBuildGenerator:
2286
2294
 
2287
2295
  if compiler_lang == CompilerLangSol():
2288
2296
  settings_dict: Dict[str, Any] = {}
2289
- self._fill_codegen_related_options(build_arg_contract_file, settings_dict,
2297
+ self._fill_codegen_related_options(Path(build_arg_contract_file), settings_dict,
2290
2298
  compiler_collector_for_contract_file)
2291
2299
  solc_optimizer_on, solc_optimizer_runs = self.solc_setting_optimizer_runs(settings_dict)
2292
2300
  solc_via_ir = self.solc_setting_via_ir(settings_dict)
@@ -2416,7 +2424,7 @@ class CertoraBuildGenerator:
2416
2424
  # if no function finder mode set, determine based on viaIR enabled or not:
2417
2425
  if self.context.function_finder_mode is None:
2418
2426
  # in via-ir, should not compress
2419
- if self.context.solc_via_ir:
2427
+ if self.get_solc_via_ir_value(Path(contract_file)):
2420
2428
  should_compress = False
2421
2429
  else:
2422
2430
  should_compress = True
@@ -3614,7 +3622,7 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3614
3622
 
3615
3623
  try:
3616
3624
  if overwrite:
3617
- # expecting target path to exist.
3625
+ # expecting the target path to exist.
3618
3626
  if target_path.exists():
3619
3627
  build_logger.debug(f"Overwriting {target_path} by copying from {source_path}")
3620
3628
  else:
@@ -3632,7 +3640,7 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3632
3640
  cwd_file_path.parent.mkdir(parents=True, exist_ok=True)
3633
3641
  cwd_file_path.touch()
3634
3642
 
3635
- # the empty file .project_directory is written in the source tree to denote the projecct directory
3643
+ # the empty file .project_directory is written in the source tree to denote the project directory
3636
3644
  rust_proj_dir = getattr(context, 'rust_project_directory', None)
3637
3645
  if rust_proj_dir:
3638
3646
  proj_dir_parent_relative = os.path.relpath(rust_proj_dir, os.getcwd())
@@ -3683,8 +3691,9 @@ def build_from_scratch(context: CertoraContext,
3683
3691
 
3684
3692
  # add to cache also source files that were found in the SDCs (e.g., storage extensions)
3685
3693
  paths_set = sdc.all_contract_files
3686
- for p in context.files:
3687
- paths_set.add(Path(p).absolute())
3694
+ for f in context.files:
3695
+ path = f.split(':')[0] # `f` is either 'path/to/file.sol' or 'path/to/file.sol:ContractName'
3696
+ paths_set.add(Path(path).absolute())
3688
3697
 
3689
3698
  # the contract files in SDCs are relative to .certora_sources. Which isn't good for us here.
3690
3699
  # Need to be relative to original paths
@@ -3724,8 +3733,7 @@ def build_from_scratch(context: CertoraContext,
3724
3733
 
3725
3734
  def build_from_cache_or_scratch(context: CertoraContext,
3726
3735
  certora_build_generator: CertoraBuildGenerator,
3727
- certora_verify_generator: CertoraVerifyGenerator,
3728
- certora_build_cache_manager: CertoraBuildCacheManager) \
3736
+ certora_verify_generator: CertoraVerifyGenerator) \
3729
3737
  -> Tuple[bool, bool, CachedFiles]:
3730
3738
  """
3731
3739
  Builds either from cache (fast path) or from scratch (slow path)
@@ -3742,10 +3750,10 @@ def build_from_cache_or_scratch(context: CertoraContext,
3742
3750
  False)
3743
3751
  return cache_hit, False, cached_files
3744
3752
 
3745
- build_cache_applicable = certora_build_cache_manager.cache_is_applicable(context)
3753
+ build_cache_applicable = CertoraBuildCacheManager.cache_is_applicable(context)
3746
3754
 
3747
3755
  if not build_cache_applicable:
3748
- build_cache_disabling_options = certora_build_cache_manager.cache_disabling_options(context)
3756
+ build_cache_disabling_options = CertoraBuildCacheManager.cache_disabling_options(context)
3749
3757
  build_logger.warning("Requested to enable the build cache, but the build cache is not applicable "
3750
3758
  f"to this run because of the given options: {build_cache_disabling_options}")
3751
3759
  cached_files = build_from_scratch(context, certora_build_generator,
@@ -3753,7 +3761,7 @@ def build_from_cache_or_scratch(context: CertoraContext,
3753
3761
  False)
3754
3762
  return cache_hit, False, cached_files
3755
3763
 
3756
- cached_files = certora_build_cache_manager.build_from_cache(context)
3764
+ cached_files = CertoraBuildCacheManager.build_from_cache(context)
3757
3765
  # if no match, will rebuild from scratch
3758
3766
  if cached_files is not None:
3759
3767
  # write to .certora_build.json
@@ -3792,7 +3800,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3792
3800
 
3793
3801
  try:
3794
3802
  input_config = InputConfig(context)
3795
-
3803
+ context.main_cache_key = CertoraBuildCacheManager.get_main_cache_key(context)
3796
3804
  # Create generators
3797
3805
  certora_build_generator = CertoraBuildGenerator(input_config, context)
3798
3806
 
@@ -3808,12 +3816,9 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3808
3816
  else:
3809
3817
  Ctx.run_local_spec_check(False, context)
3810
3818
 
3811
- certora_build_cache_manager = CertoraBuildCacheManager()
3812
-
3813
3819
  cache_hit, build_cache_enabled, cached_files = build_from_cache_or_scratch(context,
3814
3820
  certora_build_generator,
3815
- certora_verify_generator,
3816
- certora_build_cache_manager)
3821
+ certora_verify_generator)
3817
3822
 
3818
3823
  # avoid running the same test over and over again for each split run, context.split_rules is true only for
3819
3824
  # the first run and is set to [] for split runs
@@ -3833,7 +3838,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3833
3838
 
3834
3839
  # save in build cache
3835
3840
  if not cache_hit and build_cache_enabled and cached_files.may_store_in_build_cache:
3836
- certora_build_cache_manager.save_build_cache(context, cached_files)
3841
+ CertoraBuildCacheManager.save_build_cache(context, cached_files)
3837
3842
 
3838
3843
  certora_verify_generator.update_certora_verify_struct(True)
3839
3844
  certora_verify_generator.dump() # second dump with properly rooted specs
@@ -66,10 +66,9 @@ class CertoraBuildCacheManager:
66
66
  @returns None if no cache hit, otherwise - the matching `.certora_build.json` file, and a set of source files
67
67
  """
68
68
  build_cache_dir = Util.get_certora_build_cache_dir()
69
- main_cache_key = CertoraBuildCacheManager.get_main_cache_key(context)
70
- main_cache_entry_dir = build_cache_dir / main_cache_key
69
+ main_cache_entry_dir = build_cache_dir / context.main_cache_key
71
70
  if not main_cache_entry_dir.exists():
72
- build_cache_logger.info(f"cache miss on build cache key {main_cache_key}")
71
+ build_cache_logger.info(f"cache miss on build cache key {context.main_cache_key}")
73
72
  return None
74
73
 
75
74
  # here's the tricky matching part:
@@ -92,7 +91,7 @@ class CertoraBuildCacheManager:
92
91
  build_cache_logger.debug(f"Current sub build cache key computed for {file_list_file} is invalid")
93
92
  continue
94
93
  elif sub_cache_key_current_for_list == sub_cache_key_from_saved_entry:
95
- build_cache_logger.info(f"We have a match on build cache key {main_cache_key} and "
94
+ build_cache_logger.info(f"We have a match on build cache key {context.main_cache_key} and "
96
95
  f"{sub_cache_key_current_for_list}")
97
96
  sub_cache_key = sub_cache_key_current_for_list
98
97
  all_contract_files = set([Path(f) for f in file_list])
@@ -103,24 +102,24 @@ class CertoraBuildCacheManager:
103
102
 
104
103
  if sub_cache_key is None:
105
104
  build_cache_logger.info("All sub-cache-key file list files missed, cache miss on build cache key "
106
- f"{main_cache_key}")
105
+ f"{context.main_cache_key}")
107
106
  return None
108
107
 
109
108
  # cache hit
110
109
  certora_build_file = main_cache_entry_dir / f"{sub_cache_key}.{CachedFiles.certora_build_suffix}"
111
110
  if not certora_build_file.exists():
112
- build_cache_logger.warning(f"Got a cache-hit on build cache key {main_cache_key} and {sub_cache_key} "
111
+ build_cache_logger.warning(f"Got a cache-hit on build cache key {context.main_cache_key} and {sub_cache_key} "
113
112
  "but .certora_build.json file does not exist")
114
113
  return None
115
114
 
116
115
  if all_contract_files is None: # should not be feasible
117
- build_cache_logger.warning(f"Got a cache-hit on build cache key {main_cache_key} and {sub_cache_key} "
118
- "but file list file does not exist")
116
+ build_cache_logger.warning(f"Got a cache-hit on build cache key {context.main_cache_key} and "
117
+ f"{sub_cache_key} but file list file does not exist")
119
118
  return None
120
119
 
121
120
  build_output_props_file = main_cache_entry_dir / f"{sub_cache_key}.{CachedFiles.build_output_props_suffix}"
122
121
  if not build_output_props_file.exists():
123
- build_cache_logger.warning(f"Got a cache-hit on build cache key {main_cache_key} and {sub_cache_key} "
122
+ build_cache_logger.warning(f"Got a cache-hit on build cache key {context.main_cache_key} and {sub_cache_key} "
124
123
  f"but {CachedFiles.build_output_props_suffix} file does not exist")
125
124
  return None
126
125
 
@@ -131,26 +130,26 @@ class CertoraBuildCacheManager:
131
130
  @staticmethod
132
131
  def save_build_cache(context: CertoraContext, cached_files: CachedFiles) -> None:
133
132
  build_cache_dir = Util.get_certora_build_cache_dir()
134
- main_cache_key = CertoraBuildCacheManager.get_main_cache_key(context)
135
- main_cache_entry_dir = build_cache_dir / main_cache_key
133
+ main_cache_entry_dir = build_cache_dir / context.main_cache_key
136
134
  sub_cache_key = CertoraBuildCacheManager.get_sub_cache_key(cached_files.all_contract_files)
137
135
  if sub_cache_key is None:
138
- build_cache_logger.warning(f"Cannot save cache for main build cache key {main_cache_key} "
136
+ build_cache_logger.warning(f"Cannot save cache for main build cache key {context.main_cache_key} "
139
137
  "as sub-cache-key could not be computed")
140
138
  return
141
139
 
142
140
  if main_cache_entry_dir.exists():
143
- build_cache_logger.info(f"main build cache key already exists {main_cache_key}, "
141
+ build_cache_logger.info(f"main build cache key already exists {context.main_cache_key}, "
144
142
  f"saving sub build cache key {sub_cache_key}")
145
143
  if cached_files.all_exist(main_cache_entry_dir, sub_cache_key):
146
144
  build_cache_logger.debug("cache already saved under this build cache key, override")
147
145
  else:
148
- build_cache_logger.debug(f"cache was corrupted, need to re-save build cache key {main_cache_key} "
149
- f"and sub cache key {sub_cache_key}")
146
+ build_cache_logger.debug(f"cache was corrupted, need to re-save build cache key "
147
+ f"{context.main_cache_key} and sub cache key {sub_cache_key}")
150
148
  CertoraBuildCacheManager.save_files(cached_files, main_cache_entry_dir,
151
149
  sub_cache_key)
152
150
  else:
153
- build_cache_logger.info(f"saving main build cache key {main_cache_key} and sub cache key {sub_cache_key}")
151
+ build_cache_logger.info(f"saving main build cache key {context.main_cache_key} and sub cache key "
152
+ f"{sub_cache_key}")
154
153
  safe_create_dir(main_cache_entry_dir)
155
154
  CertoraBuildCacheManager.save_files(cached_files, main_cache_entry_dir,
156
155
  sub_cache_key)
@@ -51,6 +51,7 @@ def build_rust_app(context: CertoraContext) -> None:
51
51
  feature_flag = '--features'
52
52
  if context.cargo_tools_version:
53
53
  build_command.extend(["--tools-version", context.cargo_tools_version])
54
+ context.rust_project_directory = Util.find_nearest_cargo_toml()
54
55
 
55
56
  if context.cargo_features is not None:
56
57
  build_command.append(feature_flag)
@@ -102,7 +103,7 @@ def add_solana_files_to_sources(context: CertoraContext, sources: Set[Path]) ->
102
103
 
103
104
 
104
105
  def collect_files_from_rust_sources(context: CertoraContext, sources: Set[Path]) -> None:
105
- patterns = ["*.rs", "*.so", "*.wasm", "Cargo.toml", "Cargo.lock", "justfile"]
106
+ patterns = ["*.rs", "*.so", "*.wasm", Util.CARGO_TOML_FILE, "Cargo.lock", "justfile"]
106
107
  exclude_dirs = [".certora_internal"]
107
108
 
108
109
  if hasattr(context, 'rust_project_directory'):
@@ -760,42 +760,20 @@ class CloudVerification:
760
760
  Util.get_configuration_layout_data_file(),
761
761
  Util.get_build_dir() / Path(self.context.files[0]).name]
762
762
 
763
+ if Util.get_debug_log_file().exists():
764
+ files_list.append(Util.get_debug_log_file())
765
+
763
766
  if Util.get_certora_sources_dir().exists():
764
767
  files_list.append(Util.get_certora_sources_dir())
765
768
 
766
769
  if hasattr(self.context, 'build_script') and self.context.build_script:
767
- result = compress_files(self.logZipFilePath, Util.get_debug_log_file(),
768
- short_output=Ctx.is_minimal_cli_output(self.context))
769
-
770
- if not result:
771
- return False
772
- files_list.append(self.logZipFilePath)
773
-
774
- # Create a .RustExecution file to classify zipInput as a rust source code
775
- rust_execution_file = Util.get_build_dir() / ".RustExecution"
776
- rust_execution_file.touch(exist_ok=True)
777
- files_list.append(rust_execution_file)
778
-
779
770
  if attr_file := getattr(self.context, 'rust_logs_stdout', None):
780
771
  files_list.append(Util.get_build_dir() / Path(attr_file).name)
781
772
  if attr_file := getattr(self.context, 'rust_logs_stderr', None):
782
773
  files_list.append(Util.get_build_dir() / Path(attr_file).name)
783
774
 
784
- result = compress_files(self.ZipFilePath, *files_list,
785
- short_output=Ctx.is_minimal_cli_output(self.context))
786
-
787
- elif Attrs.is_solana_app() or Attrs.is_soroban_app():
788
- for file in self.context.files:
789
- files_list.append(Path(file))
790
-
791
- result = compress_files(self.ZipFilePath, *files_list,
792
- short_output=Ctx.is_minimal_cli_output(self.context))
793
- else:
794
- raise Util.CertoraUserInputError(
795
- 'When running rust application, context should either have attribute "rust_executables" '
796
- 'provided by build_script execution, '
797
- 'or either is_solana_app(), is_soroban_app() should be set to True'
798
- )
775
+ result = compress_files(self.ZipFilePath, *files_list,
776
+ short_output=Ctx.is_minimal_cli_output(self.context))
799
777
 
800
778
  else:
801
779
  # Zip the log file first separately and again with the rest of the files, so it will not be decompressed
@@ -953,6 +931,8 @@ class CloudVerification:
953
931
  for i in range(self.verification_request_retries):
954
932
  try:
955
933
  response = requests.post(verify_url, json=auth_data, headers=headers, timeout=60)
934
+ cloud_logger.debug(f"\n\n=================\n\nresponse - Status Code: {response.status_code}\n\n"
935
+ f"Headers:\n {response.headers}\n\nText:\n {response.text}\n\n=================\n\n")
956
936
  if response is None:
957
937
  continue
958
938
  status = response.status_code
@@ -25,12 +25,13 @@ sys.path.insert(0, str(scripts_dir_path))
25
25
  import CertoraProver.certoraContextAttributes as Attrs
26
26
  from CertoraProver.certoraCollectRunMetadata import RunMetaData, MetadataEncoder
27
27
  import Shared.certoraUtils as Utils
28
+ from typing import List
28
29
 
29
30
 
30
31
  class MainSection(Enum):
31
32
  GENERAL = "GENERAL"
33
+ OPTIONS = "OPTIONS"
32
34
  SOLIDITY_COMPILER = "SOLIDITY_COMPILER"
33
- GIT = "GIT"
34
35
  NEW_SECTION = "NEW_SECTION"
35
36
 
36
37
 
@@ -47,13 +48,11 @@ class InnerContent:
47
48
  content: Any
48
49
  doc_link: str = ''
49
50
  tooltip: str = ''
50
- unsound: str = 'false'
51
+ unsound: bool = False
51
52
 
52
53
  def __post_init__(self) -> None:
53
54
  if isinstance(self.content, bool):
54
55
  self.content = 'true' if self.content else 'false'
55
- if isinstance(self.unsound, bool):
56
- self.unsound = 'true' if self.unsound else 'false'
57
56
 
58
57
 
59
58
  @dataclasses.dataclass
@@ -65,6 +64,7 @@ class CardContent:
65
64
 
66
65
  DOC_LINK_PREFIX = 'https://docs.certora.com/en/latest/docs/'
67
66
  GIT_ATTRIBUTES = ['origin', 'revision', 'branch', 'dirty']
67
+ ARG_LIST_ATTRIBUTES = ['prover_args', 'java_args']
68
68
 
69
69
 
70
70
  class AttributeJobConfigData:
@@ -73,13 +73,13 @@ class AttributeJobConfigData:
73
73
  This should be added to the AttributeDefinition and configured for every new attribute
74
74
  presented in the Rule report.
75
75
 
76
- Note: Attributes which do not contain specific information will be assigned as a Flag in the General main section!
76
+ Note: Attributes that do not contain specific information will be presented in the OPTIONS main section!
77
77
 
78
78
  arguments:
79
79
  - main_section : MainSection -- the main section inside the config tab
80
- default: MainSection.GENERAL
81
- - subsection : str -- the subsection within the main_section (e.g Flags)
82
- default: Flags
80
+ default: MainSection.OPTIONS
81
+ - subsection : str -- the subsection within the main_section
82
+ default: None - they will be presented inside the OPTIONS card
83
83
  - doc_link : Optional[str] -- a link to the Documentation page of this attribute (if exists)
84
84
  default: 'https://docs.certora.com/en/latest/docs/' + Solana/EVM path + #<attribute_name>
85
85
  - tooltip : Optional[str] -- a description of this attribute to present in the config tab
@@ -88,7 +88,7 @@ class AttributeJobConfigData:
88
88
  default: False
89
89
  """
90
90
 
91
- def __init__(self, main_section: MainSection = MainSection.GENERAL, subsection: str = '',
91
+ def __init__(self, main_section: MainSection = MainSection.OPTIONS, subsection: str = '',
92
92
  doc_link: Optional[str] = '', tooltip: Optional[str] = '', unsound: bool = False):
93
93
  self.main_section = main_section
94
94
  self.subsection = subsection
@@ -201,6 +201,40 @@ def create_or_get_card_content(output: list[CardContent], name: str) -> CardCont
201
201
  return main_section
202
202
 
203
203
 
204
+ def split_and_sort_arg_list_value(args_list: List[str]) -> List[str]:
205
+ """
206
+ Splits a unified CLI argument list of strings into a sorted list of flag+value groups.
207
+ This is useful mainly for --prover_args and --java_args.
208
+
209
+ For example:
210
+ "-depth 15 -adaptiveSolverConfig false" → ["-adaptiveSolverConfig false", "-depth 15"]
211
+
212
+ Assumes each flag starts with '-' and its value follows immediately, if exists.
213
+ Lines are sorted alphabetically.
214
+ """
215
+ unified_args = ''.join(args_list)
216
+
217
+ if not unified_args.strip():
218
+ return []
219
+
220
+ lines: List[str] = []
221
+ tokens = unified_args.split()
222
+ curr_line = ""
223
+
224
+ for token in tokens:
225
+ if token.startswith('-'):
226
+ if curr_line:
227
+ lines.append(curr_line)
228
+ curr_line = token
229
+ else:
230
+ curr_line += f" {token}"
231
+
232
+ if curr_line:
233
+ lines.append(curr_line)
234
+
235
+ return sorted(lines)
236
+
237
+
204
238
  def create_inner_content(name: str, content_type: ContentType, value: Any, doc_link: str,
205
239
  config_data: AttributeJobConfigData) -> InnerContent:
206
240
  return InnerContent(
@@ -209,7 +243,7 @@ def create_inner_content(name: str, content_type: ContentType, value: Any, doc_l
209
243
  content=value,
210
244
  doc_link=doc_link,
211
245
  tooltip=config_data.tooltip or '',
212
- unsound='true' if config_data.unsound else 'false'
246
+ unsound=config_data.unsound
213
247
  )
214
248
 
215
249
 
@@ -260,35 +294,23 @@ def collect_attribute_configs(metadata: dict) -> list[CardContent]:
260
294
  )
261
295
  continue
262
296
 
263
- # Find or create the subsection (if it exists)
297
+ # Find or create the subsection (if it doesn't exist)
264
298
  if isinstance(attr_value, list):
265
- current_section: Any = main_section
266
299
  content_type = ContentType.SIMPLE
300
+ if attr_name in ARG_LIST_ATTRIBUTES:
301
+ attr_value = split_and_sort_arg_list_value(attr_value)
267
302
 
268
303
  elif isinstance(attr_value, dict):
269
- current_section = main_section
270
304
  content_type = ContentType.COMPLEX
271
305
  attr_value = [
272
306
  create_inner_content(key, ContentType.FLAG, value, doc_link, config_data)
273
307
  for key, value in attr_value.items()
274
308
  ]
275
309
  else:
276
- # this attribute is a value attribute without a subsection, it will be placed in flags.
277
- subsection_key = config_data.subsection.lower() if config_data.subsection else 'flags'
278
- current_section = next((section for section in main_section.content
279
- if section.inner_title == subsection_key), None)
280
- if current_section is None:
281
- current_section = InnerContent(
282
- inner_title=subsection_key,
283
- content_type=ContentType.COMPLEX.value,
284
- content=[]
285
- )
286
- main_section.content.append(current_section)
287
-
288
310
  content_type = ContentType.FLAG
289
311
 
290
312
  # Update the current section with attribute details
291
- current_section.content.append(
313
+ main_section.content.append(
292
314
  create_inner_content(attr_name, content_type, attr_value, doc_link, config_data)
293
315
  )
294
316
 
@@ -300,37 +322,23 @@ def collect_run_config_from_metadata(attributes_configs: list[CardContent], meta
300
322
  Adding CLI and Git configuration from metadata
301
323
  """
302
324
 
303
- # Define a mapping of metadata attributes to their keys in general_section
304
- metadata_mappings = {
305
- 'CLI Version': metadata.get('CLI_version'),
306
- 'Verify': metadata.get('conf', {}).get('verify'),
307
- }
308
-
309
325
  general_section = create_or_get_card_content(attributes_configs, MainSection.GENERAL.value.lower())
310
326
 
311
- # Add metadata attributes dynamically if they exist
312
- for key, value in metadata_mappings.items():
313
- if value:
314
- general_section.content.append(InnerContent(
315
- inner_title=key,
316
- content_type=ContentType.FLAG.value,
317
- content=value,
318
- ))
327
+ if cli_version := metadata.get('CLI_version'):
328
+ general_section.content.append(InnerContent(
329
+ inner_title='CLI Version',
330
+ content_type=ContentType.FLAG.value,
331
+ content=cli_version,
332
+ ))
319
333
 
320
- # Adding GIT configuration from metadata only if attributes are found
321
- git_content = []
322
334
  for attr in GIT_ATTRIBUTES:
323
335
  if attr_value := metadata.get(attr):
324
- git_content.append(InnerContent(
336
+ general_section.content.append(InnerContent(
325
337
  inner_title=attr,
326
338
  content_type=ContentType.FLAG.value,
327
339
  content=attr_value,
328
340
  ))
329
341
 
330
- if git_content:
331
- git_section = create_or_get_card_content(attributes_configs, MainSection.GIT.value.lower())
332
- git_section.content = git_content
333
-
334
342
  return attributes_configs
335
343
 
336
344
 
@@ -338,14 +346,17 @@ def sort_configuration_layout(data: list[CardContent]) -> list[CardContent]:
338
346
  """
339
347
  Sorts a configuration layout:
340
348
  - Top-level sorted by 'card_title'
341
- - Nested content sorted by 'inner_title', with 'Verify' first
349
+ - Nested content sorted by 'inner_title', with 'verify' first
342
350
  """
343
351
  priority = {
344
- "Verify": 0,
352
+ # Priorities for top-level cards
345
353
  "general": 0,
354
+ "files": 1,
355
+ "options": 2,
356
+ # Top level items inside their respective cards
357
+ "verify": 0,
346
358
  "solc": 0,
347
- "CLI Version": 1,
348
- "flags": 2
359
+ "CLI Version": 0
349
360
  }
350
361
 
351
362
  def inner_sort_key(item: Any) -> Any:
@@ -17,6 +17,7 @@ import json5
17
17
  import logging
18
18
  from pathlib import Path
19
19
  from typing import Dict, Any
20
+ from collections import OrderedDict
20
21
 
21
22
  import CertoraProver.certoraContext as Ctx
22
23
  import CertoraProver.certoraContextAttributes as Attrs
@@ -75,7 +76,7 @@ def read_from_conf_file(context: CertoraContext) -> None:
75
76
 
76
77
  try:
77
78
  with conf_file_path.open() as conf_file:
78
- context.conf_file_attr = json5.load(conf_file, allow_duplicate_keys=False)
79
+ context.conf_file_attr = json5.load(conf_file, allow_duplicate_keys=False, object_pairs_hook=OrderedDict)
79
80
  try:
80
81
  check_conf_content(context)
81
82
  except Util.CertoraUserInputError as e: