certora-cli-beta-mirror 7.31.0__py3-none-any.whl → 8.1.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 (45) hide show
  1. certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +1 -3
  2. certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +3 -0
  3. certora_cli/CertoraProver/certoraApp.py +49 -0
  4. certora_cli/CertoraProver/certoraBuild.py +197 -44
  5. certora_cli/CertoraProver/certoraBuildCacheManager.py +2 -0
  6. certora_cli/CertoraProver/certoraBuildDataClasses.py +3 -1
  7. certora_cli/CertoraProver/certoraBuildRust.py +32 -21
  8. certora_cli/{Shared/rustProverCommon.py → CertoraProver/certoraBuildSui.py} +24 -18
  9. certora_cli/CertoraProver/certoraCloudIO.py +37 -8
  10. certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +1 -1
  11. certora_cli/CertoraProver/certoraCollectRunMetadata.py +20 -5
  12. certora_cli/CertoraProver/certoraConfigIO.py +23 -22
  13. certora_cli/CertoraProver/certoraContext.py +77 -45
  14. certora_cli/CertoraProver/certoraContextAttributes.py +122 -195
  15. certora_cli/CertoraProver/certoraContextValidator.py +78 -49
  16. certora_cli/CertoraProver/certoraContractFuncs.py +6 -0
  17. certora_cli/CertoraProver/certoraType.py +10 -1
  18. certora_cli/CertoraProver/certoraVerifyGenerator.py +10 -4
  19. certora_cli/CertoraProver/splitRules.py +20 -17
  20. certora_cli/CertoraProver/storageExtension.py +0 -35
  21. certora_cli/EquivalenceCheck/equivCheck.py +2 -1
  22. certora_cli/Mutate/mutateApp.py +28 -16
  23. certora_cli/Mutate/mutateAttributes.py +11 -0
  24. certora_cli/Mutate/mutateValidate.py +2 -2
  25. certora_cli/Shared/certoraAttrUtil.py +11 -5
  26. certora_cli/Shared/certoraUtils.py +99 -35
  27. certora_cli/Shared/certoraValidateFuncs.py +24 -12
  28. certora_cli/Shared/proverCommon.py +10 -8
  29. certora_cli/certoraCVLFormatter.py +76 -0
  30. certora_cli/certoraConcord.py +39 -0
  31. certora_cli/certoraEVMProver.py +2 -2
  32. certora_cli/certoraRanger.py +2 -2
  33. certora_cli/certoraRun.py +58 -98
  34. certora_cli/certoraSolanaProver.py +3 -3
  35. certora_cli/certoraSorobanProver.py +3 -4
  36. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/METADATA +4 -3
  37. certora_cli_beta_mirror-8.1.0.dist-info/RECORD +79 -0
  38. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/entry_points.txt +1 -0
  39. certora_jars/ASTExtraction.jar +0 -0
  40. certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
  41. certora_jars/Typechecker.jar +0 -0
  42. certora_cli_beta_mirror-7.31.0.dist-info/RECORD +0 -75
  43. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/LICENSE +0 -0
  44. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/WHEEL +0 -0
  45. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.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
@@ -31,7 +32,7 @@ from CertoraProver.certoraContextClass import CertoraContext
31
32
  from Shared import certoraUtils as Util
32
33
  from Shared import certoraAttrUtil as AttrUtil
33
34
  from CertoraProver.certoraProjectScanner import scan_project
34
-
35
+ import CertoraProver.certoraApp as App
35
36
 
36
37
  scripts_dir_path = Path(__file__).parent.resolve() # containing directory
37
38
  sys.path.insert(0, str(scripts_dir_path))
@@ -45,10 +46,28 @@ class CertoraContextValidator:
45
46
  def __init__(self, context: CertoraContext):
46
47
  self.context = context
47
48
 
49
+ def handle_concord_attrs(self) -> None:
50
+ if self.context.app == App.ConcordApp:
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
+
48
67
  def handle_ranger_attrs(self) -> None:
49
68
  # unset unsupported attributes
50
- if Attrs.is_ranger_app():
51
- for attr in Attrs.RangerAttributes.ranger_unsupported_attributes():
69
+ if self.context.app == App.RangerApp:
70
+ for attr in Attrs.RangerAttributes.unsupported_attributes():
52
71
  attr_name = attr.get_conf_key()
