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
@@ -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
 
@@ -118,8 +118,8 @@ class MutateValidator:
118
118
 
119
119
  if value is None:
120
120
  raise RuntimeError(f"calling validate_type_string with null value {key}")
121
- if not isinstance(value, str) and not isinstance(value, Path):
122
- raise Util.CertoraUserInputError(f"value of {key} {value} is not a string")
121
+ if not isinstance(value, (str, Path, int)):
122
+ raise Util.CertoraUserInputError(f"value of {key} {value} is not a string, integer, or Path")
123
123
  attr.validate_value(str(value))
124
124
 
125
125
  def validate_type_any(self, attr: AttrUtil.AttributeDefinition) -> None:
@@ -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 []
@@ -16,6 +16,7 @@
16
16
  import csv
17
17
  import json
18
18
  import os
19
+ import io
19
20
  import subprocess
20
21
  from abc import ABCMeta
21
22
  from enum import Enum, unique, auto
@@ -30,8 +31,9 @@ import urllib3.util
30
31
  from collections import defaultdict
31
32
  from types import SimpleNamespace
32
33
 
33
- from typing import Any, Callable, Dict, List, Optional, Set, Union, Generator, Tuple, Iterable, Sequence, TypeVar
34
+ from typing import Any, Callable, Dict, List, Optional, Set, Union, Generator, Tuple, Iterable, Sequence, TypeVar, OrderedDict
34
35
  from pathlib import Path
36
+ import json5
35
37
 
36
38
  scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
37
39
  sys.path.insert(0, str(scripts_dir_path))
@@ -90,6 +92,7 @@ CERTORA_RUN_SCRIPT = "certoraRun.py"
90
92
  CERTORA_RUN_APP = "certoraRun"
91
93
  PACKAGE_FILE = Path("package.json")
92
94
  REMAPPINGS_FILE = Path("remappings.txt")
95
+ FOUNDRY_TOML_FILE = Path("foundry.toml")
93
96
  RECENT_JOBS_FILE = Path(".certora_recent_jobs.json")
94
97
  LAST_CONF_FILE = Path("run.conf")
95
98
  EMV_JAR = Path("emv.jar")
@@ -114,7 +117,8 @@ EVM_SOURCE_EXTENSIONS = (SOL_EXT, VY_EXT, YUL_EXT)
114
117
  EVM_EXTENSIONS = EVM_SOURCE_EXTENSIONS + ('.tac', '.json')
115
118
  SOLANA_EXEC_EXTENSION = '.so'
116
119
  SOROBAN_EXEC_EXTENSION = '.wasm'
117
- VALID_FILE_EXTENSIONS = ['.conf'] + list(EVM_EXTENSIONS) + [SOLANA_EXEC_EXTENSION, SOROBAN_EXEC_EXTENSION]
120
+ VALID_EVM_EXTENSIONS = list(EVM_EXTENSIONS) + ['.conf']
121
+ VALID_FILE_EXTENSIONS = VALID_EVM_EXTENSIONS + [SOLANA_EXEC_EXTENSION, SOROBAN_EXEC_EXTENSION]
118
122
  # Type alias definition, not a variable
119
123
  CompilerVersion = Tuple[int, int, int]
120
124
  MAP_SUFFIX = '_map'
@@ -368,11 +372,8 @@ def remove_file(file_path: Union[str, Path]) -> None: # TODO - accept only Path
368
372
  except OSError:
369
373
  pass
370
374
  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
375
+ file_path.unlink(missing_ok=True)
376
+
376
377
 
377
378
  def abs_norm_path(file_path: Union[str, Path]) -> Path:
378
379
  """
@@ -959,18 +960,6 @@ def flatten_set_list(set_list: List[Set[Any]]) -> List[Any]:
959
960
  return list(ret_set)
960
961
 
961
962
 
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
963
  def find_jar(jar_name: str) -> Path:
975
964
  # if we are a dev running certoraRun.py (local version), we want to get the local jar
976
965
  # if we are a dev running an installed version of certoraRun, we want to get the installed jar
@@ -982,7 +971,7 @@ def find_jar(jar_name: str) -> Path:
982
971
 
983
972
  if certora_home != "":
984
973
  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():
974
+ if Path(__file__).is_relative_to(Path(certora_home)) and local_certora_path.is_file():
986
975
  return local_certora_path
987
976
 
988
977
  return get_package_resource(CERTORA_JARS / jar_name)
@@ -997,31 +986,46 @@ def get_package_resource(resource: Path) -> Path:
997
986
  return Path(__file__).parents[2] / resource
998
987
 
999
988
 
1000
- def is_java_installed() -> bool:
989
+ def get_java_version() -> str:
1001
990
  """
