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.
- certora_cli/CertoraProver/certoraBuild.py +19 -18
- certora_cli/CertoraProver/certoraBuildCacheManager.py +2 -0
- certora_cli/CertoraProver/certoraBuildRust.py +31 -20
- certora_cli/{Shared/rustProverCommon.py → CertoraProver/certoraBuildSui.py} +24 -18
- certora_cli/CertoraProver/certoraCloudIO.py +37 -8
- certora_cli/CertoraProver/certoraCollectRunMetadata.py +20 -5
- certora_cli/CertoraProver/certoraConfigIO.py +15 -13
- certora_cli/CertoraProver/certoraContext.py +33 -8
- certora_cli/CertoraProver/certoraContextAttributes.py +113 -195
- certora_cli/CertoraProver/certoraContextValidator.py +69 -40
- certora_cli/CertoraProver/certoraVerifyGenerator.py +9 -4
- certora_cli/CertoraProver/splitRules.py +2 -0
- certora_cli/CertoraProver/storageExtension.py +0 -35
- certora_cli/Mutate/mutateApp.py +16 -10
- certora_cli/Mutate/mutateAttributes.py +11 -0
- certora_cli/Shared/certoraAttrUtil.py +11 -5
- certora_cli/Shared/certoraUtils.py +29 -28
- certora_cli/Shared/certoraValidateFuncs.py +24 -12
- certora_cli/Shared/proverCommon.py +4 -2
- certora_cli/certoraCVLFormatter.py +76 -0
- certora_cli/certoraConcord.py +39 -0
- certora_cli/certoraRun.py +55 -95
- certora_cli/certoraSolanaProver.py +1 -1
- certora_cli/certoraSorobanProver.py +1 -1
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/METADATA +3 -3
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/RECORD +33 -30
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/entry_points.txt +1 -0
- certora_jars/ASTExtraction.jar +0 -0
- certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
- certora_jars/Typechecker.jar +0 -0
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/LICENSE +0 -0
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/WHEEL +0 -0
- {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}")
|
certora_cli/Mutate/mutateApp.py
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
885
|
-
Util.restore_backup(backup_path)
|
|
886
|
-
self.
|
|
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.
|
|
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)
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
self.
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
|
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
|
|
986
|
+
def get_java_version() -> str:
|
|
1001
987
|
"""
|
|
1002
|
-
|
|
1003
|
-
@return
|
|
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
|
-
|
|
1014
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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,
|
|
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(
|
|
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()
|