53
72
  if getattr(self.context, attr_name):
54
73
  if attr.arg_type == AttrUtil.AttrArgType.BOOLEAN:
@@ -66,7 +85,7 @@ class CertoraContextValidator:
66
85
  f"ignoring the set value of {self.context.loop_iter}")
67
86
  self.context.loop_iter = self.context.loop_iter or Util.DEFAULT_RANGER_LOOP_ITER
68
87
 
69
- for attr in Attrs.RangerAttributes.ranger_true_by_default_attributes():
88
+ for attr in Attrs.RangerAttributes.true_by_default_attributes():
70
89
  attr_name = attr.get_conf_key()
71
90
  setattr(self.context, attr_name, True)
72
91
 
@@ -80,7 +99,7 @@ class CertoraContextValidator:
80
99
 
81
100
  def validate(self) -> None:
82
101
 
83
- for attr_def in Attrs.get_attribute_class().attribute_list():
102
+ for attr_def in self.context.app.attr_class.attribute_list():
84
103
  conf_key = attr_def.get_conf_key()
85
104
  attr = getattr(self.context, conf_key, None)
86
105
  if attr_def.deprecation_msg and attr:
@@ -141,9 +160,9 @@ class CertoraContextValidator:
141
160
 
142
161
  if value is None:
143
162
  raise RuntimeError(f"calling validate_type_string with null value {conf_key}")
144
- if not isinstance(value, str):
145
- raise Util.CertoraUserInputError(f"value of {conf_key} {value} is not a string")
146
- attr.validate_value(value)
163
+ if not isinstance(value, (str, int)):
164
+ raise Util.CertoraUserInputError(f"value of {conf_key} {value} is not a string or an integer")
165
+ attr.validate_value(str(value))
147
166
 
148
167
  def validate_type_boolean(self, attr: AttrUtil.AttributeDefinition) -> None:
149
168
  conf_key, value = self.__get_key_and_value(attr)
@@ -164,6 +183,12 @@ class CertoraContextValidator:
164
183
  if len(context.files) > 1:
165
184
  raise Util.CertoraUserInputError("Rust projects must specify exactly one executable in 'files'.")
166
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
+
167
192
  def check_args_post_argparse(self) -> None:
168
193
  """
169
194
  Performs checks over the arguments after basic argparse parsing
@@ -201,7 +226,7 @@ class CertoraContextValidator:
201
226
  check_contract_name_arg_inputs(context) # Here context.contracts is set
202
227
  Util.check_packages_arguments(context)
203
228
  convert_to_compiler_map(context)
204
- if Attrs.is_evm_app():
229
+ if Ctx.is_evm_app_class(context):
205
230
  if context.compiler_map is None and context.bytecode_jsons is None: # xxx - we would need to reiterate here
206
231
  # once we allow combining raw bytecode with Solidity
207
232
  context.solc = Vf.is_solc_file_valid(context.solc)
@@ -218,10 +243,18 @@ class CertoraContextValidator:
218
243
  if context.compilation_steps_only and context.build_only:
219
244
  raise Util.CertoraUserInputError("cannot use both 'compilation_steps_only' and 'build_only'")
220
245
 
221
- 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))
222
249
  if context.wait_for_results and context.wait_for_results != str(Vf.WaitForResultOptions.NONE) and context.local:
223
250
  validation_logger.warning("'wait_for_results' has no effect in local tool runs")
224
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
+
225
258
  # packages must be in a normal form (no unneeded . or ..)
226
259
  if context.packages:
227
260
  for idx, el in enumerate(context.packages):
@@ -427,14 +460,6 @@ def check_contract_name_arg_inputs(context: CertoraContext) -> None:
427
460
  check_conflicting_link_args(context)
428
461
 
429
462
  context.verified_contract_files = []
430
- if context.assert_contracts is not None:
431
- for assert_arg in context.assert_contracts:
432
- contract = Util.get_trivial_contract_name(assert_arg)
433
- if contract not in contract_names:
434
- __suggest_contract_name(f"'assert' argument, {contract}, doesn't match any contract name", contract,
435
- contract_names, contract_to_file)
436
- else:
437
- context.verified_contract_files.append(contract_to_file[contract])
438
463
 
439
464
  context.spec_file = None
440
465
 
@@ -448,7 +473,7 @@ def check_contract_name_arg_inputs(context: CertoraContext) -> None:
448
473
  context.spec_file = spec
449
474
 
450
475
  contract_to_address = dict()
451
- if context.address:
476
+ if getattr(context, 'address', None):
452
477
  for address_str in context.address:
453
478
  contract = address_str.split(':')[0]
454
479
  if contract not in contract_names:
@@ -464,7 +489,7 @@ def check_contract_name_arg_inputs(context: CertoraContext) -> None:
464
489
  all_warnings.add(f'address {number} for contract {contract} defined twice')
465
490
  context.address = contract_to_address
466
491
 
467
- if context.struct_link:
492
+ if getattr(context, 'struct_link', None):
468
493
  contract_slot_to_contract = dict()
469
494
  for link in context.struct_link:
470
495
  location = link.split('=')[0]
@@ -580,31 +605,32 @@ def check_mode_of_operation(context: CertoraContext) -> None:
580
605
 
581
606
  @param context: A namespace including all CLI arguments provided
582
607
  @raise an CertoraUserInputError when:
583
- 1. .conf|.tac|.json file is used with --assert_contracts flags
584
- 2. when both --assert_contracts and --verify flags were given
585
- 3. when the file is not .tac|.conf|.json and neither --assert_contracts nor --verify were used
586
- 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.
587
611
  """
