certora-cli-beta-mirror 8.0.0__py3-none-macosx_10_9_universal2.whl → 8.1.0__py3-none-macosx_10_9_universal2.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 +178 -26
- certora_cli/CertoraProver/certoraBuildDataClasses.py +3 -1
- certora_cli/CertoraProver/certoraBuildRust.py +1 -1
- certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +1 -1
- certora_cli/CertoraProver/certoraConfigIO.py +11 -12
- certora_cli/CertoraProver/certoraContext.py +61 -54
- certora_cli/CertoraProver/certoraContextAttributes.py +9 -0
- certora_cli/CertoraProver/certoraContextValidator.py +12 -12
- certora_cli/CertoraProver/certoraContractFuncs.py +6 -0
- certora_cli/CertoraProver/certoraType.py +10 -1
- certora_cli/CertoraProver/certoraVerifyGenerator.py +2 -1
- certora_cli/CertoraProver/splitRules.py +18 -17
- certora_cli/EquivalenceCheck/equivCheck.py +2 -1
- certora_cli/Mutate/mutateApp.py +12 -6
- certora_cli/Mutate/mutateValidate.py +2 -2
- certora_cli/Shared/certoraUtils.py +70 -7
- certora_cli/Shared/proverCommon.py +6 -6
- certora_cli/certoraConcord.py +2 -2
- certora_cli/certoraEVMProver.py +2 -2
- certora_cli/certoraRanger.py +2 -2
- certora_cli/certoraRun.py +9 -9
- certora_cli/certoraSolanaProver.py +2 -2
- certora_cli/certoraSorobanProver.py +2 -3
- {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/METADATA +3 -2
- {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/RECORD +35 -34
- 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-8.0.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/LICENSE +0 -0
- {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/WHEEL +0 -0
- {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/entry_points.txt +0 -0
- {certora_cli_beta_mirror-8.0.0.dist-info → certora_cli_beta_mirror-8.1.0.dist-info}/top_level.txt +0 -0
|
@@ -51,10 +51,8 @@ def get_relevant_compiler(contract_file_path: Path, context: CertoraContext) ->
|
|
|
51
51
|
|
|
52
52
|
if context.compiler_map:
|
|
53
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")
|
|
56
54
|
if match:
|
|
57
|
-
return match
|
|
55
|
+
return str(match)
|
|
58
56
|
else:
|
|
59
57
|
raise RuntimeError(f'cannot match compiler to {contract_file_path} from compiler_map')
|
|
60
58
|
|
|
@@ -104,14 +104,17 @@ class CompilerLangYul(CompilerLangSol, metaclass=Singleton):
|
|
|
104
104
|
notpayable=notpayable,
|
|
105
105
|
fromLib=False,
|
|
106
106
|
isConstructor=False,
|
|
107
|
+
is_free_func=False,
|
|
107
108
|
stateMutability=state_mutability,
|
|
108
109
|
visibility=visibility,
|
|
109
110
|
implemented=True,
|
|
110
111
|
overrides=False,
|
|
112
|
+
virtual=False,
|
|
111
113
|
contractName=contract_name,
|
|
112
114
|
source_bytes=None,
|
|
113
115
|
ast_id=None,
|
|
114
116
|
original_file=None,
|
|
117
|
+
location=None,
|
|
115
118
|
body_location=None,
|
|
116
119
|
)
|
|
117
120
|
funcs.append(func)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# The Certora Prover
|
|
2
|
+
# Copyright (C) 2025 Certora Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, version 3 of the License.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
import CertoraProver.certoraContextAttributes as Attrs
|
|
17
|
+
from Shared import certoraAttrUtil as AttrUtil
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
from abc import ABC
|
|
21
|
+
from typing import Type
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CertoraApp(ABC):
|
|
25
|
+
attr_class: Type[AttrUtil.Attributes] = Attrs.EvmProverAttributes
|
|
26
|
+
|
|
27
|
+
class EvmAppClass(CertoraApp):
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
class EvmApp(EvmAppClass):
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
class RustAppClass(CertoraApp):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
class SolanaApp(RustAppClass):
|
|
37
|
+
attr_class = Attrs.SolanaProverAttributes
|
|
38
|
+
|
|
39
|
+
class SorobanApp(RustAppClass):
|
|
40
|
+
attr_class = Attrs.SorobanProverAttributes
|
|
41
|
+
|
|
42
|
+
class RangerApp(EvmAppClass):
|
|
43
|
+
attr_class = Attrs.RangerAttributes
|
|
44
|
+
|
|
45
|
+
class ConcordApp(EvmAppClass):
|
|
46
|
+
attr_class = Attrs.ConcordAttributes
|
|
47
|
+
|
|
48
|
+
class SuiApp(EvmAppClass):
|
|
49
|
+
attr_class = Attrs.SuiProverAttributes
|
|
@@ -834,6 +834,7 @@ class CertoraBuildGenerator:
|
|
|
834
834
|
body_location = body_node["src"]
|
|
835
835
|
elif body_node is None and func_def["implemented"]:
|
|
836
836
|
ast_logger.debug(f"No body for {func_def} but ast claims it is implemented")
|
|
837
|
+
location: Optional[str] = func_def.get("src", None)
|
|
837
838
|
|
|
838
839
|
if original_contract is not None:
|
|
839
840
|
if method := original_contract.find_method(func_name, solidity_type_args):
|
|
@@ -858,14 +859,17 @@ class CertoraBuildGenerator:
|
|
|
858
859
|
func_def[STATEMUT] in ["nonpayable", "view", "pure"],
|
|
859
860
|
c_is_lib,
|
|
860
861
|
is_constructor,
|
|
862
|
+
func_def.get("kind") == CertoraBuildGenerator.FREEFUNCTION_STRING,
|
|
861
863
|
func_def[STATEMUT],
|
|
862
864
|
func_visibility,
|
|
863
865
|
func_def["implemented"],
|
|
864
866
|
func_def.get("overrides", None) is not None,
|
|
867
|
+
func_def.get("virtual", False),
|
|
865
868
|
contract_name,
|
|
866
869
|
source_bytes,
|
|
867
870
|
ast_id=func_def.get("id", None),
|
|
868
871
|
original_file=original_file,
|
|
872
|
+
location=location,
|
|
869
873
|
body_location=body_location,
|
|
870
874
|
)
|
|
871
875
|
|
|
@@ -917,15 +921,18 @@ class CertoraBuildGenerator:
|
|
|
917
921
|
notpayable=is_not_payable,
|
|
918
922
|
fromLib=c_is_lib,
|
|
919
923
|
isConstructor=False,
|
|
924
|
+
is_free_func=False,
|
|
920
925
|
stateMutability=state_mutability,
|
|
921
926
|
implemented=True,
|
|
922
927
|
overrides=public_state_var.get("overrides", None) is not None,
|
|
928
|
+
virtual=False,
|
|
923
929
|
# according to Solidity docs, getter functions have external visibility
|
|
924
930
|
visibility="external",
|
|
925
931
|
contractName=contract_name,
|
|
926
932
|
source_bytes=SourceBytes.from_ast_node(public_state_var),
|
|
927
933
|
ast_id=None,
|
|
928
934
|
original_file=c_file,
|
|
935
|
+
location=None,
|
|
929
936
|
body_location=None,
|
|
930
937
|
)
|
|
931
938
|
)
|
|
@@ -959,6 +966,8 @@ class CertoraBuildGenerator:
|
|
|
959
966
|
ret = solc_type == "address"
|
|
960
967
|
elif isinstance(ct_type, CT.StructType):
|
|
961
968
|
ret = solc_type == "tuple"
|
|
969
|
+
elif isinstance(ct_type, CT.EnumType):
|
|
970
|
+
ret = solc_type == "uint8"
|
|
962
971
|
return ret
|
|
963
972
|
|
|
964
973
|
fs = [f for f in fs if all(compareTypes(a.type, i)
|
|
@@ -979,7 +988,7 @@ class CertoraBuildGenerator:
|
|
|
979
988
|
assert len(f.returns) == len(fabi["outputs"]), \
|
|
980
989
|
f"function collected for {fabi['name']} has the wrong number of return values"
|
|
981
990
|
assert all(compareTypes(a.type, i) for a, i in zip(f.returns, fabi["outputs"])), \
|
|
982
|
-
f"function collected for {fabi['name']} has the wrong types of return values"
|
|
991
|
+
f"function collected for {fabi['name']} has the wrong types of return values. {[t.type.type_string for t in f.returns]} vs {fabi['outputs']}"
|
|
983
992
|
|
|
984
993
|
verify_collected_all_abi_funcs(
|
|
985
994
|
[f for f in data["abi"] if f["type"] == "function"],
|
|
@@ -1375,7 +1384,9 @@ class CertoraBuildGenerator:
|
|
|
1375
1384
|
|
|
1376
1385
|
def get_solc_optimize_value(self, contract_file_path: Path) -> Optional[str]:
|
|
1377
1386
|
match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_optimize')
|
|
1378
|
-
assert isinstance(match, (str, type(None))), f"Expected solc_optimize to be string
|
|
1387
|
+
assert isinstance(match, (str, int, type(None))), f"Expected solc_optimize to be string, integer , got {type(match)}"
|
|
1388
|
+
if isinstance(match, int):
|
|
1389
|
+
match = str(match)
|
|
1379
1390
|
return match
|
|
1380
1391
|
|
|
1381
1392
|
def _handle_via_ir(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
|
|
@@ -2217,14 +2228,17 @@ class CertoraBuildGenerator:
|
|
|
2217
2228
|
notpayable=x.notpayable,
|
|
2218
2229
|
fromLib=x.fromLib,
|
|
2219
2230
|
isConstructor=x.isConstructor,
|
|
2231
|
+
is_free_func=False,
|
|
2220
2232
|
stateMutability=x.stateMutability,
|
|
2221
2233
|
visibility=x.visibility,
|
|
2222
2234
|
implemented=x.implemented,
|
|
2223
2235
|
overrides=x.overrides,
|
|
2236
|
+
virtual=False,
|
|
2224
2237
|
contractName=x.contractName,
|
|
2225
2238
|
ast_id=x.ast_id,
|
|
2226
2239
|
source_bytes=x.source_bytes,
|
|
2227
2240
|
original_file=x.original_file,
|
|
2241
|
+
location=None,
|
|
2228
2242
|
body_location=x.body_location,
|
|
2229
2243
|
) for x in clfuncs]
|
|
2230
2244
|
elif compiler_lang == CompilerLangYul():
|
|
@@ -2491,6 +2505,114 @@ class CertoraBuildGenerator:
|
|
|
2491
2505
|
mut=InsertAfter())
|
|
2492
2506
|
return function_finder_by_contract, function_finder_instrumentation
|
|
2493
2507
|
|
|
2508
|
+
def add_internal_func_harnesses(self, contract_file: str, sdc: SDC, specCalls: List[str]) -> Optional[Tuple[Dict[str, str], Dict[str, Dict[int, Instrumentation]]]]:
|
|
2509
|
+
# contract file -> byte offset -> to insert
|
|
2510
|
+
harness_function_instrumentation: Dict[str, Dict[int, Instrumentation]] = defaultdict(dict)
|
|
2511
|
+
# internal function name -> harness fuction name
|
|
2512
|
+
harness_function_names: Dict[str, str] = {}
|
|
2513
|
+
|
|
2514
|
+
if not isinstance(sdc.compiler_collector, CompilerCollectorSol):
|
|
2515
|
+
raise Exception(f"Encountered a compiler collector that is not solc for file {contract_file}"
|
|
2516
|
+
" when trying to add function autofinders")
|
|
2517
|
+
instrumentation_logger.debug(f"Using {sdc.compiler_collector} compiler to "
|
|
2518
|
+
f"add external function harnesses to contract {sdc.primary_contract}")
|
|
2519
|
+
|
|
2520
|
+
for c in sdc.contracts:
|
|
2521
|
+
for f in c.internal_funcs:
|
|
2522
|
+
if f"{sdc.primary_contract}.{f.name}" not in specCalls:
|
|
2523
|
+
continue
|
|
2524
|
+
|
|
2525
|
+
if f.fromLib:
|
|
2526
|
+
# Even external library functions can't be called directly from spec,
|
|
2527
|
+
# so skip harnessing internal ones.
|
|
2528
|
+
continue
|
|
2529
|
+
|
|
2530
|
+
if f.isConstructor:
|
|
2531
|
+
continue
|
|
2532
|
+
|
|
2533
|
+
if f.is_free_func:
|
|
2534
|
+
# Free functions (declared outside of any contract/library) don't have visibility modifiers
|
|
2535
|
+
continue
|
|
2536
|
+
|
|
2537
|
+
orig_file = f.original_file
|
|
2538
|
+
if not orig_file:
|
|
2539
|
+
instrumentation_logger.debug(f"missing file location for {f.name}")
|
|
2540
|
+
continue
|
|
2541
|
+
|
|
2542
|
+
loc = f.location
|
|
2543
|
+
if not loc:
|
|
2544
|
+
instrumentation_logger.debug(f"Found a function {f.name} in "
|
|
2545
|
+
f"{c.name} that doesn't have a location")
|
|
2546
|
+
continue
|
|
2547
|
+
|
|
2548
|
+
if f.implemented:
|
|
2549
|
+
expected_end_char = b"}"
|
|
2550
|
+
else:
|
|
2551
|
+
# This is a virtual function with no implementation. Declare also a virtual harness so that when
|
|
2552
|
+
# Implementing a harness for the override we can just add the `override` keyword to the harness
|
|
2553
|
+
# function as well without needing to have any extra logic at that point.
|
|
2554
|
+
expected_end_char = b";"
|
|
2555
|
+
|
|
2556
|
+
if len(f.fullArgs) != len(f.paramNames):
|
|
2557
|
+
instrumentation_logger.debug(f"Do not have argument names for {f.name} in"
|
|
2558
|
+
f" {c.name}, giving up internal function harnessing")
|
|
2559
|
+
continue
|
|
2560
|
+
|
|
2561
|
+
if any(ty.location == CT.TypeLocation.STORAGE for ty in f.fullArgs + f.returns):
|
|
2562
|
+
instrumentation_logger.debug(f"Function {f.name} has input arguments with 'storage' location - cannot harness it")
|
|
2563
|
+
continue
|
|
2564
|
+
|
|
2565
|
+
instrumentation_path = str(Util.abs_norm_path(orig_file))
|
|
2566
|
+
per_file_inst = harness_function_instrumentation[instrumentation_path]
|
|
2567
|
+
|
|
2568
|
+
start, size, _ = loc.split(":")
|
|
2569
|
+
body_end_byte = int(start) + int(size) - 1
|
|
2570
|
+
|
|
2571
|
+
def get_local_type_name(ty: CT.TypeInstance) -> str:
|
|
2572
|
+
# Handles imports that use 'as'. E.g. `import {A as B} from "A.sol";`
|
|
2573
|
+
ret = ty.get_source_str()
|
|
2574
|
+
assert orig_file
|
|
2575
|
+
for node in self.asts[sdc.sdc_origin_file][orig_file].values():
|
|
2576
|
+
if node["nodeType"] != "ImportDirective":
|
|
2577
|
+
continue
|
|
2578
|
+
for alias in node["symbolAliases"]:
|
|
2579
|
+
if alias["foreign"]["name"] == ty.get_source_str() and "local" in alias:
|
|
2580
|
+
ret = alias["local"]
|
|
2581
|
+
break
|
|
2582
|
+
|
|
2583
|
+
# Now add the location
|
|
2584
|
+
return ret + (f" {ty.location.value}" if ty.location != CT.TypeLocation.STACK else "")
|
|
2585
|
+
|
|
2586
|
+
harness_name = f"{f.name}_external_harness"
|
|
2587
|
+
harness_string = f" function {harness_name}({', '.join(f'{get_local_type_name(ty)} {n}' for ty, n in zip(f.fullArgs, f.paramNames))}) external"
|
|
2588
|
+
|
|
2589
|
+
if f.stateMutability in ["pure", "view"]:
|
|
2590
|
+
harness_string += f" {f.stateMutability}"
|
|
2591
|
+
|
|
2592
|
+
if f.virtual:
|
|
2593
|
+
harness_string += " virtual"
|
|
2594
|
+
|
|
2595
|
+
if f.overrides:
|
|
2596
|
+
harness_string += " override"
|
|
2597
|
+
|
|
2598
|
+
if f.returns:
|
|
2599
|
+
harness_string += f" returns ({', '.join(get_local_type_name(r) for r in f.returns)})"
|
|
2600
|
+
|
|
2601
|
+
if f.implemented:
|
|
2602
|
+
harness_call = f"{f.name}({', '.join(f.paramNames)})"
|
|
2603
|
+
if f.returns:
|
|
2604
|
+
harness_string += f" {{ return {harness_call}; }}"
|
|
2605
|
+
else:
|
|
2606
|
+
harness_string += f" {{ {harness_call}; }}"
|
|
2607
|
+
else:
|
|
2608
|
+
harness_string += ";"
|
|
2609
|
+
|
|
2610
|
+
per_file_inst[body_end_byte] = Instrumentation(expected=expected_end_char, to_ins=harness_string,
|
|
2611
|
+
mut=InsertAfter())
|
|
2612
|
+
harness_function_names[f.name] = harness_name
|
|
2613
|
+
|
|
2614
|
+
return harness_function_names, harness_function_instrumentation
|
|
2615
|
+
|
|
2494
2616
|
def cleanup(self) -> None:
|
|
2495
2617
|
for sdc_name, smart_contract_lang in self.__compiled_artifacts_to_clean:
|
|
2496
2618
|
self.cleanup_compiler_outputs(sdc_name, smart_contract_lang)
|
|
@@ -2546,6 +2668,16 @@ class CertoraBuildGenerator:
|
|
|
2546
2668
|
|
|
2547
2669
|
def build(self, certora_verify_generator: CertoraVerifyGenerator) -> None:
|
|
2548
2670
|
context = self.context
|
|
2671
|
+
|
|
2672
|
+
specCalls: List[str] = []
|
|
2673
|
+
if context.verify and not context.disallow_internal_function_calls:
|
|
2674
|
+
with tempfile.NamedTemporaryFile("r", dir=Util.get_build_dir()) as tmp_file:
|
|
2675
|
+
try:
|
|
2676
|
+
Ctx.run_local_spec_check(False, context, ["-listCalls", tmp_file.name], print_errors=False)
|
|
2677
|
+
specCalls = tmp_file.read().split("\n")
|
|
2678
|
+
except Exception as e:
|
|
2679
|
+
instrumentation_logger.warning(f"Failed to get calls from spec\n{e}")
|
|
2680
|
+
|
|
2549
2681
|
self.context.remappings = []
|
|
2550
2682
|
for i, build_arg_contract_file in enumerate(sorted(self.input_config.sorted_files)):
|
|
2551
2683
|
build_logger.debug(f"\nbuilding file {build_arg_contract_file}")
|
|
@@ -2597,7 +2729,7 @@ class CertoraBuildGenerator:
|
|
|
2597
2729
|
# We start by trying to instrument _all_ finders, both autofinders and source finders
|
|
2598
2730
|
added_finders, all_finders_success, src_finders_gen_success, post_backup_dir = self.finders_compilation_round(
|
|
2599
2731
|
build_arg_contract_file, i, ignore_patterns, path_for_compiler_collector_file, pre_backup_dir,
|
|
2600
|
-
sdc_pre_finders, not context.disable_source_finders)
|
|
2732
|
+
sdc_pre_finders, not context.disable_source_finders, specCalls)
|
|
2601
2733
|
|
|
2602
2734
|
# we could have a case where source finders failed but regular finders succeeded.
|
|
2603
2735
|
# e.g. if we processed the AST wrong and skipped source finders generation
|
|
@@ -2609,21 +2741,21 @@ class CertoraBuildGenerator:
|
|
|
2609
2741
|
# let's try just the function autofinders
|
|
2610
2742
|
added_finders, function_autofinder_success, _, post_backup_dir = self.finders_compilation_round(
|
|
2611
2743
|
build_arg_contract_file, i, ignore_patterns, path_for_compiler_collector_file, pre_backup_dir,
|
|
2612
|
-
sdc_pre_finders, False)
|
|
2744
|
+
sdc_pre_finders, False, specCalls)
|
|
2613
2745
|
|
|
2614
2746
|
if not function_autofinder_success:
|
|
2615
2747
|
self.auto_finders_failed = True
|
|
2616
2748
|
|
|
2617
2749
|
if not self.auto_finders_failed or not self.source_finders_failed:
|
|
2618
2750
|
# setup source_dir. note that post_backup_dir must include the finders in this case
|
|
2619
|
-
for _, _, sdc in added_finders:
|
|
2751
|
+
for _, _, _, sdc in added_finders:
|
|
2620
2752
|
sdc.source_dir = str(post_backup_dir.relative_to(Util.get_certora_sources_dir()))
|
|
2621
2753
|
sdc.orig_source_dir = str(pre_backup_dir.relative_to(Util.get_certora_sources_dir()))
|
|
2622
2754
|
else:
|
|
2623
2755
|
# no point in running autofinders in vyper right now
|
|
2624
|
-
added_finders = [({}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders]
|
|
2756
|
+
added_finders = [({}, {}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders]
|
|
2625
2757
|
|
|
2626
|
-
for added_func_finders, added_source_finders, sdc in added_finders:
|
|
2758
|
+
for added_func_finders, added_source_finders, added_internal_function_harnesses, sdc in added_finders:
|
|
2627
2759
|
for contract in sdc.contracts:
|
|
2628
2760
|
all_functions: List[Func] = list()
|
|
2629
2761
|
for k, v in added_func_finders.items():
|
|
@@ -2631,6 +2763,8 @@ class CertoraBuildGenerator:
|
|
|
2631
2763
|
contract.function_finders[k] = v
|
|
2632
2764
|
for source_key, source_value in added_source_finders.items():
|
|
2633
2765
|
contract.local_assignments[source_key] = source_value
|
|
2766
|
+
if contract.name == sdc.primary_contract:
|
|
2767
|
+
contract.internal_function_harnesses = added_internal_function_harnesses
|
|
2634
2768
|
all_functions.extend(contract.methods)
|
|
2635
2769
|
all_functions.extend(contract.internal_funcs)
|
|
2636
2770
|
functions_unique_by_internal_rep = list() # type: List[Func]
|
|
@@ -2954,12 +3088,13 @@ class CertoraBuildGenerator:
|
|
|
2954
3088
|
path_for_compiler_collector_file: str,
|
|
2955
3089
|
pre_backup_dir: Path,
|
|
2956
3090
|
sdc_pre_finders: List[SDC],
|
|
2957
|
-
with_source_finders: bool
|
|
2958
|
-
|
|
3091
|
+
with_source_finders: bool,
|
|
3092
|
+
specCalls: List[str]) -> Tuple[
|
|
3093
|
+
List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]], bool, bool, Path]:
|
|
2959
3094
|
added_finders_to_sdc, finders_compilation_success, source_finders_gen_success = \
|
|
2960
3095
|
self.instrument_auto_finders(
|
|
2961
3096
|
build_arg_contract_file, i, sdc_pre_finders,
|
|
2962
|
-
path_for_compiler_collector_file, with_source_finders)
|
|
3097
|
+
path_for_compiler_collector_file, with_source_finders, specCalls)
|
|
2963
3098
|
# successful or not, we backup current .certora_sources for either debuggability, or for availability
|
|
2964
3099
|
# of sources.
|
|
2965
3100
|
post_backup_dir = self.get_fresh_backupdir(Util.POST_AUTOFINDER_BACKUP_DIR)
|
|
@@ -3031,11 +3166,12 @@ class CertoraBuildGenerator:
|
|
|
3031
3166
|
def instrument_auto_finders(self, build_arg_contract_file: str, i: int,
|
|
3032
3167
|
sdc_pre_finders: List[SDC],
|
|
3033
3168
|
path_for_compiler_collector_file: str,
|
|
3034
|
-
instrument_source_finders: bool
|
|
3035
|
-
|
|
3169
|
+
instrument_source_finders: bool,
|
|
3170
|
+
specCalls: List[str]) -> Tuple[
|
|
3171
|
+
List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]], bool, bool]:
|
|
3036
3172
|
|
|
3037
3173
|
# initialization
|
|
3038
|
-
ret
|
|
3174
|
+
ret: List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]] = []
|
|
3039
3175
|
instrumentation_logger.debug(f"Instrumenting auto finders in {build_arg_contract_file}")
|
|
3040
3176
|
# all of the [SDC]s inside [sdc_pre_finders] have the same list of [ContractInSDC]s
|
|
3041
3177
|
# (generated in the [collect_from_file] function).
|
|
@@ -3044,10 +3180,19 @@ class CertoraBuildGenerator:
|
|
|
3044
3180
|
if added_function_finders_tuple is None:
|
|
3045
3181
|
instrumentation_logger.warning(
|
|
3046
3182
|
f"Computing function finder instrumentation failed for {build_arg_contract_file}")
|
|
3047
|
-
return [({}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
3183
|
+
return [({}, {}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
3048
3184
|
|
|
3049
3185
|
(added_function_finders, function_instr) = added_function_finders_tuple
|
|
3050
3186
|
|
|
3187
|
+
instr = function_instr
|
|
3188
|
+
|
|
3189
|
+
added_internal_function_harnesses: Dict[str, str] = {}
|
|
3190
|
+
if not self.context.disallow_internal_function_calls:
|
|
3191
|
+
added_internal_func_harness_tuple = self.add_internal_func_harnesses(build_arg_contract_file, sdc_pre_finder, specCalls)
|
|
3192
|
+
if added_internal_func_harness_tuple:
|
|
3193
|
+
instr = CertoraBuildGenerator.merge_dicts_instrumentation(function_instr, added_internal_func_harness_tuple[1])
|
|
3194
|
+
added_internal_function_harnesses = added_internal_func_harness_tuple[0]
|
|
3195
|
+
|
|
3051
3196
|
source_finders_gen_succeeded = False
|
|
3052
3197
|
if instrument_source_finders:
|
|
3053
3198
|
try:
|
|
@@ -3056,15 +3201,13 @@ class CertoraBuildGenerator:
|
|
|
3056
3201
|
(added_source_finders, source_instr) = added_source_finders_tuple
|
|
3057
3202
|
# Update instr with additional instrumentations. Recall it is a map file -> offset -> instr.
|
|
3058
3203
|
# Function finders take precedence
|
|
3059
|
-
instr = CertoraBuildGenerator.merge_dicts_instrumentation(
|
|
3204
|
+
instr = CertoraBuildGenerator.merge_dicts_instrumentation(instr, source_instr)
|
|
3060
3205
|
source_finders_gen_succeeded = True
|
|
3061
3206
|
except: # noqa: E722
|
|
3062
3207
|
instrumentation_logger.warning(
|
|
3063
3208
|
f"Computing source finder instrumentation failed for {build_arg_contract_file}")
|
|
3064
|
-
instr = function_instr
|
|
3065
3209
|
added_source_finders = {}
|
|
3066
3210
|
else:
|
|
3067
|
-
instr = function_instr
|
|
3068
3211
|
added_source_finders = {}
|
|
3069
3212
|
|
|
3070
3213
|
abs_build_arg_contract_file = Util.abs_posix_path(build_arg_contract_file)
|
|
@@ -3084,7 +3227,7 @@ class CertoraBuildGenerator:
|
|
|
3084
3227
|
# instrumentation should be keyed only using absolute paths
|
|
3085
3228
|
instrumentation_logger.warning(f"Already generated autofinder for {new_name}, "
|
|
3086
3229
|
f"cannot instrument again for {contract_file}")
|
|
3087
|
-
return [({}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
3230
|
+
return [({}, {}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
3088
3231
|
|
|
3089
3232
|
autofinder_remappings[new_name] = contract_file
|
|
3090
3233
|
|
|
@@ -3113,7 +3256,7 @@ class CertoraBuildGenerator:
|
|
|
3113
3256
|
instrumentation_logger.warning("Skipping source finder generation!")
|
|
3114
3257
|
else:
|
|
3115
3258
|
instrumentation_logger.warning("Skipping internal function finder generation!")
|
|
3116
|
-
return [({}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
3259
|
+
return [({}, {}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
3117
3260
|
to_skip = to_insert.mut.insert(to_insert.to_ins, to_insert.expected, output)
|
|
3118
3261
|
if to_skip != 0:
|
|
3119
3262
|
in_file.read(to_skip)
|
|
@@ -3125,7 +3268,7 @@ class CertoraBuildGenerator:
|
|
|
3125
3268
|
build_arg_contract_file]
|
|
3126
3269
|
|
|
3127
3270
|
# add generated file to map attributes
|
|
3128
|
-
for map_attr in
|
|
3271
|
+
for map_attr in self.context.app.attr_class.all_map_attrs():
|
|
3129
3272
|
map_attr_value = getattr(self.context, map_attr)
|
|
3130
3273
|
if map_attr_value and build_arg_contract_file in map_attr_value:
|
|
3131
3274
|
map_attr_value[new_file] = map_attr_value[build_arg_contract_file]
|
|
@@ -3151,7 +3294,7 @@ class CertoraBuildGenerator:
|
|
|
3151
3294
|
fail_on_compilation_error=False,
|
|
3152
3295
|
reroute_main_path=True)
|
|
3153
3296
|
for new_sdc in new_sdcs:
|
|
3154
|
-
ret.append((added_function_finders, added_source_finders, new_sdc))
|
|
3297
|
+
ret.append((added_function_finders, added_source_finders, added_internal_function_harnesses, new_sdc))
|
|
3155
3298
|
|
|
3156
3299
|
except Util.SolcCompilationException as e:
|
|
3157
3300
|
print(f"Encountered an exception generating autofinder {new_file} ({e}), falling back to original "
|
|
@@ -3160,7 +3303,7 @@ class CertoraBuildGenerator:
|
|
|
3160
3303
|
f"falling back to the original file {Path(build_arg_contract_file).name}", exc_info=e)
|
|
3161
3304
|
# clean up mutation
|
|
3162
3305
|
self.function_finder_file_remappings = {}
|
|
3163
|
-
return [({}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders], False, False
|
|
3306
|
+
return [({}, {}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders], False, False
|
|
3164
3307
|
return ret, True, source_finders_gen_succeeded
|
|
3165
3308
|
|
|
3166
3309
|
def to_autofinder_file(self, contract_file: str) -> str:
|
|
@@ -3555,6 +3698,15 @@ class CertoraBuildGenerator:
|
|
|
3555
3698
|
add_to_sources(Util.PACKAGE_FILE)
|
|
3556
3699
|
if Util.REMAPPINGS_FILE.exists():
|
|
3557
3700
|
add_to_sources(Util.REMAPPINGS_FILE)
|
|
3701
|
+
foundry_toml = Util.find_nearest_foundry_toml()
|
|
3702
|
+
if foundry_toml:
|
|
3703
|
+
# if we find foundry.toml we add it to source tree and, if exists, the remappings.txt file from the
|
|
3704
|
+
# root directory
|
|
3705
|
+
foundry_root = foundry_toml.parent
|
|
3706
|
+
add_to_sources(foundry_root / Util.FOUNDRY_TOML_FILE)
|
|
3707
|
+
remappings_file_in_root = foundry_root / Util.REMAPPINGS_FILE
|
|
3708
|
+
if remappings_file_in_root.exists():
|
|
3709
|
+
add_to_sources(remappings_file_in_root)
|
|
3558
3710
|
if context.bytecode_jsons:
|
|
3559
3711
|
for file in context.bytecode_jsons:
|
|
3560
3712
|
add_to_sources(Path(file))
|
|
@@ -3811,11 +3963,11 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
|
|
|
3811
3963
|
|
|
3812
3964
|
# Start by syntax checking, if we're in the right mode
|
|
3813
3965
|
if Cv.mode_has_spec_file(context) and not context.build_only and not ignore_spec_syntax_check:
|
|
3814
|
-
if context
|
|
3966
|
+
if Ctx.should_run_local_speck_check(context):
|
|
3967
|
+
Ctx.run_local_spec_check(False, context)
|
|
3968
|
+
else:
|
|
3815
3969
|
build_logger.warning(
|
|
3816
3970
|
"Local checks of CVL specification files disabled. It is recommended to enable the checks.")
|
|
3817
|
-
else:
|
|
3818
|
-
Ctx.run_local_spec_check(False, context)
|
|
3819
3971
|
|
|
3820
3972
|
cache_hit, build_cache_enabled, cached_files = build_from_cache_or_scratch(context,
|
|
3821
3973
|
certora_build_generator,
|
|
@@ -3823,7 +3975,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
|
|
|
3823
3975
|
|
|
3824
3976
|
# avoid running the same test over and over again for each split run, context.split_rules is true only for
|
|
3825
3977
|
# the first run and is set to [] for split runs
|
|
3826
|
-
if context.split_rules:
|
|
3978
|
+
if Ctx.should_run_local_speck_check(context) and context.split_rules:
|
|
3827
3979
|
Ctx.run_local_spec_check(True, context)
|
|
3828
3980
|
|
|
3829
3981
|
# .certora_verify.json is always constructed even if build cache is enabled
|
|
@@ -163,6 +163,7 @@ class ContractInSDC:
|
|
|
163
163
|
self.local_assignments = local_assignments
|
|
164
164
|
self.branches = branches
|
|
165
165
|
self.requires = requires
|
|
166
|
+
self.internal_function_harnesses: Dict[str, str] = {}
|
|
166
167
|
|
|
167
168
|
def as_dict(self) -> Dict[str, Any]:
|
|
168
169
|
"""
|
|
@@ -196,7 +197,8 @@ class ContractInSDC:
|
|
|
196
197
|
"sourceBytes": None if self.source_bytes is None else self.source_bytes.as_dict(),
|
|
197
198
|
"extensionContracts": [e.as_dict() for e in self.extension_contracts],
|
|
198
199
|
"localAssignments": {k: v.as_dict() for k, v in self.local_assignments.items()},
|
|
199
|
-
"internalFunctionStarts": self.internal_starts
|
|
200
|
+
"internalFunctionStarts": self.internal_starts,
|
|
201
|
+
"internalFunctionHarnesses": self.internal_function_harnesses
|
|
200
202
|
}
|
|
201
203
|
# "sourceHints": {"localAssignments": {k: v.as_dict() for k, v in self.local_assignments.items()},
|
|
202
204
|
# "branches": {k: v.as_dict() for k, v in self.branches.items()},
|
|
@@ -85,7 +85,7 @@ def build_rust_app(context: CertoraContext) -> None:
|
|
|
85
85
|
|
|
86
86
|
if context.cargo_features is not None:
|
|
87
87
|
build_command.append(feature_flag)
|
|
88
|
-
build_command.
|
|
88
|
+
build_command.append(' '.join(context.cargo_features))
|
|
89
89
|
|
|
90
90
|
if context.test == str(Util.TestValue.SOLANA_BUILD_CMD):
|
|
91
91
|
raise Util.TestResultsReady(build_command)
|
|
@@ -212,7 +212,7 @@ def split_and_sort_arg_list_value(args_list: List[str]) -> List[str]:
|
|
|
212
212
|
Assumes each flag starts with '-' and its value follows immediately, if exists.
|
|
213
213
|
Lines are sorted alphabetically.
|
|
214
214
|
"""
|
|
215
|
-
unified_args = ''.join(args_list)
|
|
215
|
+
unified_args = ' '.join(str(arg) for arg in args_list)
|
|
216
216
|
|
|
217
217
|
if not unified_args.strip():
|
|
218
218
|
return []
|
|
@@ -13,14 +13,12 @@
|
|
|
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
|
-
import json5
|
|
17
16
|
import logging
|
|
18
17
|
from pathlib import Path
|
|
19
|
-
from typing import Dict, Any
|
|
20
|
-
from collections import OrderedDict
|
|
18
|
+
from typing import Dict, Any, cast
|
|
21
19
|
|
|
22
20
|
import CertoraProver.certoraContext as Ctx
|
|
23
|
-
import CertoraProver.
|
|
21
|
+
import CertoraProver.certoraApp as App
|
|
24
22
|
from CertoraProver.certoraContextClass import CertoraContext
|
|
25
23
|
from Shared import certoraUtils as Util
|
|
26
24
|
|
|
@@ -47,11 +45,12 @@ def current_conf_to_file(context: CertoraContext) -> Dict[str, Any]:
|
|
|
47
45
|
4. Parsing the .conf file is simpler, as we can ignore the null case
|
|
48
46
|
"""
|
|
49
47
|
def input_arg_with_value(k: Any, v: Any) -> Any:
|
|
50
|
-
all_conf_names =
|
|
48
|
+
all_conf_names = context.app.attr_class.all_conf_names()
|
|
51
49
|
return v is not None and v is not False and k in all_conf_names
|
|
52
50
|
|
|
53
51
|
context_to_save = {k: v for k, v in vars(context).items() if input_arg_with_value(k, v)}
|
|
54
|
-
|
|
52
|
+
context_to_save = cast(Dict[str, Any], Util.convert_str_ints(context_to_save))
|
|
53
|
+
all_conf_names = context.app.attr_class.all_conf_names()
|
|
55
54
|
context_to_save = dict(sorted(context_to_save.items(), key=lambda x: all_conf_names.index(x[0])))
|
|
56
55
|
context_to_save.pop('build_dir', None) # build dir should not be saved, each run should define its own build_dir
|
|
57
56
|
context_to_save.pop('mutation_test_id', None) # mutation_test_id should be recreated for every run
|
|
@@ -76,7 +75,7 @@ def read_from_conf_file(context: CertoraContext) -> None:
|
|
|
76
75
|
|
|
77
76
|
try:
|
|
78
77
|
with conf_file_path.open() as conf_file:
|
|
79
|
-
context.conf_file_attr =
|
|
78
|
+
context.conf_file_attr = Util.read_conf_file(conf_file)
|
|
80
79
|
try:
|
|
81
80
|
check_conf_content(context)
|
|
82
81
|
except Util.CertoraUserInputError as e:
|
|
@@ -98,9 +97,9 @@ def handle_override_base_config(context: CertoraContext) -> None:
|
|
|
98
97
|
|
|
99
98
|
if context.override_base_config:
|
|
100
99
|
try:
|
|
101
|
-
with Path(context.override_base_config).open() as conf_file:
|
|
102
|
-
override_base_config_attrs =
|
|
103
|
-
|
|
100
|
+
with (Path(context.override_base_config).open() as conf_file):
|
|
101
|
+
override_base_config_attrs = Util.read_conf_file(conf_file)
|
|
102
|
+
|
|
104
103
|
context.conf_file_attr = {**override_base_config_attrs, **context.conf_file_attr}
|
|
105
104
|
|
|
106
105
|
if 'override_base_config' in override_base_config_attrs:
|
|
@@ -142,10 +141,10 @@ def check_conf_content(context: CertoraContext) -> None:
|
|
|
142
141
|
|
|
143
142
|
handle_override_base_config(context)
|
|
144
143
|
|
|
145
|
-
if
|
|
144
|
+
if Ctx.is_evm_app_class(context) and not context.files and not context.project_sanity and not context.foundry:
|
|
146
145
|
raise Util.CertoraUserInputError("Mandatory 'files' attribute is missing from the configuration")
|
|
147
146
|
context.files = context.conf_file_attr.get('files')
|
|
148
|
-
if
|
|
147
|
+
if context.app == App.SorobanApp and not context.files and not context.build_script:
|
|
149
148
|
raise Util.CertoraUserInputError("'files' or 'build script' must be set for Soroban runs")
|
|
150
149
|
|
|
151
150
|
context.files = context.conf_file_attr.get('files')
|