1002
- Check that java is installed and with a version that is suitable for running certora jars
1003
- @return True on success
991
+ Retrieve installed java version
992
+ @return installed java version on success or empty string
1004
993
  """
1005
994
  # Check if java exists on the machine
1006
995
  java = which("java")
1007
996
  if java is None:
997
+ return ''
998
+
999
+ try:
1000
+ java_version_str = subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT).decode()
1001
+ java_version = re.search(r'version \"([\d\.]+)\"', java_version_str).groups()[0] # type: ignore[union-attr]
1002
+
1003
+ return java_version
1004
+ except (subprocess.CalledProcessError, AttributeError):
1005
+ typecheck_logger.debug("Couldn't find the installed Java version.")
1006
+ return ''
1007
+
1008
+
1009
+ def is_java_installed(java_version: str) -> bool:
1010
+ """
1011
+ Check that java is installed and with a version that is suitable for running certora jars
1012
+ @return True on success
1013
+ """
1014
+ if not java_version:
1008
1015
  typecheck_logger.warning(
1009
1016
  f"`java` is not installed. Installing Java version {MIN_JAVA_VERSION} or later will enable faster "
1010
1017
  f"CVL specification syntax checking before uploading to the cloud.")
1011
1018
  return False
1012
1019
 
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]
1020
+ else:
1021
+ major_java_version = java_version.split('.')[0]
1016
1022
  if int(major_java_version) < MIN_JAVA_VERSION:
1017
1023
  typecheck_logger.warning("Installed Java version is too old to check CVL specification files locally. "
1018
1024
  f" Java version should be at least {MIN_JAVA_VERSION} to allow local java-based "
1019
1025
  "type checking")
1020
1026
 
1021
1027
  return False
1022
- except (subprocess.CalledProcessError, AttributeError):
1023
- typecheck_logger.warning("Couldn't find the installed Java version.")
1024
- return False
1028
+
1025
1029
  return True
1026
1030
 
1027
1031
 
@@ -1349,25 +1353,49 @@ def is_local(object: Any) -> bool:
1349
1353
  default_jar_path = Path(certora_root_dir) / EMV_JAR
1350
1354
  return getattr(object, 'jar', None) or (default_jar_path.is_file() and not getattr(object, 'server', None))
1351
1355
 
1356
+ def get_mappings_from_forge_remappings() -> List[str]:
1357
+ remappings_output = ""
1358
+ try:
1359
+ result = subprocess.run(['forge', 'remappings'], capture_output=True, text=True, check=True)
1360
+ remappings_output = result.stdout
1361
+ except Exception as e:
1362
+ context_logger.debug(f"get_mappings_from_forge_remappings: forge failed to run\n{e}")
1363
+
1364
+ remappings = []
1365
+ if remappings_output:
1366
+ for line in remappings_output.strip().split('\n'):
1367
+ if '=' in line:
1368
+ remappings.append(line.strip())
1369
+
1370
+ return remappings
1371
+
1352
1372
 
1353
1373
  def handle_remappings_file(context: SimpleNamespace) -> List[str]:
1354
1374
  """"
1355
- Tries to reach packages from remappings.txt
1375
+ Tries to reach packages from remappings.txt.
1376
+ If the file exists in cwd and foundry.toml does not exist in cwd we return the mappings in
1377
+ the file (legacy implementation).
1378
+ In all other cases we add the remappings returned from running the "forge remappings" command. Forge remappings
1379
+ takes into consideration mappings in remappings.txt but also mappings in foundry.toml and mappings from auto scan
1356
1380
  :return:
1357
1381
  """
1358
- if REMAPPINGS_FILE.exists():
1382
+ remappings = []
1383
+ if REMAPPINGS_FILE.exists() and not FOUNDRY_TOML_FILE.exists():
1359
1384
  try:
1360
1385
  with REMAPPINGS_FILE.open() as remappings_file:
1361
1386
  remappings = list(filter(lambda x: x != "", map(lambda x: x.strip(), remappings_file.readlines())))
1362
1387
  keys = [s.split('=')[0] for s in remappings]
1363
1388
  if len(set(keys)) < len(keys):
1364
1389
  raise CertoraUserInputError(f"remappings.txt includes duplicated keys in: {keys}")
1365
- return remappings
1366
1390
  except CertoraUserInputError as e:
1367
1391
  raise e from None
1368
1392
  except Exception as e:
1393
+ # create CertoraUserInputError from other exceptions
1369
1394
  raise CertoraUserInputError(f"Invalid remappings file: {REMAPPINGS_FILE}", e)
1370
- return []
1395
+ elif find_nearest_foundry_toml():
1396
+ remappings = get_mappings_from_forge_remappings()
1397
+
1398
+ return remappings
1371
1399
 
1372
1400
 
1373
1401
  def get_ir_flag(solc: str) -> str:
@@ -1436,14 +1464,33 @@ def eq_by(f: Callable[[T, T], bool], a: Sequence[T], b: Sequence[T]) -> bool:
1436
1464
  """
1437
1465
  return len(a) == len(b) and all(map(f, a, b))
1438
1466
 
1439
- def find_nearest_cargo_toml() -> Optional[Path]:
1467
+
1468
+ def find_file_in_parents(file_name: Union[Path, str]) -> Optional[Path]:
1469
+ """
1470
+ find file_name in current directory or in one of its parent directories
1471
+ """
1440
1472
  current = Path.cwd()
1441
1473
  for parent in [current] + list(current.parents):
1442
- candidate = parent / CARGO_TOML_FILE
1474
+ candidate = parent / str(file_name)
1443
1475
  if candidate.is_file():
1444
1476
  return candidate
1445
1477
  return None
1446
1478
 
1479
+ def find_nearest_cargo_toml() -> Optional[Path]:
1480
+ """
1481
+ Find the nearest Cargo.toml file in the current directory or its parent directories.
1482
+ Returns the path to the Cargo.toml file if found, otherwise returns None.
1483
+ """
1484
+ return find_file_in_parents(CARGO_TOML_FILE)
1485
+
1486
+ def find_nearest_foundry_toml() -> Optional[Path]:
1487
+ """
1488
+ Find the nearest foundry.toml file in the current directory or its parent directories.
1489
+ Returns the path to the foundry.toml file if found, otherwise returns None.
1490
+ """
1491
+ return find_file_in_parents(FOUNDRY_TOML_FILE)
1492
+
1493
+
1447
1494
  def file_in_source_tree(file_path: Path) -> bool:
1448
1495
  # if the file is under .certora_source, return True
1449
1496
  file_path = Path(file_path).absolute()
@@ -1454,3 +1501,20 @@ def file_in_source_tree(file_path: Path) -> bool:
1454
1501
  return True
1455
1502
  except ValueError:
1456
1503
  return False
1504
+
1505
+ def parse_int_as_str(val: str) -> str:
1506
+ return str(val)
1507
+
1508
+ def read_conf_file(file: io.TextIOWrapper) -> OrderedDict:
1509
+ res = json5.load(file, allow_duplicate_keys=False, object_pairs_hook=OrderedDict, parse_int=parse_int_as_str)
1510
+ return res
1511
+
1512
+ def convert_str_ints(obj: Union[dict, list, str, int]) -> Union[dict, list, str, int]:
1513
+ if isinstance(obj, dict):
1514
+ return {k: convert_str_ints(v) for k, v in obj.items()}
1515
+ elif isinstance(obj, list):
1516
+ return [convert_str_ints(v) for v in obj]
1517
+ elif isinstance(obj, str) and re.fullmatch(r"-?\d+", obj):
1518
+ return int(obj)
1519
+ else:
1520
+ return obj
@@ -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
 
@@ -36,7 +36,6 @@ sys.path.insert(0, str(scripts_dir_path))
36
36
  from CertoraProver.certoraCloudIO import validate_version_and_branch
37
37
  from Shared.certoraLogging import LoggingManager
38
38
  from Shared import certoraUtils as Util
39
- from Shared.certoraAttrUtil import Attributes
40
39
  import CertoraProver.certoraContext as Ctx
41
40
  from CertoraProver.certoraContextClass import CertoraContext
42
41
  import CertoraProver.certoraContextAttributes as Attrs