588
612
  context.is_verify = context.verify is not None and len(context.verify) > 0
589
- context.is_assert = context.assert_contracts is not None and len(context.assert_contracts) > 0
590
613
  context.is_bytecode = context.bytecode_jsons is not None and len(context.bytecode_jsons) > 0
591
- context.is_equivalence = context.equivalence_contracts is not None
614
+ context.is_equivalence = context.equivalence_contracts is not None or context.app == App.ConcordApp
592
615
 
593
- if (context.project_sanity or context.foundry) and (context.is_verify or context.is_assert or context.is_bytecode or context.is_equivalence):
594
- 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")
595
620
 
596
621
  if context.project_sanity and context.foundry:
597
622
  raise Util.CertoraUserInputError("The 'project_sanity' and 'foundry' options cannot coexist")
598
623
 
599
- if len(list(filter(None, [context.is_verify, context.is_assert, context.is_equivalence]))) > 1:
600
- 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")
601
626
 
602
627
  has_bytecode_spec = context.bytecode_spec is not None
603
628
  if has_bytecode_spec != context.is_bytecode:
604
629
  raise Util.CertoraUserInputError("Must use 'bytecode' together with 'bytecode_spec'")
605
630
 
606
631
  if not context.files and not any((context.is_bytecode, context.project_sanity, context.foundry)):
607
- 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")
608
634
 
609
635
  if context.is_bytecode and context.files:
610
636
  raise Util.CertoraUserInputError("Cannot use 'bytecode_jsons' with other files")
@@ -618,7 +644,8 @@ def check_mode_of_operation(context: CertoraContext) -> None:
618
644
  if not context.files:
619
645
  file_contract_pairs = _get_project_file_contract_pairs(context)
620
646
  main_contract = file_contract_pairs[0][1] # any contract
621
- 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]
622
649
  else:
623
650
  first = context.files[0]
624
651
  if ':' in first:
@@ -630,12 +657,14 @@ def check_mode_of_operation(context: CertoraContext) -> None:
630
657
 
631
658
  context.verify = f"{main_contract}:{spec_file_name}"
632
659
  context.is_verify = True
633
- # 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
634
662
  context.auto_dispatcher = True
635
663
 
636
664
  if context.foundry:
637
665
  file_contract_pairs = _get_project_file_contract_pairs(context)
638
- 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]
639
668
  context.files = [f"{file}:{contract}" for file, contract in test_pairs]
640
669
  main_contract = test_pairs[0][1] # any contract
641
670
  spec_file_name = _generate_temp_spec(context, "use builtin rule verifyFoundryFuzzTestsNoRevert;\n")
@@ -643,7 +672,8 @@ def check_mode_of_operation(context: CertoraContext) -> None:
643
672
  context.foundry_tests_mode = True
644
673
  context.verify = f"{main_contract}:{spec_file_name}"
645
674
  context.is_verify = True
646
- # 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
647
677
  context.auto_dispatcher = True
648
678
 
649
679
  if context.files:
@@ -656,16 +686,14 @@ def check_mode_of_operation(context: CertoraContext) -> None:
656
686
  for input_file in context.files:
657
687
  special_file_type = next((suffix for suffix in special_file_suffixes if input_file.endswith(suffix)), None)
658
688
 
659
- if special_file_type:
660
- if len(context.files) > 1:
661
- raise Util.CertoraUserInputError(
662
- f"No other files are allowed with a file of type {special_file_type}")
663
- if context.is_assert:
664
- raise Util.CertoraUserInputError(
665
- 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}")
666
692
 
667
- if not any([context.is_assert, context.is_verify, context.is_bytecode, context.equivalence_contracts,
668
- special_file_type]) and not context.build_only:
693
+ if (
694
+ not context.app == App.ConcordApp and
695
+ not any([context.is_verify, context.is_bytecode,
696
+ context.equivalence_contracts, special_file_type]) and not context.build_only):
669
697
  raise Util.CertoraUserInputError("You must use 'verify' when running the Certora Prover")
670
698
 
671
699
  def find_matching_contracts(context: CertoraContext, pattern: str) -> List[str]:
@@ -883,16 +911,17 @@ def check_files_input(file_list: List[str]) -> None:
883
911
  if file.endswith(Util.SOROBAN_EXEC_EXTENSION):
884
912
  raise Util.CertoraUserInputError(f'The Soroban file {file} cannot be accompanied with other files')
885
913
 
886
- def set_wait_for_results_default(context: CertoraContext) -> None:
887
- 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:
888
917
  if Util.is_ci_or_git_action():
889
- context.wait_for_results = str(Vf.WaitForResultOptions.ALL)
918
+ setattr(context, attr_name, ci_value)
890
919
  else:
891
- context.wait_for_results = str(Vf.WaitForResultOptions.NONE)
920
+ setattr(context, attr_name, default_value)
892
921
 
893
922
 
894
923
  def mode_has_spec_file(context: CertoraContext) -> bool:
895
- 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)
896
925
 
897
926
 
898
927
  def to_relative_paths(paths: Union[str, List[str]]) -> Union[str, List[str]]:
@@ -57,14 +57,17 @@ class Func:
57
57
  notpayable: bool,
58
58
  fromLib: bool, # actually serialized
59
59
  isConstructor: bool, # not serialized
60
+ is_free_func: bool,
60
61
  stateMutability: str,
61
62
  visibility: str,
62
63
  implemented: bool, # does this function have a body? (false for interface functions)
63
64
  overrides: bool, # does this function override an interface declaration or super-contract definition?
65
+ virtual: bool,
64
66
  contractName: str,
65
67
  source_bytes: Optional[SourceBytes],
66
68
  ast_id: Optional[int],
67
69
  original_file: Optional[str],
70
+ location: Optional[str],
68
71
  body_location: Optional[str],
69
72
  ):
70
73
  self.name = name
@@ -75,13 +78,16 @@ class Func:
75
78
  self.notpayable = notpayable
76
79
  self.fromLib = fromLib
77
80
  self.isConstructor = isConstructor
81
+ self.is_free_func = is_free_func
78
82
  self.stateMutability = stateMutability
79
83
  self.visibility = visibility
80
84
  self.original_file = original_file
85
+ self.location = location
81
86
  self.body_location = body_location
82
87
  self.implemented = implemented
83
88
  self.ast_id = ast_id
84
89
  self.overrides = overrides
90
+ self.virtual = virtual
85
91
  self.contractName = contractName
86
92
  self.source_bytes = source_bytes
87
93
 
@@ -158,6 +158,15 @@ class Type(ABC):
158
158
  ast_logger.fatal(f"unexpected AST Type Name Node: {name}")
159
159
  return ret
160
160
 
161
+ def contains_mapping(self) -> bool:
162
+ if isinstance(self, MappingType):
163
+ return True
164
+ if isinstance(self, StructType):
165
+ return any(member.contains_mapping() for member in self.members)
166
+ if isinstance(self, ArrayType):
167
+ return self.elementType.contains_mapping()
168
+ return False
169
+
161
170
 
162
171
  class PrimitiveType(Type):
