certora-cli-beta-mirror 7.30.1__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/Compiler/CompilerCollectorFactory.py +11 -2
- certora_cli/CertoraProver/certoraBuild.py +68 -62
- certora_cli/CertoraProver/certoraBuildCacheManager.py +17 -16
- certora_cli/CertoraProver/certoraBuildRust.py +33 -21
- certora_cli/{Shared/rustProverCommon.py → CertoraProver/certoraBuildSui.py} +24 -18
- certora_cli/CertoraProver/certoraCloudIO.py +42 -33
- certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +62 -51
- certora_cli/CertoraProver/certoraCollectRunMetadata.py +20 -5
- certora_cli/CertoraProver/certoraConfigIO.py +17 -14
- certora_cli/CertoraProver/certoraContext.py +62 -10
- certora_cli/CertoraProver/certoraContextAttributes.py +132 -203
- certora_cli/CertoraProver/certoraContextValidator.py +108 -101
- certora_cli/CertoraProver/certoraParseBuildScript.py +4 -3
- 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 +50 -47
- certora_cli/Shared/certoraValidateFuncs.py +29 -15
- certora_cli/Shared/proverCommon.py +6 -2
- certora_cli/certoraCVLFormatter.py +76 -0
- certora_cli/certoraConcord.py +39 -0
- certora_cli/certoraRun.py +53 -91
- certora_cli/certoraSolanaProver.py +1 -1
- certora_cli/certoraSorobanProver.py +1 -1
- {certora_cli_beta_mirror-7.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/METADATA +4 -3
- {certora_cli_beta_mirror-7.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/RECORD +36 -33
- {certora_cli_beta_mirror-7.30.1.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.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/LICENSE +0 -0
- {certora_cli_beta_mirror-7.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/WHEEL +0 -0
- {certora_cli_beta_mirror-7.30.1.dist-info → certora_cli_beta_mirror-8.0.0.dist-info}/top_level.txt +0 -0
|
@@ -13,6 +13,7 @@
|
|
|
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
|
+
|
|
16
17
|
import logging
|
|
17
18
|
import os
|
|
18
19
|
import re
|
|
@@ -21,7 +22,8 @@ import itertools
|
|
|
21
22
|
import tempfile
|
|
22
23
|
import fnmatch
|
|
23
24
|
from pathlib import Path
|
|
24
|
-
from
|
|
25
|
+
from wcmatch import glob
|
|
26
|
+
from typing import Dict, List, Tuple, Set, Any, Union, OrderedDict
|
|
25
27
|
|
|
26
28
|
import CertoraProver.certoraContext as Ctx
|
|
27
29
|
import CertoraProver.certoraContextAttributes as Attrs
|
|
@@ -31,6 +33,7 @@ from Shared import certoraUtils as Util
|
|
|
31
33
|
from Shared import certoraAttrUtil as AttrUtil
|
|
32
34
|
from CertoraProver.certoraProjectScanner import scan_project
|
|
33
35
|
|
|
36
|
+
|
|
34
37
|
scripts_dir_path = Path(__file__).parent.resolve() # containing directory
|
|
35
38
|
sys.path.insert(0, str(scripts_dir_path))
|
|
36
39
|
|
|
@@ -43,10 +46,28 @@ class CertoraContextValidator:
|
|
|
43
46
|
def __init__(self, context: CertoraContext):
|
|
44
47
|
self.context = context
|
|
45
48
|
|
|
49
|
+
def handle_concord_attrs(self) -> None:
|
|
50
|
+
if Attrs.is_concord_app():
|
|
51
|
+
for attr in (Attrs.ConcordAttributes.unsupported_attributes()):
|
|
52
|
+
attr_name = attr.get_conf_key()
|
|
53
|
+
if getattr(self.context, attr_name):
|
|
54
|
+
if attr.arg_type == AttrUtil.AttrArgType.BOOLEAN:
|
|
55
|
+
setattr(self.context, attr_name, False)
|
|
56
|
+
else:
|
|
57
|
+
setattr(self.context, attr_name, None)
|
|
58
|
+
raise Util.CertoraUserInputError(f"Concord does not support {attr_name}")
|
|
59
|
+
if not self.context.check_method:
|
|
60
|
+
raise Util.CertoraUserInputError("'check_method' attribute/flag is mandatory when running Concord")
|
|
61
|
+
contracts = getattr(self.context, 'contracts', None)
|
|
62
|
+
if not contracts:
|
|
63
|
+
raise Util.CertoraUserInputError("No contracts were specified for Concord")
|
|
64
|
+
if len(contracts) != 2:
|
|
65
|
+
raise Util.CertoraUserInputError(f"expecting 2 contracts for Concord, got {len(contracts)}: {contracts}")
|
|
66
|
+
|
|
46
67
|
def handle_ranger_attrs(self) -> None:
|
|
47
68
|
# unset unsupported attributes
|
|
48
69
|
if Attrs.is_ranger_app():
|
|
49
|
-
for attr in Attrs.RangerAttributes.
|
|
70
|
+
for attr in Attrs.RangerAttributes.unsupported_attributes():
|
|
50
71
|
attr_name = attr.get_conf_key()
|
|
51
72
|
if getattr(self.context, attr_name):
|
|
52
73
|
if attr.arg_type == AttrUtil.AttrArgType.BOOLEAN:
|
|
@@ -64,16 +85,16 @@ class CertoraContextValidator:
|
|
|
64
85
|
f"ignoring the set value of {self.context.loop_iter}")
|
|
65
86
|
self.context.loop_iter = self.context.loop_iter or Util.DEFAULT_RANGER_LOOP_ITER
|
|
66
87
|
|
|
67
|
-
for attr in Attrs.RangerAttributes.
|
|
88
|
+
for attr in Attrs.RangerAttributes.true_by_default_attributes():
|
|
68
89
|
attr_name = attr.get_conf_key()
|
|
69
90
|
setattr(self.context, attr_name, True)
|
|
70
91
|
|
|
71
92
|
else:
|
|
72
93
|
if self.context.range:
|
|
73
|
-
|
|
94
|
+
self.context.range = None
|
|
74
95
|
validation_logger.info("the 'range' attribute is ignored when not running from the Ranger App")
|
|
75
96
|
if self.context.ranger_failure_limit:
|
|
76
|
-
|
|
97
|
+
self.context.ranger_failure_limit = None
|
|
77
98
|
validation_logger.info("the 'ranger_failure_limit' is ignored when not running from the Ranger App")
|
|
78
99
|
|
|
79
100
|
def validate(self) -> None:
|
|
@@ -162,6 +183,12 @@ class CertoraContextValidator:
|
|
|
162
183
|
if len(context.files) > 1:
|
|
163
184
|
raise Util.CertoraUserInputError("Rust projects must specify exactly one executable in 'files'.")
|
|
164
185
|
|
|
186
|
+
if context.url_visibility and context.local:
|
|
187
|
+
validation_logger.info("'url_visibility' has no effect in local tool runs")
|
|
188
|
+
set_attr_default(context, attr_name=Attrs.CommonAttributes.URL_VISIBILITY.name.lower(),
|
|
189
|
+
ci_value=str(Vf.UrlVisibilityOptions.PUBLIC),
|
|
190
|
+
default_value=str(Vf.UrlVisibilityOptions.PRIVATE))
|
|
191
|
+
|
|
165
192
|
def check_args_post_argparse(self) -> None:
|
|
166
193
|
"""
|
|
167
194
|
Performs checks over the arguments after basic argparse parsing
|
|
@@ -216,10 +243,18 @@ class CertoraContextValidator:
|
|
|
216
243
|
if context.compilation_steps_only and context.build_only:
|
|
217
244
|
raise Util.CertoraUserInputError("cannot use both 'compilation_steps_only' and 'build_only'")
|
|
218
245
|
|
|
219
|
-
|
|
246
|
+
set_attr_default(context, attr_name=Attrs.CommonAttributes.WAIT_FOR_RESULTS.name.lower(),
|
|
247
|
+
ci_value=str(Vf.WaitForResultOptions.ALL),
|
|
248
|
+
default_value=str(Vf.WaitForResultOptions.NONE))
|
|
220
249
|
if context.wait_for_results and context.wait_for_results != str(Vf.WaitForResultOptions.NONE) and context.local:
|
|
221
250
|
validation_logger.warning("'wait_for_results' has no effect in local tool runs")
|
|
222
251
|
|
|
252
|
+
if context.url_visibility and context.local:
|
|
253
|
+
validation_logger.info("'url_visibility' has no effect in local tool runs")
|
|
254
|
+
set_attr_default(context, attr_name=Attrs.CommonAttributes.URL_VISIBILITY.name.lower(),
|
|
255
|
+
ci_value=str(Vf.UrlVisibilityOptions.PUBLIC),
|
|
256
|
+
default_value=str(Vf.UrlVisibilityOptions.PRIVATE))
|
|
257
|
+
|
|
223
258
|
# packages must be in a normal form (no unneeded . or ..)
|
|
224
259
|
if context.packages:
|
|
225
260
|
for idx, el in enumerate(context.packages):
|
|
@@ -375,19 +410,15 @@ def convert_to_compiler_map(context: CertoraContext) -> None:
|
|
|
375
410
|
if context.solc_map:
|
|
376
411
|
context.compiler_map = context.solc_map
|
|
377
412
|
context.solc_map = None
|
|
378
|
-
|
|
379
|
-
context.compiler_map = {'*.sol': f'{context.solc}'}
|
|
380
|
-
if context.vyper:
|
|
381
|
-
context.compiler_map = {'*.vy': f'{context.vyper}'}
|
|
413
|
+
|
|
382
414
|
|
|
383
415
|
def check_vyper_flag(context: CertoraContext) -> None:
|
|
384
416
|
if context.vyper:
|
|
385
|
-
|
|
386
|
-
if
|
|
387
|
-
|
|
417
|
+
vy_paths = [path for path in context.files if path.endswith(".vy")]
|
|
418
|
+
if not vy_paths:
|
|
419
|
+
validation_logger.warning("vyper attribute was set but no Vyper files were set")
|
|
388
420
|
if context.solc:
|
|
389
421
|
raise Util.CertoraUserInputError("cannot set both vyper attribute and solc attribute")
|
|
390
|
-
context.solc = context.vyper
|
|
391
422
|
|
|
392
423
|
def check_contract_name_arg_inputs(context: CertoraContext) -> None:
|
|
393
424
|
"""
|
|
@@ -429,14 +460,6 @@ def check_contract_name_arg_inputs(context: CertoraContext) -> None:
|
|
|
429
460
|
check_conflicting_link_args(context)
|
|
430
461
|
|
|
431
462
|
context.verified_contract_files = []
|
|
432
|
-
if context.assert_contracts is not None:
|
|
433
|
-
for assert_arg in context.assert_contracts:
|
|
434
|
-
contract = Util.get_trivial_contract_name(assert_arg)
|
|
435
|
-
if contract not in contract_names:
|
|
436
|
-
__suggest_contract_name(f"'assert' argument, {contract}, doesn't match any contract name", contract,
|
|
437
|
-
contract_names, contract_to_file)
|
|
438
|
-
else:
|
|
439
|
-
context.verified_contract_files.append(contract_to_file[contract])
|
|
440
463
|
|
|
441
464
|
context.spec_file = None
|
|
442
465
|
|
|
@@ -582,31 +605,32 @@ def check_mode_of_operation(context: CertoraContext) -> None:
|
|
|
582
605
|
|
|
583
606
|
@param context: A namespace including all CLI arguments provided
|
|
584
607
|
@raise an CertoraUserInputError when:
|
|
585
|
-
1.
|
|
586
|
-
2. when
|
|
587
|
-
3.
|
|
588
|
-
4. If either --bytecode_jsons or --bytecode_spec was used without the other.
|
|
608
|
+
1. when both --equivalence_contracts and --verify flags were given
|
|
609
|
+
2. when the file is not .tac|.conf|.json and neither --equivalence_contracts nor --verify were used
|
|
610
|
+
3. If either --bytecode_jsons or --bytecode_spec was used without the other.
|
|
589
611
|
"""
|
|
590
612
|
context.is_verify = context.verify is not None and len(context.verify) > 0
|
|
591
|
-
context.is_assert = context.assert_contracts is not None and len(context.assert_contracts) > 0
|
|
592
613
|
context.is_bytecode = context.bytecode_jsons is not None and len(context.bytecode_jsons) > 0
|
|
593
|
-
context.is_equivalence = context.equivalence_contracts is not None
|
|
614
|
+
context.is_equivalence = context.equivalence_contracts is not None or context.is_concord_app
|
|
594
615
|
|
|
595
|
-
if (context.project_sanity or context.foundry) and
|
|
596
|
-
|
|
616
|
+
if (context.project_sanity or context.foundry) and \
|
|
617
|
+
(context.is_verify or context.is_bytecode or context.is_equivalence):
|
|
618
|
+
raise Util.CertoraUserInputError("The 'project_sanity' and 'foundry' options cannot coexist with the 'verify', "
|
|
619
|
+
"'assert_contract' or 'bytecode_jsons' options")
|
|
597
620
|
|
|
598
621
|
if context.project_sanity and context.foundry:
|
|
599
622
|
raise Util.CertoraUserInputError("The 'project_sanity' and 'foundry' options cannot coexist")
|
|
600
623
|
|
|
601
|
-
if len(list(filter(None, [context.is_verify, context.
|
|
602
|
-
raise Util.CertoraUserInputError("only one option of '
|
|
624
|
+
if len(list(filter(None, [context.is_verify, context.is_equivalence]))) > 1:
|
|
625
|
+
raise Util.CertoraUserInputError("only one option of 'verify', 'equivalence' can be used")
|
|
603
626
|
|
|
604
627
|
has_bytecode_spec = context.bytecode_spec is not None
|
|
605
628
|
if has_bytecode_spec != context.is_bytecode:
|
|
606
629
|
raise Util.CertoraUserInputError("Must use 'bytecode' together with 'bytecode_spec'")
|
|
607
630
|
|
|
608
631
|
if not context.files and not any((context.is_bytecode, context.project_sanity, context.foundry)):
|
|
609
|
-
raise Util.CertoraUserInputError("Should always provide input files, unless 'bytecode_jsons' or
|
|
632
|
+
raise Util.CertoraUserInputError("Should always provide input files, unless 'bytecode_jsons' or "
|
|
633
|
+
"'project_sanity' or 'foundry' are used")
|
|
610
634
|
|
|
611
635
|
if context.is_bytecode and context.files:
|
|
612
636
|
raise Util.CertoraUserInputError("Cannot use 'bytecode_jsons' with other files")
|
|
@@ -620,7 +644,8 @@ def check_mode_of_operation(context: CertoraContext) -> None:
|
|
|
620
644
|
if not context.files:
|
|
621
645
|
file_contract_pairs = _get_project_file_contract_pairs(context)
|
|
622
646
|
main_contract = file_contract_pairs[0][1] # any contract
|
|
623
|
-
context.files =
|
|
647
|
+
context.files = \
|
|
648
|
+
[f"{file}:{contract}" if file.stem != contract else str(file) for file, contract in file_contract_pairs]
|
|
624
649
|
else:
|
|
625
650
|
first = context.files[0]
|
|
626
651
|
if ':' in first:
|
|
@@ -632,12 +657,14 @@ def check_mode_of_operation(context: CertoraContext) -> None:
|
|
|
632
657
|
|
|
633
658
|
context.verify = f"{main_contract}:{spec_file_name}"
|
|
634
659
|
context.is_verify = True
|
|
635
|
-
# In this mode we want to make life easy for the user,
|
|
660
|
+
# In this mode we want to make life easy for the user,
|
|
661
|
+
# so use the auto-dispatcher mode to avoid many unresolved calls
|
|
636
662
|
context.auto_dispatcher = True
|
|
637
663
|
|
|
638
664
|
if context.foundry:
|
|
639
665
|
file_contract_pairs = _get_project_file_contract_pairs(context)
|
|
640
|
-
test_pairs = [(file, contract) for file, contract in file_contract_pairs
|
|
666
|
+
test_pairs = [(file, contract) for file, contract in file_contract_pairs
|
|
667
|
+
if file.stem.endswith(".t") and 'lib' not in file.parents]
|
|
641
668
|
context.files = [f"{file}:{contract}" for file, contract in test_pairs]
|
|
642
669
|
main_contract = test_pairs[0][1] # any contract
|
|
643
670
|
spec_file_name = _generate_temp_spec(context, "use builtin rule verifyFoundryFuzzTestsNoRevert;\n")
|
|
@@ -645,7 +672,8 @@ def check_mode_of_operation(context: CertoraContext) -> None:
|
|
|
645
672
|
context.foundry_tests_mode = True
|
|
646
673
|
context.verify = f"{main_contract}:{spec_file_name}"
|
|
647
674
|
context.is_verify = True
|
|
648
|
-
# In this mode we want to make life easy for the user,
|
|
675
|
+
# In this mode we want to make life easy for the user,
|
|
676
|
+
# so use the auto-dispatcher mode to avoid many unresolved calls
|
|
649
677
|
context.auto_dispatcher = True
|
|
650
678
|
|
|
651
679
|
if context.files:
|
|
@@ -658,49 +686,30 @@ def check_mode_of_operation(context: CertoraContext) -> None:
|
|
|
658
686
|
for input_file in context.files:
|
|
659
687
|
special_file_type = next((suffix for suffix in special_file_suffixes if input_file.endswith(suffix)), None)
|
|
660
688
|
|
|
661
|
-
if special_file_type:
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
f"No other files are allowed with a file of type {special_file_type}")
|
|
665
|
-
if context.is_assert:
|
|
666
|
-
raise Util.CertoraUserInputError(
|
|
667
|
-
f"Option 'assert_contracts' cannot be used with a {special_file_type} file {input_file}")
|
|
689
|
+
if special_file_type and len(context.files) > 1:
|
|
690
|
+
raise Util.CertoraUserInputError(
|
|
691
|
+
f"No other files are allowed with a file of type {special_file_type}")
|
|
668
692
|
|
|
669
|
-
if
|
|
670
|
-
|
|
693
|
+
if (
|
|
694
|
+
not Attrs.is_concord_app() and
|
|
695
|
+
not any([context.is_verify, context.is_bytecode,
|
|
696
|
+
context.equivalence_contracts, special_file_type]) and not context.build_only):
|
|
671
697
|
raise Util.CertoraUserInputError("You must use 'verify' when running the Certora Prover")
|
|
672
698
|
|
|
699
|
+
def find_matching_contracts(context: CertoraContext, pattern: str) -> List[str]:
|
|
700
|
+
result = []
|
|
701
|
+
for contract in context.contract_to_file:
|
|
702
|
+
if fnmatch.fnmatch(contract, pattern):
|
|
703
|
+
result.append(context.contract_to_file[contract])
|
|
704
|
+
return result
|
|
673
705
|
|
|
674
|
-
def
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
:param context:
|
|
678
|
-
:param map_attr_name: name of the attribute e.g. solc_optimize_map
|
|
679
|
-
:param map_attr: the value of the map attribute, a dictionary mapping contract/file to a value
|
|
680
|
-
:return:
|
|
681
|
-
|
|
682
|
-
all solc_XXX_map attributes are used to set attributes for the Solidity compiler. The value is based on the
|
|
683
|
-
Solidity file to be compiled, therefore it translates all keys to a relative path to a file
|
|
684
|
-
"""
|
|
685
|
-
|
|
686
|
-
def find_matching_files(context: CertoraContext, pattern: str) -> List[str]:
|
|
687
|
-
file_part = pattern.split(':')[0]
|
|
688
|
-
if Path(file_part).suffix == "": # no suffix means the key is a contract
|
|
689
|
-
matching_contracts = fnmatch.filter(context.contracts, pattern) # find matching contracts
|
|
690
|
-
return [context.contract_to_file[contract] for contract in matching_contracts]
|
|
691
|
-
else:
|
|
692
|
-
return [file_part]
|
|
693
|
-
|
|
694
|
-
new_map_attr: Dict[str, str] = {}
|
|
695
|
-
for (pattern, value) in map_attr.copy().items():
|
|
696
|
-
matching_files = find_matching_files(context, pattern)
|
|
697
|
-
for file in matching_files:
|
|
698
|
-
file = str(Path(file).resolve(strict=False).relative_to(Path.cwd()))
|
|
699
|
-
attr_value = new_map_attr.get(file)
|
|
700
|
-
if attr_value is None: # key in file paths but no mapping yet
|
|
701
|
-
new_map_attr[file] = value
|
|
702
|
-
return new_map_attr
|
|
706
|
+
def find_matching_files(context: CertoraContext, pattern: str) -> List[str]:
|
|
707
|
+
result = []
|
|
703
708
|
|
|
709
|
+
for path in context.file_paths:
|
|
710
|
+
if glob.globmatch(path, pattern, flags=glob.GLOBSTAR):
|
|
711
|
+
result.append(path)
|
|
712
|
+
return result
|
|
704
713
|
|
|
705
714
|
def check_map_attributes(context: CertoraContext) -> None:
|
|
706
715
|
|
|
@@ -718,27 +727,24 @@ def check_map_attributes(context: CertoraContext) -> None:
|
|
|
718
727
|
# we also check the map value was not set to False explicitly in the conf file
|
|
719
728
|
if base_attr and map_attr and not (base_attr is False and context.conf_options.get(base_attr) is not None):
|
|
720
729
|
raise Util.CertoraUserInputError(f"You cannot use both '{attr_prefix}' and '{map_attr_name}' arguments")
|
|
721
|
-
if not isinstance(map_attr,
|
|
722
|
-
raise RuntimeError(f"map_attr is not dictionary, got {map_attr}")
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
for
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
paths_not_set = [path for path, value in sources_mappings.items() if value is None]
|
|
740
|
-
if paths_not_set:
|
|
741
|
-
raise Util.CertoraUserInputError(f"{' '.join(paths_not_set)} was not set in {map_attr_name}")
|
|
730
|
+
if not isinstance(map_attr, OrderedDict):
|
|
731
|
+
raise RuntimeError(f"`map_attr` is not an ordered dictionary, got {map_attr}")
|
|
732
|
+
|
|
733
|
+
# will check that all the contract files are matched
|
|
734
|
+
file_list: Dict[str, bool] = {path: False for path in context.file_paths}
|
|
735
|
+
|
|
736
|
+
for key, value in map_attr.items():
|
|
737
|
+
pattern = key.split(':')[0] # ignore the contract part
|
|
738
|
+
if Path(pattern).suffix == "":
|
|
739
|
+
for path in find_matching_contracts(context, pattern):
|
|
740
|
+
file_list[path] = True
|
|
741
|
+
else:
|
|
742
|
+
for path in find_matching_files(context, pattern):
|
|
743
|
+
file_list[path] = True
|
|
744
|
+
|
|
745
|
+
none_keys = [k for k, v in file_list.items() if v is False]
|
|
746
|
+
if none_keys:
|
|
747
|
+
raise Util.CertoraUserInputError(f"The following files are not matched in {map_attr_name}: {none_keys}")
|
|
742
748
|
|
|
743
749
|
|
|
744
750
|
def check_parametric_contracts(context: CertoraContext) -> None:
|
|
@@ -905,16 +911,17 @@ def check_files_input(file_list: List[str]) -> None:
|
|
|
905
911
|
if file.endswith(Util.SOROBAN_EXEC_EXTENSION):
|
|
906
912
|
raise Util.CertoraUserInputError(f'The Soroban file {file} cannot be accompanied with other files')
|
|
907
913
|
|
|
908
|
-
|
|
909
|
-
|
|
914
|
+
|
|
915
|
+
def set_attr_default(context: CertoraContext, attr_name: str, ci_value: str, default_value: str) -> None:
|
|
916
|
+
if getattr(context, attr_name, None) is None:
|
|
910
917
|
if Util.is_ci_or_git_action():
|
|
911
|
-
context
|
|
918
|
+
setattr(context, attr_name, ci_value)
|
|
912
919
|
else:
|
|
913
|
-
context
|
|
920
|
+
setattr(context, attr_name, default_value)
|
|
914
921
|
|
|
915
922
|
|
|
916
923
|
def mode_has_spec_file(context: CertoraContext) -> bool:
|
|
917
|
-
return not context.
|
|
924
|
+
return not (context.is_tac or context.is_equivalence)
|
|
918
925
|
|
|
919
926
|
|
|
920
927
|
def to_relative_paths(paths: Union[str, List[str]]) -> Union[str, List[str]]:
|
|
@@ -60,7 +60,7 @@ def add_solana_files_to_context(context: CertoraContext, json_obj: dict) -> None
|
|
|
60
60
|
def run_rust_build(context: CertoraContext, build_cmd: List[str]) -> None:
|
|
61
61
|
|
|
62
62
|
try:
|
|
63
|
-
build_script_logger.info(f"Building by calling {build_cmd}")
|
|
63
|
+
build_script_logger.info(f"Building by calling `{' '.join(build_cmd)}`")
|
|
64
64
|
result = subprocess.run(build_cmd, capture_output=True, text=True)
|
|
65
65
|
|
|
66
66
|
# Check if the script executed successfully
|
|
@@ -90,11 +90,12 @@ def run_rust_build(context: CertoraContext, build_cmd: List[str]) -> None:
|
|
|
90
90
|
|
|
91
91
|
add_solana_files_to_context(context, json_obj)
|
|
92
92
|
|
|
93
|
-
if context.test == str(Util.TestValue.AFTER_BUILD_RUST):
|
|
94
|
-
raise Util.TestResultsReady(context)
|
|
95
93
|
assert not context.files, f"run_rust_build: expecting files to be empty, got: {context.files}"
|
|
96
94
|
context.files = [os.path.join(context.rust_project_directory, context.rust_executables)]
|
|
97
95
|
|
|
96
|
+
if context.test == str(Util.TestValue.AFTER_BUILD_RUST):
|
|
97
|
+
raise Util.TestResultsReady(context)
|
|
98
|
+
|
|
98
99
|
except Util.TestResultsReady as e:
|
|
99
100
|
raise e
|
|
100
101
|
except FileNotFoundError as e:
|
|
@@ -47,10 +47,6 @@ class CertoraVerifyGenerator:
|
|
|
47
47
|
# we need to build once because of the early typechecking...
|
|
48
48
|
self.update_certora_verify_struct(False)
|
|
49
49
|
|
|
50
|
-
elif self.context.assert_contracts is not None:
|
|
51
|
-
contract_to_check_asserts_for = self.context.assert_contracts
|
|
52
|
-
self.certora_verify_struct = {"type": "assertion",
|
|
53
|
-
"primaryContracts": contract_to_check_asserts_for}
|
|
54
50
|
elif self.context.equivalence_contracts is not None:
|
|
55
51
|
if self.context.method is None:
|
|
56
52
|
raise Util.CertoraUserInputError("Argument `method` is required for equivalence checks")
|
|
@@ -63,6 +59,15 @@ class CertoraVerifyGenerator:
|
|
|
63
59
|
"primary_contract": equiv_contracts[0],
|
|
64
60
|
"secondary_contract": equiv_contracts[1]
|
|
65
61
|
}
|
|
62
|
+
elif context.is_concord_app:
|
|
63
|
+
if len(context.contracts) != 2:
|
|
64
|
+
raise Util.CertoraUserInputError(f"Expecting 2 contracts but got {len(context.contracts)}: {context.contracts}")
|
|
65
|
+
contract_list = list(context.contracts)
|
|
66
|
+
self.certora_verify_struct = {
|
|
67
|
+
"type": "equivalence",
|
|
68
|
+
"primary_contract": contract_list[0],
|
|
69
|
+
"secondary_contract": contract_list[1]
|
|
70
|
+
}
|
|
66
71
|
|
|
67
72
|
def update_certora_verify_struct(self, in_certora_sources: bool) -> None:
|
|
68
73
|
"""
|
|
@@ -96,8 +96,10 @@ class SplitRulesHandler():
|
|
|
96
96
|
rule_flag = Attrs.EvmProverAttributes.RULE.get_flag()
|
|
97
97
|
split_rules_flag = Attrs.EvmProverAttributes.SPLIT_RULES.get_flag()
|
|
98
98
|
msg_flag = Attrs.CommonAttributes.MSG.get_flag()
|
|
99
|
+
|
|
99
100
|
# it is important to use the cache, when the difference between the runs is only the rules that apply
|
|
100
101
|
build_cache_flag = Attrs.EvmProverAttributes.BUILD_CACHE.get_flag()
|
|
102
|
+
|
|
101
103
|
group_id_flag = Attrs.EvmProverAttributes.GROUP_ID.get_flag()
|
|
102
104
|
disable_local_typechecking_flag = Attrs.EvmProverAttributes.DISABLE_LOCAL_TYPECHECKING.get_flag()
|
|
103
105
|
|
|
@@ -32,8 +32,6 @@ from CertoraProver.certoraBuildDataClasses import ContractInSDC
|
|
|
32
32
|
from CertoraProver import erc7201
|
|
33
33
|
from Shared import certoraUtils as Util
|
|
34
34
|
from CertoraProver.certoraBuildDataClasses import SDC
|
|
35
|
-
from CertoraProver.Compiler.CompilerCollectorFactory import get_relevant_compiler
|
|
36
|
-
from CertoraProver.certoraContextClass import CertoraContext
|
|
37
35
|
|
|
38
36
|
NameSpacedStorage: TypeAlias = Tuple[str, str]
|
|
39
37
|
NewStorageFields = List[Dict[str, Any]]
|
|
@@ -351,36 +349,3 @@ def validate_new_fields(
|
|
|
351
349
|
raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} is already mapped by {target_contract.name}")
|
|
352
350
|
if var in target_vars:
|
|
353
351
|
raise Util.CertoraUserInputError(f"Var '{var}' added to {target_contract.name} by {ext} is already declared by {target_contract.name}")
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
def add_harness_to_compiler_map(original_file: str, harness_file: Any, context: CertoraContext) -> None:
|
|
357
|
-
"""
|
|
358
|
-
Associates the generated harness file with the same compiler version as the original file.
|
|
359
|
-
|
|
360
|
-
This ensures the harness contract is compiled with the same Solidity compiler version
|
|
361
|
-
as the contract it's extending, maintaining compatibility.
|
|
362
|
-
|
|
363
|
-
Args:
|
|
364
|
-
original_file (str): Path to the original source file
|
|
365
|
-
harness_file (Any): File-like object representing the generated harness file
|
|
366
|
-
context (CertoraContext): The context object containing compiler mapping information
|
|
367
|
-
|
|
368
|
-
Returns:
|
|
369
|
-
None
|
|
370
|
-
"""
|
|
371
|
-
# Validate prerequisites before updating compiler map
|
|
372
|
-
if context.compiler_map is None:
|
|
373
|
-
storage_extension_logger.debug("Cannot add compiler for harness: compiler_map is None (not a dict). Using the default compiler")
|
|
374
|
-
return
|
|
375
|
-
|
|
376
|
-
# Get the compiler version used for the original file
|
|
377
|
-
compiler_version = get_relevant_compiler(Path(original_file), context)
|
|
378
|
-
|
|
379
|
-
# Extract just the filename from the harness file path
|
|
380
|
-
harness_filename = Path(harness_file.name).name
|
|
381
|
-
|
|
382
|
-
# Add the compiler version to the context using glob pattern for the harness file
|
|
383
|
-
map_key = f"*{harness_filename}"
|
|
384
|
-
context.compiler_map[map_key] = compiler_version
|
|
385
|
-
|
|
386
|
-
storage_extension_logger.debug(f"Added compiler mapping: {map_key} -> {compiler_version}")
|
certora_cli/Mutate/mutateApp.py
CHANGED
|
@@ -710,6 +710,7 @@ class MutateApp:
|
|
|
710
710
|
dump_csv: Optional[Path]
|
|
711
711
|
sync: bool
|
|
712
712
|
wait_for_original_run: bool
|
|
713
|
+
url_visibility: Vf.UrlVisibilityOptions
|
|
713
714
|
collect_file: Optional[Path]
|
|
714
715
|
poll_timeout: Optional[int]
|
|
715
716
|
max_timeout_attempts_count: Optional[int]
|
|
@@ -722,7 +723,7 @@ class MutateApp:
|
|
|
722
723
|
def __init__(self, args_list: List[str]) -> None:
|
|
723
724
|
self.mutation_test_id = ''
|
|
724
725
|
self.numb_of_jobs: int = 0
|
|
725
|
-
self.
|
|
726
|
+
self.backup_path: Optional[Path] = None
|
|
726
727
|
self.sources_dir: Optional[Path] = None
|
|
727
728
|
self.with_split_stats_data = False
|
|
728
729
|
self.manual_mutants_list: List[Mutant] = list()
|
|
@@ -881,9 +882,9 @@ class MutateApp:
|
|
|
881
882
|
" verification results. Please remove or replace the mutation")
|
|
882
883
|
|
|
883
884
|
def restore_backups(self) -> None:
|
|
884
|
-
|
|
885
|
-
Util.restore_backup(backup_path)
|
|
886
|
-
self.
|
|
885
|
+
if self.backup_path:
|
|
886
|
+
Util.restore_backup(self.backup_path)
|
|
887
|
+
self.backup_path = None
|
|
887
888
|
|
|
888
889
|
def submit_soroban(self) -> None:
|
|
889
890
|
|
|
@@ -930,12 +931,14 @@ class MutateApp:
|
|
|
930
931
|
# run mutants
|
|
931
932
|
mutant_runs = []
|
|
932
933
|
try:
|
|
933
|
-
self.backup_paths = []
|
|
934
934
|
for mutant in all_mutants:
|
|
935
935
|
# call certoraRun for each mutant we found
|
|
936
936
|
mutant_runs.append(self.run_mutant_soroban(mutant))
|
|
937
|
+
self.restore_backups()
|
|
937
938
|
finally:
|
|
938
|
-
self.
|
|
939
|
+
if self.backup_path:
|
|
940
|
+
mutation_logger.warning("Not empty backup path")
|
|
941
|
+
self.restore_backups()
|
|
939
942
|
|
|
940
943
|
# wrap it all up and make the input for the 2nd step: the collector
|
|
941
944
|
assert self.collect_file, "submit_soroban: no collect_file"
|
|
@@ -1143,10 +1146,11 @@ class MutateApp:
|
|
|
1143
1146
|
def run_mutant_soroban(self, mutant: Mutant) -> MutantJob:
|
|
1144
1147
|
file_to_mutate = Path(mutant.original_filename)
|
|
1145
1148
|
# create a backup copy (if needed) by adding '.backup' to the end of the file
|
|
1146
|
-
if Util.get_backup_path(file_to_mutate)
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
self.
|
|
1149
|
+
if self.backup_path and Util.get_backup_path(file_to_mutate) != self.backup_path:
|
|
1150
|
+
raise Util.ImplementationError("backup_path is set but does not match the file to mutate")
|
|
1151
|
+
if not self.backup_path:
|
|
1152
|
+
self.backup_path = Util.create_backup(file_to_mutate)
|
|
1153
|
+
assert self.backup_path, f"run_mutant_soroban: create_backup for {file_to_mutate} failed"
|
|
1150
1154
|
shutil.copy(mutant.filename, file_to_mutate)
|
|
1151
1155
|
|
|
1152
1156
|
try:
|
|
@@ -1405,6 +1409,8 @@ class MutateApp:
|
|
|
1405
1409
|
certora_args = [str(conf_file), "--run_source", "MUTATION", "--msg", msg]
|
|
1406
1410
|
if self.wait_for_original_run:
|
|
1407
1411
|
certora_args.append("--wait_for_results")
|
|
1412
|
+
if self.url_visibility:
|
|
1413
|
+
certora_args.extend(["--url_visibility", str(self.url_visibility)])
|
|
1408
1414
|
if self.with_split_stats_data and os.environ.get("WITH_AUTOCONFING", False) == '1':
|
|
1409
1415
|
certora_args += ['--prover_resource_files', f"ac:{MConstants.SPLIT_STATS_DATA}"]
|
|
1410
1416
|
if mutation_test_id:
|
|
@@ -250,6 +250,17 @@ class MutateAttributes(AttrUtil.Attributes):
|
|
|
250
250
|
}
|
|
251
251
|
)
|
|
252
252
|
|
|
253
|
+
URL_VISIBILITY = MutateAttributeDefinition(
|
|
254
|
+
attr_validation_func=Vf.validate_url_visibility,
|
|
255
|
+
argparse_args={
|
|
256
|
+
'nargs': AttrUtil.SINGLE_OR_NONE_OCCURRENCES,
|
|
257
|
+
'action': AttrUtil.UniqueStore,
|
|
258
|
+
'default': None, # 'default': when --url_visibility was not used
|
|
259
|
+
# when --url_visibility was used without an argument its probably because the link should be public
|
|
260
|
+
'const': str(Vf.UrlVisibilityOptions.PUBLIC)
|
|
261
|
+
}
|
|
262
|
+
)
|
|
263
|
+
|
|
253
264
|
|
|
254
265
|
def get_args(args_list: List[str]) -> Dict:
|
|
255
266
|
|
|
@@ -167,9 +167,12 @@ class Attributes:
|
|
|
167
167
|
table.add_column(Text("Description"), width=desc_col_width)
|
|
168
168
|
table.add_column(Text("Default"), width=default_col_width)
|
|
169
169
|
|
|
170
|
+
unsupported_attribute_names = [attr.name for attr in cls.unsupported_attributes()]
|
|
170
171
|
for name in dir(cls):
|
|
171
172
|
if name in cls.hide_attributes():
|
|
172
173
|
continue
|
|
174
|
+
if name in unsupported_attribute_names:
|
|
175
|
+
continue
|
|
173
176
|
if name.isupper():
|
|
174
177
|
attr = getattr(cls, name, None)
|
|
175
178
|
assert isinstance(attr, AttributeDefinition), "print_attr_help: type(attr) == Attribute"
|
|
@@ -190,11 +193,9 @@ class Attributes:
|
|
|
190
193
|
v.name = name
|
|
191
194
|
return v
|
|
192
195
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
# 'compiler_map' does not have a matching 'compiler' attribute
|
|
197
|
-
cls._all_map_attrs = [attr for attr in cls._all_conf_names if attr.endswith(Util.MAP_SUFFIX)]
|
|
196
|
+
cls._attribute_list = [set_name(name) for name in dir(cls) if name.isupper()]
|
|
197
|
+
cls._all_conf_names = [attr.name.lower() for attr in cls.attribute_list()]
|
|
198
|
+
cls._all_map_attrs = [attr for attr in cls._all_conf_names if attr.endswith(Util.MAP_SUFFIX)]
|
|
198
199
|
|
|
199
200
|
@classmethod
|
|
200
201
|
def hide_attributes(cls) -> List[str]:
|
|
@@ -203,3 +204,8 @@ class Attributes:
|
|
|
203
204
|
:return: A list of attribute names to be hidden.
|
|
204
205
|
"""
|
|
205
206
|
return []
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
def unsupported_attributes(cls) -> list:
|
|
210
|
+
# Return a list of AttributeDefinition objects that are unsupported
|
|
211
|
+
return []
|