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.
- certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +1 -3
- certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +3 -0
- certora_cli/CertoraProver/certoraApp.py +49 -0
- certora_cli/CertoraProver/certoraBuild.py +197 -44
- certora_cli/CertoraProver/certoraBuildCacheManager.py +2 -0
- certora_cli/CertoraProver/certoraBuildDataClasses.py +3 -1
- certora_cli/CertoraProver/certoraBuildRust.py +32 -21
- certora_cli/{Shared/rustProverCommon.py → CertoraProver/certoraBuildSui.py} +24 -18
- certora_cli/CertoraProver/certoraCloudIO.py +37 -8
- certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +1 -1
- certora_cli/CertoraProver/certoraCollectRunMetadata.py +20 -5
- certora_cli/CertoraProver/certoraConfigIO.py +23 -22
- certora_cli/CertoraProver/certoraContext.py +77 -45
- certora_cli/CertoraProver/certoraContextAttributes.py +122 -195
- certora_cli/CertoraProver/certoraContextValidator.py +78 -49
- certora_cli/CertoraProver/certoraContractFuncs.py +6 -0
- certora_cli/CertoraProver/certoraType.py +10 -1
- certora_cli/CertoraProver/certoraVerifyGenerator.py +10 -4
- certora_cli/CertoraProver/splitRules.py +20 -17
- certora_cli/CertoraProver/storageExtension.py +0 -35
- certora_cli/EquivalenceCheck/equivCheck.py +2 -1
- certora_cli/Mutate/mutateApp.py +28 -16
- certora_cli/Mutate/mutateAttributes.py +11 -0
- certora_cli/Mutate/mutateValidate.py +2 -2
- certora_cli/Shared/certoraAttrUtil.py +11 -5
- certora_cli/Shared/certoraUtils.py +99 -35
- certora_cli/Shared/certoraValidateFuncs.py +24 -12
- certora_cli/Shared/proverCommon.py +10 -8
- certora_cli/certoraCVLFormatter.py +76 -0
- certora_cli/certoraConcord.py +39 -0
- certora_cli/certoraEVMProver.py +2 -2
- certora_cli/certoraRanger.py +2 -2
- certora_cli/certoraRun.py +58 -98
- certora_cli/certoraSolanaProver.py +3 -3
- certora_cli/certoraSorobanProver.py +3 -4
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/METADATA +4 -3
- certora_cli_beta_mirror-8.1.0.dist-info/RECORD +79 -0
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.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/RECORD +0 -75
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/LICENSE +0 -0
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/WHEEL +0 -0
- {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
|
|
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
|
-
|
|
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 []
|
|
@@ -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
|
-
|
|
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
|
-
|
|
372
|
-
|
|
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
|
|
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
|
|
989
|
+
def get_java_version() -> str:
|
|
1001
990
|
"""
|
|
1002
|
-
|
|
1003
|
-
@return
|
|
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
|
-
|
|
1014
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 /
|
|
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
|
-
|
|
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
|
|
|
@@ -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],
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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.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()
|
certora_cli/certoraEVMProver.py
CHANGED
|
@@ -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.
|
|
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,
|
|
31
|
+
return run_certora(args, App.EvmApp)
|
|
32
32
|
|
|
33
33
|
def entry_point() -> None:
|
|
34
34
|
run_evm_prover(sys.argv[1:])
|
certora_cli/certoraRanger.py
CHANGED
|
@@ -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.
|
|
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,
|
|
32
|
+
return run_certora(args, App.RangerApp, prover_cmd=sys.argv[0])
|
|
33
33
|
|
|
34
34
|
@catch_exits
|
|
35
35
|
def entry_point() -> None:
|