163
172
  allowed_primitive_type_names = {
@@ -204,7 +213,7 @@ class PrimitiveType(Type):
204
213
  return PrimitiveType.canonical_primitive_name(self.name)
205
214
 
206
215
  def get_source_str(self) -> str:
207
- return self.name
216
+ return self.type_string # this is different from `self.name` in the case of a payable address
208
217
 
209
218
 
210
219
  class StringType(Type):
@@ -23,6 +23,7 @@ from typing import Dict, Any, Set, List, Tuple, Optional
23
23
  from CertoraProver.certoraMiniSpecParser import SpecImportLexer, SpecImportParser, SpecWithImports
24
24
  from CertoraProver.certoraContextClass import CertoraContext
25
25
  from Shared import certoraUtils as Util
26
+ import CertoraProver.certoraApp as App
26
27
 
27
28
  build_logger = logging.getLogger("build_conf")
28
29
 
@@ -47,10 +48,6 @@ class CertoraVerifyGenerator:
47
48
  # we need to build once because of the early typechecking...
48
49
  self.update_certora_verify_struct(False)
49
50
 
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
51
  elif self.context.equivalence_contracts is not None:
55
52
  if self.context.method is None:
56
53
  raise Util.CertoraUserInputError("Argument `method` is required for equivalence checks")
@@ -63,6 +60,15 @@ class CertoraVerifyGenerator:
63
60
  "primary_contract": equiv_contracts[0],
64
61
  "secondary_contract": equiv_contracts[1]
65
62
  }
63
+ elif context.app == App.ConcordApp:
64
+ if len(context.contracts) != 2:
65
+ raise Util.CertoraUserInputError(f"Expecting 2 contracts but got {len(context.contracts)}: {context.contracts}")
66
+ contract_list = list(context.contracts)
67
+ self.certora_verify_struct = {
68
+ "type": "equivalence",
69
+ "primary_contract": contract_list[0],
70
+ "secondary_contract": contract_list[1]
71
+ }
66
72
 
67
73
  def update_certora_verify_struct(self, in_certora_sources: bool) -> None:
68
74
  """
@@ -18,11 +18,14 @@ import sys
18
18
  import re
19
19
  from pathlib import Path
20
20
  import subprocess
21
+ import tempfile
21
22
  import uuid
22
23
 
23
24
  from typing import List, Set, Optional
24
25
 
26
+ import CertoraProver.certoraContext as Ctx
25
27
  import CertoraProver.certoraContextAttributes as Attrs
28
+ import CertoraProver.certoraApp as App
26
29
  from CertoraProver.certoraContextClass import CertoraContext
27
30
  from Shared import certoraUtils as Util
28
31
 
@@ -71,33 +74,33 @@ class SplitRulesHandler():
71
74
  def jar_list_value(list_attr: List[str]) -> str:
72
75
  return ','.join(list_attr)
73
76
 
74
- path_to_typechecker = Util.find_jar("Typechecker.jar")
77
+ with tempfile.NamedTemporaryFile("r", dir=Util.get_build_dir()) as tmp_file:
78
+ args = ["-listRules", tmp_file.name]
75
79
 
76
- command = ["java", "-jar", str(path_to_typechecker), "-listRules", "-buildDirectory", str(Util.get_build_dir())]
80
+ if self.context.exclude_rule:
81
+ args += ['-excludeRule', jar_list_value(self.context.exclude_rule)]
77
82
 
78
- if self.context.exclude_rule:
79
- command += ['-excludeRule', jar_list_value(self.context.exclude_rule)]
83
+ if not split_rules and self.context.rule:
84
+ args += ['-rule', jar_list_value(self.context.rule)]
85
+ elif split_rules and self.context.split_rules:
86
+ args += ['-rule', jar_list_value(self.context.split_rules)]
80
87
 
81
- if not split_rules and self.context.rule:
82
- command += ['-rule', jar_list_value(self.context.rule)]
83
- elif split_rules and self.context.split_rules:
84
- command += ['-rule', jar_list_value(self.context.split_rules)]
85
- try:
88
+ try:
89
+ Ctx.run_local_spec_check(False, self.context, args, print_errors=False)
90
+ lines = tmp_file.read().split("\n")
91
+ return set(lines)
86
92
 
87
- result = subprocess.run(command, capture_output=True, text=True, check=True)
88
- lines = result.stdout.strip().split("\n")
89
- filtered_lines = [s for s in lines if not (s.startswith("Warning:") or " " in s)]
90
- return set(filtered_lines)
91
-
92
- except subprocess.CalledProcessError as e:
93
- raise Util.CertoraUserInputError(f"Failed to get {'split ' if split_rules else ''}rules\ncommand: {command}\n{e}")
93
+ except Exception as e:
94
+ raise Util.CertoraUserInputError(f"Failed to get {'split ' if split_rules else ''}rules\n{e}")
94
95
 
95
96
  def run_commands(self) -> int:
96
97
  rule_flag = Attrs.EvmProverAttributes.RULE.get_flag()
97
98
  split_rules_flag = Attrs.EvmProverAttributes.SPLIT_RULES.get_flag()
98
99
  msg_flag = Attrs.CommonAttributes.MSG.get_flag()
100
+
99
101
  # it is important to use the cache, when the difference between the runs is only the rules that apply
100
102
  build_cache_flag = Attrs.EvmProverAttributes.BUILD_CACHE.get_flag()
103
+
101
104
  group_id_flag = Attrs.EvmProverAttributes.GROUP_ID.get_flag()
102
105
  disable_local_typechecking_flag = Attrs.EvmProverAttributes.DISABLE_LOCAL_TYPECHECKING.get_flag()
103
106
 
@@ -120,7 +123,7 @@ class SplitRulesHandler():
120
123
  if called as library then if running in local mode we use certoraRun.py otherwise certoraRun (from package)
121
124
  :return:
122
125
  """
