certora-cli-beta-mirror 7.31.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 +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 +80 -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 -76
- {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
|
@@ -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
|
|
@@ -39,8 +39,6 @@ from CertoraProver.certoraSourceFinders import add_source_finders
|
|
|
39
39
|
from CertoraProver.certoraVerifyGenerator import CertoraVerifyGenerator
|
|
40
40
|
from CertoraProver.certoraContractFuncs import Func, InternalFunc, STATEMUT, SourceBytes
|
|
41
41
|
|
|
42
|
-
from Shared.certoraUtils import is_relative_to
|
|
43
|
-
|
|
44
42
|
scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
|
|
45
43
|
sys.path.insert(0, str(scripts_dir_path))
|
|
46
44
|
from CertoraProver.Compiler.CompilerCollector import CompilerLang, CompilerCollector
|
|
@@ -454,8 +452,8 @@ def convert_pathname_to_posix(json_dict: Dict[str, Any], entry: str, smart_contr
|
|
|
454
452
|
# protecting against long strings
|
|
455
453
|
if len(json_dict_str) > 200:
|
|
456
454
|
json_dict_str = json_dict_str[:200] + '...'
|
|
457
|
-
fatal_error(compiler_logger, f"The path of the source file {file_path}"
|
|
458
|
-
f"in the standard json file
|
|
455
|
+
fatal_error(compiler_logger, f"The path of the source file {file_path} "
|
|
456
|
+
f"in the standard json file does not exist!\n{json_dict_str} ")
|
|
459
457
|
json_dict[entry] = json_dict_posix_paths
|
|
460
458
|
|
|
461
459
|
|
|
@@ -836,6 +834,7 @@ class CertoraBuildGenerator:
|
|
|
836
834
|
body_location = body_node["src"]
|
|
837
835
|
elif body_node is None and func_def["implemented"]:
|
|
838
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)
|
|
839
838
|
|
|
840
839
|
if original_contract is not None:
|
|
841
840
|
if method := original_contract.find_method(func_name, solidity_type_args):
|
|
@@ -860,14 +859,17 @@ class CertoraBuildGenerator:
|
|
|
860
859
|
func_def[STATEMUT] in ["nonpayable", "view", "pure"],
|
|
861
860
|
c_is_lib,
|
|
862
861
|
is_constructor,
|
|
862
|
+
func_def.get("kind") == CertoraBuildGenerator.FREEFUNCTION_STRING,
|
|
863
863
|
func_def[STATEMUT],
|
|
864
864
|
func_visibility,
|
|
865
865
|
func_def["implemented"],
|
|
866
866
|
func_def.get("overrides", None) is not None,
|
|
867
|
+
func_def.get("virtual", False),
|
|
867
868
|
contract_name,
|
|
868
869
|
source_bytes,
|
|
869
870
|
ast_id=func_def.get("id", None),
|
|
870
871
|
original_file=original_file,
|
|
872
|
+
location=location,
|
|
871
873
|
body_location=body_location,
|
|
872
874
|
)
|
|
873
875
|
|
|
@@ -919,15 +921,18 @@ class CertoraBuildGenerator:
|
|
|
919
921
|
notpayable=is_not_payable,
|
|
920
922
|
fromLib=c_is_lib,
|
|
921
923
|
isConstructor=False,
|
|
924
|
+
is_free_func=False,
|
|
922
925
|
stateMutability=state_mutability,
|
|
923
926
|
implemented=True,
|
|
924
927
|
overrides=public_state_var.get("overrides", None) is not None,
|
|
928
|
+
virtual=False,
|
|
925
929
|
# according to Solidity docs, getter functions have external visibility
|
|
926
930
|
visibility="external",
|
|
927
931
|
contractName=contract_name,
|
|
928
932
|
source_bytes=SourceBytes.from_ast_node(public_state_var),
|
|
929
933
|
ast_id=None,
|
|
930
934
|
original_file=c_file,
|
|
935
|
+
location=None,
|
|
931
936
|
body_location=None,
|
|
932
937
|
)
|
|
933
938
|
)
|
|
@@ -961,6 +966,8 @@ class CertoraBuildGenerator:
|
|
|
961
966
|
ret = solc_type == "address"
|
|
962
967
|
elif isinstance(ct_type, CT.StructType):
|
|
963
968
|
ret = solc_type == "tuple"
|
|
969
|
+
elif isinstance(ct_type, CT.EnumType):
|
|
970
|
+
ret = solc_type == "uint8"
|
|
964
971
|
return ret
|
|
965
972
|
|
|
966
973
|
fs = [f for f in fs if all(compareTypes(a.type, i)
|
|
@@ -981,7 +988,7 @@ class CertoraBuildGenerator:
|
|
|
981
988
|
assert len(f.returns) == len(fabi["outputs"]), \
|
|
982
989
|
f"function collected for {fabi['name']} has the wrong number of return values"
|
|
983
990
|
assert all(compareTypes(a.type, i) for a, i in zip(f.returns, fabi["outputs"])), \
|
|
984
|
-
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']}"
|
|
985
992
|
|
|
986
993
|
verify_collected_all_abi_funcs(
|
|
987
994
|
[f for f in data["abi"] if f["type"] == "function"],
|
|
@@ -1377,7 +1384,9 @@ class CertoraBuildGenerator:
|
|
|
1377
1384
|
|
|
1378
1385
|
def get_solc_optimize_value(self, contract_file_path: Path) -> Optional[str]:
|
|
1379
1386
|
match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_optimize')
|
|
1380
|
-
assert isinstance(match, (str, type(None))), f"Expected solc_optimize to be string
|
|
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)
|
|
1381
1390
|
return match
|
|
1382
1391
|
|
|
1383
1392
|
def _handle_via_ir(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
|
|
@@ -1432,8 +1441,11 @@ class CertoraBuildGenerator:
|
|
|
1432
1441
|
compiler_version = compiler_collector.compiler_version
|
|
1433
1442
|
major, minor, patch = compiler_version
|
|
1434
1443
|
|
|
1435
|
-
err_msg =
|
|
1436
|
-
|
|
1444
|
+
err_msg = \
|
|
1445
|
+
f"Unsupported solc version {major}.{minor}.{patch} for `solc_via_ir`. " \
|
|
1446
|
+
f"Supported versions: 0.6.7 – 0.8.25.\n" \
|
|
1447
|
+
f"Use `solc_via_ir_map` to disable `solc_via_ir` for specific files with older compiler versions."
|
|
1448
|
+
|
|
1437
1449
|
yul_optimizer_steps = None
|
|
1438
1450
|
if major != 0:
|
|
1439
1451
|
raise Util.CertoraUserInputError(err_msg)
|
|
@@ -2216,14 +2228,17 @@ class CertoraBuildGenerator:
|
|
|
2216
2228
|
notpayable=x.notpayable,
|
|
2217
2229
|
fromLib=x.fromLib,
|
|
2218
2230
|
isConstructor=x.isConstructor,
|
|
2231
|
+
is_free_func=False,
|
|
2219
2232
|
stateMutability=x.stateMutability,
|
|
2220
2233
|
visibility=x.visibility,
|
|
2221
2234
|
implemented=x.implemented,
|
|
2222
2235
|
overrides=x.overrides,
|
|
2236
|
+
virtual=False,
|
|
2223
2237
|
contractName=x.contractName,
|
|
2224
2238
|
ast_id=x.ast_id,
|
|
2225
2239
|
source_bytes=x.source_bytes,
|
|
2226
2240
|
original_file=x.original_file,
|
|
2241
|
+
location=None,
|
|
2227
2242
|
body_location=x.body_location,
|
|
2228
2243
|
) for x in clfuncs]
|
|
2229
2244
|
elif compiler_lang == CompilerLangYul():
|
|
@@ -2490,6 +2505,114 @@ class CertoraBuildGenerator:
|
|
|
2490
2505
|
mut=InsertAfter())
|
|
2491
2506
|
return function_finder_by_contract, function_finder_instrumentation
|
|
2492
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
|
+
|
|
2493
2616
|
def cleanup(self) -> None:
|
|
2494
2617
|
for sdc_name, smart_contract_lang in self.__compiled_artifacts_to_clean:
|
|
2495
2618
|
self.cleanup_compiler_outputs(sdc_name, smart_contract_lang)
|
|
@@ -2545,6 +2668,16 @@ class CertoraBuildGenerator:
|
|
|
2545
2668
|
|
|
2546
2669
|
def build(self, certora_verify_generator: CertoraVerifyGenerator) -> None:
|
|
2547
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
|
+
|
|
2548
2681
|
self.context.remappings = []
|
|
2549
2682
|
for i, build_arg_contract_file in enumerate(sorted(self.input_config.sorted_files)):
|
|
2550
2683
|
build_logger.debug(f"\nbuilding file {build_arg_contract_file}")
|
|
@@ -2596,7 +2729,7 @@ class CertoraBuildGenerator:
|
|
|
2596
2729
|
# We start by trying to instrument _all_ finders, both autofinders and source finders
|
|
2597
2730
|
added_finders, all_finders_success, src_finders_gen_success, post_backup_dir = self.finders_compilation_round(
|
|
2598
2731
|
build_arg_contract_file, i, ignore_patterns, path_for_compiler_collector_file, pre_backup_dir,
|
|
2599
|
-
sdc_pre_finders, not context.disable_source_finders)
|
|
2732
|
+
sdc_pre_finders, not context.disable_source_finders, specCalls)
|
|
2600
2733
|
|
|
2601
2734
|
# we could have a case where source finders failed but regular finders succeeded.
|
|
2602
2735
|
# e.g. if we processed the AST wrong and skipped source finders generation
|
|
@@ -2608,21 +2741,21 @@ class CertoraBuildGenerator:
|
|
|
2608
2741
|
# let's try just the function autofinders
|
|
2609
2742
|
added_finders, function_autofinder_success, _, post_backup_dir = self.finders_compilation_round(
|
|
2610
2743
|
build_arg_contract_file, i, ignore_patterns, path_for_compiler_collector_file, pre_backup_dir,
|
|
2611
|
-
sdc_pre_finders, False)
|
|
2744
|
+
sdc_pre_finders, False, specCalls)
|
|
2612
2745
|
|
|
2613
2746
|
if not function_autofinder_success:
|
|
2614
2747
|
self.auto_finders_failed = True
|
|
2615
2748
|
|
|
2616
2749
|
if not self.auto_finders_failed or not self.source_finders_failed:
|
|
2617
2750
|
# setup source_dir. note that post_backup_dir must include the finders in this case
|
|
2618
|
-
for _, _, sdc in added_finders:
|
|
2751
|
+
for _, _, _, sdc in added_finders:
|
|
2619
2752
|
sdc.source_dir = str(post_backup_dir.relative_to(Util.get_certora_sources_dir()))
|
|
2620
2753
|
sdc.orig_source_dir = str(pre_backup_dir.relative_to(Util.get_certora_sources_dir()))
|
|
2621
2754
|
else:
|
|
2622
2755
|
# no point in running autofinders in vyper right now
|
|
2623
|
-
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]
|
|
2624
2757
|
|
|
2625
|
-
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:
|
|
2626
2759
|
for contract in sdc.contracts:
|
|
2627
2760
|
all_functions: List[Func] = list()
|
|
2628
2761
|
for k, v in added_func_finders.items():
|
|
@@ -2630,6 +2763,8 @@ class CertoraBuildGenerator:
|
|
|
2630
2763
|
contract.function_finders[k] = v
|
|
2631
2764
|
for source_key, source_value in added_source_finders.items():
|
|
2632
2765
|
contract.local_assignments[source_key] = source_value
|
|
2766
|
+
if contract.name == sdc.primary_contract:
|
|
2767
|
+
contract.internal_function_harnesses = added_internal_function_harnesses
|
|
2633
2768
|
all_functions.extend(contract.methods)
|
|
2634
2769
|
all_functions.extend(contract.internal_funcs)
|
|
2635
2770
|
functions_unique_by_internal_rep = list() # type: List[Func]
|
|
@@ -2690,7 +2825,7 @@ class CertoraBuildGenerator:
|
|
|
2690
2825
|
self.handle_erc7201_annotations()
|
|
2691
2826
|
self.handle_storage_extension_harnesses()
|
|
2692
2827
|
|
|
2693
|
-
def extract_slayout(self, original_file: str, ns_storage: Set[NameSpacedStorage]) -> NewStorageInfo:
|
|
2828
|
+
def extract_slayout(self, original_file: str, ns_storage: Set[NameSpacedStorage], compiler_version: str) -> NewStorageInfo:
|
|
2694
2829
|
"""
|
|
2695
2830
|
Given a file containing a contract with namespaced storage, extract the storage information
|
|
2696
2831
|
corresponding to the namespaced types.
|
|
@@ -2715,7 +2850,9 @@ class CertoraBuildGenerator:
|
|
|
2715
2850
|
# original file is accessed also in the actual code, and compiling it
|
|
2716
2851
|
# directly can cause issues.
|
|
2717
2852
|
tmp_file.write(f"import \"{original_file}\";\n\n")
|
|
2718
|
-
|
|
2853
|
+
rel_path = os.path.relpath(tmp_file.name, Path.cwd())
|
|
2854
|
+
if self.context.compiler_map:
|
|
2855
|
+
self.context.compiler_map.update({rel_path: compiler_version}, last=False)
|
|
2719
2856
|
# Write the harness contract with dummy fields for each namespaced storage
|
|
2720
2857
|
var_to_slot = storageExtension.write_harness_contract(tmp_file, harness_name, ns_storage)
|
|
2721
2858
|
tmp_file.flush()
|
|
@@ -2728,13 +2865,6 @@ class CertoraBuildGenerator:
|
|
|
2728
2865
|
build_dir / f"{Path(original_file).stem}_storage_extension.sol"
|
|
2729
2866
|
)
|
|
2730
2867
|
|
|
2731
|
-
# Add harness to compiler map
|
|
2732
|
-
storageExtension.add_harness_to_compiler_map(
|
|
2733
|
-
original_file,
|
|
2734
|
-
tmp_file,
|
|
2735
|
-
self.context
|
|
2736
|
-
)
|
|
2737
|
-
|
|
2738
2868
|
# normalize the path exactly the way collect_for_file expects it:
|
|
2739
2869
|
abs_path = Util.abs_posix_path(tmp_file.name)
|
|
2740
2870
|
self.context.file_to_contract[abs_path] = {harness_name}
|
|
@@ -2802,7 +2932,8 @@ class CertoraBuildGenerator:
|
|
|
2802
2932
|
continue
|
|
2803
2933
|
|
|
2804
2934
|
# Now that we have all the storage layout information, extract it once
|
|
2805
|
-
slayouts[key] = self.extract_slayout(imported_file, ns_storage
|
|
2935
|
+
slayouts[key] = self.extract_slayout(imported_file, ns_storage,
|
|
2936
|
+
get_relevant_compiler(Path(target_file), self.context))
|
|
2806
2937
|
|
|
2807
2938
|
if self.context.test == str(Util.TestValue.STORAGE_EXTENSION_LAYOUT):
|
|
2808
2939
|
raise Util.TestResultsReady(slayouts)
|
|
@@ -2957,12 +3088,13 @@ class CertoraBuildGenerator:
|
|
|
2957
3088
|
path_for_compiler_collector_file: str,
|
|
2958
3089
|
pre_backup_dir: Path,
|
|
2959
3090
|
sdc_pre_finders: List[SDC],
|
|
2960
|
-
with_source_finders: bool
|
|
2961
|
-
|
|
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]:
|
|
2962
3094
|
added_finders_to_sdc, finders_compilation_success, source_finders_gen_success = \
|
|
2963
3095
|
self.instrument_auto_finders(
|
|
2964
3096
|
build_arg_contract_file, i, sdc_pre_finders,
|
|
2965
|
-
path_for_compiler_collector_file, with_source_finders)
|
|
3097
|
+
path_for_compiler_collector_file, with_source_finders, specCalls)
|
|
2966
3098
|
# successful or not, we backup current .certora_sources for either debuggability, or for availability
|
|
2967
3099
|
# of sources.
|
|
2968
3100
|
post_backup_dir = self.get_fresh_backupdir(Util.POST_AUTOFINDER_BACKUP_DIR)
|
|
@@ -3034,11 +3166,12 @@ class CertoraBuildGenerator:
|
|
|
3034
3166
|
def instrument_auto_finders(self, build_arg_contract_file: str, i: int,
|
|
3035
3167
|
sdc_pre_finders: List[SDC],
|
|
3036
3168
|
path_for_compiler_collector_file: str,
|
|
3037
|
-
instrument_source_finders: bool
|
|
3038
|
-
|
|
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]:
|
|
3039
3172
|
|
|
3040
3173
|
# initialization
|
|
3041
|
-
ret
|
|
3174
|
+
ret: List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]] = []
|
|
3042
3175
|
instrumentation_logger.debug(f"Instrumenting auto finders in {build_arg_contract_file}")
|
|
3043
3176
|
# all of the [SDC]s inside [sdc_pre_finders] have the same list of [ContractInSDC]s
|
|
3044
3177
|
# (generated in the [collect_from_file] function).
|
|
@@ -3047,10 +3180,19 @@ class CertoraBuildGenerator:
|
|
|
3047
3180
|
if added_function_finders_tuple is None:
|
|
3048
3181
|
instrumentation_logger.warning(
|
|
3049
3182
|
f"Computing function finder instrumentation failed for {build_arg_contract_file}")
|
|
3050
|
-
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
|
|
3051
3184
|
|
|
3052
3185
|
(added_function_finders, function_instr) = added_function_finders_tuple
|
|
3053
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
|
+
|
|
3054
3196
|
source_finders_gen_succeeded = False
|
|
3055
3197
|
if instrument_source_finders:
|
|
3056
3198
|
try:
|
|
@@ -3059,15 +3201,13 @@ class CertoraBuildGenerator:
|
|
|
3059
3201
|
(added_source_finders, source_instr) = added_source_finders_tuple
|
|
3060
3202
|
# Update instr with additional instrumentations. Recall it is a map file -> offset -> instr.
|
|
3061
3203
|
# Function finders take precedence
|
|
3062
|
-
instr = CertoraBuildGenerator.merge_dicts_instrumentation(
|
|
3204
|
+
instr = CertoraBuildGenerator.merge_dicts_instrumentation(instr, source_instr)
|
|
3063
3205
|
source_finders_gen_succeeded = True
|
|
3064
3206
|
except: # noqa: E722
|
|
3065
3207
|
instrumentation_logger.warning(
|
|
3066
3208
|
f"Computing source finder instrumentation failed for {build_arg_contract_file}")
|
|
3067
|
-
instr = function_instr
|
|
3068
3209
|
added_source_finders = {}
|
|
3069
3210
|
else:
|
|
3070
|
-
instr = function_instr
|
|
3071
3211
|
added_source_finders = {}
|
|
3072
3212
|
|
|
3073
3213
|
abs_build_arg_contract_file = Util.abs_posix_path(build_arg_contract_file)
|
|
@@ -3087,7 +3227,7 @@ class CertoraBuildGenerator:
|
|
|
3087
3227
|
# instrumentation should be keyed only using absolute paths
|
|
3088
3228
|
instrumentation_logger.warning(f"Already generated autofinder for {new_name}, "
|
|
3089
3229
|
f"cannot instrument again for {contract_file}")
|
|
3090
|
-
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
|
|
3091
3231
|
|
|
3092
3232
|
autofinder_remappings[new_name] = contract_file
|
|
3093
3233
|
|
|
@@ -3116,7 +3256,7 @@ class CertoraBuildGenerator:
|
|
|
3116
3256
|
instrumentation_logger.warning("Skipping source finder generation!")
|
|
3117
3257
|
else:
|
|
3118
3258
|
instrumentation_logger.warning("Skipping internal function finder generation!")
|
|
3119
|
-
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
|
|
3120
3260
|
to_skip = to_insert.mut.insert(to_insert.to_ins, to_insert.expected, output)
|
|
3121
3261
|
if to_skip != 0:
|
|
3122
3262
|
in_file.read(to_skip)
|
|
@@ -3128,7 +3268,7 @@ class CertoraBuildGenerator:
|
|
|
3128
3268
|
build_arg_contract_file]
|
|
3129
3269
|
|
|
3130
3270
|
# add generated file to map attributes
|
|
3131
|
-
for map_attr in
|
|
3271
|
+
for map_attr in self.context.app.attr_class.all_map_attrs():
|
|
3132
3272
|
map_attr_value = getattr(self.context, map_attr)
|
|
3133
3273
|
if map_attr_value and build_arg_contract_file in map_attr_value:
|
|
3134
3274
|
map_attr_value[new_file] = map_attr_value[build_arg_contract_file]
|
|
@@ -3154,7 +3294,7 @@ class CertoraBuildGenerator:
|
|
|
3154
3294
|
fail_on_compilation_error=False,
|
|
3155
3295
|
reroute_main_path=True)
|
|
3156
3296
|
for new_sdc in new_sdcs:
|
|
3157
|
-
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))
|
|
3158
3298
|
|
|
3159
3299
|
except Util.SolcCompilationException as e:
|
|
3160
3300
|
print(f"Encountered an exception generating autofinder {new_file} ({e}), falling back to original "
|
|
@@ -3163,7 +3303,7 @@ class CertoraBuildGenerator:
|
|
|
3163
3303
|
f"falling back to the original file {Path(build_arg_contract_file).name}", exc_info=e)
|
|
3164
3304
|
# clean up mutation
|
|
3165
3305
|
self.function_finder_file_remappings = {}
|
|
3166
|
-
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
|
|
3167
3307
|
return ret, True, source_finders_gen_succeeded
|
|
3168
3308
|
|
|
3169
3309
|
def to_autofinder_file(self, contract_file: str) -> str:
|
|
@@ -3558,6 +3698,15 @@ class CertoraBuildGenerator:
|
|
|
3558
3698
|
add_to_sources(Util.PACKAGE_FILE)
|
|
3559
3699
|
if Util.REMAPPINGS_FILE.exists():
|
|
3560
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)
|
|
3561
3710
|
if context.bytecode_jsons:
|
|
3562
3711
|
for file in context.bytecode_jsons:
|
|
3563
3712
|
add_to_sources(Path(file))
|
|
@@ -3583,7 +3732,11 @@ class CertoraBuildGenerator:
|
|
|
3583
3732
|
return sources
|
|
3584
3733
|
|
|
3585
3734
|
def __del__(self) -> None:
|
|
3586
|
-
|
|
3735
|
+
try:
|
|
3736
|
+
self.cleanup()
|
|
3737
|
+
except ImportError:
|
|
3738
|
+
# Avoiding Python interpreter shutdown exceptions which are safe to ignore
|
|
3739
|
+
pass
|
|
3587
3740
|
|
|
3588
3741
|
|
|
3589
3742
|
# make sure each source file exists and its path is in absolute format
|
|
@@ -3698,7 +3851,7 @@ def build_from_scratch(context: CertoraContext,
|
|
|
3698
3851
|
# the contract files in SDCs are relative to .certora_sources. Which isn't good for us here.
|
|
3699
3852
|
# Need to be relative to original paths
|
|
3700
3853
|
for f in paths_set:
|
|
3701
|
-
if is_relative_to(
|
|
3854
|
+
if f.is_relative_to(absolute_sources_dir):
|
|
3702
3855
|
rel_f = f.relative_to(absolute_sources_dir)
|
|
3703
3856
|
else:
|
|
3704
3857
|
# may be an absolute path already outside .certora_sources, so we can keep it.
|
|
@@ -3810,11 +3963,11 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
|
|
|
3810
3963
|
|
|
3811
3964
|
# Start by syntax checking, if we're in the right mode
|
|
3812
3965
|
if Cv.mode_has_spec_file(context) and not context.build_only and not ignore_spec_syntax_check:
|
|
3813
|
-
if context
|
|
3966
|
+
if Ctx.should_run_local_speck_check(context):
|
|
3967
|
+
Ctx.run_local_spec_check(False, context)
|
|
3968
|
+
else:
|
|
3814
3969
|
build_logger.warning(
|
|
3815
3970
|
"Local checks of CVL specification files disabled. It is recommended to enable the checks.")
|
|
3816
|
-
else:
|
|
3817
|
-
Ctx.run_local_spec_check(False, context)
|
|
3818
3971
|
|
|
3819
3972
|
cache_hit, build_cache_enabled, cached_files = build_from_cache_or_scratch(context,
|
|
3820
3973
|
certora_build_generator,
|
|
@@ -3822,7 +3975,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
|
|
|
3822
3975
|
|
|
3823
3976
|
# avoid running the same test over and over again for each split run, context.split_rules is true only for
|
|
3824
3977
|
# the first run and is set to [] for split runs
|
|
3825
|
-
if context.split_rules:
|
|
3978
|
+
if Ctx.should_run_local_speck_check(context) and context.split_rules:
|
|
3826
3979
|
Ctx.run_local_spec_check(True, context)
|
|
3827
3980
|
|
|
3828
3981
|
# .certora_verify.json is always constructed even if build cache is enabled
|
|
@@ -179,6 +179,8 @@ class CertoraBuildCacheManager:
|
|
|
179
179
|
trg = trg_path_with_additional_included_files / post_autofinder_dir.name
|
|
180
180
|
if post_autofinder_dir != trg:
|
|
181
181
|
if post_autofinder_dir.is_dir():
|
|
182
|
+
if trg.exists():
|
|
183
|
+
shutil.rmtree(trg)
|
|
182
184
|
Util.safe_copy_folder(post_autofinder_dir, trg, shutil.ignore_patterns())
|
|
183
185
|
else:
|
|
184
186
|
# highly unlikely .post_autofinder.[digit] will be a file and not a directory,
|
|
@@ -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()},
|