certora-cli-beta-mirror 7.28.0__py3-none-any.whl → 8.5.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 +10 -3
- certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +51 -16
- certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +3 -0
- certora_cli/CertoraProver/castingInstrumenter.py +192 -0
- certora_cli/CertoraProver/certoraApp.py +52 -0
- certora_cli/CertoraProver/certoraBuild.py +694 -207
- certora_cli/CertoraProver/certoraBuildCacheManager.py +21 -17
- certora_cli/CertoraProver/certoraBuildDataClasses.py +8 -2
- certora_cli/CertoraProver/certoraBuildRust.py +88 -54
- certora_cli/CertoraProver/certoraBuildSui.py +112 -0
- certora_cli/CertoraProver/certoraCloudIO.py +97 -96
- certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +230 -84
- certora_cli/CertoraProver/certoraCollectRunMetadata.py +52 -6
- certora_cli/CertoraProver/certoraCompilerParameters.py +11 -0
- certora_cli/CertoraProver/certoraConfigIO.py +43 -35
- certora_cli/CertoraProver/certoraContext.py +128 -54
- certora_cli/CertoraProver/certoraContextAttributes.py +415 -234
- certora_cli/CertoraProver/certoraContextValidator.py +152 -105
- certora_cli/CertoraProver/certoraContractFuncs.py +34 -1
- certora_cli/CertoraProver/certoraParseBuildScript.py +8 -10
- certora_cli/CertoraProver/certoraType.py +10 -1
- certora_cli/CertoraProver/certoraVerifyGenerator.py +22 -4
- certora_cli/CertoraProver/erc7201.py +45 -0
- certora_cli/CertoraProver/splitRules.py +23 -18
- certora_cli/CertoraProver/storageExtension.py +351 -0
- certora_cli/EquivalenceCheck/Eq_default.conf +0 -1
- certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -1
- certora_cli/EquivalenceCheck/equivCheck.py +2 -1
- certora_cli/Mutate/mutateApp.py +41 -22
- certora_cli/Mutate/mutateAttributes.py +11 -0
- certora_cli/Mutate/mutateValidate.py +42 -2
- certora_cli/Shared/certoraAttrUtil.py +21 -5
- certora_cli/Shared/certoraUtils.py +180 -60
- certora_cli/Shared/certoraValidateFuncs.py +68 -26
- certora_cli/Shared/proverCommon.py +308 -0
- certora_cli/certoraCVLFormatter.py +76 -0
- certora_cli/certoraConcord.py +39 -0
- certora_cli/certoraEVMProver.py +4 -3
- certora_cli/certoraRanger.py +39 -0
- certora_cli/certoraRun.py +83 -223
- certora_cli/certoraSolanaProver.py +40 -128
- certora_cli/certoraSorobanProver.py +59 -4
- certora_cli/certoraSuiProver.py +93 -0
- certora_cli_beta_mirror-8.5.0.dist-info/LICENSE +15 -0
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/METADATA +21 -5
- certora_cli_beta_mirror-8.5.0.dist-info/RECORD +81 -0
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/WHEEL +1 -1
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/entry_points.txt +3 -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.28.0.dist-info/LICENSE +0 -22
- certora_cli_beta_mirror-7.28.0.dist-info/RECORD +0 -70
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/top_level.txt +0 -0
|
@@ -18,6 +18,8 @@ import sys
|
|
|
18
18
|
import argparse
|
|
19
19
|
import json
|
|
20
20
|
import logging
|
|
21
|
+
from collections import OrderedDict
|
|
22
|
+
|
|
21
23
|
import json5
|
|
22
24
|
import re
|
|
23
25
|
import urllib3.util
|
|
@@ -89,7 +91,7 @@ class RunSources(Util.NoValEnum):
|
|
|
89
91
|
MUTATION = auto()
|
|
90
92
|
BENCHMARK = auto()
|
|
91
93
|
LIGHT_TEST = auto()
|
|
92
|
-
|
|
94
|
+
REPORT = auto()
|
|
93
95
|
|
|
94
96
|
|
|
95
97
|
class WaitForResultOptions(Util.NoValEnum):
|
|
@@ -97,6 +99,11 @@ class WaitForResultOptions(Util.NoValEnum):
|
|
|
97
99
|
ALL = auto()
|
|
98
100
|
|
|
99
101
|
|
|
102
|
+
class UrlVisibilityOptions(Util.NoValEnum):
|
|
103
|
+
PRIVATE = auto()
|
|
104
|
+
PUBLIC = auto()
|
|
105
|
+
|
|
106
|
+
|
|
100
107
|
def is_solc_file_valid(orig_filename: Optional[str]) -> str:
|
|
101
108
|
"""
|
|
102
109
|
Verifies that a given --solc argument is valid:
|
|
@@ -122,7 +129,7 @@ def is_solc_file_valid(orig_filename: Optional[str]) -> str:
|
|
|
122
129
|
if filename.endswith(f".{suffix}"):
|
|
123
130
|
raise Util.CertoraUserInputError(f"wrong Solidity executable given: {filename}")
|
|
124
131
|
|
|
125
|
-
# 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
|
|
126
133
|
solc_location = shutil.which(filename, os.F_OK)
|
|
127
134
|
if solc_location is not None:
|
|
128
135
|
solc_path = Path(solc_location)
|
|
@@ -423,7 +430,7 @@ def validate_contract_extension_attr(map: Any) -> Dict[str, List[Dict[str, Any]]
|
|
|
423
430
|
return map
|
|
424
431
|
|
|
425
432
|
|
|
426
|
-
def
|
|
433
|
+
def validate_evm_input_file(file: str) -> str:
|
|
427
434
|
# [file[:contractName] ...] or CONF_FILE.conf or TAC_FILE.tac
|
|
428
435
|
|
|
429
436
|
if Util.SOL_EXT in file:
|
|
@@ -467,12 +474,12 @@ def validate_input_file(file: str) -> str:
|
|
|
467
474
|
except Exception as e:
|
|
468
475
|
raise Util.CertoraUserInputError(f"Cannot access file {file} : {e}")
|
|
469
476
|
return file
|
|
470
|
-
elif any(file.endswith(ext) for ext in Util.
|
|
477
|
+
elif any(file.endswith(ext) for ext in Util.VALID_EVM_EXTENSIONS):
|
|
471
478
|
validate_readable_file(file)
|
|
472
479
|
return file
|
|
473
480
|
|
|
474
481
|
raise Util.CertoraUserInputError(
|
|
475
|
-
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})")
|
|
476
483
|
|
|
477
484
|
|
|
478
485
|
def validate_json_file(file: str) -> str:
|
|
@@ -543,12 +550,12 @@ def validate_struct_link(link: str) -> str:
|
|
|
543
550
|
return link
|
|
544
551
|
|
|
545
552
|
|
|
546
|
-
def
|
|
547
|
-
if not re.match(Util.
|
|
553
|
+
def validate_equivalence_contracts(equiv_string: str) -> str:
|
|
554
|
+
if not re.match(f'^{Util.SOLIDITY_ID_SUBSTRING_RE}={Util.SOLIDITY_ID_SUBSTRING_RE}$', equiv_string):
|
|
548
555
|
raise Util.CertoraUserInputError(
|
|
549
|
-
f"
|
|
550
|
-
|
|
551
|
-
|
|
556
|
+
f"Equivalence check must be ContractA=ContractB, got {equiv_string}"
|
|
557
|
+
)
|
|
558
|
+
return equiv_string
|
|
552
559
|
|
|
553
560
|
def validate_packages(package: str) -> str:
|
|
554
561
|
if not re.search("^[^=]+=[^=]+$", package):
|
|
@@ -660,14 +667,14 @@ def validate_address(value: str) -> str:
|
|
|
660
667
|
return value
|
|
661
668
|
|
|
662
669
|
|
|
663
|
-
def
|
|
670
|
+
def validate_boolean_map(args: Dict[str, bool], attr_name: str) -> None:
|
|
671
|
+
attr_map_name = attr_name + "_map"
|
|
664
672
|
if not isinstance(args, dict):
|
|
665
|
-
raise Util.CertoraUserInputError("'
|
|
666
|
-
f"(type was {type(args).__name__})")
|
|
673
|
+
raise Util.CertoraUserInputError(f"'{attr_map_name}' should be stored as a map (type was {type(args).__name__})")
|
|
667
674
|
|
|
668
675
|
for contract, value in args.items():
|
|
669
676
|
if not isinstance(value, bool):
|
|
670
|
-
raise Util.CertoraUserInputError(f"'
|
|
677
|
+
raise Util.CertoraUserInputError(f"'{attr_map_name}' should map {contract} to a boolean value, "
|
|
671
678
|
f"got ({value}, type: {type(value).__name__})")
|
|
672
679
|
|
|
673
680
|
values = args.values()
|
|
@@ -675,9 +682,15 @@ def validate_solc_via_ir_map(args: Dict[str, bool]) -> None:
|
|
|
675
682
|
|
|
676
683
|
if all(x == first for x in values):
|
|
677
684
|
if first:
|
|
678
|
-
validation_logger.warning("all
|
|
685
|
+
validation_logger.warning(f"all {attr_map_name} values are set to True '{attr_name}' can be used instead")
|
|
679
686
|
else:
|
|
680
|
-
validation_logger.warning("all
|
|
687
|
+
validation_logger.warning(f"all {attr_map_name} values are set to False, this flag/attribute can be omitted")
|
|
688
|
+
|
|
689
|
+
def validate_solc_via_ir_map(args: Dict[str, bool]) -> None:
|
|
690
|
+
validate_boolean_map(args, 'solc_via_ir')
|
|
691
|
+
|
|
692
|
+
def validate_vyper_venom_map(args: Dict[str, bool]) -> None:
|
|
693
|
+
validate_boolean_map(args, 'vyper_venom')
|
|
681
694
|
|
|
682
695
|
def validate_solc_evm_version_map(args: Dict[str, str]) -> None:
|
|
683
696
|
if not isinstance(args, dict):
|
|
@@ -751,8 +764,18 @@ def validate_git_hash(git_hash: str) -> str:
|
|
|
751
764
|
raise Util.CertoraUserInputError("Git hash must consist of between 1 and 40 characters")
|
|
752
765
|
return git_hash
|
|
753
766
|
|
|
767
|
+
def validate_check_method_flag(method: str) -> str:
|
|
768
|
+
if '.' in method:
|
|
769
|
+
raise Util.CertoraUserInputError(f"Malformed `check_mathod` argument '{method}': checked method cannot contain a dot. Use only the method name without the contract prefix."
|
|
770
|
+
"the contract part is not allowed in `--check_method`")
|
|
771
|
+
if ' ' in method:
|
|
772
|
+
raise Util.CertoraUserInputError(f"Malformed method '{method}' in `--check_method`: remove all whitespace")
|
|
773
|
+
|
|
774
|
+
if not __validate_matching_parens(method):
|
|
775
|
+
raise Util.CertoraUserInputError(f"Malformed method '{method}' in `--check_method`: unmatched parenthesis")
|
|
776
|
+
return method
|
|
754
777
|
|
|
755
|
-
def
|
|
778
|
+
def validate_evm_method_flag(method: str) -> str:
|
|
756
779
|
contract_and_method = method.split('.')
|
|
757
780
|
if len(contract_and_method) > 2:
|
|
758
781
|
raise Util.CertoraUserInputError(f"Malformed method '{method}' in `--method` list: a method should be of the form `[ContractName.]functionABISignature(...)`")
|
|
@@ -771,6 +794,8 @@ def validate_method_flag(method: str) -> str:
|
|
|
771
794
|
|
|
772
795
|
return method
|
|
773
796
|
|
|
797
|
+
def validate_move_method_flag(method: str) -> str:
|
|
798
|
+
return validate_move_function_name(method)
|
|
774
799
|
|
|
775
800
|
def __validate_matching_parens(s: str) -> bool:
|
|
776
801
|
stack = []
|
|
@@ -806,17 +831,30 @@ def __validate_solidity_id(string: str, object: str) -> str:
|
|
|
806
831
|
def validate_contract_name(contract_name: str) -> str:
|
|
807
832
|
return __validate_solidity_id(contract_name, "contract")
|
|
808
833
|
|
|
809
|
-
|
|
810
|
-
def validate_rule_name(rule_str: str) -> str:
|
|
811
|
-
if ("*" not in rule_str):
|
|
812
|
-
return __validate_solidity_id(rule_str, "rule")
|
|
813
|
-
|
|
814
|
-
# we have a rule pattern string
|
|
834
|
+
def validate_rule_pattern_string(rule_str: str) -> str:
|
|
815
835
|
if not re.match(r"^[a-zA-Z0-9_$*]+$", rule_str):
|
|
816
836
|
raise Util.CertoraUserInputError(f"invalid rule pattern \"{rule_str}\": rule patterns must contain only "
|
|
817
837
|
"letters, digits, dollar signs, underscores, or asterisks")
|
|
818
838
|
return rule_str
|
|
819
839
|
|
|
840
|
+
def validate_evm_rule_name(rule_str: str) -> str:
|
|
841
|
+
if ("*" in rule_str):
|
|
842
|
+
return validate_rule_pattern_string(rule_str)
|
|
843
|
+
else:
|
|
844
|
+
return __validate_solidity_id(rule_str, "rule")
|
|
845
|
+
|
|
846
|
+
def validate_move_function_name(name: str) -> str:
|
|
847
|
+
if not re.match(r"^(0x[0-9a-fA-F]+|([a-zA-Z_][a-zA-Z0-9_]*))::[a-zA-Z_][a-zA-Z0-9_]*::[a-zA-Z_][a-zA-Z0-9_]*$", name):
|
|
848
|
+
raise Util.CertoraUserInputError(f"invalid Move function name \"{name}\": must be a fully-qualified Move "
|
|
849
|
+
"function name")
|
|
850
|
+
return name
|
|
851
|
+
|
|
852
|
+
def validate_move_rule_name(rule_str: str) -> str:
|
|
853
|
+
if ("*" in rule_str):
|
|
854
|
+
return validate_rule_pattern_string(rule_str)
|
|
855
|
+
else:
|
|
856
|
+
return validate_move_function_name(rule_str)
|
|
857
|
+
|
|
820
858
|
|
|
821
859
|
MAX_MSG_LEN: int = 256
|
|
822
860
|
|
|
@@ -827,7 +865,7 @@ def validate_msg(msg: str) -> str:
|
|
|
827
865
|
msg = (msg[:MAX_MSG_LEN - 3] + "...")
|
|
828
866
|
validation_logger.warning(f"'msg' can't accept strings longer than {MAX_MSG_LEN} chars, string was truncated")
|
|
829
867
|
|
|
830
|
-
additional_chars = {'(', ' ', ',', '/', '[', "'", '-', '"', '_', ']', '.', ')', ':', '\\', '='}
|
|
868
|
+
additional_chars = {'(', ' ', ',', '/', '[', "'", '-', '"', '_', ']', '.', ')', ':', '\\', '=', '*', '$'}
|
|
831
869
|
valid_chars = set(string.ascii_letters) | set(string.digits) | additional_chars
|
|
832
870
|
invalid_chars = set(msg) - valid_chars
|
|
833
871
|
if len(invalid_chars) > 0:
|
|
@@ -931,7 +969,7 @@ def validate_on_off(value: str) -> str:
|
|
|
931
969
|
return __validate_enum_value(value, OnOffValue)
|
|
932
970
|
|
|
933
971
|
|
|
934
|
-
def
|
|
972
|
+
def parse_ordered_dict(conf_key: str, input_string: str, value_type: Type = str) -> OrderedDict[str, Union[str, bool]]:
|
|
935
973
|
"""
|
|
936
974
|
convert CLI flag value string of the form <key>=<value>,<key>=<value>,.. to a Dict.
|
|
937
975
|
Keys with different values raise an exception
|
|
@@ -964,7 +1002,7 @@ def parse_dict(conf_key: str, input_string: str, value_type: Type = str) -> Dict
|
|
|
964
1002
|
raise argparse.ArgumentTypeError(f"{conf_key} argument {input_string} is of wrong format. Must be of format:"
|
|
965
1003
|
f"<key>=<value>[,..]")
|
|
966
1004
|
|
|
967
|
-
return_dict =
|
|
1005
|
+
return_dict = OrderedDict() # type: OrderedDict[str, Union[str, bool]]
|
|
968
1006
|
|
|
969
1007
|
for match in input_string.split(','):
|
|
970
1008
|
key, value = match.split('=')
|
|
@@ -986,6 +1024,10 @@ def validate_wait_for_results(value: str) -> str:
|
|
|
986
1024
|
return __validate_enum_value(value, WaitForResultOptions)
|
|
987
1025
|
|
|
988
1026
|
|
|
1027
|
+
def validate_url_visibility(value: str) -> str:
|
|
1028
|
+
return __validate_enum_value(value, UrlVisibilityOptions)
|
|
1029
|
+
|
|
1030
|
+
|
|
989
1031
|
def validate_json5_file(file: str) -> str:
|
|
990
1032
|
file_exists_and_readable(file)
|
|
991
1033
|
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
# The Certora Prover
|
|
2
|
+
# Copyright (C) 2025 Certora Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, version 3 of the License.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
"""
|
|
17
|
+
Shared helpers for certoraRun entry points (Solana & Soroban & EVM).
|
|
18
|
+
|
|
19
|
+
Placing the context_build environment preparation logic and verification helpers in one module
|
|
20
|
+
reduces duplication from the dedicated entry scripts.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import sys
|
|
26
|
+
import logging
|
|
27
|
+
import functools
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
from dataclasses import dataclass
|
|
30
|
+
from rich.console import Console
|
|
31
|
+
from typing import List, Tuple, Type, Optional, Dict, NoReturn, Callable
|
|
32
|
+
from os import getenv
|
|
33
|
+
|
|
34
|
+
scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
|
|
35
|
+
sys.path.insert(0, str(scripts_dir_path))
|
|
36
|
+
|
|
37
|
+
from CertoraProver.certoraCloudIO import validate_version_and_branch
|
|
38
|
+
from Shared.certoraLogging import LoggingManager
|
|
39
|
+
from Shared import certoraUtils as Util
|
|
40
|
+
import CertoraProver.certoraContext as Ctx
|
|
41
|
+
from CertoraProver.certoraContextClass import CertoraContext
|
|
42
|
+
import CertoraProver.certoraContextAttributes as Attrs
|
|
43
|
+
from CertoraProver.certoraCollectRunMetadata import collect_run_metadata
|
|
44
|
+
from CertoraProver.certoraCollectConfigurationLayout import collect_configuration_layout
|
|
45
|
+
from CertoraProver import certoraContextValidator as Cv
|
|
46
|
+
from CertoraProver.certoraCloudIO import CloudVerification
|
|
47
|
+
import CertoraProver.certoraApp as App
|
|
48
|
+
|
|
49
|
+
log = logging.getLogger(__name__)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
VIOLATIONS_EXIT_CODE = 100
|
|
53
|
+
|
|
54
|
+
@dataclass
|
|
55
|
+
class CertoraRunResult:
|
|
56
|
+
link: Optional[str] # Path to emv_dir if running locally, or the link to the job status page
|
|
57
|
+
is_local_link: bool
|
|
58
|
+
src_dir: Path
|
|
59
|
+
rule_report_link: Optional[str]
|
|
60
|
+
|
|
61
|
+
class CertoraFoundViolations(Exception):
|
|
62
|
+
def __init__(self, message: str, results: Optional[CertoraRunResult] = None) -> None:
|
|
63
|
+
super().__init__(message)
|
|
64
|
+
self.results = results
|
|
65
|
+
|
|
66
|
+
# --------------------------------------------------------------------------- #
|
|
67
|
+
# Setup Environment
|
|
68
|
+
# --------------------------------------------------------------------------- #
|
|
69
|
+
def build_context(args: List[str], app: Type[App.CertoraApp]) -> Tuple[CertoraContext, LoggingManager]:
|
|
70
|
+
"""
|
|
71
|
+
Build the context for the Certora Prover.
|
|
72
|
+
This function is responsible for setting up the context and logging manager
|
|
73
|
+
for the Certora Prover. It handles the following tasks:
|
|
74
|
+
1. Setting up the logging manager
|
|
75
|
+
2. Parsing the command line arguments
|
|
76
|
+
3. Setting up the context
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
args: The command line arguments to parse.
|
|
80
|
+
app: The application
|
|
81
|
+
Returns:
|
|
82
|
+
A tuple containing the CertoraContext object and the LoggingManager object.
|
|
83
|
+
"""
|
|
84
|
+
Attrs.set_attribute_class(app.attr_class)
|
|
85
|
+
non_str_els = [x for x in args if not isinstance(x, str)]
|
|
86
|
+
if non_str_els:
|
|
87
|
+
print(f"args for run_certora that are not strings: {non_str_els}")
|
|
88
|
+
exit(1)
|
|
89
|
+
|
|
90
|
+
# If we are not in debug mode, we do not want to print the traceback in case of exceptions.
|
|
91
|
+
if '--debug' not in args: # We check manually, because we want no traceback in argument parsing exceptions
|
|
92
|
+
sys.tracebacklimit = 0
|
|
93
|
+
|
|
94
|
+
# creating the default internal dir, files may be copied to user defined build directory after
|
|
95
|
+
# parsing the input
|
|
96
|
+
|
|
97
|
+
if not ('--help' in args or '--version' in args):
|
|
98
|
+
Util.reset_certora_internal_dir()
|
|
99
|
+
Util.safe_create_dir(Util.get_build_dir())
|
|
100
|
+
logging_manager = LoggingManager()
|
|
101
|
+
|
|
102
|
+
Ctx.handle_flags_in_args(args, app)
|
|
103
|
+
context = Ctx.get_args(args, app) # Parse arguments
|
|
104
|
+
|
|
105
|
+
assert logging_manager, "logging manager was not set"
|
|
106
|
+
logging_manager.set_log_level_and_format(is_quiet=Ctx.is_minimal_cli_output(context),
|
|
107
|
+
debug=context.debug,
|
|
108
|
+
debug_topics=context.debug_topics,
|
|
109
|
+
show_debug_topics=context.show_debug_topics)
|
|
110
|
+
|
|
111
|
+
return context, logging_manager
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def collect_and_dump_metadata(context: CertoraContext) -> None:
|
|
115
|
+
"""
|
|
116
|
+
Collect and validate run metadata.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
context: The Certora context containing verification settings
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
Util.TestResultsReady: If this is a metadata test run
|
|
123
|
+
"""
|
|
124
|
+
metadata = collect_run_metadata(wd=Path.cwd(), raw_args=sys.argv, context=context)
|
|
125
|
+
|
|
126
|
+
if context.test == str(Util.TestValue.CHECK_METADATA):
|
|
127
|
+
raise Util.TestResultsReady(metadata)
|
|
128
|
+
|
|
129
|
+
metadata.dump()
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def collect_and_dump_config_layout(context: CertoraContext) -> None:
|
|
133
|
+
"""
|
|
134
|
+
Collect and dump the configuration layout.
|
|
135
|
+
|
|
136
|
+
Args:
|
|
137
|
+
context: The Certora context containing verification settings
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
Util.TestResultsReady: If this is a configuration layout test run
|
|
141
|
+
"""
|
|
142
|
+
configuration_layout = collect_configuration_layout()
|
|
143
|
+
|
|
144
|
+
if context.test == str(Util.TestValue.CHECK_CONFIG_LAYOUT):
|
|
145
|
+
raise Util.TestResultsReady(configuration_layout)
|
|
146
|
+
|
|
147
|
+
configuration_layout.dump()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def ensure_version_compatibility(context: CertoraContext) -> None:
|
|
151
|
+
if not (context.local or context.build_only or context.compilation_steps_only):
|
|
152
|
+
"""
|
|
153
|
+
Before running the local type checker, we see if the current package version is compatible with
|
|
154
|
+
the latest. We check it before running the local type checker, because local type checking
|
|
155
|
+
errors could be simply a result of syntax introduced in the newest version.
|
|
156
|
+
The line below will raise an exception if the local version is incompatible.
|
|
157
|
+
"""
|
|
158
|
+
validate_version_and_branch(context)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
# --------------------------------------------------------------------------- #
|
|
162
|
+
# Verification helpers
|
|
163
|
+
# --------------------------------------------------------------------------- #
|
|
164
|
+
|
|
165
|
+
def run_local(context: CertoraContext, timings: Dict, additional_commands: Optional[List[str]] = None,
|
|
166
|
+
compare_with_expected_file: bool = False) -> int:
|
|
167
|
+
"""
|
|
168
|
+
Run the verifier locally and return its exit code (0 = success).
|
|
169
|
+
Args:
|
|
170
|
+
context: The CertoraContext object containing the configuration.
|
|
171
|
+
timings: A dictionary to store timing information.
|
|
172
|
+
additional_commands: A list of additional commands to pass to the verifier.
|
|
173
|
+
Returns:
|
|
174
|
+
An integer representing the exit code of the verifier run.
|
|
175
|
+
"""
|
|
176
|
+
cmd: List[str] = Ctx.get_local_run_cmd(context)
|
|
177
|
+
|
|
178
|
+
if additional_commands:
|
|
179
|
+
cmd.extend(additional_commands)
|
|
180
|
+
|
|
181
|
+
print(f'Verifier run command:\n {" ".join(cmd)}')
|
|
182
|
+
if context.test == str(Util.TestValue.BEFORE_LOCAL_PROVER_CALL):
|
|
183
|
+
raise Util.TestResultsReady(' '.join(cmd))
|
|
184
|
+
rc = Util.run_jar_cmd(
|
|
185
|
+
cmd,
|
|
186
|
+
override_exit_code=compare_with_expected_file,
|
|
187
|
+
logger_topic="verification",
|
|
188
|
+
print_output=True,
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
if rc == 0:
|
|
192
|
+
Util.print_completion_message("Finished running verifier:")
|
|
193
|
+
print(f'\t{" ".join(cmd)}')
|
|
194
|
+
timings.setdefault("buildTime", 0.0) # ensure key exists
|
|
195
|
+
return 0
|
|
196
|
+
|
|
197
|
+
return 1
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def run_remote(
|
|
201
|
+
context: CertoraContext,
|
|
202
|
+
args: List[str],
|
|
203
|
+
timings: Dict,
|
|
204
|
+
) -> Tuple[int, Optional[CertoraRunResult]]:
|
|
205
|
+
"""
|
|
206
|
+
Run verification in Certora Cloud.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
context: The CertoraContext object containing the configuration.
|
|
210
|
+
args: The command line arguments to pass to the cloud verification.
|
|
211
|
+
timings: A dictionary to store timing information.
|
|
212
|
+
Returns:
|
|
213
|
+
A tuple containing the exit code (0 = success) and an optional CertoraRunResult object.
|
|
214
|
+
"""
|
|
215
|
+
if context.compilation_steps_only:
|
|
216
|
+
return 0, CertoraRunResult(None, False, Util.get_certora_sources_dir(), None)
|
|
217
|
+
|
|
218
|
+
context.key = Cv.validate_certora_key()
|
|
219
|
+
cloud = CloudVerification(context, timings)
|
|
220
|
+
|
|
221
|
+
pretty_args = [f"'{a}'" if " " in a else a for a in args]
|
|
222
|
+
|
|
223
|
+
ok = cloud.cli_verify_and_report(" ".join(pretty_args), context.wait_for_results)
|
|
224
|
+
|
|
225
|
+
exit_code = 0 if ok else VIOLATIONS_EXIT_CODE
|
|
226
|
+
result: Optional[CertoraRunResult] = None
|
|
227
|
+
if cloud.statusUrl:
|
|
228
|
+
result = CertoraRunResult(
|
|
229
|
+
cloud.statusUrl,
|
|
230
|
+
False,
|
|
231
|
+
Util.get_certora_sources_dir(),
|
|
232
|
+
cloud.reportUrl,
|
|
233
|
+
)
|
|
234
|
+
return exit_code, result
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def handle_exit(exit_code: int, return_value: Optional[CertoraRunResult]) -> Optional[CertoraRunResult]:
|
|
238
|
+
"""
|
|
239
|
+
Handle the exit code of the verification run.
|
|
240
|
+
Args:
|
|
241
|
+
exit_code: The exit code of the verification run.
|
|
242
|
+
return_value: The CertoraRunResult object containing the results of the verification run.
|
|
243
|
+
Raises:
|
|
244
|
+
CertoraFoundViolations: If violations were found during the verification run.
|
|
245
|
+
Util.CertoraUserInputError: If there was an error with the user input.
|
|
246
|
+
Returns:
|
|
247
|
+
The CertoraRunResult object containing the results of the verification run.
|
|
248
|
+
"""
|
|
249
|
+
if exit_code == VIOLATIONS_EXIT_CODE:
|
|
250
|
+
raise CertoraFoundViolations("violations were found", return_value)
|
|
251
|
+
if exit_code != 0:
|
|
252
|
+
raise Util.CertoraUserInputError(f"run_certora failed (code {exit_code})")
|
|
253
|
+
return return_value
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
# --------------------------------------------------------------------------- #
|
|
257
|
+
# Entry point decorator
|
|
258
|
+
# --------------------------------------------------------------------------- #
|
|
259
|
+
|
|
260
|
+
console = Console()
|
|
261
|
+
|
|
262
|
+
def catch_exits(fn: Callable[..., None]) -> Callable[..., NoReturn]:
|
|
263
|
+
"""
|
|
264
|
+
Wrap any entry-point in a standard try/except + sys.exit logic.
|
|
265
|
+
The wrapped function should do its work and then return normally;
|
|
266
|
+
this decorator will exit(0) on success or exit(1/other) on failure.
|
|
267
|
+
"""
|
|
268
|
+
@functools.wraps(fn)
|
|
269
|
+
def wrapper(*args, **kwargs) -> NoReturn: # type: ignore
|
|
270
|
+
try:
|
|
271
|
+
fn(*args, **kwargs)
|
|
272
|
+
sys.exit(0)
|
|
273
|
+
|
|
274
|
+
except KeyboardInterrupt:
|
|
275
|
+
console.print("[bold red]\nInterrupted by user")
|
|
276
|
+
sys.exit(1)
|
|
277
|
+
|
|
278
|
+
except Util.TestResultsReady:
|
|
279
|
+
print("reached checkpoint")
|
|
280
|
+
sys.exit(0)
|
|
281
|
+
|
|
282
|
+
except CertoraFoundViolations as e:
|
|
283
|
+
link = getattr(e.results, "rule_report_link", None)
|
|
284
|
+
if link:
|
|
285
|
+
print(f"report url: {link}")
|
|
286
|
+
console.print("[bold red]\nViolations were found\n")
|
|
287
|
+
sys.exit(1)
|
|
288
|
+
|
|
289
|
+
except Util.CertoraUserInputError as e:
|
|
290
|
+
if e.orig:
|
|
291
|
+
print(f"\n{str(e.orig).strip()}")
|
|
292
|
+
if e.more_info:
|
|
293
|
+
print(f"\n{e.more_info.strip()}")
|
|
294
|
+
console.print(f"[bold red]\n{e}\n")
|
|
295
|
+
sys.exit(1)
|
|
296
|
+
|
|
297
|
+
except Util.ExitException as e:
|
|
298
|
+
console.print(f"[bold red]{e}")
|
|
299
|
+
sys.exit(e.exit_code)
|
|
300
|
+
|
|
301
|
+
except Exception as e:
|
|
302
|
+
console.print(f"[bold red]{e}")
|
|
303
|
+
if getenv('CERTORA_DEV_MODE'):
|
|
304
|
+
import traceback
|
|
305
|
+
console.print(f"Traceback: {traceback.format_exc()}")
|
|
306
|
+
sys.exit(1)
|
|
307
|
+
|
|
308
|
+
return wrapper
|
|
@@ -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,13 +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.
|
|
25
|
-
from
|
|
24
|
+
import CertoraProver.certoraApp as App
|
|
25
|
+
from Shared.proverCommon import CertoraRunResult
|
|
26
|
+
from certoraRun import run_certora
|
|
26
27
|
from typing import List, Optional
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def run_evm_prover(args: List[str]) -> Optional[CertoraRunResult]:
|
|
30
|
-
return run_certora(args,
|
|
31
|
+
return run_certora(args, App.EvmApp)
|
|
31
32
|
|
|
32
33
|
def entry_point() -> None:
|
|
33
34
|
run_evm_prover(sys.argv[1:])
|