123
- assert Attrs.is_evm_app(), "Split rules is supported only for EVM apps"
126
+ assert self.context.app == App.EvmApp, "Split rules is supported only for EVM apps"
124
127
  if hasattr(self.context, 'prover_cmd'):
125
128
  return self.context.prover_cmd
126
129
  if self.context.local:
@@ -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}")
@@ -30,6 +30,7 @@ from CertoraProver.certoraBuild import InputConfig, CertoraBuildGenerator, Funct
30
30
  from CertoraProver.certoraVerifyGenerator import CertoraVerifyGenerator
31
31
  from CertoraProver.certoraContext import get_args
32
32
  from certoraRun import run_certora
33
+ import CertoraProver.certoraApp as App
33
34
 
34
35
  LOG_PATH = Path('EqCheck_log.log')
35
36
  SANITY_PATH = Path('sanity.spec')
@@ -140,7 +141,7 @@ class EquivalenceChecker:
140
141
  tmp_conf = self.path / TMP_SANITY_CONF
141
142
  with tmp_conf.open('w') as temp_sanity:
142
143
  json.dump(contents, temp_sanity, indent=4)
143
- context = get_args([str(tmp_conf)])
144
+ context = get_args([str(tmp_conf)], App.EvmApp)
144
145
  config = InputConfig(context)
145
146
  cfb = CertoraBuildGenerator(config, context)
146
147
  certora_verify_generator = CertoraVerifyGenerator(context)
@@ -28,7 +28,6 @@ from typing import Optional, Any, List, Dict, Tuple, Set, Union
28
28
  import logging
29
29
  from types import SimpleNamespace
30
30
  import tarfile
31
- import json5
32
31
  import csv
33
32
  import time
34
33
  import requests
@@ -54,6 +53,7 @@ from Mutate import mutateAttributes as MutAttrs
54
53
  import CertoraProver.certoraContextAttributes as Attrs
55
54
  from CertoraProver.certoraContextClass import CertoraContext
56
55
  from rustMutator import run_universal_mutator
56
+ from CertoraProver import certoraContextValidator as Cv
57
57
 
58
58
  class RunTimedout(Exception):
59
59
  pass
@@ -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:
@@ -1182,6 +1186,7 @@ class MutateApp:
1182
1186
  if solc:
1183
1187
  compiler = solc
1184
1188
  elif compiler_map:
1189
+ Cv.check_contract_name_arg_inputs(self.prover_context)
1185
1190
  compiler = get_relevant_compiler(path_to_file, self.prover_context)
1186
1191
  if not compiler:
