certora-cli-beta-mirror 7.31.0__py3-none-any.whl → 8.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +1 -3
- certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +3 -0
- certora_cli/CertoraProver/certoraApp.py +49 -0
- certora_cli/CertoraProver/certoraBuild.py +197 -44
- certora_cli/CertoraProver/certoraBuildCacheManager.py +2 -0
- certora_cli/CertoraProver/certoraBuildDataClasses.py +3 -1
- certora_cli/CertoraProver/certoraBuildRust.py +32 -21
- certora_cli/{Shared/rustProverCommon.py → CertoraProver/certoraBuildSui.py} +24 -18
- certora_cli/CertoraProver/certoraCloudIO.py +37 -8
- certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +1 -1
- certora_cli/CertoraProver/certoraCollectRunMetadata.py +20 -5
- certora_cli/CertoraProver/certoraConfigIO.py +23 -22
- certora_cli/CertoraProver/certoraContext.py +77 -45
- certora_cli/CertoraProver/certoraContextAttributes.py +122 -195
- certora_cli/CertoraProver/certoraContextValidator.py +78 -49
- certora_cli/CertoraProver/certoraContractFuncs.py +6 -0
- certora_cli/CertoraProver/certoraType.py +10 -1
- certora_cli/CertoraProver/certoraVerifyGenerator.py +10 -4
- certora_cli/CertoraProver/splitRules.py +20 -17
- certora_cli/CertoraProver/storageExtension.py +0 -35
- certora_cli/EquivalenceCheck/equivCheck.py +2 -1
- certora_cli/Mutate/mutateApp.py +28 -16
- certora_cli/Mutate/mutateAttributes.py +11 -0
- certora_cli/Mutate/mutateValidate.py +2 -2
- certora_cli/Shared/certoraAttrUtil.py +11 -5
- certora_cli/Shared/certoraUtils.py +99 -35
- certora_cli/Shared/certoraValidateFuncs.py +24 -12
- certora_cli/Shared/proverCommon.py +10 -8
- certora_cli/certoraCVLFormatter.py +76 -0
- certora_cli/certoraConcord.py +39 -0
- certora_cli/certoraEVMProver.py +2 -2
- certora_cli/certoraRanger.py +2 -2
- certora_cli/certoraRun.py +58 -98
- certora_cli/certoraSolanaProver.py +3 -3
- certora_cli/certoraSorobanProver.py +3 -4
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/METADATA +4 -3
- certora_cli_beta_mirror-8.1.0.dist-info/RECORD +79 -0
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/entry_points.txt +1 -0
- certora_jars/ASTExtraction.jar +0 -0
- certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
- certora_jars/Typechecker.jar +0 -0
- certora_cli_beta_mirror-7.31.0.dist-info/RECORD +0 -75
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/LICENSE +0 -0
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/WHEEL +0 -0
- {certora_cli_beta_mirror-7.31.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
@@ -31,7 +32,7 @@ from CertoraProver.certoraContextClass import CertoraContext
|
|
|
31
32
|
from Shared import certoraUtils as Util
|
|
32
33
|
from Shared import certoraAttrUtil as AttrUtil
|
|
33
34
|
from CertoraProver.certoraProjectScanner import scan_project
|
|
34
|
-
|
|
35
|
+
import CertoraProver.certoraApp as App
|
|
35
36
|
|
|
36
37
|
scripts_dir_path = Path(__file__).parent.resolve() # containing directory
|
|
37
38
|
sys.path.insert(0, str(scripts_dir_path))
|
|
@@ -45,10 +46,28 @@ class CertoraContextValidator:
|
|
|
45
46
|
def __init__(self, context: CertoraContext):
|
|
46
47
|
self.context = context
|
|
47
48
|
|
|
49
|
+
def handle_concord_attrs(self) -> None:
|
|
50
|
+
if self.context.app == App.ConcordApp:
|
|
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
|
+
|
|
48
67
|
def handle_ranger_attrs(self) -> None:
|
|
49
68
|
# unset unsupported attributes
|
|
50
|
-
if
|
|
51
|
-
for attr in Attrs.RangerAttributes.
|
|
69
|
+
if self.context.app == App.RangerApp:
|
|
70
|
+
for attr in Attrs.RangerAttributes.unsupported_attributes():
|
|
52
71
|
attr_name = attr.get_conf_key()
|
|
53
72
|
if getattr(self.context, attr_name):
|
|
54
73
|
if attr.arg_type == AttrUtil.AttrArgType.BOOLEAN:
|
|
@@ -66,7 +85,7 @@ class CertoraContextValidator:
|
|
|
66
85
|
f"ignoring the set value of {self.context.loop_iter}")
|
|
67
86
|
self.context.loop_iter = self.context.loop_iter or Util.DEFAULT_RANGER_LOOP_ITER
|
|
68
87
|
|
|
69
|
-
for attr in Attrs.RangerAttributes.
|
|
88
|
+
for attr in Attrs.RangerAttributes.true_by_default_attributes():
|
|
70
89
|
attr_name = attr.get_conf_key()
|
|
71
90
|
setattr(self.context, attr_name, True)
|
|
72
91
|
|
|
@@ -80,7 +99,7 @@ class CertoraContextValidator:
|
|
|
80
99
|
|
|
81
100
|
def validate(self) -> None:
|
|
82
101
|
|
|
83
|
-
for attr_def in
|
|
102
|
+
for attr_def in self.context.app.attr_class.attribute_list():
|
|
84
103
|
conf_key = attr_def.get_conf_key()
|
|
85
104
|
attr = getattr(self.context, conf_key, None)
|
|
86
105
|
if attr_def.deprecation_msg and attr:
|
|
@@ -141,9 +160,9 @@ class CertoraContextValidator:
|
|
|
141
160
|
|
|
142
161
|
if value is None:
|
|
143
162
|
raise RuntimeError(f"calling validate_type_string with null value {conf_key}")
|
|
144
|
-
if not isinstance(value, str):
|
|
145
|
-
raise Util.CertoraUserInputError(f"value of {conf_key} {value} is not a string")
|
|
146
|
-
attr.validate_value(value)
|
|
163
|
+
if not isinstance(value, (str, int)):
|
|
164
|
+
raise Util.CertoraUserInputError(f"value of {conf_key} {value} is not a string or an integer")
|
|
165
|
+
attr.validate_value(str(value))
|
|
147
166
|
|
|
148
167
|
def validate_type_boolean(self, attr: AttrUtil.AttributeDefinition) -> None:
|
|
149
168
|
conf_key, value = self.__get_key_and_value(attr)
|
|
@@ -164,6 +183,12 @@ class CertoraContextValidator:
|
|
|
164
183
|
if len(context.files) > 1:
|
|
165
184
|
raise Util.CertoraUserInputError("Rust projects must specify exactly one executable in 'files'.")
|
|
166
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
|
+
|
|
167
192
|
def check_args_post_argparse(self) -> None:
|
|
168
193
|
"""
|
|
169
194
|
Performs checks over the arguments after basic argparse parsing
|
|
@@ -201,7 +226,7 @@ class CertoraContextValidator:
|
|
|
201
226
|
check_contract_name_arg_inputs(context) # Here context.contracts is set
|
|
202
227
|
Util.check_packages_arguments(context)
|
|
203
228
|
convert_to_compiler_map(context)
|
|
204
|
-
if
|
|
229
|
+
if Ctx.is_evm_app_class(context):
|
|
205
230
|
if context.compiler_map is None and context.bytecode_jsons is None: # xxx - we would need to reiterate here
|
|
206
231
|
# once we allow combining raw bytecode with Solidity
|
|
207
232
|
context.solc = Vf.is_solc_file_valid(context.solc)
|
|
@@ -218,10 +243,18 @@ class CertoraContextValidator:
|
|
|
218
243
|
if context.compilation_steps_only and context.build_only:
|
|
219
244
|
raise Util.CertoraUserInputError("cannot use both 'compilation_steps_only' and 'build_only'")
|
|
220
245
|
|
|
221
|
-
|
|
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))
|
|
222
249
|
if context.wait_for_results and context.wait_for_results != str(Vf.WaitForResultOptions.NONE) and context.local:
|
|
223
250
|
validation_logger.warning("'wait_for_results' has no effect in local tool runs")
|
|
224
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
|
+
|
|
225
258
|
# packages must be in a normal form (no unneeded . or ..)
|
|
226
259
|
if context.packages:
|
|
227
260
|
for idx, el in enumerate(context.packages):
|
|
@@ -427,14 +460,6 @@ def check_contract_name_arg_inputs(context: CertoraContext) -> None:
|
|
|
427
460
|
check_conflicting_link_args(context)
|
|
428
461
|
|
|
429
462
|
context.verified_contract_files = []
|
|
430
|
-
if context.assert_contracts is not None:
|
|
431
|
-
for assert_arg in context.assert_contracts:
|
|
432
|
-
contract = Util.get_trivial_contract_name(assert_arg)
|
|
433
|
-
if contract not in contract_names:
|
|
434
|
-
__suggest_contract_name(f"'assert' argument, {contract}, doesn't match any contract name", contract,
|
|
435
|
-
contract_names, contract_to_file)
|
|
436
|
-
else:
|
|
437
|
-
context.verified_contract_files.append(contract_to_file[contract])
|
|
438
463
|
|
|
439
464
|
context.spec_file = None
|
|
440
465
|
|
|
@@ -448,7 +473,7 @@ def check_contract_name_arg_inputs(context: CertoraContext) -> None:
|
|
|
448
473
|
context.spec_file = spec
|
|
449
474
|
|
|
450
475
|
contract_to_address = dict()
|
|
451
|
-
if context
|
|
476
|
+
if getattr(context, 'address', None):
|
|
452
477
|
for address_str in context.address:
|
|
453
478
|
contract = address_str.split(':')[0]
|
|
454
479
|
if contract not in contract_names:
|
|
@@ -464,7 +489,7 @@ def check_contract_name_arg_inputs(context: CertoraContext) -> None:
|
|
|
464
489
|
all_warnings.add(f'address {number} for contract {contract} defined twice')
|
|
465
490
|
context.address = contract_to_address
|
|
466
491
|
|
|
467
|
-
if context
|
|
492
|
+
if getattr(context, 'struct_link', None):
|
|
468
493
|
contract_slot_to_contract = dict()
|
|
469
494
|
for link in context.struct_link:
|
|
470
495
|
location = link.split('=')[0]
|
|
@@ -580,31 +605,32 @@ def check_mode_of_operation(context: CertoraContext) -> None:
|
|
|
580
605
|
|
|
581
606
|
@param context: A namespace including all CLI arguments provided
|
|
582
607
|
@raise an CertoraUserInputError when:
|
|
583
|
-
1.
|
|
584
|
-
2. when
|
|
585
|
-
3.
|
|
586
|
-
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.
|
|
587
611
|
"""
|
|
588
612
|
context.is_verify = context.verify is not None and len(context.verify) > 0
|
|
589
|
-
context.is_assert = context.assert_contracts is not None and len(context.assert_contracts) > 0
|
|
590
613
|
context.is_bytecode = context.bytecode_jsons is not None and len(context.bytecode_jsons) > 0
|
|
591
|
-
context.is_equivalence = context.equivalence_contracts is not None
|
|
614
|
+
context.is_equivalence = context.equivalence_contracts is not None or context.app == App.ConcordApp
|
|
592
615
|
|
|
593
|
-
if (context.project_sanity or context.foundry) and
|
|
594
|
-
|
|
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")
|
|
595
620
|
|
|
596
621
|
if context.project_sanity and context.foundry:
|
|
597
622
|
raise Util.CertoraUserInputError("The 'project_sanity' and 'foundry' options cannot coexist")
|
|
598
623
|
|
|
599
|
-
if len(list(filter(None, [context.is_verify, context.
|
|
600
|
-
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")
|
|
601
626
|
|
|
602
627
|
has_bytecode_spec = context.bytecode_spec is not None
|
|
603
628
|
if has_bytecode_spec != context.is_bytecode:
|
|
604
629
|
raise Util.CertoraUserInputError("Must use 'bytecode' together with 'bytecode_spec'")
|
|
605
630
|
|
|
606
631
|
if not context.files and not any((context.is_bytecode, context.project_sanity, context.foundry)):
|
|
607
|
-
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")
|
|
608
634
|
|
|
609
635
|
if context.is_bytecode and context.files:
|
|
610
636
|
raise Util.CertoraUserInputError("Cannot use 'bytecode_jsons' with other files")
|
|
@@ -618,7 +644,8 @@ def check_mode_of_operation(context: CertoraContext) -> None:
|
|
|
618
644
|
if not context.files:
|
|
619
645
|
file_contract_pairs = _get_project_file_contract_pairs(context)
|
|
620
646
|
main_contract = file_contract_pairs[0][1] # any contract
|
|
621
|
-
context.files =
|
|
647
|
+
context.files = \
|
|
648
|
+
[f"{file}:{contract}" if file.stem != contract else str(file) for file, contract in file_contract_pairs]
|
|
622
649
|
else:
|
|
623
650
|
first = context.files[0]
|
|
624
651
|
if ':' in first:
|
|
@@ -630,12 +657,14 @@ def check_mode_of_operation(context: CertoraContext) -> None:
|
|
|
630
657
|
|
|
631
658
|
context.verify = f"{main_contract}:{spec_file_name}"
|
|
632
659
|
context.is_verify = True
|
|
633
|
-
# 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
|
|
634
662
|
context.auto_dispatcher = True
|
|
635
663
|
|
|
636
664
|
if context.foundry:
|
|
637
665
|
file_contract_pairs = _get_project_file_contract_pairs(context)
|
|
638
|
-
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]
|
|
639
668
|
context.files = [f"{file}:{contract}" for file, contract in test_pairs]
|
|
640
669
|
main_contract = test_pairs[0][1] # any contract
|
|
641
670
|
spec_file_name = _generate_temp_spec(context, "use builtin rule verifyFoundryFuzzTestsNoRevert;\n")
|
|
@@ -643,7 +672,8 @@ def check_mode_of_operation(context: CertoraContext) -> None:
|
|
|
643
672
|
context.foundry_tests_mode = True
|
|
644
673
|
context.verify = f"{main_contract}:{spec_file_name}"
|
|
645
674
|
context.is_verify = True
|
|
646
|
-
# 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
|
|
647
677
|
context.auto_dispatcher = True
|
|
648
678
|
|
|
649
679
|
if context.files:
|
|
@@ -656,16 +686,14 @@ def check_mode_of_operation(context: CertoraContext) -> None:
|
|
|
656
686
|
for input_file in context.files:
|
|
657
687
|
special_file_type = next((suffix for suffix in special_file_suffixes if input_file.endswith(suffix)), None)
|
|
658
688
|
|
|
659
|
-
if special_file_type:
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
f"No other files are allowed with a file of type {special_file_type}")
|
|
663
|
-
if context.is_assert:
|
|
664
|
-
raise Util.CertoraUserInputError(
|
|
665
|
-
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}")
|
|
666
692
|
|
|
667
|
-
if
|
|
668
|
-
|
|
693
|
+
if (
|
|
694
|
+
not context.app == App.ConcordApp and
|
|
695
|
+
not any([context.is_verify, context.is_bytecode,
|
|
696
|
+
context.equivalence_contracts, special_file_type]) and not context.build_only):
|
|
669
697
|
raise Util.CertoraUserInputError("You must use 'verify' when running the Certora Prover")
|
|
670
698
|
|
|
671
699
|
def find_matching_contracts(context: CertoraContext, pattern: str) -> List[str]:
|
|
@@ -883,16 +911,17 @@ def check_files_input(file_list: List[str]) -> None:
|
|
|
883
911
|
if file.endswith(Util.SOROBAN_EXEC_EXTENSION):
|
|
884
912
|
raise Util.CertoraUserInputError(f'The Soroban file {file} cannot be accompanied with other files')
|
|
885
913
|
|
|
886
|
-
|
|
887
|
-
|
|
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:
|
|
888
917
|
if Util.is_ci_or_git_action():
|
|
889
|
-
context
|
|
918
|
+
setattr(context, attr_name, ci_value)
|
|
890
919
|
else:
|
|
891
|
-
context
|
|
920
|
+
setattr(context, attr_name, default_value)
|
|
892
921
|
|
|
893
922
|
|
|
894
923
|
def mode_has_spec_file(context: CertoraContext) -> bool:
|
|
895
|
-
return not context.
|
|
924
|
+
return not (context.is_tac or context.is_equivalence)
|
|
896
925
|
|
|
897
926
|
|
|
898
927
|
def to_relative_paths(paths: Union[str, List[str]]) -> Union[str, List[str]]:
|
|
@@ -57,14 +57,17 @@ class Func:
|
|
|
57
57
|
notpayable: bool,
|
|
58
58
|
fromLib: bool, # actually serialized
|
|
59
59
|
isConstructor: bool, # not serialized
|
|
60
|
+
is_free_func: bool,
|
|
60
61
|
stateMutability: str,
|
|
61
62
|
visibility: str,
|
|
62
63
|
implemented: bool, # does this function have a body? (false for interface functions)
|
|
63
64
|
overrides: bool, # does this function override an interface declaration or super-contract definition?
|
|
65
|
+
virtual: bool,
|
|
64
66
|
contractName: str,
|
|
65
67
|
source_bytes: Optional[SourceBytes],
|
|
66
68
|
ast_id: Optional[int],
|
|
67
69
|
original_file: Optional[str],
|
|
70
|
+
location: Optional[str],
|
|
68
71
|
body_location: Optional[str],
|
|
69
72
|
):
|
|
70
73
|
self.name = name
|
|
@@ -75,13 +78,16 @@ class Func:
|
|
|
75
78
|
self.notpayable = notpayable
|
|
76
79
|
self.fromLib = fromLib
|
|
77
80
|
self.isConstructor = isConstructor
|
|
81
|
+
self.is_free_func = is_free_func
|
|
78
82
|
self.stateMutability = stateMutability
|
|
79
83
|
self.visibility = visibility
|
|
80
84
|
self.original_file = original_file
|
|
85
|
+
self.location = location
|
|
81
86
|
self.body_location = body_location
|
|
82
87
|
self.implemented = implemented
|
|
83
88
|
self.ast_id = ast_id
|
|
84
89
|
self.overrides = overrides
|
|
90
|
+
self.virtual = virtual
|
|
85
91
|
self.contractName = contractName
|
|
86
92
|
self.source_bytes = source_bytes
|
|
87
93
|
|
|
@@ -158,6 +158,15 @@ class Type(ABC):
|
|
|
158
158
|
ast_logger.fatal(f"unexpected AST Type Name Node: {name}")
|
|
159
159
|
return ret
|
|
160
160
|
|
|
161
|
+
def contains_mapping(self) -> bool:
|
|
162
|
+
if isinstance(self, MappingType):
|
|
163
|
+
return True
|
|
164
|
+
if isinstance(self, StructType):
|
|
165
|
+
return any(member.contains_mapping() for member in self.members)
|
|
166
|
+
if isinstance(self, ArrayType):
|
|
167
|
+
return self.elementType.contains_mapping()
|
|
168
|
+
return False
|
|
169
|
+
|
|
161
170
|
|
|
162
171
|
class PrimitiveType(Type):
|
|
163
172
|
allowed_primitive_type_names = {
|
|
@@ -204,7 +213,7 @@ class PrimitiveType(Type):
|
|
|
204
213
|
return PrimitiveType.canonical_primitive_name(self.name)
|
|
205
214
|
|
|
206
215
|
def get_source_str(self) -> str:
|
|
207
|
-
return self.name
|
|
216
|
+
return self.type_string # this is different from `self.name` in the case of a payable address
|
|
208
217
|
|
|
209
218
|
|
|
210
219
|
class StringType(Type):
|
|
@@ -23,6 +23,7 @@ from typing import Dict, Any, Set, List, Tuple, Optional
|
|
|
23
23
|
from CertoraProver.certoraMiniSpecParser import SpecImportLexer, SpecImportParser, SpecWithImports
|
|
24
24
|
from CertoraProver.certoraContextClass import CertoraContext
|
|
25
25
|
from Shared import certoraUtils as Util
|
|
26
|
+
import CertoraProver.certoraApp as App
|
|
26
27
|
|
|
27
28
|
build_logger = logging.getLogger("build_conf")
|
|
28
29
|
|
|
@@ -47,10 +48,6 @@ class CertoraVerifyGenerator:
|
|
|
47
48
|
# we need to build once because of the early typechecking...
|
|
48
49
|
self.update_certora_verify_struct(False)
|
|
49
50
|
|
|
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
51
|
elif self.context.equivalence_contracts is not None:
|
|
55
52
|
if self.context.method is None:
|
|
56
53
|
raise Util.CertoraUserInputError("Argument `method` is required for equivalence checks")
|
|
@@ -63,6 +60,15 @@ class CertoraVerifyGenerator:
|
|
|
63
60
|
"primary_contract": equiv_contracts[0],
|
|
64
61
|
"secondary_contract": equiv_contracts[1]
|
|
65
62
|
}
|
|
63
|
+
elif context.app == App.ConcordApp:
|
|
64
|
+
if len(context.contracts) != 2:
|
|
65
|
+
raise Util.CertoraUserInputError(f"Expecting 2 contracts but got {len(context.contracts)}: {context.contracts}")
|
|
66
|
+
contract_list = list(context.contracts)
|
|
67
|
+
self.certora_verify_struct = {
|
|
68
|
+
"type": "equivalence",
|
|
69
|
+
"primary_contract": contract_list[0],
|
|
70
|
+
"secondary_contract": contract_list[1]
|
|
71
|
+
}
|
|
66
72
|
|
|
67
73
|
def update_certora_verify_struct(self, in_certora_sources: bool) -> None:
|
|
68
74
|
"""
|
|
@@ -18,11 +18,14 @@ import sys
|
|
|
18
18
|
import re
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
import subprocess
|
|
21
|
+
import tempfile
|
|
21
22
|
import uuid
|
|
22
23
|
|
|
23
24
|
from typing import List, Set, Optional
|
|
24
25
|
|
|
26
|
+
import CertoraProver.certoraContext as Ctx
|
|
25
27
|
import CertoraProver.certoraContextAttributes as Attrs
|
|
28
|
+
import CertoraProver.certoraApp as App
|
|
26
29
|
from CertoraProver.certoraContextClass import CertoraContext
|
|
27
30
|
from Shared import certoraUtils as Util
|
|
28
31
|
|
|
@@ -71,33 +74,33 @@ class SplitRulesHandler():
|
|
|
71
74
|
def jar_list_value(list_attr: List[str]) -> str:
|
|
72
75
|
return ','.join(list_attr)
|
|
73
76
|
|
|
74
|
-
|
|
77
|
+
with tempfile.NamedTemporaryFile("r", dir=Util.get_build_dir()) as tmp_file:
|
|
78
|
+
args = ["-listRules", tmp_file.name]
|
|
75
79
|
|
|
76
|
-
|
|
80
|
+
if self.context.exclude_rule:
|
|
81
|
+
args += ['-excludeRule', jar_list_value(self.context.exclude_rule)]
|
|
77
82
|
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
if not split_rules and self.context.rule:
|
|
84
|
+
args += ['-rule', jar_list_value(self.context.rule)]
|
|
85
|
+
elif split_rules and self.context.split_rules:
|
|
86
|
+
args += ['-rule', jar_list_value(self.context.split_rules)]
|
|
80
87
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
try:
|
|
88
|
+
try:
|
|
89
|
+
Ctx.run_local_spec_check(False, self.context, args, print_errors=False)
|
|
90
|
+
lines = tmp_file.read().split("\n")
|
|
91
|
+
return set(lines)
|
|
86
92
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
filtered_lines = [s for s in lines if not (s.startswith("Warning:") or " " in s)]
|
|
90
|
-
return set(filtered_lines)
|
|
91
|
-
|
|
92
|
-
except subprocess.CalledProcessError as e:
|
|
93
|
-
raise Util.CertoraUserInputError(f"Failed to get {'split ' if split_rules else ''}rules\ncommand: {command}\n{e}")
|
|
93
|
+
except Exception as e:
|
|
94
|
+
raise Util.CertoraUserInputError(f"Failed to get {'split ' if split_rules else ''}rules\n{e}")
|
|
94
95
|
|
|
95
96
|
def run_commands(self) -> int:
|
|
96
97
|
rule_flag = Attrs.EvmProverAttributes.RULE.get_flag()
|
|
97
98
|
split_rules_flag = Attrs.EvmProverAttributes.SPLIT_RULES.get_flag()
|
|
98
99
|
msg_flag = Attrs.CommonAttributes.MSG.get_flag()
|
|
100
|
+
|
|
99
101
|
# it is important to use the cache, when the difference between the runs is only the rules that apply
|
|
100
102
|
build_cache_flag = Attrs.EvmProverAttributes.BUILD_CACHE.get_flag()
|
|
103
|
+
|
|
101
104
|
group_id_flag = Attrs.EvmProverAttributes.GROUP_ID.get_flag()
|
|
102
105
|
disable_local_typechecking_flag = Attrs.EvmProverAttributes.DISABLE_LOCAL_TYPECHECKING.get_flag()
|
|
103
106
|
|
|
@@ -120,7 +123,7 @@ class SplitRulesHandler():
|
|
|
120
123
|
if called as library then if running in local mode we use certoraRun.py otherwise certoraRun (from package)
|
|
121
124
|
:return:
|
|
122
125
|
"""
|
|
123
|
-
assert
|
|
126
|
+
assert self.context.app == App.EvmApp, "Split rules is supported only for EVM apps"
|
|
124
127
|
if hasattr(self.context, 'prover_cmd'):
|
|
125
128
|
return self.context.prover_cmd
|
|
126
129
|
if self.context.local:
|
|
@@ -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}")
|
|
@@ -30,6 +30,7 @@ from CertoraProver.certoraBuild import InputConfig, CertoraBuildGenerator, Funct
|
|
|
30
30
|
from CertoraProver.certoraVerifyGenerator import CertoraVerifyGenerator
|
|
31
31
|
from CertoraProver.certoraContext import get_args
|
|
32
32
|
from certoraRun import run_certora
|
|
33
|
+
import CertoraProver.certoraApp as App
|
|
33
34
|
|
|
34
35
|
LOG_PATH = Path('EqCheck_log.log')
|
|
35
36
|
SANITY_PATH = Path('sanity.spec')
|
|
@@ -140,7 +141,7 @@ class EquivalenceChecker:
|
|
|
140
141
|
tmp_conf = self.path / TMP_SANITY_CONF
|
|
141
142
|
with tmp_conf.open('w') as temp_sanity:
|
|
142
143
|
json.dump(contents, temp_sanity, indent=4)
|
|
143
|
-
context = get_args([str(tmp_conf)])
|
|
144
|
+
context = get_args([str(tmp_conf)], App.EvmApp)
|
|
144
145
|
config = InputConfig(context)
|
|
145
146
|
cfb = CertoraBuildGenerator(config, context)
|
|
146
147
|
certora_verify_generator = CertoraVerifyGenerator(context)
|
certora_cli/Mutate/mutateApp.py
CHANGED
|
@@ -28,7 +28,6 @@ from typing import Optional, Any, List, Dict, Tuple, Set, Union
|
|
|
28
28
|
import logging
|
|
29
29
|
from types import SimpleNamespace
|
|
30
30
|
import tarfile
|
|
31
|
-
import json5
|
|
32
31
|
import csv
|
|
33
32
|
import time
|
|
34
33
|
import requests
|
|
@@ -54,6 +53,7 @@ from Mutate import mutateAttributes as MutAttrs
|
|
|
54
53
|
import CertoraProver.certoraContextAttributes as Attrs
|
|
55
54
|
from CertoraProver.certoraContextClass import CertoraContext
|
|
56
55
|
from rustMutator import run_universal_mutator
|
|
56
|
+
from CertoraProver import certoraContextValidator as Cv
|
|
57
57
|
|
|
58
58
|
class RunTimedout(Exception):
|
|
59
59
|
pass
|
|
@@ -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:
|
|
@@ -1182,6 +1186,7 @@ class MutateApp:
|
|
|
1182
1186
|
if solc:
|
|
1183
1187
|
compiler = solc
|
|
1184
1188
|
elif compiler_map:
|
|
1189
|
+
Cv.check_contract_name_arg_inputs(self.prover_context)
|
|
1185
1190
|
compiler = get_relevant_compiler(path_to_file, self.prover_context)
|
|
1186
1191
|
if not compiler:
|
|
1187
1192
|
raise Util.CertoraUserInputError(f"Cannot resolve Solidity compiler for {path_to_file}: "
|
|
@@ -1257,6 +1262,8 @@ class MutateApp:
|
|
|
1257
1262
|
for gambit_obj in self.gambit:
|
|
1258
1263
|
if MConstants.NUM_MUTANTS not in gambit_obj:
|
|
1259
1264
|
gambit_obj[MConstants.NUM_MUTANTS] = DEFAULT_NUM_MUTANTS
|
|
1265
|
+
else:
|
|
1266
|
+
gambit_obj[MConstants.NUM_MUTANTS] = int(gambit_obj[MConstants.NUM_MUTANTS])
|
|
1260
1267
|
gambit_obj[MConstants.SOLC] = self.get_solc_version(Path(gambit_obj[MConstants.FILENAME]))
|
|
1261
1268
|
gambit_obj.update(shared_attributes)
|
|
1262
1269
|
with MConstants.TMP_GAMBIT_PATH.open('w') as f:
|
|
@@ -1375,7 +1382,7 @@ class MutateApp:
|
|
|
1375
1382
|
for mutant in self.universal_mutator:
|
|
1376
1383
|
file_to_mutate = Path(os.path.normpath(mutant[MConstants.FILE_TO_MUTATE]))
|
|
1377
1384
|
mutants_location = Path(mutant[MConstants.MUTANTS_LOCATION])
|
|
1378
|
-
num_of_mutants = mutant[MConstants.NUM_MUTANTS]
|
|
1385
|
+
num_of_mutants = int(mutant[MConstants.NUM_MUTANTS])
|
|
1379
1386
|
run_universal_mutator(file_to_mutate, self.prover_context.build_script, mutants_location, num_of_mutants)
|
|
1380
1387
|
self.add_dir_to_mutants(ret_mutants, mutants_location, file_to_mutate)
|
|
1381
1388
|
return ret_mutants
|
|
@@ -1405,6 +1412,8 @@ class MutateApp:
|
|
|
1405
1412
|
certora_args = [str(conf_file), "--run_source", "MUTATION", "--msg", msg]
|
|
1406
1413
|
if self.wait_for_original_run:
|
|
1407
1414
|
certora_args.append("--wait_for_results")
|
|
1415
|
+
if self.url_visibility:
|
|
1416
|
+
certora_args.extend(["--url_visibility", str(self.url_visibility)])
|
|
1408
1417
|
if self.with_split_stats_data and os.environ.get("WITH_AUTOCONFING", False) == '1':
|
|
1409
1418
|
certora_args += ['--prover_resource_files', f"ac:{MConstants.SPLIT_STATS_DATA}"]
|
|
1410
1419
|
if mutation_test_id:
|
|
@@ -1872,7 +1881,7 @@ class MutateApp:
|
|
|
1872
1881
|
|
|
1873
1882
|
with open(self.conf, 'r') as conf_file:
|
|
1874
1883
|
try:
|
|
1875
|
-
self.prover_context = CertoraContext(**
|
|
1884
|
+
self.prover_context = CertoraContext(**Util.read_conf_file(conf_file))
|
|
1876
1885
|
self.check_prover_context()
|
|
1877
1886
|
except Exception as e:
|
|
1878
1887
|
raise Util.CertoraUserInputError(f"Failed to parse {self.conf} as JSON", e)
|
|
@@ -1915,7 +1924,9 @@ class MutateApp:
|
|
|
1915
1924
|
if not self.manual_mutants:
|
|
1916
1925
|
return
|
|
1917
1926
|
|
|
1918
|
-
solc = self.get_solc_version(
|
|
1927
|
+
solc = self.get_solc_version(Path(mutant.original_filename))
|
|
1928
|
+
if not solc:
|
|
1929
|
+
raise Util.CertoraUserInputError(f"Unable to find a compiler for manual mutant {mutant.original_filename}")
|
|
1919
1930
|
|
|
1920
1931
|
via_ir_flag = []
|
|
1921
1932
|
if getattr(self.prover_context, MConstants.SOLC_VIA_IR, '') or \
|
|
@@ -1926,10 +1937,11 @@ class MutateApp:
|
|
|
1926
1937
|
if self.test == str(Util.TestValue.CHECK_MANUAL_COMPILATION):
|
|
1927
1938
|
raise Util.TestResultsReady(' '.join(args))
|
|
1928
1939
|
with Util.change_working_directory(find_cwd(trg_dir)):
|
|
1929
|
-
result = subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.
|
|
1940
|
+
result = subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
|
1930
1941
|
if result.returncode:
|
|
1931
1942
|
mutation_logger.debug(f"mutation compilation failed: cmd: {' '.join(args)}\n cwd: {os.getcwd()}")
|
|
1932
|
-
raise Util.CertoraUserInputError(f"mutation file {mutant.filename} failed to compile"
|
|
1943
|
+
raise Util.CertoraUserInputError(f"mutation file {mutant.filename} failed to compile\n\n"
|
|
1944
|
+
f"{result.stderr.decode()}")
|
|
1933
1945
|
|
|
1934
1946
|
|
|
1935
1947
|
def rec_collect_statuses_children(rule: Dict[str, Any], statuses: List[str]) -> None:
|