certora-cli-beta-mirror 7.30.0__py3-none-any.whl → 7.31.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 +11 -2
- certora_cli/CertoraProver/certoraBuild.py +50 -45
- certora_cli/CertoraProver/certoraBuildCacheManager.py +15 -16
- certora_cli/CertoraProver/certoraBuildRust.py +2 -1
- certora_cli/CertoraProver/certoraCloudIO.py +7 -27
- certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +62 -51
- certora_cli/CertoraProver/certoraConfigIO.py +2 -1
- certora_cli/CertoraProver/certoraContext.py +29 -2
- certora_cli/CertoraProver/certoraContextAttributes.py +20 -9
- certora_cli/CertoraProver/certoraContextValidator.py +39 -61
- certora_cli/CertoraProver/certoraParseBuildScript.py +4 -3
- certora_cli/EquivalenceCheck/Eq_default.conf +0 -1
- certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -1
- certora_cli/Mutate/mutateApp.py +3 -1
- certora_cli/Shared/certoraUtils.py +21 -19
- certora_cli/Shared/certoraValidateFuncs.py +5 -3
- certora_cli/Shared/proverCommon.py +2 -0
- certora_cli/certoraRun.py +2 -0
- {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/METADATA +3 -2
- {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/RECORD +26 -26
- certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
- certora_jars/Typechecker.jar +0 -0
- {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/LICENSE +0 -0
- {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/WHEEL +0 -0
- {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/entry_points.txt +0 -0
- {certora_cli_beta_mirror-7.30.0.dist-info → certora_cli_beta_mirror-7.31.0.dist-info}/top_level.txt +0 -0
|
@@ -25,8 +25,9 @@ from CertoraProver.Compiler.CompilerCollector import CompilerLang, CompilerColle
|
|
|
25
25
|
from CertoraProver.Compiler.CompilerCollectorSol import CompilerCollectorSol, CompilerLangSol
|
|
26
26
|
from CertoraProver.Compiler.CompilerCollectorYul import CompilerLangYul, CompilerCollectorYul
|
|
27
27
|
from CertoraProver.Compiler.CompilerCollectorVy import CompilerCollectorVy, CompilerLangVy
|
|
28
|
-
from Shared.certoraUtils import
|
|
28
|
+
from Shared.certoraUtils import remove_file, get_certora_config_dir
|
|
29
29
|
from CertoraProver.certoraContextClass import CertoraContext
|
|
30
|
+
import CertoraProver.certoraContext as Ctx
|
|
30
31
|
|
|
31
32
|
# logger for running the Solidity compiler and reporting any errors it emits
|
|
32
33
|
compiler_logger = logging.getLogger("compiler")
|
|
@@ -42,8 +43,16 @@ def get_relevant_compiler(contract_file_path: Path, context: CertoraContext) ->
|
|
|
42
43
|
|
|
43
44
|
if contract_file_path.is_absolute():
|
|
44
45
|
contract_file_path = Path(os.path.relpath(contract_file_path, Path.cwd()))
|
|
46
|
+
|
|
47
|
+
# If the contract file is in the sources directory, we want to use the relative path from the "cwd" in the source tree.
|
|
48
|
+
if Util.get_certora_sources_dir() in contract_file_path.parents:
|
|
49
|
+
cwd_in_source = Util.get_certora_sources_dir() / context.cwd_rel_in_sources
|
|
50
|
+
contract_file_path = Path(os.path.relpath(contract_file_path, cwd_in_source))
|
|
51
|
+
|
|
45
52
|
if context.compiler_map:
|
|
46
|
-
match =
|
|
53
|
+
match = Ctx.get_map_attribute_value(context, contract_file_path, 'compiler')
|
|
54
|
+
assert isinstance(match, str), (f"In the attribute compiler_map, {contract_file_path} expected to be matched "
|
|
55
|
+
f"to a string, {match} is of type {type(match)} not a string")
|
|
47
56
|
if match:
|
|
48
57
|
return match
|
|
49
58
|
else:
|
|
@@ -450,8 +450,12 @@ def convert_pathname_to_posix(json_dict: Dict[str, Any], entry: str, smart_contr
|
|
|
450
450
|
if path_obj.is_file():
|
|
451
451
|
json_dict_posix_paths[path_obj.as_posix()] = json_dict[entry][file_path]
|
|
452
452
|
else:
|
|
453
|
+
json_dict_str = str(json_dict)
|
|
454
|
+
# protecting against long strings
|
|
455
|
+
if len(json_dict_str) > 200:
|
|
456
|
+
json_dict_str = json_dict_str[:200] + '...'
|
|
453
457
|
fatal_error(compiler_logger, f"The path of the source file {file_path}"
|
|
454
|
-
f"in the standard json file {
|
|
458
|
+
f"in the standard json file {json_dict_str} does not exist")
|
|
455
459
|
json_dict[entry] = json_dict_posix_paths
|
|
456
460
|
|
|
457
461
|
|
|
@@ -1361,46 +1365,48 @@ class CertoraBuildGenerator:
|
|
|
1361
1365
|
|
|
1362
1366
|
return bytecode
|
|
1363
1367
|
|
|
1364
|
-
def
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1368
|
+
def get_solc_via_ir_value(self, contract_file_path: Path) -> bool:
|
|
1369
|
+
match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_via_ir')
|
|
1370
|
+
assert isinstance(match, (bool, type(None))), f"Expected solc_via_ir to be bool or None, got {type(match)}"
|
|
1371
|
+
return bool(match)
|
|
1372
|
+
|
|
1373
|
+
def get_solc_evm_version_value(self, contract_file_path: Path) -> Optional[str]:
|
|
1374
|
+
match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_evm_version')
|
|
1375
|
+
assert isinstance(match, (str, type(None))), f"Expected solc_evm_version to be string or None, got {type(match)}"
|
|
1376
|
+
return match
|
|
1377
|
+
|
|
1378
|
+
def get_solc_optimize_value(self, contract_file_path: Path) -> Optional[str]:
|
|
1379
|
+
match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_optimize')
|
|
1380
|
+
assert isinstance(match, (str, type(None))), f"Expected solc_optimize to be string or None, got {type(match)}"
|
|
1381
|
+
return match
|
|
1382
|
+
|
|
1383
|
+
def _handle_via_ir(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
|
|
1384
|
+
if self.get_solc_via_ir_value(contract_file_path):
|
|
1372
1385
|
settings_dict["viaIR"] = True
|
|
1373
1386
|
|
|
1374
|
-
def _handle_evm_version(self, contract_file_path:
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
settings_dict["evmVersion"] = match
|
|
1379
|
-
elif self.context.solc_evm_version:
|
|
1380
|
-
settings_dict["evmVersion"] = self.context.solc_evm_version
|
|
1387
|
+
def _handle_evm_version(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
|
|
1388
|
+
match = self.get_solc_evm_version_value(contract_file_path)
|
|
1389
|
+
if match:
|
|
1390
|
+
settings_dict["evmVersion"] = match
|
|
1381
1391
|
|
|
1382
|
-
def _handle_optimize(self, contract_file_path:
|
|
1392
|
+
def _handle_optimize(self, contract_file_path: Path, settings_dict: Dict[str, Any],
|
|
1383
1393
|
compiler_collector: CompilerCollector) -> None:
|
|
1384
1394
|
"""
|
|
1385
1395
|
@param contract_file_path: the contract that we are working on
|
|
1386
1396
|
@param settings_dict: data structure for sending to the solc compiler
|
|
1387
1397
|
"""
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
if match and int(match) > 0:
|
|
1391
|
-
settings_dict["optimizer"] = {"enabled": True}
|
|
1392
|
-
settings_dict["optimizer"]['runs'] = int(match)
|
|
1393
|
-
elif self.context.solc_optimize:
|
|
1398
|
+
match = self.get_solc_optimize_value(contract_file_path)
|
|
1399
|
+
if match:
|
|
1394
1400
|
settings_dict["optimizer"] = {"enabled": True}
|
|
1395
|
-
if int(
|
|
1396
|
-
settings_dict["optimizer"]['runs'] = int(
|
|
1401
|
+
if int(match) > 0:
|
|
1402
|
+
settings_dict["optimizer"]['runs'] = int(match)
|
|
1397
1403
|
|
|
1398
1404
|
# if optimizer is true, we should also define settings_dict["optimizer"]["details"]
|
|
1399
1405
|
# for both optimize map and optimize
|
|
1400
1406
|
optimizer = settings_dict.get("optimizer")
|
|
1401
1407
|
if optimizer and isinstance(optimizer, dict) and optimizer.get('enabled'):
|
|
1402
1408
|
# if we are not disabling finder friendly optimizer specifically, enable it whenever viaIR is also enabled
|
|
1403
|
-
if not self.context.strict_solc_optimizer and self.
|
|
1409
|
+
if not self.context.strict_solc_optimizer and self.get_solc_via_ir_value(contract_file_path):
|
|
1404
1410
|
# The default optimizer steps (taken from libsolidity/interface/OptimiserSettings.h) but with the
|
|
1405
1411
|
# full inliner step removed
|
|
1406
1412
|
solc0_8_26_to_0_8_30 = ("dhfoDgvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLCTUca[r]LSsTFOtfDnca[r]" +
|
|
@@ -1433,6 +1439,8 @@ class CertoraBuildGenerator:
|
|
|
1433
1439
|
raise Util.CertoraUserInputError(err_msg)
|
|
1434
1440
|
elif minor < 6 or (minor == 6 and patch < 7):
|
|
1435
1441
|
raise Util.CertoraUserInputError(err_msg)
|
|
1442
|
+
elif self.context.yul_optimizer_steps:
|
|
1443
|
+
yul_optimizer_steps = self.context.yul_optimizer_steps
|
|
1436
1444
|
elif (minor == 6 and patch >= 7) or (minor == 7 and 0 <= patch <= 1):
|
|
1437
1445
|
yul_optimizer_steps = solc0_6_7_to_0_7_1
|
|
1438
1446
|
elif minor == 7 and 2 <= patch <= 5:
|
|
@@ -1470,7 +1478,7 @@ class CertoraBuildGenerator:
|
|
|
1470
1478
|
for opt_pass in self.context.disable_solc_optimizers:
|
|
1471
1479
|
settings_dict["optimizer"]["details"][opt_pass] = False
|
|
1472
1480
|
|
|
1473
|
-
def _fill_codegen_related_options(self, contract_file_path:
|
|
1481
|
+
def _fill_codegen_related_options(self, contract_file_path: Path, settings_dict: Dict[str, Any],
|
|
1474
1482
|
compiler_collector: CompilerCollector) -> None:
|
|
1475
1483
|
"""
|
|
1476
1484
|
Fills options that control how we call solc and affect the bytecode in some way
|
|
@@ -1551,7 +1559,7 @@ class CertoraBuildGenerator:
|
|
|
1551
1559
|
}
|
|
1552
1560
|
}
|
|
1553
1561
|
|
|
1554
|
-
self._fill_codegen_related_options(contract_file_as_provided, settings_dict, compiler_collector)
|
|
1562
|
+
self._fill_codegen_related_options(Path(contract_file_as_provided), settings_dict, compiler_collector)
|
|
1555
1563
|
|
|
1556
1564
|
result_dict = {"language": compiler_collector_lang.name, "sources": sources_dict, "settings": settings_dict}
|
|
1557
1565
|
# debug_print("Standard json input")
|
|
@@ -2286,7 +2294,7 @@ class CertoraBuildGenerator:
|
|
|
2286
2294
|
|
|
2287
2295
|
if compiler_lang == CompilerLangSol():
|
|
2288
2296
|
settings_dict: Dict[str, Any] = {}
|
|
2289
|
-
self._fill_codegen_related_options(build_arg_contract_file, settings_dict,
|
|
2297
|
+
self._fill_codegen_related_options(Path(build_arg_contract_file), settings_dict,
|
|
2290
2298
|
compiler_collector_for_contract_file)
|
|
2291
2299
|
solc_optimizer_on, solc_optimizer_runs = self.solc_setting_optimizer_runs(settings_dict)
|
|
2292
2300
|
solc_via_ir = self.solc_setting_via_ir(settings_dict)
|
|
@@ -2416,7 +2424,7 @@ class CertoraBuildGenerator:
|
|
|
2416
2424
|
# if no function finder mode set, determine based on viaIR enabled or not:
|
|
2417
2425
|
if self.context.function_finder_mode is None:
|
|
2418
2426
|
# in via-ir, should not compress
|
|
2419
|
-
if self.
|
|
2427
|
+
if self.get_solc_via_ir_value(Path(contract_file)):
|
|
2420
2428
|
should_compress = False
|
|
2421
2429
|
else:
|
|
2422
2430
|
should_compress = True
|
|
@@ -3614,7 +3622,7 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
|
|
|
3614
3622
|
|
|
3615
3623
|
try:
|
|
3616
3624
|
if overwrite:
|
|
3617
|
-
# expecting target path to exist.
|
|
3625
|
+
# expecting the target path to exist.
|
|
3618
3626
|
if target_path.exists():
|
|
3619
3627
|
build_logger.debug(f"Overwriting {target_path} by copying from {source_path}")
|
|
3620
3628
|
else:
|
|
@@ -3632,7 +3640,7 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
|
|
|
3632
3640
|
cwd_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
3633
3641
|
cwd_file_path.touch()
|
|
3634
3642
|
|
|
3635
|
-
# the empty file .project_directory is written in the source tree to denote the
|
|
3643
|
+
# the empty file .project_directory is written in the source tree to denote the project directory
|
|
3636
3644
|
rust_proj_dir = getattr(context, 'rust_project_directory', None)
|
|
3637
3645
|
if rust_proj_dir:
|
|
3638
3646
|
proj_dir_parent_relative = os.path.relpath(rust_proj_dir, os.getcwd())
|
|
@@ -3683,8 +3691,9 @@ def build_from_scratch(context: CertoraContext,
|
|
|
3683
3691
|
|
|
3684
3692
|
# add to cache also source files that were found in the SDCs (e.g., storage extensions)
|
|
3685
3693
|
paths_set = sdc.all_contract_files
|
|
3686
|
-
for
|
|
3687
|
-
|
|
3694
|
+
for f in context.files:
|
|
3695
|
+
path = f.split(':')[0] # `f` is either 'path/to/file.sol' or 'path/to/file.sol:ContractName'
|
|
3696
|
+
paths_set.add(Path(path).absolute())
|
|
3688
3697
|
|
|
3689
3698
|
# the contract files in SDCs are relative to .certora_sources. Which isn't good for us here.
|
|
3690
3699
|
# Need to be relative to original paths
|
|
@@ -3724,8 +3733,7 @@ def build_from_scratch(context: CertoraContext,
|
|
|
3724
3733
|
|
|
3725
3734
|
def build_from_cache_or_scratch(context: CertoraContext,
|
|
3726
3735
|
certora_build_generator: CertoraBuildGenerator,
|
|
3727
|
-
certora_verify_generator: CertoraVerifyGenerator
|
|
3728
|
-
certora_build_cache_manager: CertoraBuildCacheManager) \
|
|
3736
|
+
certora_verify_generator: CertoraVerifyGenerator) \
|
|
3729
3737
|
-> Tuple[bool, bool, CachedFiles]:
|
|
3730
3738
|
"""
|
|
3731
3739
|
Builds either from cache (fast path) or from scratch (slow path)
|
|
@@ -3742,10 +3750,10 @@ def build_from_cache_or_scratch(context: CertoraContext,
|
|
|
3742
3750
|
False)
|
|
3743
3751
|
return cache_hit, False, cached_files
|
|
3744
3752
|
|
|
3745
|
-
build_cache_applicable =
|
|
3753
|
+
build_cache_applicable = CertoraBuildCacheManager.cache_is_applicable(context)
|
|
3746
3754
|
|
|
3747
3755
|
if not build_cache_applicable:
|
|
3748
|
-
build_cache_disabling_options =
|
|
3756
|
+
build_cache_disabling_options = CertoraBuildCacheManager.cache_disabling_options(context)
|
|
3749
3757
|
build_logger.warning("Requested to enable the build cache, but the build cache is not applicable "
|
|
3750
3758
|
f"to this run because of the given options: {build_cache_disabling_options}")
|
|
3751
3759
|
cached_files = build_from_scratch(context, certora_build_generator,
|
|
@@ -3753,7 +3761,7 @@ def build_from_cache_or_scratch(context: CertoraContext,
|
|
|
3753
3761
|
False)
|
|
3754
3762
|
return cache_hit, False, cached_files
|
|
3755
3763
|
|
|
3756
|
-
cached_files =
|
|
3764
|
+
cached_files = CertoraBuildCacheManager.build_from_cache(context)
|
|
3757
3765
|
# if no match, will rebuild from scratch
|
|
3758
3766
|
if cached_files is not None:
|
|
3759
3767
|
# write to .certora_build.json
|
|
@@ -3792,7 +3800,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
|
|
|
3792
3800
|
|
|
3793
3801
|
try:
|
|
3794
3802
|
input_config = InputConfig(context)
|
|
3795
|
-
|
|
3803
|
+
context.main_cache_key = CertoraBuildCacheManager.get_main_cache_key(context)
|
|
3796
3804
|
# Create generators
|
|
3797
3805
|
certora_build_generator = CertoraBuildGenerator(input_config, context)
|
|
3798
3806
|
|
|
@@ -3808,12 +3816,9 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
|
|
|
3808
3816
|
else:
|
|
3809
3817
|
Ctx.run_local_spec_check(False, context)
|
|
3810
3818
|
|
|
3811
|
-
certora_build_cache_manager = CertoraBuildCacheManager()
|
|
3812
|
-
|
|
3813
3819
|
cache_hit, build_cache_enabled, cached_files = build_from_cache_or_scratch(context,
|
|
3814
3820
|
certora_build_generator,
|
|
3815
|
-
certora_verify_generator
|
|
3816
|
-
certora_build_cache_manager)
|
|
3821
|
+
certora_verify_generator)
|
|
3817
3822
|
|
|
3818
3823
|
# avoid running the same test over and over again for each split run, context.split_rules is true only for
|
|
3819
3824
|
# the first run and is set to [] for split runs
|
|
@@ -3833,7 +3838,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
|
|
|
3833
3838
|
|
|
3834
3839
|
# save in build cache
|
|
3835
3840
|
if not cache_hit and build_cache_enabled and cached_files.may_store_in_build_cache:
|
|
3836
|
-
|
|
3841
|
+
CertoraBuildCacheManager.save_build_cache(context, cached_files)
|
|
3837
3842
|
|
|
3838
3843
|
certora_verify_generator.update_certora_verify_struct(True)
|
|
3839
3844
|
certora_verify_generator.dump() # second dump with properly rooted specs
|
|
@@ -66,10 +66,9 @@ class CertoraBuildCacheManager:
|
|
|
66
66
|
@returns None if no cache hit, otherwise - the matching `.certora_build.json` file, and a set of source files
|
|
67
67
|
"""
|
|
68
68
|
build_cache_dir = Util.get_certora_build_cache_dir()
|
|
69
|
-
|
|
70
|
-
main_cache_entry_dir = build_cache_dir / main_cache_key
|
|
69
|
+
main_cache_entry_dir = build_cache_dir / context.main_cache_key
|
|
71
70
|
if not main_cache_entry_dir.exists():
|
|
72
|
-
build_cache_logger.info(f"cache miss on build cache key {main_cache_key}")
|
|
71
|
+
build_cache_logger.info(f"cache miss on build cache key {context.main_cache_key}")
|
|
73
72
|
return None
|
|
74
73
|
|
|
75
74
|
# here's the tricky matching part:
|
|
@@ -92,7 +91,7 @@ class CertoraBuildCacheManager:
|
|
|
92
91
|
build_cache_logger.debug(f"Current sub build cache key computed for {file_list_file} is invalid")
|
|
93
92
|
continue
|
|
94
93
|
elif sub_cache_key_current_for_list == sub_cache_key_from_saved_entry:
|
|
95
|
-
build_cache_logger.info(f"We have a match on build cache key {main_cache_key} and "
|
|
94
|
+
build_cache_logger.info(f"We have a match on build cache key {context.main_cache_key} and "
|
|
96
95
|
f"{sub_cache_key_current_for_list}")
|
|
97
96
|
sub_cache_key = sub_cache_key_current_for_list
|
|
98
97
|
all_contract_files = set([Path(f) for f in file_list])
|
|
@@ -103,24 +102,24 @@ class CertoraBuildCacheManager:
|
|
|
103
102
|
|
|
104
103
|
if sub_cache_key is None:
|
|
105
104
|
build_cache_logger.info("All sub-cache-key file list files missed, cache miss on build cache key "
|
|
106
|
-
f"{main_cache_key}")
|
|
105
|
+
f"{context.main_cache_key}")
|
|
107
106
|
return None
|
|
108
107
|
|
|
109
108
|
# cache hit
|
|
110
109
|
certora_build_file = main_cache_entry_dir / f"{sub_cache_key}.{CachedFiles.certora_build_suffix}"
|
|
111
110
|
if not certora_build_file.exists():
|
|
112
|
-
build_cache_logger.warning(f"Got a cache-hit on build cache key {main_cache_key} and {sub_cache_key} "
|
|
111
|
+
build_cache_logger.warning(f"Got a cache-hit on build cache key {context.main_cache_key} and {sub_cache_key} "
|
|
113
112
|
"but .certora_build.json file does not exist")
|
|
114
113
|
return None
|
|
115
114
|
|
|
116
115
|
if all_contract_files is None: # should not be feasible
|
|
117
|
-
build_cache_logger.warning(f"Got a cache-hit on build cache key {main_cache_key} and
|
|
118
|
-
"but file list file does not exist")
|
|
116
|
+
build_cache_logger.warning(f"Got a cache-hit on build cache key {context.main_cache_key} and "
|
|
117
|
+
f"{sub_cache_key} but file list file does not exist")
|
|
119
118
|
return None
|
|
120
119
|
|
|
121
120
|
build_output_props_file = main_cache_entry_dir / f"{sub_cache_key}.{CachedFiles.build_output_props_suffix}"
|
|
122
121
|
if not build_output_props_file.exists():
|
|
123
|
-
build_cache_logger.warning(f"Got a cache-hit on build cache key {main_cache_key} and {sub_cache_key} "
|
|
122
|
+
build_cache_logger.warning(f"Got a cache-hit on build cache key {context.main_cache_key} and {sub_cache_key} "
|
|
124
123
|
f"but {CachedFiles.build_output_props_suffix} file does not exist")
|
|
125
124
|
return None
|
|
126
125
|
|
|
@@ -131,26 +130,26 @@ class CertoraBuildCacheManager:
|
|
|
131
130
|
@staticmethod
|
|
132
131
|
def save_build_cache(context: CertoraContext, cached_files: CachedFiles) -> None:
|
|
133
132
|
build_cache_dir = Util.get_certora_build_cache_dir()
|
|
134
|
-
|
|
135
|
-
main_cache_entry_dir = build_cache_dir / main_cache_key
|
|
133
|
+
main_cache_entry_dir = build_cache_dir / context.main_cache_key
|
|
136
134
|
sub_cache_key = CertoraBuildCacheManager.get_sub_cache_key(cached_files.all_contract_files)
|
|
137
135
|
if sub_cache_key is None:
|
|
138
|
-
build_cache_logger.warning(f"Cannot save cache for main build cache key {main_cache_key} "
|
|
136
|
+
build_cache_logger.warning(f"Cannot save cache for main build cache key {context.main_cache_key} "
|
|
139
137
|
"as sub-cache-key could not be computed")
|
|
140
138
|
return
|
|
141
139
|
|
|
142
140
|
if main_cache_entry_dir.exists():
|
|
143
|
-
build_cache_logger.info(f"main build cache key already exists {main_cache_key}, "
|
|
141
|
+
build_cache_logger.info(f"main build cache key already exists {context.main_cache_key}, "
|
|
144
142
|
f"saving sub build cache key {sub_cache_key}")
|
|
145
143
|
if cached_files.all_exist(main_cache_entry_dir, sub_cache_key):
|
|
146
144
|
build_cache_logger.debug("cache already saved under this build cache key, override")
|
|
147
145
|
else:
|
|
148
|
-
build_cache_logger.debug(f"cache was corrupted, need to re-save build cache key
|
|
149
|
-
f"and sub cache key {sub_cache_key}")
|
|
146
|
+
build_cache_logger.debug(f"cache was corrupted, need to re-save build cache key "
|
|
147
|
+
f"{context.main_cache_key} and sub cache key {sub_cache_key}")
|
|
150
148
|
CertoraBuildCacheManager.save_files(cached_files, main_cache_entry_dir,
|
|
151
149
|
sub_cache_key)
|
|
152
150
|
else:
|
|
153
|
-
build_cache_logger.info(f"saving main build cache key {main_cache_key} and sub cache key
|
|
151
|
+
build_cache_logger.info(f"saving main build cache key {context.main_cache_key} and sub cache key "
|
|
152
|
+
f"{sub_cache_key}")
|
|
154
153
|
safe_create_dir(main_cache_entry_dir)
|
|
155
154
|
CertoraBuildCacheManager.save_files(cached_files, main_cache_entry_dir,
|
|
156
155
|
sub_cache_key)
|
|
@@ -51,6 +51,7 @@ def build_rust_app(context: CertoraContext) -> None:
|
|
|
51
51
|
feature_flag = '--features'
|
|
52
52
|
if context.cargo_tools_version:
|
|
53
53
|
build_command.extend(["--tools-version", context.cargo_tools_version])
|
|
54
|
+
context.rust_project_directory = Util.find_nearest_cargo_toml()
|
|
54
55
|
|
|
55
56
|
if context.cargo_features is not None:
|
|
56
57
|
build_command.append(feature_flag)
|
|
@@ -102,7 +103,7 @@ def add_solana_files_to_sources(context: CertoraContext, sources: Set[Path]) ->
|
|
|
102
103
|
|
|
103
104
|
|
|
104
105
|
def collect_files_from_rust_sources(context: CertoraContext, sources: Set[Path]) -> None:
|
|
105
|
-
patterns = ["*.rs", "*.so", "*.wasm",
|
|
106
|
+
patterns = ["*.rs", "*.so", "*.wasm", Util.CARGO_TOML_FILE, "Cargo.lock", "justfile"]
|
|
106
107
|
exclude_dirs = [".certora_internal"]
|
|
107
108
|
|
|
108
109
|
if hasattr(context, 'rust_project_directory'):
|
|
@@ -760,42 +760,20 @@ class CloudVerification:
|
|
|
760
760
|
Util.get_configuration_layout_data_file(),
|
|
761
761
|
Util.get_build_dir() / Path(self.context.files[0]).name]
|
|
762
762
|
|
|
763
|
+
if Util.get_debug_log_file().exists():
|
|
764
|
+
files_list.append(Util.get_debug_log_file())
|
|
765
|
+
|
|
763
766
|
if Util.get_certora_sources_dir().exists():
|
|
764
767
|
files_list.append(Util.get_certora_sources_dir())
|
|
765
768
|
|
|
766
769
|
if hasattr(self.context, 'build_script') and self.context.build_script:
|
|
767
|
-
result = compress_files(self.logZipFilePath, Util.get_debug_log_file(),
|
|
768
|
-
short_output=Ctx.is_minimal_cli_output(self.context))
|
|
769
|
-
|
|
770
|
-
if not result:
|
|
771
|
-
return False
|
|
772
|
-
files_list.append(self.logZipFilePath)
|
|
773
|
-
|
|
774
|
-
# Create a .RustExecution file to classify zipInput as a rust source code
|
|
775
|
-
rust_execution_file = Util.get_build_dir() / ".RustExecution"
|
|
776
|
-
rust_execution_file.touch(exist_ok=True)
|
|
777
|
-
files_list.append(rust_execution_file)
|
|
778
|
-
|
|
779
770
|
if attr_file := getattr(self.context, 'rust_logs_stdout', None):
|
|
780
771
|
files_list.append(Util.get_build_dir() / Path(attr_file).name)
|
|
781
772
|
if attr_file := getattr(self.context, 'rust_logs_stderr', None):
|
|
782
773
|
files_list.append(Util.get_build_dir() / Path(attr_file).name)
|
|
783
774
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
elif Attrs.is_solana_app() or Attrs.is_soroban_app():
|
|
788
|
-
for file in self.context.files:
|
|
789
|
-
files_list.append(Path(file))
|
|
790
|
-
|
|
791
|
-
result = compress_files(self.ZipFilePath, *files_list,
|
|
792
|
-
short_output=Ctx.is_minimal_cli_output(self.context))
|
|
793
|
-
else:
|
|
794
|
-
raise Util.CertoraUserInputError(
|
|
795
|
-
'When running rust application, context should either have attribute "rust_executables" '
|
|
796
|
-
'provided by build_script execution, '
|
|
797
|
-
'or either is_solana_app(), is_soroban_app() should be set to True'
|
|
798
|
-
)
|
|
775
|
+
result = compress_files(self.ZipFilePath, *files_list,
|
|
776
|
+
short_output=Ctx.is_minimal_cli_output(self.context))
|
|
799
777
|
|
|
800
778
|
else:
|
|
801
779
|
# Zip the log file first separately and again with the rest of the files, so it will not be decompressed
|
|
@@ -953,6 +931,8 @@ class CloudVerification:
|
|
|
953
931
|
for i in range(self.verification_request_retries):
|
|
954
932
|
try:
|
|
955
933
|
response = requests.post(verify_url, json=auth_data, headers=headers, timeout=60)
|
|
934
|
+
cloud_logger.debug(f"\n\n=================\n\nresponse - Status Code: {response.status_code}\n\n"
|
|
935
|
+
f"Headers:\n {response.headers}\n\nText:\n {response.text}\n\n=================\n\n")
|
|
956
936
|
if response is None:
|
|
957
937
|
continue
|
|
958
938
|
status = response.status_code
|
|
@@ -25,12 +25,13 @@ sys.path.insert(0, str(scripts_dir_path))
|
|
|
25
25
|
import CertoraProver.certoraContextAttributes as Attrs
|
|
26
26
|
from CertoraProver.certoraCollectRunMetadata import RunMetaData, MetadataEncoder
|
|
27
27
|
import Shared.certoraUtils as Utils
|
|
28
|
+
from typing import List
|
|
28
29
|
|
|
29
30
|
|
|
30
31
|
class MainSection(Enum):
|
|
31
32
|
GENERAL = "GENERAL"
|
|
33
|
+
OPTIONS = "OPTIONS"
|
|
32
34
|
SOLIDITY_COMPILER = "SOLIDITY_COMPILER"
|
|
33
|
-
GIT = "GIT"
|
|
34
35
|
NEW_SECTION = "NEW_SECTION"
|
|
35
36
|
|
|
36
37
|
|
|
@@ -47,13 +48,11 @@ class InnerContent:
|
|
|
47
48
|
content: Any
|
|
48
49
|
doc_link: str = ''
|
|
49
50
|
tooltip: str = ''
|
|
50
|
-
unsound:
|
|
51
|
+
unsound: bool = False
|
|
51
52
|
|
|
52
53
|
def __post_init__(self) -> None:
|
|
53
54
|
if isinstance(self.content, bool):
|
|
54
55
|
self.content = 'true' if self.content else 'false'
|
|
55
|
-
if isinstance(self.unsound, bool):
|
|
56
|
-
self.unsound = 'true' if self.unsound else 'false'
|
|
57
56
|
|
|
58
57
|
|
|
59
58
|
@dataclasses.dataclass
|
|
@@ -65,6 +64,7 @@ class CardContent:
|
|
|
65
64
|
|
|
66
65
|
DOC_LINK_PREFIX = 'https://docs.certora.com/en/latest/docs/'
|
|
67
66
|
GIT_ATTRIBUTES = ['origin', 'revision', 'branch', 'dirty']
|
|
67
|
+
ARG_LIST_ATTRIBUTES = ['prover_args', 'java_args']
|
|
68
68
|
|
|
69
69
|
|
|
70
70
|
class AttributeJobConfigData:
|
|
@@ -73,13 +73,13 @@ class AttributeJobConfigData:
|
|
|
73
73
|
This should be added to the AttributeDefinition and configured for every new attribute
|
|
74
74
|
presented in the Rule report.
|
|
75
75
|
|
|
76
|
-
Note: Attributes
|
|
76
|
+
Note: Attributes that do not contain specific information will be presented in the OPTIONS main section!
|
|
77
77
|
|
|
78
78
|
arguments:
|
|
79
79
|
- main_section : MainSection -- the main section inside the config tab
|
|
80
|
-
default: MainSection.
|
|
81
|
-
- subsection : str -- the subsection within the main_section
|
|
82
|
-
default:
|
|
80
|
+
default: MainSection.OPTIONS
|
|
81
|
+
- subsection : str -- the subsection within the main_section
|
|
82
|
+
default: None - they will be presented inside the OPTIONS card
|
|
83
83
|
- doc_link : Optional[str] -- a link to the Documentation page of this attribute (if exists)
|
|
84
84
|
default: 'https://docs.certora.com/en/latest/docs/' + Solana/EVM path + #<attribute_name>
|
|
85
85
|
- tooltip : Optional[str] -- a description of this attribute to present in the config tab
|
|
@@ -88,7 +88,7 @@ class AttributeJobConfigData:
|
|
|
88
88
|
default: False
|
|
89
89
|
"""
|
|
90
90
|
|
|
91
|
-
def __init__(self, main_section: MainSection = MainSection.
|
|
91
|
+
def __init__(self, main_section: MainSection = MainSection.OPTIONS, subsection: str = '',
|
|
92
92
|
doc_link: Optional[str] = '', tooltip: Optional[str] = '', unsound: bool = False):
|
|
93
93
|
self.main_section = main_section
|
|
94
94
|
self.subsection = subsection
|
|
@@ -201,6 +201,40 @@ def create_or_get_card_content(output: list[CardContent], name: str) -> CardCont
|
|
|
201
201
|
return main_section
|
|
202
202
|
|
|
203
203
|
|
|
204
|
+
def split_and_sort_arg_list_value(args_list: List[str]) -> List[str]:
|
|
205
|
+
"""
|
|
206
|
+
Splits a unified CLI argument list of strings into a sorted list of flag+value groups.
|
|
207
|
+
This is useful mainly for --prover_args and --java_args.
|
|
208
|
+
|
|
209
|
+
For example:
|
|
210
|
+
"-depth 15 -adaptiveSolverConfig false" → ["-adaptiveSolverConfig false", "-depth 15"]
|
|
211
|
+
|
|
212
|
+
Assumes each flag starts with '-' and its value follows immediately, if exists.
|
|
213
|
+
Lines are sorted alphabetically.
|
|
214
|
+
"""
|
|
215
|
+
unified_args = ''.join(args_list)
|
|
216
|
+
|
|
217
|
+
if not unified_args.strip():
|
|
218
|
+
return []
|
|
219
|
+
|
|
220
|
+
lines: List[str] = []
|
|
221
|
+
tokens = unified_args.split()
|
|
222
|
+
curr_line = ""
|
|
223
|
+
|
|
224
|
+
for token in tokens:
|
|
225
|
+
if token.startswith('-'):
|
|
226
|
+
if curr_line:
|
|
227
|
+
lines.append(curr_line)
|
|
228
|
+
curr_line = token
|
|
229
|
+
else:
|
|
230
|
+
curr_line += f" {token}"
|
|
231
|
+
|
|
232
|
+
if curr_line:
|
|
233
|
+
lines.append(curr_line)
|
|
234
|
+
|
|
235
|
+
return sorted(lines)
|
|
236
|
+
|
|
237
|
+
|
|
204
238
|
def create_inner_content(name: str, content_type: ContentType, value: Any, doc_link: str,
|
|
205
239
|
config_data: AttributeJobConfigData) -> InnerContent:
|
|
206
240
|
return InnerContent(
|
|
@@ -209,7 +243,7 @@ def create_inner_content(name: str, content_type: ContentType, value: Any, doc_l
|
|
|
209
243
|
content=value,
|
|
210
244
|
doc_link=doc_link,
|
|
211
245
|
tooltip=config_data.tooltip or '',
|
|
212
|
-
unsound=
|
|
246
|
+
unsound=config_data.unsound
|
|
213
247
|
)
|
|
214
248
|
|
|
215
249
|
|
|
@@ -260,35 +294,23 @@ def collect_attribute_configs(metadata: dict) -> list[CardContent]:
|
|
|
260
294
|
)
|
|
261
295
|
continue
|
|
262
296
|
|
|
263
|
-
# Find or create the subsection (if it
|
|
297
|
+
# Find or create the subsection (if it doesn't exist)
|
|
264
298
|
if isinstance(attr_value, list):
|
|
265
|
-
current_section: Any = main_section
|
|
266
299
|
content_type = ContentType.SIMPLE
|
|
300
|
+
if attr_name in ARG_LIST_ATTRIBUTES:
|
|
301
|
+
attr_value = split_and_sort_arg_list_value(attr_value)
|
|
267
302
|
|
|
268
303
|
elif isinstance(attr_value, dict):
|
|
269
|
-
current_section = main_section
|
|
270
304
|
content_type = ContentType.COMPLEX
|
|
271
305
|
attr_value = [
|
|
272
306
|
create_inner_content(key, ContentType.FLAG, value, doc_link, config_data)
|
|
273
307
|
for key, value in attr_value.items()
|
|
274
308
|
]
|
|
275
309
|
else:
|
|
276
|
-
# this attribute is a value attribute without a subsection, it will be placed in flags.
|
|
277
|
-
subsection_key = config_data.subsection.lower() if config_data.subsection else 'flags'
|
|
278
|
-
current_section = next((section for section in main_section.content
|
|
279
|
-
if section.inner_title == subsection_key), None)
|
|
280
|
-
if current_section is None:
|
|
281
|
-
current_section = InnerContent(
|
|
282
|
-
inner_title=subsection_key,
|
|
283
|
-
content_type=ContentType.COMPLEX.value,
|
|
284
|
-
content=[]
|
|
285
|
-
)
|
|
286
|
-
main_section.content.append(current_section)
|
|
287
|
-
|
|
288
310
|
content_type = ContentType.FLAG
|
|
289
311
|
|
|
290
312
|
# Update the current section with attribute details
|
|
291
|
-
|
|
313
|
+
main_section.content.append(
|
|
292
314
|
create_inner_content(attr_name, content_type, attr_value, doc_link, config_data)
|
|
293
315
|
)
|
|
294
316
|
|
|
@@ -300,37 +322,23 @@ def collect_run_config_from_metadata(attributes_configs: list[CardContent], meta
|
|
|
300
322
|
Adding CLI and Git configuration from metadata
|
|
301
323
|
"""
|
|
302
324
|
|
|
303
|
-
# Define a mapping of metadata attributes to their keys in general_section
|
|
304
|
-
metadata_mappings = {
|
|
305
|
-
'CLI Version': metadata.get('CLI_version'),
|
|
306
|
-
'Verify': metadata.get('conf', {}).get('verify'),
|
|
307
|
-
}
|
|
308
|
-
|
|
309
325
|
general_section = create_or_get_card_content(attributes_configs, MainSection.GENERAL.value.lower())
|
|
310
326
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
content=value,
|
|
318
|
-
))
|
|
327
|
+
if cli_version := metadata.get('CLI_version'):
|
|
328
|
+
general_section.content.append(InnerContent(
|
|
329
|
+
inner_title='CLI Version',
|
|
330
|
+
content_type=ContentType.FLAG.value,
|
|
331
|
+
content=cli_version,
|
|
332
|
+
))
|
|
319
333
|
|
|
320
|
-
# Adding GIT configuration from metadata only if attributes are found
|
|
321
|
-
git_content = []
|
|
322
334
|
for attr in GIT_ATTRIBUTES:
|
|
323
335
|
if attr_value := metadata.get(attr):
|
|
324
|
-
|
|
336
|
+
general_section.content.append(InnerContent(
|
|
325
337
|
inner_title=attr,
|
|
326
338
|
content_type=ContentType.FLAG.value,
|
|
327
339
|
content=attr_value,
|
|
328
340
|
))
|
|
329
341
|
|
|
330
|
-
if git_content:
|
|
331
|
-
git_section = create_or_get_card_content(attributes_configs, MainSection.GIT.value.lower())
|
|
332
|
-
git_section.content = git_content
|
|
333
|
-
|
|
334
342
|
return attributes_configs
|
|
335
343
|
|
|
336
344
|
|
|
@@ -338,14 +346,17 @@ def sort_configuration_layout(data: list[CardContent]) -> list[CardContent]:
|
|
|
338
346
|
"""
|
|
339
347
|
Sorts a configuration layout:
|
|
340
348
|
- Top-level sorted by 'card_title'
|
|
341
|
-
- Nested content sorted by 'inner_title', with '
|
|
349
|
+
- Nested content sorted by 'inner_title', with 'verify' first
|
|
342
350
|
"""
|
|
343
351
|
priority = {
|
|
344
|
-
|
|
352
|
+
# Priorities for top-level cards
|
|
345
353
|
"general": 0,
|
|
354
|
+
"files": 1,
|
|
355
|
+
"options": 2,
|
|
356
|
+
# Top level items inside their respective cards
|
|
357
|
+
"verify": 0,
|
|
346
358
|
"solc": 0,
|
|
347
|
-
"CLI Version":
|
|
348
|
-
"flags": 2
|
|
359
|
+
"CLI Version": 0
|
|
349
360
|
}
|
|
350
361
|
|
|
351
362
|
def inner_sort_key(item: Any) -> Any:
|
|
@@ -17,6 +17,7 @@ import json5
|
|
|
17
17
|
import logging
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
from typing import Dict, Any
|
|
20
|
+
from collections import OrderedDict
|
|
20
21
|
|
|
21
22
|
import CertoraProver.certoraContext as Ctx
|
|
22
23
|
import CertoraProver.certoraContextAttributes as Attrs
|
|
@@ -75,7 +76,7 @@ def read_from_conf_file(context: CertoraContext) -> None:
|
|
|
75
76
|
|
|
76
77
|
try:
|
|
77
78
|
with conf_file_path.open() as conf_file:
|
|
78
|
-
context.conf_file_attr = json5.load(conf_file, allow_duplicate_keys=False)
|
|
79
|
+
context.conf_file_attr = json5.load(conf_file, allow_duplicate_keys=False, object_pairs_hook=OrderedDict)
|
|
79
80
|
try:
|
|
80
81
|
check_conf_content(context)
|
|
81
82
|
except Util.CertoraUserInputError as e:
|