certora-cli-beta-mirror 7.30.1__py3-none-any.whl → 8.0.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 (36) hide show
  1. certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +11 -2
  2. certora_cli/CertoraProver/certoraBuild.py +68 -62
  3. certora_cli/CertoraProver/certoraBuildCacheManager.py +17 -16
  4. certora_cli/CertoraProver/certoraBuildRust.py +33 -21
  5. certora_cli/{Shared/rustProverCommon.py → CertoraProver/certoraBuildSui.py} +24 -18
  6. certora_cli/CertoraProver/certoraCloudIO.py +42 -33
  7. certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +62 -51
  8. certora_cli/CertoraProver/certoraCollectRunMetadata.py +20 -5
  9. certora_cli/CertoraProver/certoraConfigIO.py +17 -14
  10. certora_cli/CertoraProver/certoraContext.py +62 -10
  11. certora_cli/CertoraProver/certoraContextAttributes.py +132 -203
  12. certora_cli/CertoraProver/certoraContextValidator.py +108 -101
  13. certora_cli/CertoraProver/certoraParseBuildScript.py +4 -3
  14. certora_cli/CertoraProver/certoraVerifyGenerator.py +9 -4
  15. certora_cli/CertoraProver/splitRules.py +2 -0
  16. certora_cli/CertoraProver/storageExtension.py +0 -35
  17. certora_cli/Mutate/mutateApp.py +16 -10
  18. certora_cli/Mutate/mutateAttributes.py +11 -0
  19. certora_cli/Shared/certoraAttrUtil.py +11 -5
  20. certora_cli/Shared/certoraUtils.py +50 -47
  21. certora_cli/Shared/certoraValidateFuncs.py +29 -15
  22. certora_cli/Shared/proverCommon.py +6 -2
  23. certora_cli/certoraCVLFormatter.py +76 -0
  24. certora_cli/certoraConcord.py +39 -0
  25. certora_cli/certoraRun.py +53 -91
  26. certora_cli/certoraSolanaProver.py +1 -1
  27. certora_cli/certoraSorobanProver.py +1 -1
  28. {certora_cli_beta_mirror-7.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/METADATA +4 -3
  29. {certora_cli_beta_mirror-7.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/RECORD +36 -33
  30. {certora_cli_beta_mirror-7.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/entry_points.txt +1 -0
  31. certora_jars/ASTExtraction.jar +0 -0
  32. certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
  33. certora_jars/Typechecker.jar +0 -0
  34. {certora_cli_beta_mirror-7.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/LICENSE +0 -0
  35. {certora_cli_beta_mirror-7.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/WHEEL +0 -0
  36. {certora_cli_beta_mirror-7.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/top_level.txt +0 -0
@@ -13,6 +13,7 @@
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
+
16
17
  import logging
17
18
  import os
18
19
  import re
@@ -21,7 +22,8 @@ import itertools
21
22
  import tempfile
22
23
  import fnmatch
23
24
  from pathlib import Path
24
- from typing import Dict, List, Tuple, Set, Any, Union, Optional
25
+ from wcmatch import glob
26
+ from typing import Dict, List, Tuple, Set, Any, Union, OrderedDict
25
27
 
26
28
  import CertoraProver.certoraContext as Ctx
27
29
  import CertoraProver.certoraContextAttributes as Attrs
@@ -31,6 +33,7 @@ from Shared import certoraUtils as Util
31
33
  from Shared import certoraAttrUtil as AttrUtil
32
34
  from CertoraProver.certoraProjectScanner import scan_project
33
35
 
36
+
34
37
  scripts_dir_path = Path(__file__).parent.resolve() # containing directory
35
38
  sys.path.insert(0, str(scripts_dir_path))
36
39
 
@@ -43,10 +46,28 @@ class CertoraContextValidator:
43
46
  def __init__(self, context: CertoraContext):
44
47
  self.context = context
45
48
 
49
+ def handle_concord_attrs(self) -> None:
50
+ if Attrs.is_concord_app():
51
+ for attr in (Attrs.ConcordAttributes.unsupported_attributes()):
52
+ attr_name = attr.get_conf_key()
53
+ if getattr(self.context, attr_name):
54
+ if attr.arg_type == AttrUtil.AttrArgType.BOOLEAN:
55
+ setattr(self.context, attr_name, False)
56
+ else:
57
+ setattr(self.context, attr_name, None)
58
+ raise Util.CertoraUserInputError(f"Concord does not support {attr_name}")
59
+ if not self.context.check_method:
60
+ raise Util.CertoraUserInputError("'check_method' attribute/flag is mandatory when running Concord")
61
+ contracts = getattr(self.context, 'contracts', None)
62
+ if not contracts:
63
+ raise Util.CertoraUserInputError("No contracts were specified for Concord")
64
+ if len(contracts) != 2:
65
+ raise Util.CertoraUserInputError(f"expecting 2 contracts for Concord, got {len(contracts)}: {contracts}")
66
+
46
67
  def handle_ranger_attrs(self) -> None:
47
68
  # unset unsupported attributes
48
69
  if Attrs.is_ranger_app():
49
- for attr in Attrs.RangerAttributes.ranger_unsupported_attributes():
70
+ for attr in Attrs.RangerAttributes.unsupported_attributes():
50
71
  attr_name = attr.get_conf_key()
51
72
  if getattr(self.context, attr_name):
52
73
  if attr.arg_type == AttrUtil.AttrArgType.BOOLEAN:
@@ -64,16 +85,16 @@ class CertoraContextValidator:
64
85
  f"ignoring the set value of {self.context.loop_iter}")
65
86
  self.context.loop_iter = self.context.loop_iter or Util.DEFAULT_RANGER_LOOP_ITER
66
87
 
67
- for attr in Attrs.RangerAttributes.ranger_true_by_default_attributes():
88
+ for attr in Attrs.RangerAttributes.true_by_default_attributes():
68
89
  attr_name = attr.get_conf_key()
69
90
  setattr(self.context, attr_name, True)
70
91
 
71
92
  else:
72
93
  if self.context.range:
73
- # self.context.range = None
94
+ self.context.range = None
74
95
  validation_logger.info("the 'range' attribute is ignored when not running from the Ranger App")
75
96
  if self.context.ranger_failure_limit:
76
- # self.context.ranger_failure_limit = None
97
+ self.context.ranger_failure_limit = None
77
98
  validation_logger.info("the 'ranger_failure_limit' is ignored when not running from the Ranger App")
78
99
 
79
100
  def validate(self) -> None:
@@ -162,6 +183,12 @@ class CertoraContextValidator:
162
183
  if len(context.files) > 1:
163
184
  raise Util.CertoraUserInputError("Rust projects must specify exactly one executable in 'files'.")
164
185
 
186
+ if context.url_visibility and context.local:
187
+ validation_logger.info("'url_visibility' has no effect in local tool runs")
188
+ set_attr_default(context, attr_name=Attrs.CommonAttributes.URL_VISIBILITY.name.lower(),
189
+ ci_value=str(Vf.UrlVisibilityOptions.PUBLIC),
190
+ default_value=str(Vf.UrlVisibilityOptions.PRIVATE))
191
+
165
192
  def check_args_post_argparse(self) -> None:
166
193
  """
167
194
  Performs checks over the arguments after basic argparse parsing
@@ -216,10 +243,18 @@ class CertoraContextValidator:
216
243
  if context.compilation_steps_only and context.build_only:
217
244
  raise Util.CertoraUserInputError("cannot use both 'compilation_steps_only' and 'build_only'")
218
245
 
219
- set_wait_for_results_default(context)
246
+ set_attr_default(context, attr_name=Attrs.CommonAttributes.WAIT_FOR_RESULTS.name.lower(),
247
+ ci_value=str(Vf.WaitForResultOptions.ALL),
248
+ default_value=str(Vf.WaitForResultOptions.NONE))
220
249
  if context.wait_for_results and context.wait_for_results != str(Vf.WaitForResultOptions.NONE) and context.local:
221
250
  validation_logger.warning("'wait_for_results' has no effect in local tool runs")
222
251
 
252
+ if context.url_visibility and context.local:
253
+ validation_logger.info("'url_visibility' has no effect in local tool runs")
254
+ set_attr_default(context, attr_name=Attrs.CommonAttributes.URL_VISIBILITY.name.lower(),
255
+ ci_value=str(Vf.UrlVisibilityOptions.PUBLIC),
256
+ default_value=str(Vf.UrlVisibilityOptions.PRIVATE))
257
+
223
258
  # packages must be in a normal form (no unneeded . or ..)
224
259
  if context.packages:
225
260
  for idx, el in enumerate(context.packages):
@@ -375,19 +410,15 @@ def convert_to_compiler_map(context: CertoraContext) -> None:
375
410
  if context.solc_map:
376
411
  context.compiler_map = context.solc_map
377
412
  context.solc_map = None
378
- if context.solc:
379
- context.compiler_map = {'*.sol': f'{context.solc}'}
380
- if context.vyper:
381
- context.compiler_map = {'*.vy': f'{context.vyper}'}
413
+
382
414
 
383
415
  def check_vyper_flag(context: CertoraContext) -> None:
384
416
  if context.vyper:
385
- non_vy_paths = [path for path in context.files if not path.endswith(".vy")]
386
- if non_vy_paths:
387
- raise Util.CertoraUserInputError(f"vyper attribute can only be set for Vyper files: {non_vy_paths}")
417
+ vy_paths = [path for path in context.files if path.endswith(".vy")]
418
+ if not vy_paths:
419
+ validation_logger.warning("vyper attribute was set but no Vyper files were set")
388
420
  if context.solc:
389
421
  raise Util.CertoraUserInputError("cannot set both vyper attribute and solc attribute")
390
- context.solc = context.vyper
391
422
 
392
423
  def check_contract_name_arg_inputs(context: CertoraContext) -> None:
393
424
  """
@@ -429,14 +460,6 @@ def check_contract_name_arg_inputs(context: CertoraContext) -> None:
429
460
  check_conflicting_link_args(context)
430
461
 
431
462
  context.verified_contract_files = []
432
- if context.assert_contracts is not None:
433
- for assert_arg in context.assert_contracts:
434
- contract = Util.get_trivial_contract_name(assert_arg)
435
- if contract not in contract_names:
436
- __suggest_contract_name(f"'assert' argument, {contract}, doesn't match any contract name", contract,
437
- contract_names, contract_to_file)
438
- else:
439
- context.verified_contract_files.append(contract_to_file[contract])
440
463
 
441
464
  context.spec_file = None
442
465
 
@@ -582,31 +605,32 @@ def check_mode_of_operation(context: CertoraContext) -> None:
582
605
 
583
606
  @param context: A namespace including all CLI arguments provided
584
607
  @raise an CertoraUserInputError when:
585
- 1. .conf|.tac|.json file is used with --assert_contracts flags
586
- 2. when both --assert_contracts and --verify flags were given
587
- 3. when the file is not .tac|.conf|.json and neither --assert_contracts nor --verify were used
588
- 4. If either --bytecode_jsons or --bytecode_spec was used without the other.
608
+ 1. when both --equivalence_contracts and --verify flags were given
609
+ 2. when the file is not .tac|.conf|.json and neither --equivalence_contracts nor --verify were used
610
+ 3. If either --bytecode_jsons or --bytecode_spec was used without the other.
589
611
  """
590
612
  context.is_verify = context.verify is not None and len(context.verify) > 0
591
- context.is_assert = context.assert_contracts is not None and len(context.assert_contracts) > 0
592
613
  context.is_bytecode = context.bytecode_jsons is not None and len(context.bytecode_jsons) > 0
593
- context.is_equivalence = context.equivalence_contracts is not None
614
+ context.is_equivalence = context.equivalence_contracts is not None or context.is_concord_app
594
615
 
595
- if (context.project_sanity or context.foundry) and (context.is_verify or context.is_assert or context.is_bytecode or context.is_equivalence):
596
- raise Util.CertoraUserInputError("The 'project_sanity' and 'foundry' options cannot coexist with the 'verify', 'assert_contract' or 'bytecode_jsons' options")
616
+ if (context.project_sanity or context.foundry) and \
617
+ (context.is_verify or context.is_bytecode or context.is_equivalence):
618
+ raise Util.CertoraUserInputError("The 'project_sanity' and 'foundry' options cannot coexist with the 'verify', "
619
+ "'assert_contract' or 'bytecode_jsons' options")
597
620
 
598
621
  if context.project_sanity and context.foundry:
599
622
  raise Util.CertoraUserInputError("The 'project_sanity' and 'foundry' options cannot coexist")
600
623
 
601
- if len(list(filter(None, [context.is_verify, context.is_assert, context.is_equivalence]))) > 1:
602
- raise Util.CertoraUserInputError("only one option of 'assert_contracts', 'verify', 'equivalence' can be used")
624
+ if len(list(filter(None, [context.is_verify, context.is_equivalence]))) > 1:
625
+ raise Util.CertoraUserInputError("only one option of 'verify', 'equivalence' can be used")
603
626
 
604
627
  has_bytecode_spec = context.bytecode_spec is not None
605
628
  if has_bytecode_spec != context.is_bytecode:
606
629
  raise Util.CertoraUserInputError("Must use 'bytecode' together with 'bytecode_spec'")
607
630
 
608
631
  if not context.files and not any((context.is_bytecode, context.project_sanity, context.foundry)):
609
- raise Util.CertoraUserInputError("Should always provide input files, unless 'bytecode_jsons' or 'project_sanity' or 'foundry' are used")
632
+ raise Util.CertoraUserInputError("Should always provide input files, unless 'bytecode_jsons' or "
633
+ "'project_sanity' or 'foundry' are used")
610
634
 
611
635
  if context.is_bytecode and context.files:
612
636
  raise Util.CertoraUserInputError("Cannot use 'bytecode_jsons' with other files")
@@ -620,7 +644,8 @@ def check_mode_of_operation(context: CertoraContext) -> None:
620
644
  if not context.files:
621
645
  file_contract_pairs = _get_project_file_contract_pairs(context)
622
646
  main_contract = file_contract_pairs[0][1] # any contract
623
- context.files = [f"{file}:{contract}" if file.stem != contract else str(file) for file, contract in file_contract_pairs]
647
+ context.files = \
648
+ [f"{file}:{contract}" if file.stem != contract else str(file) for file, contract in file_contract_pairs]
624
649
  else:
625
650
  first = context.files[0]
626
651
  if ':' in first:
@@ -632,12 +657,14 @@ def check_mode_of_operation(context: CertoraContext) -> None:
632
657
 
633
658
  context.verify = f"{main_contract}:{spec_file_name}"
634
659
  context.is_verify = True
635
- # In this mode we want to make life easy for the user, so use the auto-dispatcher mode to avoid many unresolved calls
660
+ # In this mode we want to make life easy for the user,
661
+ # so use the auto-dispatcher mode to avoid many unresolved calls
636
662
  context.auto_dispatcher = True
637
663
 
638
664
  if context.foundry:
639
665
  file_contract_pairs = _get_project_file_contract_pairs(context)
640
- test_pairs = [(file, contract) for file, contract in file_contract_pairs if file.stem.endswith(".t") and 'lib' not in file.parents]
666
+ test_pairs = [(file, contract) for file, contract in file_contract_pairs
667
+ if file.stem.endswith(".t") and 'lib' not in file.parents]
641
668
  context.files = [f"{file}:{contract}" for file, contract in test_pairs]
642
669
  main_contract = test_pairs[0][1] # any contract
643
670
  spec_file_name = _generate_temp_spec(context, "use builtin rule verifyFoundryFuzzTestsNoRevert;\n")
@@ -645,7 +672,8 @@ def check_mode_of_operation(context: CertoraContext) -> None:
645
672
  context.foundry_tests_mode = True
646
673
  context.verify = f"{main_contract}:{spec_file_name}"
647
674
  context.is_verify = True
648
- # In this mode we want to make life easy for the user, so use the auto-dispatcher mode to avoid many unresolved calls
675
+ # In this mode we want to make life easy for the user,
676
+ # so use the auto-dispatcher mode to avoid many unresolved calls
649
677
  context.auto_dispatcher = True
650
678
 
651
679
  if context.files:
@@ -658,49 +686,30 @@ def check_mode_of_operation(context: CertoraContext) -> None:
658
686
  for input_file in context.files:
659
687
  special_file_type = next((suffix for suffix in special_file_suffixes if input_file.endswith(suffix)), None)
660
688
 
661
- if special_file_type:
662
- if len(context.files) > 1:
663
- raise Util.CertoraUserInputError(
664
- f"No other files are allowed with a file of type {special_file_type}")
665
- if context.is_assert:
666
- raise Util.CertoraUserInputError(
667
- f"Option 'assert_contracts' cannot be used with a {special_file_type} file {input_file}")
689
+ if special_file_type and len(context.files) > 1:
690
+ raise Util.CertoraUserInputError(
691
+ f"No other files are allowed with a file of type {special_file_type}")
668
692
 
669
- if not any([context.is_assert, context.is_verify, context.is_bytecode, context.equivalence_contracts,
670
- special_file_type]) and not context.build_only:
693
+ if (
694
+ not Attrs.is_concord_app() and
695
+ not any([context.is_verify, context.is_bytecode,
696
+ context.equivalence_contracts, special_file_type]) and not context.build_only):
671
697
  raise Util.CertoraUserInputError("You must use 'verify' when running the Certora Prover")
672
698
 
699
+ def find_matching_contracts(context: CertoraContext, pattern: str) -> List[str]:
700
+ result = []
701
+ for contract in context.contract_to_file:
702
+ if fnmatch.fnmatch(contract, pattern):
703
+ result.append(context.contract_to_file[contract])
704
+ return result
673
705
 
674
- def _normalize_maps(context: CertoraContext, map_attr_name: str, map_attr: Dict) -> Dict[str, str]:
675
- """
676
-
677
- :param context:
678
- :param map_attr_name: name of the attribute e.g. solc_optimize_map
679
- :param map_attr: the value of the map attribute, a dictionary mapping contract/file to a value
680
- :return:
681
-
682
- all solc_XXX_map attributes are used to set attributes for the Solidity compiler. The value is based on the
683
- Solidity file to be compiled, therefore it translates all keys to a relative path to a file
684
- """
685
-
686
- def find_matching_files(context: CertoraContext, pattern: str) -> List[str]:
687
- file_part = pattern.split(':')[0]
688
- if Path(file_part).suffix == "": # no suffix means the key is a contract
689
- matching_contracts = fnmatch.filter(context.contracts, pattern) # find matching contracts
690
- return [context.contract_to_file[contract] for contract in matching_contracts]
691
- else:
692
- return [file_part]
693
-
694
- new_map_attr: Dict[str, str] = {}
695
- for (pattern, value) in map_attr.copy().items():
696
- matching_files = find_matching_files(context, pattern)
697
- for file in matching_files:
698
- file = str(Path(file).resolve(strict=False).relative_to(Path.cwd()))
699
- attr_value = new_map_attr.get(file)
700
- if attr_value is None: # key in file paths but no mapping yet
701
- new_map_attr[file] = value
702
- return new_map_attr
706
+ def find_matching_files(context: CertoraContext, pattern: str) -> List[str]:
707
+ result = []
703
708
 
709
+ for path in context.file_paths:
710
+ if glob.globmatch(path, pattern, flags=glob.GLOBSTAR):
711
+ result.append(path)
712
+ return result
704
713
 
705
714
  def check_map_attributes(context: CertoraContext) -> None:
706
715
 
@@ -718,27 +727,24 @@ def check_map_attributes(context: CertoraContext) -> None:
718
727
  # we also check the map value was not set to False explicitly in the conf file
719
728
  if base_attr and map_attr and not (base_attr is False and context.conf_options.get(base_attr) is not None):
720
729
  raise Util.CertoraUserInputError(f"You cannot use both '{attr_prefix}' and '{map_attr_name}' arguments")
721
- if not isinstance(map_attr, dict):
722
- raise RuntimeError(f"map_attr is not dictionary, got {map_attr}")
723
- map_attr = _normalize_maps(context, map_attr_name, map_attr)
724
- setattr(context, f"{map_attr_name}", map_attr)
725
-
726
- sources_mappings: Dict[str, Optional[str]] = {file_path: None for file_path in context.file_paths} # initially mapping files to None
727
- for file in sources_mappings.keys():
728
- match: Optional[str] = None
729
- for (pattern, value) in map_attr.items():
730
- if fnmatch.fnmatch(file, pattern):
731
- match = pattern
732
- break
733
- if not match:
734
- raise Util.CertoraUserInputError(f"No matching for {file} in {map_attr_name}. Pattens: {map_attr}")
735
- elif not sources_mappings.get(file): # key in file paths but no mapping yet
736
- sources_mappings[file] = match
737
-
738
- # Now we check if all sources were mapped
739
- paths_not_set = [path for path, value in sources_mappings.items() if value is None]
740
- if paths_not_set:
741
- raise Util.CertoraUserInputError(f"{' '.join(paths_not_set)} was not set in {map_attr_name}")
730
+ if not isinstance(map_attr, OrderedDict):
731
+ raise RuntimeError(f"`map_attr` is not an ordered dictionary, got {map_attr}")
732
+
733
+ # will check that all the contract files are matched
734
+ file_list: Dict[str, bool] = {path: False for path in context.file_paths}
735
+
736
+ for key, value in map_attr.items():
737
+ pattern = key.split(':')[0] # ignore the contract part
738
+ if Path(pattern).suffix == "":
739
+ for path in find_matching_contracts(context, pattern):
740
+ file_list[path] = True
741
+ else:
742
+ for path in find_matching_files(context, pattern):
743
+ file_list[path] = True
744
+
745
+ none_keys = [k for k, v in file_list.items() if v is False]
746
+ if none_keys:
747
+ raise Util.CertoraUserInputError(f"The following files are not matched in {map_attr_name}: {none_keys}")
742
748
 
743
749
 
744
750
  def check_parametric_contracts(context: CertoraContext) -> None:
@@ -905,16 +911,17 @@ def check_files_input(file_list: List[str]) -> None:
905
911
  if file.endswith(Util.SOROBAN_EXEC_EXTENSION):
906
912
  raise Util.CertoraUserInputError(f'The Soroban file {file} cannot be accompanied with other files')
907
913
 
908
- def set_wait_for_results_default(context: CertoraContext) -> None:
909
- if context.wait_for_results is None:
914
+
915
+ def set_attr_default(context: CertoraContext, attr_name: str, ci_value: str, default_value: str) -> None:
916
+ if getattr(context, attr_name, None) is None:
910
917
  if Util.is_ci_or_git_action():
911
- context.wait_for_results = str(Vf.WaitForResultOptions.ALL)
918
+ setattr(context, attr_name, ci_value)
912
919
  else:
913
- context.wait_for_results = str(Vf.WaitForResultOptions.NONE)
920
+ setattr(context, attr_name, default_value)
914
921
 
915
922
 
916
923
  def mode_has_spec_file(context: CertoraContext) -> bool:
917
- return not context.is_assert and not context.is_tac and not context.is_equivalence
924
+ return not (context.is_tac or context.is_equivalence)
918
925
 
919
926
 
920
927
  def to_relative_paths(paths: Union[str, List[str]]) -> Union[str, List[str]]:
@@ -60,7 +60,7 @@ def add_solana_files_to_context(context: CertoraContext, json_obj: dict) -> None
60
60
  def run_rust_build(context: CertoraContext, build_cmd: List[str]) -> None:
61
61
 
62
62
  try:
63
- build_script_logger.info(f"Building by calling {build_cmd}")
63
+ build_script_logger.info(f"Building by calling `{' '.join(build_cmd)}`")
64
64
  result = subprocess.run(build_cmd, capture_output=True, text=True)
65
65
 
66
66
  # Check if the script executed successfully
@@ -90,11 +90,12 @@ def run_rust_build(context: CertoraContext, build_cmd: List[str]) -> None:
90
90
 
91
91
  add_solana_files_to_context(context, json_obj)
92
92
 
93
- if context.test == str(Util.TestValue.AFTER_BUILD_RUST):
94
- raise Util.TestResultsReady(context)
95
93
  assert not context.files, f"run_rust_build: expecting files to be empty, got: {context.files}"
96
94
  context.files = [os.path.join(context.rust_project_directory, context.rust_executables)]
97
95
 
96
+ if context.test == str(Util.TestValue.AFTER_BUILD_RUST):
97
+ raise Util.TestResultsReady(context)
98
+
98
99
  except Util.TestResultsReady as e:
99
100
  raise e
100
101
  except FileNotFoundError as e:
@@ -47,10 +47,6 @@ class CertoraVerifyGenerator:
47
47
  # we need to build once because of the early typechecking...
48
48
  self.update_certora_verify_struct(False)
49
49
 
50
- elif self.context.assert_contracts is not None:
51
- contract_to_check_asserts_for = self.context.assert_contracts
52
- self.certora_verify_struct = {"type": "assertion",
53
- "primaryContracts": contract_to_check_asserts_for}
54
50
  elif self.context.equivalence_contracts is not None:
55
51
  if self.context.method is None:
56
52
  raise Util.CertoraUserInputError("Argument `method` is required for equivalence checks")
@@ -63,6 +59,15 @@ class CertoraVerifyGenerator:
63
59
  "primary_contract": equiv_contracts[0],
64
60
  "secondary_contract": equiv_contracts[1]
65
61
  }
62
+ elif context.is_concord_app:
63
+ if len(context.contracts) != 2:
64
+ raise Util.CertoraUserInputError(f"Expecting 2 contracts but got {len(context.contracts)}: {context.contracts}")
65
+ contract_list = list(context.contracts)
66
+ self.certora_verify_struct = {
67
+ "type": "equivalence",
68
+ "primary_contract": contract_list[0],
69
+ "secondary_contract": contract_list[1]
70
+ }
66
71
 
67
72
  def update_certora_verify_struct(self, in_certora_sources: bool) -> None:
68
73
  """
@@ -96,8 +96,10 @@ class SplitRulesHandler():
96
96
  rule_flag = Attrs.EvmProverAttributes.RULE.get_flag()
97
97
  split_rules_flag = Attrs.EvmProverAttributes.SPLIT_RULES.get_flag()
98
98
  msg_flag = Attrs.CommonAttributes.MSG.get_flag()
99
+
99
100
  # it is important to use the cache, when the difference between the runs is only the rules that apply
100
101
  build_cache_flag = Attrs.EvmProverAttributes.BUILD_CACHE.get_flag()
102
+
101
103
  group_id_flag = Attrs.EvmProverAttributes.GROUP_ID.get_flag()
102
104
  disable_local_typechecking_flag = Attrs.EvmProverAttributes.DISABLE_LOCAL_TYPECHECKING.get_flag()
103
105
 
@@ -32,8 +32,6 @@ from CertoraProver.certoraBuildDataClasses import ContractInSDC
32
32
  from CertoraProver import erc7201
33
33
  from Shared import certoraUtils as Util
34
34
  from CertoraProver.certoraBuildDataClasses import SDC
35
- from CertoraProver.Compiler.CompilerCollectorFactory import get_relevant_compiler
36
- from CertoraProver.certoraContextClass import CertoraContext
37
35
 
38
36
  NameSpacedStorage: TypeAlias = Tuple[str, str]
39
37
  NewStorageFields = List[Dict[str, Any]]
@@ -351,36 +349,3 @@ def validate_new_fields(
351
349
  raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} is already mapped by {target_contract.name}")
352
350
  if var in target_vars:
353
351
  raise Util.CertoraUserInputError(f"Var '{var}' added to {target_contract.name} by {ext} is already declared by {target_contract.name}")
354
-
355
-
356
- def add_harness_to_compiler_map(original_file: str, harness_file: Any, context: CertoraContext) -> None:
357
- """
358
- Associates the generated harness file with the same compiler version as the original file.
359
-
360
- This ensures the harness contract is compiled with the same Solidity compiler version
361
- as the contract it's extending, maintaining compatibility.
362
-
363
- Args:
364
- original_file (str): Path to the original source file
365
- harness_file (Any): File-like object representing the generated harness file
366
- context (CertoraContext): The context object containing compiler mapping information
367
-
368
- Returns:
369
- None
370
- """
371
- # Validate prerequisites before updating compiler map
372
- if context.compiler_map is None:
373
- storage_extension_logger.debug("Cannot add compiler for harness: compiler_map is None (not a dict). Using the default compiler")
374
- return
375
-
376
- # Get the compiler version used for the original file
377
- compiler_version = get_relevant_compiler(Path(original_file), context)
378
-
379
- # Extract just the filename from the harness file path
380
- harness_filename = Path(harness_file.name).name
381
-
382
- # Add the compiler version to the context using glob pattern for the harness file
383
- map_key = f"*{harness_filename}"
384
- context.compiler_map[map_key] = compiler_version
385
-
386
- storage_extension_logger.debug(f"Added compiler mapping: {map_key} -> {compiler_version}")
@@ -710,6 +710,7 @@ class MutateApp:
710
710
  dump_csv: Optional[Path]
711
711
  sync: bool
712
712
  wait_for_original_run: bool
713
+ url_visibility: Vf.UrlVisibilityOptions
713
714
  collect_file: Optional[Path]
714
715
  poll_timeout: Optional[int]
715
716
  max_timeout_attempts_count: Optional[int]
@@ -722,7 +723,7 @@ class MutateApp:
722
723
  def __init__(self, args_list: List[str]) -> None:
723
724
  self.mutation_test_id = ''
724
725
  self.numb_of_jobs: int = 0
725
- self.backup_paths: List[Path] = []
726
+ self.backup_path: Optional[Path] = None
726
727
  self.sources_dir: Optional[Path] = None
727
728
  self.with_split_stats_data = False
728
729
  self.manual_mutants_list: List[Mutant] = list()
@@ -881,9 +882,9 @@ class MutateApp:
881
882
  " verification results. Please remove or replace the mutation")
882
883
 
883
884
  def restore_backups(self) -> None:
884
- for backup_path in self.backup_paths:
885
- Util.restore_backup(backup_path)
886
- self.backup_paths = []
885
+ if self.backup_path:
886
+ Util.restore_backup(self.backup_path)
887
+ self.backup_path = None
887
888
 
888
889
  def submit_soroban(self) -> None:
889
890
 
@@ -930,12 +931,14 @@ class MutateApp:
930
931
  # run mutants
931
932
  mutant_runs = []
932
933
  try:
933
- self.backup_paths = []
934
934
  for mutant in all_mutants:
935
935
  # call certoraRun for each mutant we found
936
936
  mutant_runs.append(self.run_mutant_soroban(mutant))
937
+ self.restore_backups()
937
938
  finally:
938
- self.restore_backups()
939
+ if self.backup_path:
940
+ mutation_logger.warning("Not empty backup path")
941
+ self.restore_backups()
939
942
 
940
943
  # wrap it all up and make the input for the 2nd step: the collector
941
944
  assert self.collect_file, "submit_soroban: no collect_file"
@@ -1143,10 +1146,11 @@ class MutateApp:
1143
1146
  def run_mutant_soroban(self, mutant: Mutant) -> MutantJob:
1144
1147
  file_to_mutate = Path(mutant.original_filename)
1145
1148
  # create a backup copy (if needed) by adding '.backup' to the end of the file
1146
- if Util.get_backup_path(file_to_mutate) not in self.backup_paths:
1147
- backup_file = Util.create_backup(file_to_mutate)
1148
- assert backup_file, f"run_mutant_soroban: create_backup for {file_to_mutate} failed"
1149
- self.backup_paths.append(backup_file)
1149
+ if self.backup_path and Util.get_backup_path(file_to_mutate) != self.backup_path:
1150
+ raise Util.ImplementationError("backup_path is set but does not match the file to mutate")
1151
+ if not self.backup_path:
1152
+ self.backup_path = Util.create_backup(file_to_mutate)
1153
+ assert self.backup_path, f"run_mutant_soroban: create_backup for {file_to_mutate} failed"
1150
1154
  shutil.copy(mutant.filename, file_to_mutate)
1151
1155
 
1152
1156
  try:
@@ -1405,6 +1409,8 @@ class MutateApp:
1405
1409
  certora_args = [str(conf_file), "--run_source", "MUTATION", "--msg", msg]
1406
1410
  if self.wait_for_original_run:
1407
1411
  certora_args.append("--wait_for_results")
1412
+ if self.url_visibility:
1413
+ certora_args.extend(["--url_visibility", str(self.url_visibility)])
1408
1414
  if self.with_split_stats_data and os.environ.get("WITH_AUTOCONFING", False) == '1':
1409
1415
  certora_args += ['--prover_resource_files', f"ac:{MConstants.SPLIT_STATS_DATA}"]
1410
1416
  if mutation_test_id:
@@ -250,6 +250,17 @@ class MutateAttributes(AttrUtil.Attributes):
250
250
  }
251
251
  )
252
252
 
253
+ URL_VISIBILITY = MutateAttributeDefinition(
254
+ attr_validation_func=Vf.validate_url_visibility,
255
+ argparse_args={
256
+ 'nargs': AttrUtil.SINGLE_OR_NONE_OCCURRENCES,
257
+ 'action': AttrUtil.UniqueStore,
258
+ 'default': None, # 'default': when --url_visibility was not used
259
+ # when --url_visibility was used without an argument its probably because the link should be public
260
+ 'const': str(Vf.UrlVisibilityOptions.PUBLIC)
261
+ }
262
+ )
263
+
253
264
 
254
265
  def get_args(args_list: List[str]) -> Dict:
255
266
 
@@ -167,9 +167,12 @@ class Attributes:
167
167
  table.add_column(Text("Description"), width=desc_col_width)
168
168
  table.add_column(Text("Default"), width=default_col_width)
169
169
 
170
+ unsupported_attribute_names = [attr.name for attr in cls.unsupported_attributes()]
170
171
  for name in dir(cls):
171
172
  if name in cls.hide_attributes():
172
173
  continue
174
+ if name in unsupported_attribute_names:
175
+ continue
173
176
  if name.isupper():
174
177
  attr = getattr(cls, name, None)
175
178
  assert isinstance(attr, AttributeDefinition), "print_attr_help: type(attr) == Attribute"
@@ -190,11 +193,9 @@ class Attributes:
190
193
  v.name = name
191
194
  return v
192
195
 
193
- if not cls._attribute_list:
194
- cls._attribute_list = [set_name(name) for name in dir(cls) if name.isupper()]
195
- cls._all_conf_names = [attr.name.lower() for attr in cls.attribute_list()]
196
- # 'compiler_map' does not have a matching 'compiler' attribute
197
- cls._all_map_attrs = [attr for attr in cls._all_conf_names if attr.endswith(Util.MAP_SUFFIX)]
196
+ cls._attribute_list = [set_name(name) for name in dir(cls) if name.isupper()]
197
+ cls._all_conf_names = [attr.name.lower() for attr in cls.attribute_list()]
198
+ cls._all_map_attrs = [attr for attr in cls._all_conf_names if attr.endswith(Util.MAP_SUFFIX)]
198
199
 
199
200
  @classmethod
200
201
  def hide_attributes(cls) -> List[str]:
@@ -203,3 +204,8 @@ class Attributes:
203
204
  :return: A list of attribute names to be hidden.
204
205
  """
205
206
  return []
207
+
208
+ @classmethod
209
+ def unsupported_attributes(cls) -> list:
210
+ # Return a list of AttributeDefinition objects that are unsupported
211
+ return []