@@ -44,6 +43,7 @@ from CertoraProver.certoraCollectRunMetadata import collect_run_metadata
44
43
  from CertoraProver.certoraCollectConfigurationLayout import collect_configuration_layout
45
44
  from CertoraProver import certoraContextValidator as Cv
46
45
  from CertoraProver.certoraCloudIO import CloudVerification
46
+ import CertoraProver.certoraApp as App
47
47
 
48
48
  log = logging.getLogger(__name__)
49
49
 
@@ -65,7 +65,7 @@ class CertoraFoundViolations(Exception):
65
65
  # --------------------------------------------------------------------------- #
66
66
  # Setup Environment
67
67
  # --------------------------------------------------------------------------- #
68
- def build_context(args: List[str], attributes: Type[Attributes]) -> Tuple[CertoraContext, LoggingManager]:
68
+ def build_context(args: List[str], app: Type[App.CertoraApp]) -> Tuple[CertoraContext, LoggingManager]:
69
69
  """
70
70
  Build the context for the Certora Prover.
71
71
  This function is responsible for setting up the context and logging manager
@@ -76,11 +76,11 @@ def build_context(args: List[str], attributes: Type[Attributes]) -> Tuple[Certor
76
76
 
77
77
  Args:
78
78
  args: The command line arguments to parse.
79
- attributes: The attributes class to use for the context. (e.g., SolanaProverAttributes or SorobanProverAttributes)
79
+ app: The application
80
80
  Returns:
81
81
  A tuple containing the CertoraContext object and the LoggingManager object.
82
82
  """
83
- Attrs.set_attribute_class(attributes)
83
+ Attrs.set_attribute_class(app.attr_class)
84
84
  non_str_els = [x for x in args if not isinstance(x, str)]
85
85
  if non_str_els:
86
86
  print(f"args for run_certora that are not strings: {non_str_els}")
@@ -98,8 +98,8 @@ def build_context(args: List[str], attributes: Type[Attributes]) -> Tuple[Certor
98
98
  Util.safe_create_dir(Util.get_build_dir())
99
99
  logging_manager = LoggingManager()
100
100
 
101
- Ctx.handle_flags_in_args(args)
102
- context = Ctx.get_args(args) # Parse arguments
101
+ Ctx.handle_flags_in_args(args, app)
102
+ context = Ctx.get_args(args, app) # Parse arguments
103
103
 
104
104
  assert logging_manager, "logging manager was not set"
105
105
  logging_manager.set_log_level_and_format(is_quiet=Ctx.is_minimal_cli_output(context),
@@ -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.certoraApp as App
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, App.ConcordApp, 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()
@@ -21,14 +21,14 @@ from pathlib import Path
21
21
  scripts_dir_path = Path(__file__).parent.resolve() # containing directory
22
22
  sys.path.insert(0, str(scripts_dir_path))
23
23
 
24
- import CertoraProver.certoraContextAttributes as Attrs
24
+ import CertoraProver.certoraApp as App
25
25
  from Shared.proverCommon import CertoraRunResult
26
26
  from certoraRun import run_certora
27
27
  from typing import List, Optional
28
28
 
29
29
 
30
30
  def run_evm_prover(args: List[str]) -> Optional[CertoraRunResult]:
31
- return run_certora(args, Attrs.EvmProverAttributes)
31
+ return run_certora(args, App.EvmApp)
32
32
 
33
33
  def entry_point() -> None:
34
34
  run_evm_prover(sys.argv[1:])
@@ -21,7 +21,7 @@ from pathlib import Path
21
21
  scripts_dir_path = Path(__file__).parent.resolve() # containing directory
22
22
  sys.path.insert(0, str(scripts_dir_path))
23
23
 
24
- import CertoraProver.certoraContextAttributes as Attrs
24
+ import CertoraProver.certoraApp as App
25
25
  from certoraRun import run_certora
26
26
  from Shared.proverCommon import CertoraRunResult, catch_exits
27
27
 
@@ -29,7 +29,7 @@ from typing import List, Optional
29
29
 
30
30
 
31
31
  def run_ranger(args: List[str]) -> Optional[CertoraRunResult]:
32
- return run_certora(args, Attrs.RangerAttributes, prover_cmd=sys.argv[0])
32
+ return run_certora(args, App.RangerApp, prover_cmd=sys.argv[0])
33
33
 
34
34
  @catch_exits
35
35
  def entry_point() -> None: