certora-cli-beta-mirror 7.31.0__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 (33) hide show
  1. certora_cli/CertoraProver/certoraBuild.py +19 -18
  2. certora_cli/CertoraProver/certoraBuildCacheManager.py +2 -0
  3. certora_cli/CertoraProver/certoraBuildRust.py +31 -20
  4. certora_cli/{Shared/rustProverCommon.py → CertoraProver/certoraBuildSui.py} +24 -18
  5. certora_cli/CertoraProver/certoraCloudIO.py +37 -8
  6. certora_cli/CertoraProver/certoraCollectRunMetadata.py +20 -5
  7. certora_cli/CertoraProver/certoraConfigIO.py +15 -13
  8. certora_cli/CertoraProver/certoraContext.py +33 -8
  9. certora_cli/CertoraProver/certoraContextAttributes.py +113 -195
  10. certora_cli/CertoraProver/certoraContextValidator.py +69 -40
  11. certora_cli/CertoraProver/certoraVerifyGenerator.py +9 -4
  12. certora_cli/CertoraProver/splitRules.py +2 -0
  13. certora_cli/CertoraProver/storageExtension.py +0 -35
  14. certora_cli/Mutate/mutateApp.py +16 -10
  15. certora_cli/Mutate/mutateAttributes.py +11 -0
  16. certora_cli/Shared/certoraAttrUtil.py +11 -5
  17. certora_cli/Shared/certoraUtils.py +29 -28
  18. certora_cli/Shared/certoraValidateFuncs.py +24 -12
  19. certora_cli/Shared/proverCommon.py +4 -2
  20. certora_cli/certoraCVLFormatter.py +76 -0
  21. certora_cli/certoraConcord.py +39 -0
  22. certora_cli/certoraRun.py +55 -95
  23. certora_cli/certoraSolanaProver.py +1 -1
  24. certora_cli/certoraSorobanProver.py +1 -1
  25. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/METADATA +3 -3
  26. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/RECORD +33 -30
  27. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/entry_points.txt +1 -0
  28. certora_jars/ASTExtraction.jar +0 -0
  29. certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
  30. certora_jars/Typechecker.jar +0 -0
  31. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/LICENSE +0 -0
  32. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/WHEEL +0 -0
  33. {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/top_level.txt +0 -0
@@ -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 []
@@ -114,7 +114,8 @@ EVM_SOURCE_EXTENSIONS = (SOL_EXT, VY_EXT, YUL_EXT)
114
114
  EVM_EXTENSIONS = EVM_SOURCE_EXTENSIONS + ('.tac', '.json')
115
115
  SOLANA_EXEC_EXTENSION = '.so'
116
116
  SOROBAN_EXEC_EXTENSION = '.wasm'
117
- VALID_FILE_EXTENSIONS = ['.conf'] + list(EVM_EXTENSIONS) + [SOLANA_EXEC_EXTENSION, SOROBAN_EXEC_EXTENSION]
117
+ VALID_EVM_EXTENSIONS = list(EVM_EXTENSIONS) + ['.conf']
118
+ VALID_FILE_EXTENSIONS = VALID_EVM_EXTENSIONS + [SOLANA_EXEC_EXTENSION, SOROBAN_EXEC_EXTENSION]
118
119
  # Type alias definition, not a variable
119
120
  CompilerVersion = Tuple[int, int, int]
120
121
  MAP_SUFFIX = '_map'
@@ -368,11 +369,8 @@ def remove_file(file_path: Union[str, Path]) -> None: # TODO - accept only Path
368
369
  except OSError:
369
370
  pass
370
371
  else:
371
- try:
372
- # When we upgrade to Python 3.8, we can use unlink(missing_ok=True) and remove the try/except clauses
373
- file_path.unlink()
374
- except FileNotFoundError:
375
- pass
372
+ file_path.unlink(missing_ok=True)
373
+
376
374
 
377
375
  def abs_norm_path(file_path: Union[str, Path]) -> Path:
378
376
  """
@@ -959,18 +957,6 @@ def flatten_set_list(set_list: List[Set[Any]]) -> List[Any]:
959
957
  return list(ret_set)
960
958
 
961
959
 
962
- def is_relative_to(path1: Path, path2: Path) -> bool:
963
- """certora-cli currently requires python3.8 and it's the last version without support for is_relative_to.
964
- Shamelessly copying.
965
- """
966
- # return path1.is_relative_to(path2)
967
- try:
968
- path1.relative_to(path2)
969
- return True
970
- except ValueError:
971
- return False
972
-
973
-
974
960
  def find_jar(jar_name: str) -> Path:
975
961
  # if we are a dev running certoraRun.py (local version), we want to get the local jar
976
962
  # if we are a dev running an installed version of certoraRun, we want to get the installed jar
@@ -982,7 +968,7 @@ def find_jar(jar_name: str) -> Path:
982
968
 
983
969
  if certora_home != "":
984
970
  local_certora_path = Path(certora_home) / CERTORA_JARS / jar_name
985
- if is_relative_to(Path(__file__), Path(certora_home)) and local_certora_path.is_file():
971
+ if Path(__file__).is_relative_to(Path(certora_home)) and local_certora_path.is_file():
986
972
  return local_certora_path
987
973
 
988
974
  return get_package_resource(CERTORA_JARS / jar_name)
@@ -997,31 +983,46 @@ def get_package_resource(resource: Path) -> Path:
997
983
  return Path(__file__).parents[2] / resource
998
984
 
999
985
 
1000
- def is_java_installed() -> bool:
986
+ def get_java_version() -> str:
1001
987
  """
1002
- Check that java is installed and with a version that is suitable for running certora jars
1003
- @return True on success
988
+ Retrieve installed java version
989
+ @return installed java version on success or empty string
1004
990
  """
1005
991
  # Check if java exists on the machine
1006
992
  java = which("java")
1007
993
  if java is None:
994
+ return ''
995
+
996
+ try:
997
+ java_version_str = subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT).decode()
998
+ java_version = re.search(r'version \"([\d\.]+)\"', java_version_str).groups()[0] # type: ignore[union-attr]
999
+
1000
+ return java_version
1001
+ except (subprocess.CalledProcessError, AttributeError):
1002
+ typecheck_logger.debug("Couldn't find the installed Java version.")
1003
+ return ''
1004
+
1005
+
1006
+ def is_java_installed(java_version: str) -> bool:
1007
+ """
1008
+ Check that java is installed and with a version that is suitable for running certora jars
1009
+ @return True on success
1010
+ """
1011
+ if not java_version:
1008
1012
  typecheck_logger.warning(
1009
1013
  f"`java` is not installed. Installing Java version {MIN_JAVA_VERSION} or later will enable faster "
1010
1014
  f"CVL specification syntax checking before uploading to the cloud.")
1011
1015
  return False
1012
1016
 
1013
- try:
1014
- java_version_str = subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT).decode()
1015
- major_java_version = re.search(r'version \"(\d+).*', java_version_str).groups()[0] # type: ignore[union-attr]
1017
+ else:
1018
+ major_java_version = java_version.split('.')[0]
1016
1019
  if int(major_java_version) < MIN_JAVA_VERSION:
1017
1020
  typecheck_logger.warning("Installed Java version is too old to check CVL specification files locally. "
1018
1021
  f" Java version should be at least {MIN_JAVA_VERSION} to allow local java-based "
1019
1022
  "type checking")
1020
1023
 
1021
1024
  return False
1022
- except (subprocess.CalledProcessError, AttributeError):
1023
- typecheck_logger.warning("Couldn't find the installed Java version.")
1024
- return False
1025
+
1025
1026
  return True
1026
1027
 
1027
1028
 
@@ -91,7 +91,7 @@ class RunSources(Util.NoValEnum):
91
91
  MUTATION = auto()
92
92
  BENCHMARK = auto()
93
93
  LIGHT_TEST = auto()
94
- RERUN = auto()
94
+ REPORT = auto()
95
95
 
96
96
 
97
97
  class WaitForResultOptions(Util.NoValEnum):
@@ -99,6 +99,11 @@ class WaitForResultOptions(Util.NoValEnum):
99
99
  ALL = auto()
100
100
 
101
101
 
102
+ class UrlVisibilityOptions(Util.NoValEnum):
103
+ PRIVATE = auto()
104
+ PUBLIC = auto()
105
+
106
+
102
107
  def is_solc_file_valid(orig_filename: Optional[str]) -> str:
103
108
  """
104
109
  Verifies that a given --solc argument is valid:
@@ -124,7 +129,7 @@ def is_solc_file_valid(orig_filename: Optional[str]) -> str:
124
129
  if filename.endswith(f".{suffix}"):
125
130
  raise Util.CertoraUserInputError(f"wrong Solidity executable given: {filename}")
126
131
 
127
- # see https://docs.python.org/3.8/library/shutil.html#shutil.which. We use no mask to give a precise error
132
+ # see https://docs.python.org/3.9/library/shutil.html#shutil.which. We use no mask to give a precise error
128
133
  solc_location = shutil.which(filename, os.F_OK)
129
134
  if solc_location is not None:
130
135
  solc_path = Path(solc_location)
@@ -425,7 +430,7 @@ def validate_contract_extension_attr(map: Any) -> Dict[str, List[Dict[str, Any]]
425
430
  return map
426
431
 
427
432
 
428
- def validate_input_file(file: str) -> str:
433
+ def validate_evm_input_file(file: str) -> str:
429
434
  # [file[:contractName] ...] or CONF_FILE.conf or TAC_FILE.tac
430
435
 
431
436
  if Util.SOL_EXT in file:
@@ -469,12 +474,12 @@ def validate_input_file(file: str) -> str:
469
474
  except Exception as e:
470
475
  raise Util.CertoraUserInputError(f"Cannot access file {file} : {e}")
471
476
  return file
472
- elif any(file.endswith(ext) for ext in Util.VALID_FILE_EXTENSIONS):
477
+ elif any(file.endswith(ext) for ext in Util.VALID_EVM_EXTENSIONS):
473
478
  validate_readable_file(file)
474
479
  return file
475
480
 
476
481
  raise Util.CertoraUserInputError(
477
- f"input file {file} is not in one of the supported types ({Util.VALID_FILE_EXTENSIONS})")
482
+ f"input file {file} is not in one of the supported types ({Util.VALID_EVM_EXTENSIONS})")
478
483
 
479
484
 
480
485
  def validate_json_file(file: str) -> str:
@@ -545,13 +550,6 @@ def validate_struct_link(link: str) -> str:
545
550
  return link
546
551
 
547
552
 
548
- def validate_assert_contracts(contract: str) -> str:
549
- if not re.match(Util.SOLIDITY_ID_STRING_RE, contract):
550
- raise Util.CertoraUserInputError(
551
- f"Contract name {contract} can include only alphanumeric characters, dollar signs or underscores")
552
- return contract
553
-
554
-
555
553
  def validate_equivalence_contracts(equiv_string: str) -> str:
556
554
  if not re.match(f'^{Util.SOLIDITY_ID_SUBSTRING_RE}={Util.SOLIDITY_ID_SUBSTRING_RE}$', equiv_string):
557
555
  raise Util.CertoraUserInputError(
@@ -760,6 +758,16 @@ def validate_git_hash(git_hash: str) -> str:
760
758
  raise Util.CertoraUserInputError("Git hash must consist of between 1 and 40 characters")
761
759
  return git_hash
762
760
 
761
+ def validate_check_method_flag(method: str) -> str:
762
+ if '.' in method:
763
+ raise Util.CertoraUserInputError(f"Malformed `check_mathod` argument '{method}': checked method cannot contain a dot. Use only the method name without the contract prefix."
764
+ "the contract part is not allowed in `--check_method`")
765
+ if ' ' in method:
766
+ raise Util.CertoraUserInputError(f"Malformed method '{method}' in `--check_method`: remove all whitespace")
767
+
768
+ if not __validate_matching_parens(method):
769
+ raise Util.CertoraUserInputError(f"Malformed method '{method}' in `--check_method`: unmatched parenthesis")
770
+ return method
763
771
 
764
772
  def validate_method_flag(method: str) -> str:
765
773
  contract_and_method = method.split('.')
@@ -995,6 +1003,10 @@ def validate_wait_for_results(value: str) -> str:
995
1003
  return __validate_enum_value(value, WaitForResultOptions)
996
1004
 
997
1005
 
1006
+ def validate_url_visibility(value: str) -> str:
1007
+ return __validate_enum_value(value, UrlVisibilityOptions)
1008
+
1009
+
998
1010
  def validate_json5_file(file: str) -> str:
999
1011
  file_exists_and_readable(file)
1000
1012
 
@@ -156,11 +156,13 @@ def ensure_version_compatibility(context: CertoraContext) -> None:
156
156
  """
157
157
  validate_version_and_branch(context)
158
158
 
159
+
159
160
  # --------------------------------------------------------------------------- #
160
161
  # Verification helpers
161
162
  # --------------------------------------------------------------------------- #
162
163
 
163
- def run_local(context: CertoraContext, timings: Dict, additional_commands: Optional[List[str]] = None, compare_with_expected_file: bool = False) -> int:
164
+ def run_local(context: CertoraContext, timings: Dict, additional_commands: Optional[List[str]] = None,
165
+ compare_with_expected_file: bool = False) -> int:
164
166
  """
165
167
  Run the verifier locally and return its exit code (0 = success).
166
168
  Args:
@@ -187,7 +189,7 @@ def run_local(context: CertoraContext, timings: Dict, additional_commands: Optio
187
189
 
188
190
  if rc == 0:
189
191
  Util.print_completion_message("Finished running verifier:")
190
- print("\t%s", " ".join(cmd))
192
+ print(f'\t{" ".join(cmd)}')
191
193
  timings.setdefault("buildTime", 0.0) # ensure key exists
192
194
  return 0
193
195
 
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env python3
2
+ # The Certora Prover
3
+ # Copyright (C) 2025 Certora Ltd.
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, version 3 of the License.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+
18
+ import os
19
+ import sys
20
+ import argparse
21
+ import subprocess
22
+ from pathlib import Path
23
+
24
+ scripts_dir_path = Path(__file__).parent.resolve() # containing directory
25
+ sys.path.insert(0, str(scripts_dir_path))
26
+
27
+ from Shared import certoraUtils as Util
28
+ from pathlib import Path
29
+
30
+ FORMATTER_JAR = "ASTExtraction.jar"
31
+
32
+ scripts_dir_path = Path(__file__).parent.resolve()
33
+ sys.path.insert(0, str(scripts_dir_path))
34
+
35
+ def spec_file_type(spec_file: str) -> str:
36
+ if not os.path.isfile(spec_file):
37
+ raise argparse.ArgumentTypeError(f"File {spec_file} does not exist")
38
+ return spec_file
39
+
40
+ def run_formatter_from_jar(spec_file: str, overwrite: bool) -> None:
41
+ path_to_typechecker = Util.find_jar(FORMATTER_JAR)
42
+ cmd = ['java', '-jar', str(path_to_typechecker), 'format', '--file', spec_file]
43
+
44
+ result = subprocess.run(cmd, text=True, capture_output=True)
45
+
46
+ if result.returncode != 0:
47
+ raise Util.CertoraUserInputError(f"Error running formatter on {spec_file}: {result.stderr.strip() if result.stderr else ''}")
48
+
49
+ if overwrite:
50
+ with open(spec_file, 'w', encoding='utf-8') as f:
51
+ f.write(result.stdout)
52
+ else:
53
+ print(result.stdout, end='', file=sys.stdout)
54
+
55
+ def parse_args() -> tuple[str, bool]:
56
+ parser = argparse.ArgumentParser()
57
+ parser.add_argument('spec_file', type=spec_file_type, help='Path to the .spec file')
58
+ parser.add_argument('-w', '--overwrite', action='store_true', help='If set, output is written to the spec file instead of stdout')
59
+
60
+ args = parser.parse_args()
61
+ return args.spec_file, args.overwrite
62
+
63
+
64
+ def check_java_version() -> None:
65
+ if not (Util.is_java_installed(Util.get_java_version())):
66
+ raise Util.CertoraUserInputError(f"Java {Util.MIN_JAVA_VERSION} or higher is required to run the formatter. "
67
+ f"Please install Java and try again.")
68
+
69
+
70
+ def entry_point() -> None:
71
+ spec_file, overwrite = parse_args()
72
+ check_java_version()
73
+ run_formatter_from_jar(spec_file, overwrite)
74
+
75
+ if __name__ == '__main__':
76
+ entry_point()
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env python3
2
+ # The Certora Prover
3
+ # Copyright (C) 2025 Certora Ltd.
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, version 3 of the License.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
16
+
17
+
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ scripts_dir_path = Path(__file__).parent.resolve() # containing directory
22
+ sys.path.insert(0, str(scripts_dir_path))
23
+
24
+ import CertoraProver.certoraContextAttributes as Attrs
25
+ from certoraRun import run_certora
26
+ from Shared.proverCommon import CertoraRunResult, catch_exits
27
+
28
+ from typing import List, Optional
29
+
30
+
31
+ def run_concord(args: List[str]) -> Optional[CertoraRunResult]:
32
+ return run_certora(args, Attrs.ConcordAttributes, prover_cmd=sys.argv[0])
33
+
34
+ @catch_exits
35
+ def entry_point() -> None:
36
+ run_concord(sys.argv[1:])
37
+
38
+ if __name__ == '__main__':
39
+ entry_point()