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
|
@@ -39,8 +39,6 @@ from CertoraProver.certoraSourceFinders import add_source_finders
|
|
|
39
39
|
from CertoraProver.certoraVerifyGenerator import CertoraVerifyGenerator
|
|
40
40
|
from CertoraProver.certoraContractFuncs import Func, InternalFunc, STATEMUT, SourceBytes
|
|
41
41
|
|
|
42
|
-
from Shared.certoraUtils import is_relative_to
|
|
43
|
-
|
|
44
42
|
scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
|
|
45
43
|
sys.path.insert(0, str(scripts_dir_path))
|
|
46
44
|
from CertoraProver.Compiler.CompilerCollector import CompilerLang, CompilerCollector
|
|
@@ -454,8 +452,8 @@ def convert_pathname_to_posix(json_dict: Dict[str, Any], entry: str, smart_contr
|
|
|
454
452
|
# protecting against long strings
|
|
455
453
|
if len(json_dict_str) > 200:
|
|
456
454
|
json_dict_str = json_dict_str[:200] + '...'
|
|
457
|
-
fatal_error(compiler_logger, f"The path of the source file {file_path}"
|
|
458
|
-
f"in the standard json file
|
|
455
|
+
fatal_error(compiler_logger, f"The path of the source file {file_path} "
|
|
456
|
+
f"in the standard json file does not exist!\n{json_dict_str} ")
|
|
459
457
|
json_dict[entry] = json_dict_posix_paths
|
|
460
458
|
|
|
461
459
|
|
|
@@ -1432,8 +1430,11 @@ class CertoraBuildGenerator:
|
|
|
1432
1430
|
compiler_version = compiler_collector.compiler_version
|
|
1433
1431
|
major, minor, patch = compiler_version
|
|
1434
1432
|
|
|
1435
|
-
err_msg =
|
|
1436
|
-
|
|
1433
|
+
err_msg = \
|
|
1434
|
+
f"Unsupported solc version {major}.{minor}.{patch} for `solc_via_ir`. " \
|
|
1435
|
+
f"Supported versions: 0.6.7 – 0.8.25.\n" \
|
|
1436
|
+
f"Use `solc_via_ir_map` to disable `solc_via_ir` for specific files with older compiler versions."
|
|
1437
|
+
|
|
1437
1438
|
yul_optimizer_steps = None
|
|
1438
1439
|
if major != 0:
|
|
1439
1440
|
raise Util.CertoraUserInputError(err_msg)
|
|
@@ -2690,7 +2691,7 @@ class CertoraBuildGenerator:
|
|
|
2690
2691
|
self.handle_erc7201_annotations()
|
|
2691
2692
|
self.handle_storage_extension_harnesses()
|
|
2692
2693
|
|
|
2693
|
-
def extract_slayout(self, original_file: str, ns_storage: Set[NameSpacedStorage]) -> NewStorageInfo:
|
|
2694
|
+
def extract_slayout(self, original_file: str, ns_storage: Set[NameSpacedStorage], compiler_version: str) -> NewStorageInfo:
|
|
2694
2695
|
"""
|
|
2695
2696
|
Given a file containing a contract with namespaced storage, extract the storage information
|
|
2696
2697
|
corresponding to the namespaced types.
|
|
@@ -2715,7 +2716,9 @@ class CertoraBuildGenerator:
|
|
|
2715
2716
|
# original file is accessed also in the actual code, and compiling it
|
|
2716
2717
|
# directly can cause issues.
|
|
2717
2718
|
tmp_file.write(f"import \"{original_file}\";\n\n")
|
|
2718
|
-
|
|
2719
|
+
rel_path = os.path.relpath(tmp_file.name, Path.cwd())
|
|
2720
|
+
if self.context.compiler_map:
|
|
2721
|
+
self.context.compiler_map.update({rel_path: compiler_version}, last=False)
|
|
2719
2722
|
# Write the harness contract with dummy fields for each namespaced storage
|
|
2720
2723
|
var_to_slot = storageExtension.write_harness_contract(tmp_file, harness_name, ns_storage)
|
|
2721
2724
|
tmp_file.flush()
|
|
@@ -2728,13 +2731,6 @@ class CertoraBuildGenerator:
|
|
|
2728
2731
|
build_dir / f"{Path(original_file).stem}_storage_extension.sol"
|
|
2729
2732
|
)
|
|
2730
2733
|
|
|
2731
|
-
# Add harness to compiler map
|
|
2732
|
-
storageExtension.add_harness_to_compiler_map(
|
|
2733
|
-
original_file,
|
|
2734
|
-
tmp_file,
|
|
2735
|
-
self.context
|
|
2736
|
-
)
|
|
2737
|
-
|
|
2738
2734
|
# normalize the path exactly the way collect_for_file expects it:
|
|
2739
2735
|
abs_path = Util.abs_posix_path(tmp_file.name)
|
|
2740
2736
|
self.context.file_to_contract[abs_path] = {harness_name}
|
|
@@ -2802,7 +2798,8 @@ class CertoraBuildGenerator:
|
|
|
2802
2798
|
continue
|
|
2803
2799
|
|
|
2804
2800
|
# Now that we have all the storage layout information, extract it once
|
|
2805
|
-
slayouts[key] = self.extract_slayout(imported_file, ns_storage
|
|
2801
|
+
slayouts[key] = self.extract_slayout(imported_file, ns_storage,
|
|
2802
|
+
get_relevant_compiler(Path(target_file), self.context))
|
|
2806
2803
|
|
|
2807
2804
|
if self.context.test == str(Util.TestValue.STORAGE_EXTENSION_LAYOUT):
|
|
2808
2805
|
raise Util.TestResultsReady(slayouts)
|
|
@@ -3583,7 +3580,11 @@ class CertoraBuildGenerator:
|
|
|
3583
3580
|
return sources
|
|
3584
3581
|
|
|
3585
3582
|
def __del__(self) -> None:
|
|
3586
|
-
|
|
3583
|
+
try:
|
|
3584
|
+
self.cleanup()
|
|
3585
|
+
except ImportError:
|
|
3586
|
+
# Avoiding Python interpreter shutdown exceptions which are safe to ignore
|
|
3587
|
+
pass
|
|
3587
3588
|
|
|
3588
3589
|
|
|
3589
3590
|
# make sure each source file exists and its path is in absolute format
|
|
@@ -3698,7 +3699,7 @@ def build_from_scratch(context: CertoraContext,
|
|
|
3698
3699
|
# the contract files in SDCs are relative to .certora_sources. Which isn't good for us here.
|
|
3699
3700
|
# Need to be relative to original paths
|
|
3700
3701
|
for f in paths_set:
|
|
3701
|
-
if is_relative_to(
|
|
3702
|
+
if f.is_relative_to(absolute_sources_dir):
|
|
3702
3703
|
rel_f = f.relative_to(absolute_sources_dir)
|
|
3703
3704
|
else:
|
|
3704
3705
|
# may be an absolute path already outside .certora_sources, so we can keep it.
|
|
@@ -179,6 +179,8 @@ class CertoraBuildCacheManager:
|
|
|
179
179
|
trg = trg_path_with_additional_included_files / post_autofinder_dir.name
|
|
180
180
|
if post_autofinder_dir != trg:
|
|
181
181
|
if post_autofinder_dir.is_dir():
|
|
182
|
+
if trg.exists():
|
|
183
|
+
shutil.rmtree(trg)
|
|
182
184
|
Util.safe_copy_folder(post_autofinder_dir, trg, shutil.ignore_patterns())
|
|
183
185
|
else:
|
|
184
186
|
# highly unlikely .post_autofinder.[digit] will be a file and not a directory,
|
|
@@ -13,10 +13,20 @@
|
|
|
13
13
|
# You should have received a copy of the GNU General Public License
|
|
14
14
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
15
|
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import sys
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
|
|
22
|
+
sys.path.insert(0, str(scripts_dir_path))
|
|
23
|
+
|
|
16
24
|
import glob
|
|
17
25
|
import shutil
|
|
26
|
+
import time
|
|
27
|
+
import logging
|
|
18
28
|
from pathlib import Path
|
|
19
|
-
from typing import Set
|
|
29
|
+
from typing import Set, Dict
|
|
20
30
|
|
|
21
31
|
from CertoraProver.certoraBuild import build_source_tree
|
|
22
32
|
from CertoraProver.certoraContextClass import CertoraContext
|
|
@@ -25,6 +35,25 @@ import CertoraProver.certoraContextAttributes as Attrs
|
|
|
25
35
|
from Shared import certoraUtils as Util
|
|
26
36
|
|
|
27
37
|
|
|
38
|
+
log = logging.getLogger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def build_rust_project(context: CertoraContext, timings: Dict) -> None:
|
|
42
|
+
"""
|
|
43
|
+
Compile the Rust artefact and record elapsed time in *timings*.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
context: The CertoraContext object containing the configuration.
|
|
47
|
+
timings: A dictionary to store timing information.
|
|
48
|
+
"""
|
|
49
|
+
log.debug("Build Rust target")
|
|
50
|
+
start = time.perf_counter()
|
|
51
|
+
set_rust_build_directory(context)
|
|
52
|
+
timings["buildTime"] = round(time.perf_counter() - start, 4)
|
|
53
|
+
if context.test == str(Util.TestValue.AFTER_BUILD):
|
|
54
|
+
raise Util.TestResultsReady(context)
|
|
55
|
+
|
|
56
|
+
|
|
28
57
|
def set_rust_build_directory(context: CertoraContext) -> None:
|
|
29
58
|
if not context.files:
|
|
30
59
|
build_rust_app(context)
|
|
@@ -41,6 +70,7 @@ def set_rust_build_directory(context: CertoraContext) -> None:
|
|
|
41
70
|
except Exception as e:
|
|
42
71
|
raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
|
|
43
72
|
|
|
73
|
+
|
|
44
74
|
def build_rust_app(context: CertoraContext) -> None:
|
|
45
75
|
assert not context.files, "build_rust_app: expecting files to be empty"
|
|
46
76
|
if context.build_script:
|
|
@@ -62,24 +92,6 @@ def build_rust_app(context: CertoraContext) -> None:
|
|
|
62
92
|
|
|
63
93
|
run_rust_build(context, build_command)
|
|
64
94
|
|
|
65
|
-
def add_solana_files_from_prover_args(context: CertoraContext) -> None:
|
|
66
|
-
if context.prover_args:
|
|
67
|
-
inlining_file = False
|
|
68
|
-
summaries_file = False
|
|
69
|
-
for prover_arg in context.prover_args:
|
|
70
|
-
for arg in prover_arg.split(' '):
|
|
71
|
-
if inlining_file:
|
|
72
|
-
context.solana_inlining = context.solana_inlining or [Path(arg)]
|
|
73
|
-
inlining_file = False
|
|
74
|
-
if summaries_file:
|
|
75
|
-
context.solana_summaries = context.solana_summaries or [Path(arg)]
|
|
76
|
-
summaries_file = False
|
|
77
|
-
|
|
78
|
-
if arg == '-solanaInlining':
|
|
79
|
-
inlining_file = True
|
|
80
|
-
elif arg == '-solanaSummaries':
|
|
81
|
-
summaries_file = True
|
|
82
|
-
|
|
83
95
|
|
|
84
96
|
def add_solana_files_to_sources(context: CertoraContext, sources: Set[Path]) -> None:
|
|
85
97
|
for attr in [Attrs.SolanaProverAttributes.SOLANA_INLINING,
|
|
@@ -126,7 +138,6 @@ def collect_files_from_rust_sources(context: CertoraContext, sources: Set[Path])
|
|
|
126
138
|
if getattr(context, 'conf_file', None) and Path(context.conf_file).exists():
|
|
127
139
|
sources.add(Path(context.conf_file).absolute())
|
|
128
140
|
|
|
129
|
-
add_solana_files_from_prover_args(context)
|
|
130
141
|
add_solana_files_to_sources(context, sources)
|
|
131
142
|
|
|
132
143
|
|
|
@@ -13,13 +13,6 @@
|
|
|
13
13
|
# You should have received a copy of the GNU General Public License
|
|
14
14
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
15
|
|
|
16
|
-
"""
|
|
17
|
-
Shared helpers for Rust-based targets (Solana & Soroban).
|
|
18
|
-
|
|
19
|
-
Placing the build logic in one module removes
|
|
20
|
-
duplication from the dedicated entry scripts.
|
|
21
|
-
"""
|
|
22
|
-
|
|
23
16
|
from __future__ import annotations
|
|
24
17
|
|
|
25
18
|
import sys
|
|
@@ -28,25 +21,21 @@ from pathlib import Path
|
|
|
28
21
|
scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
|
|
29
22
|
sys.path.insert(0, str(scripts_dir_path))
|
|
30
23
|
|
|
24
|
+
import shutil
|
|
31
25
|
import time
|
|
32
26
|
import logging
|
|
33
|
-
from
|
|
34
|
-
|
|
35
|
-
from Shared import certoraUtils as Util
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Set, Dict
|
|
36
29
|
|
|
30
|
+
from CertoraProver.certoraBuild import build_source_tree
|
|
37
31
|
from CertoraProver.certoraContextClass import CertoraContext
|
|
38
|
-
|
|
39
|
-
from CertoraProver.certoraBuildRust import set_rust_build_directory
|
|
32
|
+
from Shared import certoraUtils as Util
|
|
40
33
|
|
|
41
34
|
|
|
42
35
|
log = logging.getLogger(__name__)
|
|
43
36
|
|
|
44
37
|
|
|
45
|
-
|
|
46
|
-
# Build
|
|
47
|
-
# --------------------------------------------------------------------------- #
|
|
48
|
-
|
|
49
|
-
def build_rust_project(context: CertoraContext, timings: Dict) -> None:
|
|
38
|
+
def build_sui_project(context: CertoraContext, timings: Dict) -> None:
|
|
50
39
|
"""
|
|
51
40
|
Compile the Rust artefact and record elapsed time in *timings*.
|
|
52
41
|
|
|
@@ -56,7 +45,24 @@ def build_rust_project(context: CertoraContext, timings: Dict) -> None:
|
|
|
56
45
|
"""
|
|
57
46
|
log.debug("Build Rust target")
|
|
58
47
|
start = time.perf_counter()
|
|
59
|
-
|
|
48
|
+
set_sui_build_directory(context)
|
|
60
49
|
timings["buildTime"] = round(time.perf_counter() - start, 4)
|
|
61
50
|
if context.test == str(Util.TestValue.AFTER_BUILD):
|
|
62
51
|
raise Util.TestResultsReady(context)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def set_sui_build_directory(context: CertoraContext) -> None:
|
|
55
|
+
assert context.move_path, "build_sui_project: expecting move_path to link to a build directory"
|
|
56
|
+
|
|
57
|
+
shutil.copytree(context.move_path, Util.get_build_dir() / Path(context.move_path).name)
|
|
58
|
+
|
|
59
|
+
sources: Set[Path] = set()
|
|
60
|
+
if getattr(context, 'conf_file', None) and Path(context.conf_file).exists():
|
|
61
|
+
sources.add(Path(context.conf_file).absolute())
|
|
62
|
+
|
|
63
|
+
try:
|
|
64
|
+
# Create generators
|
|
65
|
+
build_source_tree(sources, context)
|
|
66
|
+
|
|
67
|
+
except Exception as e:
|
|
68
|
+
raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
|
|
@@ -43,7 +43,6 @@ from tqdm import tqdm
|
|
|
43
43
|
|
|
44
44
|
import logging
|
|
45
45
|
|
|
46
|
-
|
|
47
46
|
cloud_logger = logging.getLogger("cloud")
|
|
48
47
|
|
|
49
48
|
MAX_FILE_SIZE = 25 * 1024 * 1024
|
|
@@ -74,11 +73,12 @@ class EcoEnum(Util.NoValEnum):
|
|
|
74
73
|
EVM = Util.auto()
|
|
75
74
|
SOROBAN = Util.auto()
|
|
76
75
|
SOLANA = Util.auto()
|
|
76
|
+
SUI = Util.auto()
|
|
77
77
|
|
|
78
78
|
class ProductEnum(Util.NoValEnum):
|
|
79
79
|
PROVER = Util.auto()
|
|
80
80
|
RANGER = Util.auto()
|
|
81
|
-
|
|
81
|
+
CONCORD = Util.auto()
|
|
82
82
|
|
|
83
83
|
|
|
84
84
|
class TimeError(Exception):
|
|
@@ -587,7 +587,7 @@ class CloudVerification:
|
|
|
587
587
|
"""
|
|
588
588
|
|
|
589
589
|
auth_data = {
|
|
590
|
-
"
|
|
590
|
+
"runName": self.runName,
|
|
591
591
|
"run_source": self.context.run_source,
|
|
592
592
|
"group_id": self.context.group_id,
|
|
593
593
|
"branch": self.context.prover_version if self.context.prover_version else '',
|
|
@@ -624,6 +624,13 @@ class CloudVerification:
|
|
|
624
624
|
rust_jar_settings.append(paths_in_source_dir(self.context.solana_inlining))
|
|
625
625
|
|
|
626
626
|
auth_data["jarSettings"] = rust_jar_settings + jar_settings
|
|
627
|
+
|
|
628
|
+
elif Attrs.is_sui_app():
|
|
629
|
+
sui_jar_settings = ['-movePath', Path(self.context.move_path).name]
|
|
630
|
+
auth_data["jarSettings"] = sui_jar_settings + jar_settings
|
|
631
|
+
|
|
632
|
+
elif Attrs.is_concord_app():
|
|
633
|
+
auth_data["jarSettings"] = jar_settings + ["-equivalenceCheck", "true"]
|
|
627
634
|
else:
|
|
628
635
|
auth_data["jarSettings"] = jar_settings
|
|
629
636
|
|
|
@@ -645,13 +652,15 @@ class CloudVerification:
|
|
|
645
652
|
auth_data["ecosystem"] = EcoEnum.SOLANA.name
|
|
646
653
|
elif Attrs.is_soroban_app():
|
|
647
654
|
auth_data["ecosystem"] = EcoEnum.SOROBAN.name
|
|
655
|
+
elif Attrs.is_sui_app():
|
|
656
|
+
auth_data["ecosystem"] = EcoEnum.SUI.name
|
|
648
657
|
else:
|
|
649
658
|
auth_data["ecosystem"] = EcoEnum.EVM.name
|
|
650
659
|
|
|
651
660
|
if Attrs.is_ranger_app():
|
|
652
661
|
auth_data["product"] = ProductEnum.RANGER.name
|
|
653
|
-
elif Attrs.
|
|
654
|
-
auth_data["product"] = ProductEnum.
|
|
662
|
+
elif Attrs.is_concord_app():
|
|
663
|
+
auth_data["product"] = ProductEnum.CONCORD.name
|
|
655
664
|
else:
|
|
656
665
|
auth_data["product"] = ProductEnum.PROVER.name
|
|
657
666
|
|
|
@@ -686,12 +695,12 @@ class CloudVerification:
|
|
|
686
695
|
|
|
687
696
|
def print_output_links(self) -> None:
|
|
688
697
|
print(f"Manage your jobs at {self.get_domain()}")
|
|
689
|
-
print(f"Follow your job and see verification results at {self.
|
|
698
|
+
print(f"Follow your job and see verification results at {self.url_print_format()}", flush=True)
|
|
690
699
|
|
|
691
700
|
def print_verification_summary(self) -> None:
|
|
692
701
|
report_exists = self.check_file_exists(params={"filename": "index.html", "certoraKey": self.context.key})
|
|
693
702
|
if report_exists:
|
|
694
|
-
print(f"Job is completed! View the results at {self.
|
|
703
|
+
print(f"Job is completed! View the results at {self.url_print_format()}")
|
|
695
704
|
print("Finished verification request", flush=True)
|
|
696
705
|
|
|
697
706
|
def __send_verification_request(self, cl_args: str) -> bool:
|
|
@@ -774,6 +783,16 @@ class CloudVerification:
|
|
|
774
783
|
|
|
775
784
|
result = compress_files(self.ZipFilePath, *files_list,
|
|
776
785
|
short_output=Ctx.is_minimal_cli_output(self.context))
|
|
786
|
+
elif Attrs.is_sui_app():
|
|
787
|
+
files_list = [Util.get_certora_metadata_file(),
|
|
788
|
+
Util.get_configuration_layout_data_file(),
|
|
789
|
+
Util.get_build_dir() / Path(self.context.move_path).name]
|
|
790
|
+
|
|
791
|
+
if Util.get_certora_sources_dir().exists():
|
|
792
|
+
files_list.append(Util.get_certora_sources_dir())
|
|
793
|
+
|
|
794
|
+
result = compress_files(self.ZipFilePath, *files_list,
|
|
795
|
+
short_output=Ctx.is_minimal_cli_output(self.context))
|
|
777
796
|
|
|
778
797
|
else:
|
|
779
798
|
# Zip the log file first separately and again with the rest of the files, so it will not be decompressed
|
|
@@ -851,7 +870,7 @@ class CloudVerification:
|
|
|
851
870
|
self.runName, self.reportUrl, self.msg, self.get_domain(), str(self.userId), self.anonymousKey)
|
|
852
871
|
|
|
853
872
|
# Generate a json file for the VS Code extension with the relevant url
|
|
854
|
-
self.vscode_extension_info_writer.add_field("verification_report_url", self.
|
|
873
|
+
self.vscode_extension_info_writer.add_field("verification_report_url", self.url_print_format())
|
|
855
874
|
self.vscode_extension_info_writer.write_file()
|
|
856
875
|
|
|
857
876
|
if not wait_for_results or wait_for_results == str(Vf.WaitForResultOptions.NONE):
|
|
@@ -1140,6 +1159,12 @@ class CloudVerification:
|
|
|
1140
1159
|
Util.remove_file(self.ZipFilePath)
|
|
1141
1160
|
Util.remove_file(self.logZipFilePath)
|
|
1142
1161
|
|
|
1162
|
+
def url_print_format(self) -> str:
|
|
1163
|
+
if self.reportUrl and self.context.url_visibility == str(Vf.UrlVisibilityOptions.PRIVATE):
|
|
1164
|
+
return self.privatize_url()
|
|
1165
|
+
else:
|
|
1166
|
+
return self.reportUrl
|
|
1167
|
+
|
|
1143
1168
|
@lru_cache(maxsize=1, typed=False)
|
|
1144
1169
|
def get_domain(self) -> str:
|
|
1145
1170
|
"""
|
|
@@ -1155,3 +1180,7 @@ class CloudVerification:
|
|
|
1155
1180
|
domain = Util.SupportedServers.PRODUCTION.value
|
|
1156
1181
|
|
|
1157
1182
|
return domain
|
|
1183
|
+
|
|
1184
|
+
@lru_cache(maxsize=1)
|
|
1185
|
+
def privatize_url(self) -> str:
|
|
1186
|
+
return self.reportUrl.split('?anonymousKey=')[0]
|
|
@@ -31,6 +31,7 @@ import CertoraProver.certoraContextAttributes as Attrs
|
|
|
31
31
|
|
|
32
32
|
metadata_logger = logging.getLogger("metadata")
|
|
33
33
|
|
|
34
|
+
|
|
34
35
|
def collect_args_with_jar_flags() -> List[str]:
|
|
35
36
|
return_array = []
|
|
36
37
|
for attr in Attrs.get_attribute_class().attribute_list():
|
|
@@ -70,10 +71,18 @@ class RunMetaData:
|
|
|
70
71
|
dirty -- true iff the git repository has changes (git diff is not empty)
|
|
71
72
|
main_spec -- the relative path to the main spec file that should be displayed by default at the web report
|
|
72
73
|
conf_path -- the relative path form the cwd_relative to the configuration file
|
|
74
|
+
group_id -- optional identifier for grouping this run
|
|
75
|
+
java_version -- version of Java used during the run, if available
|
|
76
|
+
python_version -- version of Python running the process
|
|
77
|
+
certora_ci_client -- name of the CI client if available, derived from environment
|
|
78
|
+
timestamp -- UTC timestamp when the run metadata was generated
|
|
79
|
+
CLI_package_name -- name of the CLI package
|
|
80
|
+
CLI_version -- version of the CLI
|
|
81
|
+
jar_flag_info -- CLI attributes which are jarFlags
|
|
73
82
|
"""
|
|
74
83
|
def __init__(self, raw_args: List[str], conf: Dict[str, Any], origin: str, revision: str,
|
|
75
84
|
branch: str, cwd_relative: Path, dirty: bool, main_spec: Optional[str],
|
|
76
|
-
conf_path: Optional[Path], group_id: Optional[str]):
|
|
85
|
+
conf_path: Optional[Path], group_id: Optional[str], java_version: str):
|
|
77
86
|
self.raw_args = raw_args
|
|
78
87
|
self.conf = conf
|
|
79
88
|
self.origin = origin
|
|
@@ -85,6 +94,7 @@ class RunMetaData:
|
|
|
85
94
|
self.conf_path = conf_path
|
|
86
95
|
self.group_id = group_id
|
|
87
96
|
self.python_version = ".".join(str(x) for x in sys.version_info[:3])
|
|
97
|
+
self.java_version = java_version
|
|
88
98
|
self.certora_ci_client = Utils.get_certora_ci_name()
|
|
89
99
|
self.timestamp = str(datetime.now(timezone.utc).timestamp())
|
|
90
100
|
_, self.CLI_package_name, self.CLI_version = Utils.get_package_and_version()
|
|
@@ -105,6 +115,7 @@ class RunMetaData:
|
|
|
105
115
|
f" conf_path: {self.conf_path}\n"
|
|
106
116
|
f" group_id: {self.group_id}\n"
|
|
107
117
|
f" python_version: {self.python_version}\n"
|
|
118
|
+
f" java_version: {self.java_version}\n"
|
|
108
119
|
f" CertoraCI client: {self.certora_ci_client}\n"
|
|
109
120
|
f" jar_flag_info: {self.jar_flag_info}\n"
|
|
110
121
|
)
|
|
@@ -182,7 +193,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
182
193
|
dirty=True,
|
|
183
194
|
main_spec=None,
|
|
184
195
|
conf_path=None,
|
|
185
|
-
group_id=None
|
|
196
|
+
group_id=None,
|
|
197
|
+
java_version=context.java_version)
|
|
186
198
|
|
|
187
199
|
# collect information about current git snapshot
|
|
188
200
|
cwd_abs = wd.absolute()
|
|
@@ -210,7 +222,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
210
222
|
dirty=True,
|
|
211
223
|
main_spec=get_main_spec(context),
|
|
212
224
|
conf_path=conf_path,
|
|
213
|
-
group_id=context.group_id
|
|
225
|
+
group_id=context.group_id,
|
|
226
|
+
java_version=context.java_version)
|
|
214
227
|
|
|
215
228
|
try:
|
|
216
229
|
sha_out = subprocess.run(['git', 'rev-parse', 'HEAD'], cwd=wd,
|
|
@@ -245,7 +258,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
245
258
|
dirty=dirty,
|
|
246
259
|
main_spec=get_main_spec(context),
|
|
247
260
|
conf_path=conf_path,
|
|
248
|
-
group_id=context.group_id
|
|
261
|
+
group_id=context.group_id,
|
|
262
|
+
java_version=context.java_version)
|
|
249
263
|
|
|
250
264
|
metadata_logger.debug(f' collected data:\n{str(data)}')
|
|
251
265
|
|
|
@@ -263,4 +277,5 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
263
277
|
dirty=True,
|
|
264
278
|
main_spec=get_main_spec(context),
|
|
265
279
|
conf_path=conf_path,
|
|
266
|
-
group_id=context.group_id
|
|
280
|
+
group_id=context.group_id,
|
|
281
|
+
java_version=context.java_version)
|
|
@@ -80,7 +80,7 @@ def read_from_conf_file(context: CertoraContext) -> None:
|
|
|
80
80
|
try:
|
|
81
81
|
check_conf_content(context)
|
|
82
82
|
except Util.CertoraUserInputError as e:
|
|
83
|
-
raise Util.CertoraUserInputError(f"Error when reading {conf_file_path}: {
|
|
83
|
+
raise Util.CertoraUserInputError(f"Error when reading {conf_file_path}: {e}") from None
|
|
84
84
|
context.conf_file = str(conf_file_path)
|
|
85
85
|
except FileNotFoundError:
|
|
86
86
|
raise Util.CertoraUserInputError(f"read_from_conf_file: {conf_file_path}: not found") from None
|
|
@@ -97,22 +97,24 @@ def handle_override_base_config(context: CertoraContext) -> None:
|
|
|
97
97
|
"""
|
|
98
98
|
|
|
99
99
|
if context.override_base_config:
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
override_base_config_attrs = json5.load(conf_file, allow_duplicate_keys=False
|
|
100
|
+
try:
|
|
101
|
+
with Path(context.override_base_config).open() as conf_file:
|
|
102
|
+
override_base_config_attrs = json5.load(conf_file, allow_duplicate_keys=False,
|
|
103
|
+
object_pairs_hook=OrderedDict)
|
|
103
104
|
context.conf_file_attr = {**override_base_config_attrs, **context.conf_file_attr}
|
|
104
105
|
|
|
105
106
|
if 'override_base_config' in override_base_config_attrs:
|
|
106
107
|
raise Util.CertoraUserInputError("base config cannot include 'override_base_config'")
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
except Exception as e:
|
|
109
|
+
raise Util.CertoraUserInputError(f"Cannot load base config: {context.override_base_config}\n{e}")
|
|
110
|
+
|
|
111
|
+
for attr in override_base_config_attrs:
|
|
112
|
+
if hasattr(context, attr):
|
|
113
|
+
value = getattr(context, attr, False)
|
|
114
|
+
if not value:
|
|
115
|
+
setattr(context, attr, override_base_config_attrs.get(attr))
|
|
116
|
+
else:
|
|
117
|
+
raise Util.CertoraUserInputError(f"{attr} appears in the base conf file {context.override_base_config} but is not a known attribute.")
|
|
116
118
|
|
|
117
119
|
|
|
118
120
|
def check_conf_content(context: CertoraContext) -> None:
|
|
@@ -122,7 +122,11 @@ def get_local_run_cmd(context: CertoraContext) -> List[str]:
|
|
|
122
122
|
"""
|
|
123
123
|
run_args = []
|
|
124
124
|
|
|
125
|
-
if Attrs.
|
|
125
|
+
if Attrs.is_sui_app():
|
|
126
|
+
# For local runs, we want path to be relative to cwd instead of zip root.
|
|
127
|
+
move_rel_path = os.path.relpath(Path(context.move_path), os.getcwd())
|
|
128
|
+
run_args.extend(['-movePath', move_rel_path])
|
|
129
|
+
elif Attrs.is_rust_app():
|
|
126
130
|
# For local runs, we want path to be relative to cwd instead of zip root.
|
|
127
131
|
rust_rel_path = os.path.relpath(Path(context.files[0]), os.getcwd())
|
|
128
132
|
run_args.append(rust_rel_path)
|
|
@@ -135,7 +139,8 @@ def get_local_run_cmd(context: CertoraContext) -> List[str]:
|
|
|
135
139
|
|
|
136
140
|
if Attrs.is_evm_app() and context.cache is not None:
|
|
137
141
|
run_args.extend(['-cache', context.cache])
|
|
138
|
-
|
|
142
|
+
if Attrs.is_concord_app():
|
|
143
|
+
run_args.extend(['-equivalenceCheck', 'true'])
|
|
139
144
|
jar_args = collect_jar_args(context)
|
|
140
145
|
run_args.extend(jar_args)
|
|
141
146
|
|
|
@@ -161,12 +166,17 @@ class ProverParser(AttrUtil.ContextAttributeParser):
|
|
|
161
166
|
|
|
162
167
|
def format_help(self) -> str:
|
|
163
168
|
console = Console()
|
|
164
|
-
if Attrs.
|
|
169
|
+
if Attrs.is_concord_app():
|
|
170
|
+
console.print("\n\nConcord - Certora’s equivalence checker for smart contracts")
|
|
171
|
+
elif Attrs.is_ranger_app():
|
|
165
172
|
console.print("\n\nRanger - Certora’s bounded model checker for smart contracts")
|
|
166
173
|
else:
|
|
167
174
|
console.print("\n\nThe Certora Prover - A formal verification tool for smart contracts")
|
|
168
175
|
# Using sys.stdout.write() as print() would color some of the strings here
|
|
169
176
|
sys.stdout.write(f"\n\nUsage: {sys.argv[0]} <Files> <Flags>\n\n")
|
|
177
|
+
if Attrs.is_concord_app():
|
|
178
|
+
sys.stdout.write("Concord supports only Solidity (.sol/.yul) and configuration (.conf) files.\n"
|
|
179
|
+
"Rust and Vyper contracts are not currently supported.\n\n")
|
|
170
180
|
if Attrs.is_ranger_app():
|
|
171
181
|
sys.stdout.write("Ranger supports only Solidity (.sol) and configuration (.conf) files.\n"
|
|
172
182
|
"Rust and Vyper contracts are not currently supported.\n\n")
|
|
@@ -210,6 +220,16 @@ def __get_argparser() -> argparse.ArgumentParser:
|
|
|
210
220
|
return parser
|
|
211
221
|
|
|
212
222
|
|
|
223
|
+
def set_apps_members(context: CertoraContext) -> None:
|
|
224
|
+
# in many cases accessing context is simpler than accessing Attrs
|
|
225
|
+
context.is_solana_app = Attrs.is_solana_app()
|
|
226
|
+
context.is_soroban_app = Attrs.is_soroban_app()
|
|
227
|
+
context.is_rust_app = Attrs.is_rust_app()
|
|
228
|
+
context.is_evm_app = Attrs.is_evm_app()
|
|
229
|
+
context.is_ranger_app = Attrs.is_ranger_app()
|
|
230
|
+
context.is_concord_app = Attrs.is_concord_app()
|
|
231
|
+
|
|
232
|
+
|
|
213
233
|
def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
|
|
214
234
|
"""
|
|
215
235
|
Compiles an argparse.Namespace from the given list of command line arguments.
|
|
@@ -244,6 +264,7 @@ def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
|
|
|
244
264
|
args = parser.parse_args(args_list)
|
|
245
265
|
context = CertoraContext(**vars(args))
|
|
246
266
|
context.args_list = args_list
|
|
267
|
+
set_apps_members(context)
|
|
247
268
|
|
|
248
269
|
__remove_parsing_whitespace(args_list)
|
|
249
270
|
format_input(context)
|
|
@@ -251,7 +272,7 @@ def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
|
|
|
251
272
|
|
|
252
273
|
if context.is_conf:
|
|
253
274
|
read_from_conf_file(context)
|
|
254
|
-
|
|
275
|
+
context.process = None
|
|
255
276
|
context.local = Util.is_local(context)
|
|
256
277
|
context.is_tac = context.files and context.files[0].endswith('.tac')
|
|
257
278
|
context.is_vyper = context.files and context.files[0].endswith('.vy')
|
|
@@ -260,8 +281,7 @@ def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
|
|
|
260
281
|
Cv.check_mode_of_operation(context) # Here boolean run characteristics are set
|
|
261
282
|
|
|
262
283
|
validator = Cv.CertoraContextValidator(context)
|
|
263
|
-
|
|
264
|
-
validator.handle_ranger_attrs()
|
|
284
|
+
|
|
265
285
|
validator.validate()
|
|
266
286
|
if Attrs.is_evm_app() or Attrs.is_rust_app():
|
|
267
287
|
current_build_directory = Util.get_build_dir()
|
|
@@ -278,6 +298,8 @@ def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
|
|
|
278
298
|
if Attrs.is_evm_app():
|
|
279
299
|
validator.check_args_post_argparse()
|
|
280
300
|
setup_cache(context) # Here context.cache, context.user_defined_cache are set
|
|
301
|
+
validator.handle_ranger_attrs()
|
|
302
|
+
validator.handle_concord_attrs()
|
|
281
303
|
if Attrs.is_rust_app():
|
|
282
304
|
validator.check_rust_args_post_argparse()
|
|
283
305
|
|
|
@@ -285,6 +307,7 @@ def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
|
|
|
285
307
|
# Setup defaults (defaults are not recorded in conf file)
|
|
286
308
|
context.expected_file = context.expected_file or "expected.json"
|
|
287
309
|
context.run_source = context.run_source or Vf.RunSources.COMMAND.name.upper()
|
|
310
|
+
context.java_version = Util.get_java_version()
|
|
288
311
|
|
|
289
312
|
context_logger.debug("parsed args successfully.")
|
|
290
313
|
context_logger.debug(f"args= {context}")
|
|
@@ -403,7 +426,7 @@ def setup_cache(context: CertoraContext) -> None:
|
|
|
403
426
|
# we have a user defined cache key if the user provided a cache key
|
|
404
427
|
context.user_defined_cache = context.cache is not None
|
|
405
428
|
if not context.disable_auto_cache_key_gen and not os.environ.get("CERTORA_DISABLE_AUTO_CACHE") is not None:
|
|
406
|
-
if context.is_verify or context.
|
|
429
|
+
if context.is_verify or context.is_conf:
|
|
407
430
|
# in local mode we don't want to create a cache key if not such is given
|
|
408
431
|
if (context.cache is None) and (not context.local):
|
|
409
432
|
optimistic_loop = context.optimistic_loop
|
|
@@ -524,6 +547,8 @@ def run_typechecker(typechecker_name: str, with_typechecking: bool, args: List[s
|
|
|
524
547
|
if with_typechecking:
|
|
525
548
|
cmd_str_list.append('-typeCheck')
|
|
526
549
|
|
|
550
|
+
context_logger.debug(f"typechecking cmd: {' '.join(cmd_str_list)}")
|
|
551
|
+
|
|
527
552
|
exit_code = Util.run_jar_cmd(cmd_str_list, False,
|
|
528
553
|
custom_error_message="Failed to run Certora Prover locally. Please check the errors "
|
|
529
554
|
"below for problems in the specifications (.spec files) or the "
|
|
@@ -547,7 +572,7 @@ def run_local_spec_check(with_typechecking: bool, context: CertoraContext) -> No
|
|
|
547
572
|
if context.disable_local_typechecking or Util.is_ci_or_git_action():
|
|
548
573
|
return
|
|
549
574
|
args = collect_jar_args(context)
|
|
550
|
-
if Util.is_java_installed():
|
|
575
|
+
if Util.is_java_installed(context.java_version):
|
|
551
576
|
run_typechecker("Typechecker.jar", with_typechecking, args)
|
|
552
577
|
else:
|
|
553
578
|
raise Util.CertoraUserInputError("Cannot run local checks because of missing a suitable java installation. "
|