1187
1192
  raise Util.CertoraUserInputError(f"Cannot resolve Solidity compiler for {path_to_file}: "
@@ -1257,6 +1262,8 @@ class MutateApp:
1257
1262
  for gambit_obj in self.gambit:
1258
1263
  if MConstants.NUM_MUTANTS not in gambit_obj:
1259
1264
  gambit_obj[MConstants.NUM_MUTANTS] = DEFAULT_NUM_MUTANTS
1265
+ else:
1266
+ gambit_obj[MConstants.NUM_MUTANTS] = int(gambit_obj[MConstants.NUM_MUTANTS])
1260
1267
  gambit_obj[MConstants.SOLC] = self.get_solc_version(Path(gambit_obj[MConstants.FILENAME]))
1261
1268
  gambit_obj.update(shared_attributes)
1262
1269
  with MConstants.TMP_GAMBIT_PATH.open('w') as f:
@@ -1375,7 +1382,7 @@ class MutateApp:
1375
1382
  for mutant in self.universal_mutator:
1376
1383
  file_to_mutate = Path(os.path.normpath(mutant[MConstants.FILE_TO_MUTATE]))
1377
1384
  mutants_location = Path(mutant[MConstants.MUTANTS_LOCATION])
1378
- num_of_mutants = mutant[MConstants.NUM_MUTANTS]
1385
+ num_of_mutants = int(mutant[MConstants.NUM_MUTANTS])
1379
1386
  run_universal_mutator(file_to_mutate, self.prover_context.build_script, mutants_location, num_of_mutants)
1380
1387
  self.add_dir_to_mutants(ret_mutants, mutants_location, file_to_mutate)
1381
1388
  return ret_mutants
@@ -1405,6 +1412,8 @@ class MutateApp:
1405
1412
  certora_args = [str(conf_file), "--run_source", "MUTATION", "--msg", msg]
1406
1413
  if self.wait_for_original_run:
1407
1414
  certora_args.append("--wait_for_results")
1415
+ if self.url_visibility:
1416
+ certora_args.extend(["--url_visibility", str(self.url_visibility)])
1408
1417
  if self.with_split_stats_data and os.environ.get("WITH_AUTOCONFING", False) == '1':
1409
1418
  certora_args += ['--prover_resource_files', f"ac:{MConstants.SPLIT_STATS_DATA}"]
1410
1419
  if mutation_test_id:
@@ -1872,7 +1881,7 @@ class MutateApp:
1872
1881
 
1873
1882
  with open(self.conf, 'r') as conf_file:
1874
1883
  try:
1875
- self.prover_context = CertoraContext(**json5.load(conf_file, allow_duplicate_keys=False))
1884
+ self.prover_context = CertoraContext(**Util.read_conf_file(conf_file))
1876
1885
  self.check_prover_context()
1877
1886
  except Exception as e:
1878
1887
  raise Util.CertoraUserInputError(f"Failed to parse {self.conf} as JSON", e)
@@ -1915,7 +1924,9 @@ class MutateApp:
1915
1924
  if not self.manual_mutants:
1916
1925
  return
1917
1926
 
1918
- solc = self.get_solc_version(trg_dir)
1927
+ solc = self.get_solc_version(Path(mutant.original_filename))
1928
+ if not solc:
1929
+ raise Util.CertoraUserInputError(f"Unable to find a compiler for manual mutant {mutant.original_filename}")
1919
1930
 
1920
1931
  via_ir_flag = []
1921
1932
  if getattr(self.prover_context, MConstants.SOLC_VIA_IR, '') or \
@@ -1926,10 +1937,11 @@ class MutateApp:
1926
1937
  if self.test == str(Util.TestValue.CHECK_MANUAL_COMPILATION):
1927
1938
  raise Util.TestResultsReady(' '.join(args))
1928
1939
  with Util.change_working_directory(find_cwd(trg_dir)):
1929
- result = subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
1940
+ result = subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
1930
1941
  if result.returncode:
1931
1942
  mutation_logger.debug(f"mutation compilation failed: cmd: {' '.join(args)}\n cwd: {os.getcwd()}")
1932
- raise Util.CertoraUserInputError(f"mutation file {mutant.filename} failed to compile")
1943
+ raise Util.CertoraUserInputError(f"mutation file {mutant.filename} failed to compile\n\n"
1944
+ f"{result.stderr.decode()}")
1933
1945
 
1934
1946
 
1935
1947
  def rec_collect_statuses_children(rule: Dict[str, Any], statuses: List[str]) -> None: