certora-cli-beta-mirror 7.28.0__py3-none-any.whl → 8.5.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 +10 -3
- certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +51 -16
- certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +3 -0
- certora_cli/CertoraProver/castingInstrumenter.py +192 -0
- certora_cli/CertoraProver/certoraApp.py +52 -0
- certora_cli/CertoraProver/certoraBuild.py +694 -207
- certora_cli/CertoraProver/certoraBuildCacheManager.py +21 -17
- certora_cli/CertoraProver/certoraBuildDataClasses.py +8 -2
- certora_cli/CertoraProver/certoraBuildRust.py +88 -54
- certora_cli/CertoraProver/certoraBuildSui.py +112 -0
- certora_cli/CertoraProver/certoraCloudIO.py +97 -96
- certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +230 -84
- certora_cli/CertoraProver/certoraCollectRunMetadata.py +52 -6
- certora_cli/CertoraProver/certoraCompilerParameters.py +11 -0
- certora_cli/CertoraProver/certoraConfigIO.py +43 -35
- certora_cli/CertoraProver/certoraContext.py +128 -54
- certora_cli/CertoraProver/certoraContextAttributes.py +415 -234
- certora_cli/CertoraProver/certoraContextValidator.py +152 -105
- certora_cli/CertoraProver/certoraContractFuncs.py +34 -1
- certora_cli/CertoraProver/certoraParseBuildScript.py +8 -10
- certora_cli/CertoraProver/certoraType.py +10 -1
- certora_cli/CertoraProver/certoraVerifyGenerator.py +22 -4
- certora_cli/CertoraProver/erc7201.py +45 -0
- certora_cli/CertoraProver/splitRules.py +23 -18
- certora_cli/CertoraProver/storageExtension.py +351 -0
- certora_cli/EquivalenceCheck/Eq_default.conf +0 -1
- certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -1
- certora_cli/EquivalenceCheck/equivCheck.py +2 -1
- certora_cli/Mutate/mutateApp.py +41 -22
- certora_cli/Mutate/mutateAttributes.py +11 -0
- certora_cli/Mutate/mutateValidate.py +42 -2
- certora_cli/Shared/certoraAttrUtil.py +21 -5
- certora_cli/Shared/certoraUtils.py +180 -60
- certora_cli/Shared/certoraValidateFuncs.py +68 -26
- certora_cli/Shared/proverCommon.py +308 -0
- certora_cli/certoraCVLFormatter.py +76 -0
- certora_cli/certoraConcord.py +39 -0
- certora_cli/certoraEVMProver.py +4 -3
- certora_cli/certoraRanger.py +39 -0
- certora_cli/certoraRun.py +83 -223
- certora_cli/certoraSolanaProver.py +40 -128
- certora_cli/certoraSorobanProver.py +59 -4
- certora_cli/certoraSuiProver.py +93 -0
- certora_cli_beta_mirror-8.5.0.dist-info/LICENSE +15 -0
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/METADATA +21 -5
- certora_cli_beta_mirror-8.5.0.dist-info/RECORD +81 -0
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/WHEEL +1 -1
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/entry_points.txt +3 -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.28.0.dist-info/LICENSE +0 -22
- certora_cli_beta_mirror-7.28.0.dist-info/RECORD +0 -70
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/top_level.txt +0 -0
|
@@ -20,22 +20,24 @@ import os
|
|
|
20
20
|
import re
|
|
21
21
|
import shutil
|
|
22
22
|
import sys
|
|
23
|
+
import tempfile
|
|
23
24
|
import typing
|
|
24
25
|
from collections import OrderedDict, defaultdict
|
|
25
26
|
from enum import Enum
|
|
26
27
|
from functools import lru_cache
|
|
27
28
|
from pathlib import Path
|
|
28
29
|
from typing import Any, Dict, List, Tuple, Optional, Set, Iterator, NoReturn
|
|
30
|
+
|
|
29
31
|
from Crypto.Hash import keccak
|
|
30
32
|
|
|
33
|
+
from CertoraProver.castingInstrumenter import generate_casting_instrumentation
|
|
31
34
|
from CertoraProver.certoraBuildCacheManager import CertoraBuildCacheManager, CachedFiles
|
|
32
35
|
from CertoraProver.certoraBuildDataClasses import CONTRACTS, ImmutableReference, ContractExtension, ContractInSDC, SDC, \
|
|
33
36
|
Instrumentation, InsertBefore, InsertAfter, UnspecializedSourceFinder, instrumentation_logger
|
|
34
37
|
from CertoraProver.certoraCompilerParameters import SolcParameters
|
|
38
|
+
from CertoraProver.certoraContractFuncs import Func, InternalFunc, STATEMUT, SourceBytes, VyperMetadata
|
|
35
39
|
from CertoraProver.certoraSourceFinders import add_source_finders
|
|
36
40
|
from CertoraProver.certoraVerifyGenerator import CertoraVerifyGenerator
|
|
37
|
-
from CertoraProver.certoraContractFuncs import Func, InternalFunc, STATEMUT, SourceBytes
|
|
38
|
-
from Shared.certoraUtils import is_relative_to
|
|
39
41
|
|
|
40
42
|
scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
|
|
41
43
|
sys.path.insert(0, str(scripts_dir_path))
|
|
@@ -54,7 +56,11 @@ from Shared import certoraValidateFuncs as Vf
|
|
|
54
56
|
from CertoraProver import certoraContextValidator as Cv
|
|
55
57
|
from Shared import certoraUtils as Util
|
|
56
58
|
import CertoraProver.certoraContext as Ctx
|
|
57
|
-
|
|
59
|
+
from CertoraProver import storageExtension
|
|
60
|
+
from CertoraProver.storageExtension import (
|
|
61
|
+
NameSpacedStorage,
|
|
62
|
+
NewStorageInfo,
|
|
63
|
+
)
|
|
58
64
|
|
|
59
65
|
BUILD_IS_LIBRARY = False
|
|
60
66
|
AUTO_FINDER_PREFIX = "autoFinder_"
|
|
@@ -72,6 +78,7 @@ MUTANTS_LOCATION = "mutants_location"
|
|
|
72
78
|
|
|
73
79
|
FunctionSig = Tuple[str, List[str], List[str], str]
|
|
74
80
|
|
|
81
|
+
|
|
75
82
|
# logger for building the abstract syntax tree
|
|
76
83
|
ast_logger = logging.getLogger("ast")
|
|
77
84
|
# logger for issues calling/shelling out to external functions
|
|
@@ -84,12 +91,10 @@ build_logger = logging.getLogger("build_conf")
|
|
|
84
91
|
# logger of the build cache
|
|
85
92
|
build_cache_logger = logging.getLogger("build_cache")
|
|
86
93
|
|
|
87
|
-
|
|
88
94
|
def fatal_error(logger: logging.Logger, msg: str) -> NoReturn:
|
|
89
95
|
logger.fatal(msg)
|
|
90
96
|
raise Exception(msg)
|
|
91
97
|
|
|
92
|
-
|
|
93
98
|
class InputConfig:
|
|
94
99
|
def __init__(self, context: CertoraContext) -> None:
|
|
95
100
|
"""
|
|
@@ -198,11 +203,18 @@ class PresetImmutableReference(ImmutableReference):
|
|
|
198
203
|
# this function is Solidity specific.
|
|
199
204
|
# todo: create certoraBuildUtilsSol.py file, where such solidity specific functions will be.
|
|
200
205
|
def generate_finder_body(f: Func, internal_id: int, sym: int, compiler_collector: CompilerCollectorSol,
|
|
201
|
-
compressed: bool = False
|
|
206
|
+
compressed: bool = False,
|
|
207
|
+
should_generate_inlining: bool = True) -> Optional[Tuple[List[int], str]]:
|
|
202
208
|
if compressed:
|
|
203
209
|
return generate_compressed_finder(
|
|
204
210
|
f, internal_id, sym, compiler_collector
|
|
205
211
|
)
|
|
212
|
+
elif not should_generate_inlining:
|
|
213
|
+
# We should not generate inlining as reported in CERT-9399, only when compressed=False.
|
|
214
|
+
# used_symbols is being generated the exact same way as in generate_full_finder
|
|
215
|
+
used_symbols = [i for i in range(len(f.fullArgs))]
|
|
216
|
+
return used_symbols, ''
|
|
217
|
+
|
|
206
218
|
else:
|
|
207
219
|
return generate_full_finder(
|
|
208
220
|
f, internal_id, sym, compiler_collector
|
|
@@ -398,8 +410,9 @@ def get_modifier_param_type_name(ind: int, def_node: Dict[str, Any], f: Func) ->
|
|
|
398
410
|
|
|
399
411
|
def generate_modifier_finder(f: Func, internal_id: int, sym: int,
|
|
400
412
|
compiler_collector: CompilerCollectorSol, def_node: Dict[str, Any],
|
|
401
|
-
compress: bool) -> Optional[Tuple[str, str]]:
|
|
402
|
-
compressed = generate_finder_body(f, internal_id, sym, compiler_collector, compressed=compress
|
|
413
|
+
compress: bool, should_generate_inlining: bool) -> Optional[Tuple[str, str]]:
|
|
414
|
+
compressed = generate_finder_body(f, internal_id, sym, compiler_collector, compressed=compress,
|
|
415
|
+
should_generate_inlining=should_generate_inlining)
|
|
403
416
|
if compressed is None:
|
|
404
417
|
return None
|
|
405
418
|
modifier_name = f"logInternal{internal_id}"
|
|
@@ -411,6 +424,8 @@ def generate_modifier_finder(f: Func, internal_id: int, sym: int,
|
|
|
411
424
|
formal_strings = []
|
|
412
425
|
arg_strings = []
|
|
413
426
|
for (logged_ty, logged_name) in zip(loggable_types, loggable_names):
|
|
427
|
+
if logged_name == "":
|
|
428
|
+
continue
|
|
414
429
|
arg_strings.append(logged_name)
|
|
415
430
|
formal_strings.append(f"{logged_ty} {logged_name}")
|
|
416
431
|
modifier_body = f"modifier {modifier_name}"
|
|
@@ -420,9 +435,10 @@ def generate_modifier_finder(f: Func, internal_id: int, sym: int,
|
|
|
420
435
|
return f'{modifier_name}({",".join(arg_strings)})', modifier_body
|
|
421
436
|
|
|
422
437
|
|
|
423
|
-
def generate_inline_finder(f: Func, internal_id: int, sym: int,
|
|
424
|
-
|
|
425
|
-
finder = generate_finder_body(f, internal_id, sym, compiler_collector, compressed=should_compress
|
|
438
|
+
def generate_inline_finder(f: Func, internal_id: int, sym: int, compiler_collector: CompilerCollectorSol,
|
|
439
|
+
should_compress: bool, should_generate_inlining: bool) -> Optional[str]:
|
|
440
|
+
finder = generate_finder_body(f, internal_id, sym, compiler_collector, compressed=should_compress,
|
|
441
|
+
should_generate_inlining=should_generate_inlining)
|
|
426
442
|
if finder is None:
|
|
427
443
|
return None
|
|
428
444
|
return finder[1]
|
|
@@ -441,8 +457,12 @@ def convert_pathname_to_posix(json_dict: Dict[str, Any], entry: str, smart_contr
|
|
|
441
457
|
if path_obj.is_file():
|
|
442
458
|
json_dict_posix_paths[path_obj.as_posix()] = json_dict[entry][file_path]
|
|
443
459
|
else:
|
|
444
|
-
|
|
445
|
-
|
|
460
|
+
json_dict_str = str(json_dict)
|
|
461
|
+
# protecting against long strings
|
|
462
|
+
if len(json_dict_str) > 200:
|
|
463
|
+
json_dict_str = json_dict_str[:200] + '...'
|
|
464
|
+
fatal_error(compiler_logger, f"The path of the source file {file_path} "
|
|
465
|
+
f"in the standard json file does not exist!\n{json_dict_str} ")
|
|
446
466
|
json_dict[entry] = json_dict_posix_paths
|
|
447
467
|
|
|
448
468
|
|
|
@@ -823,6 +843,7 @@ class CertoraBuildGenerator:
|
|
|
823
843
|
body_location = body_node["src"]
|
|
824
844
|
elif body_node is None and func_def["implemented"]:
|
|
825
845
|
ast_logger.debug(f"No body for {func_def} but ast claims it is implemented")
|
|
846
|
+
location: Optional[str] = func_def.get("src", None)
|
|
826
847
|
|
|
827
848
|
if original_contract is not None:
|
|
828
849
|
if method := original_contract.find_method(func_name, solidity_type_args):
|
|
@@ -847,14 +868,17 @@ class CertoraBuildGenerator:
|
|
|
847
868
|
func_def[STATEMUT] in ["nonpayable", "view", "pure"],
|
|
848
869
|
c_is_lib,
|
|
849
870
|
is_constructor,
|
|
871
|
+
func_def.get("kind") == CertoraBuildGenerator.FREEFUNCTION_STRING,
|
|
850
872
|
func_def[STATEMUT],
|
|
851
873
|
func_visibility,
|
|
852
874
|
func_def["implemented"],
|
|
853
875
|
func_def.get("overrides", None) is not None,
|
|
876
|
+
func_def.get("virtual", False),
|
|
854
877
|
contract_name,
|
|
855
878
|
source_bytes,
|
|
856
879
|
ast_id=func_def.get("id", None),
|
|
857
880
|
original_file=original_file,
|
|
881
|
+
location=location,
|
|
858
882
|
body_location=body_location,
|
|
859
883
|
)
|
|
860
884
|
|
|
@@ -906,15 +930,18 @@ class CertoraBuildGenerator:
|
|
|
906
930
|
notpayable=is_not_payable,
|
|
907
931
|
fromLib=c_is_lib,
|
|
908
932
|
isConstructor=False,
|
|
933
|
+
is_free_func=False,
|
|
909
934
|
stateMutability=state_mutability,
|
|
910
935
|
implemented=True,
|
|
911
936
|
overrides=public_state_var.get("overrides", None) is not None,
|
|
937
|
+
virtual=False,
|
|
912
938
|
# according to Solidity docs, getter functions have external visibility
|
|
913
939
|
visibility="external",
|
|
914
940
|
contractName=contract_name,
|
|
915
941
|
source_bytes=SourceBytes.from_ast_node(public_state_var),
|
|
916
942
|
ast_id=None,
|
|
917
943
|
original_file=c_file,
|
|
944
|
+
location=None,
|
|
918
945
|
body_location=None,
|
|
919
946
|
)
|
|
920
947
|
)
|
|
@@ -948,6 +975,8 @@ class CertoraBuildGenerator:
|
|
|
948
975
|
ret = solc_type == "address"
|
|
949
976
|
elif isinstance(ct_type, CT.StructType):
|
|
950
977
|
ret = solc_type == "tuple"
|
|
978
|
+
elif isinstance(ct_type, CT.EnumType):
|
|
979
|
+
ret = solc_type == "uint8"
|
|
951
980
|
return ret
|
|
952
981
|
|
|
953
982
|
fs = [f for f in fs if all(compareTypes(a.type, i)
|
|
@@ -968,7 +997,7 @@ class CertoraBuildGenerator:
|
|
|
968
997
|
assert len(f.returns) == len(fabi["outputs"]), \
|
|
969
998
|
f"function collected for {fabi['name']} has the wrong number of return values"
|
|
970
999
|
assert all(compareTypes(a.type, i) for a, i in zip(f.returns, fabi["outputs"])), \
|
|
971
|
-
f"function collected for {fabi['name']} has the wrong types of return values"
|
|
1000
|
+
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']}"
|
|
972
1001
|
|
|
973
1002
|
verify_collected_all_abi_funcs(
|
|
974
1003
|
[f for f in data["abi"] if f["type"] == "function"],
|
|
@@ -1198,10 +1227,15 @@ class CertoraBuildGenerator:
|
|
|
1198
1227
|
return x[TYPE] == FUNCTION or x[TYPE] == CertoraBuildGenerator.CONSTRUCTOR_STRING
|
|
1199
1228
|
|
|
1200
1229
|
@staticmethod
|
|
1201
|
-
def collect_srcmap(data: Dict[str, Any]) ->
|
|
1230
|
+
def collect_srcmap(data: Dict[str, Any]) -> Tuple[str, str]:
|
|
1202
1231
|
# no source map object in vyper
|
|
1203
|
-
|
|
1204
|
-
|
|
1232
|
+
deployed = data["evm"]["deployedBytecode"].get("sourceMap", "")
|
|
1233
|
+
if isinstance(deployed, dict):
|
|
1234
|
+
deployed = deployed.get("pc_pos_map_compressed", "")
|
|
1235
|
+
regular = data["evm"]["bytecode"].get("sourceMap", "")
|
|
1236
|
+
if isinstance(regular, dict):
|
|
1237
|
+
regular = regular.get("pc_pos_map_compressed", "")
|
|
1238
|
+
return deployed, regular
|
|
1205
1239
|
|
|
1206
1240
|
@staticmethod
|
|
1207
1241
|
def collect_varmap(contract: str, data: Dict[str, Any]) -> Any:
|
|
@@ -1347,49 +1381,63 @@ class CertoraBuildGenerator:
|
|
|
1347
1381
|
|
|
1348
1382
|
return bytecode
|
|
1349
1383
|
|
|
1350
|
-
def
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1384
|
+
def get_map_bool_attribute_value(self, contract_file_path: Path, attr_name: str) -> bool:
|
|
1385
|
+
match = Ctx.get_map_attribute_value(self.context, contract_file_path, attr_name)
|
|
1386
|
+
assert isinstance(match, (bool, type(None))), f"Expected {attr_name} to be bool or None, got {type(match)}"
|
|
1387
|
+
return bool(match)
|
|
1388
|
+
|
|
1389
|
+
def get_solc_via_ir_value(self, contract_file_path: Path) -> bool:
|
|
1390
|
+
return self.get_map_bool_attribute_value(contract_file_path, 'solc_via_ir')
|
|
1391
|
+
|
|
1392
|
+
def get_vyper_venom_value(self, contract_file_path: Path) -> bool:
|
|
1393
|
+
return self.get_map_bool_attribute_value(contract_file_path, 'vyper_venom')
|
|
1394
|
+
|
|
1395
|
+
def get_solc_evm_version_value(self, contract_file_path: Path) -> Optional[str]:
|
|
1396
|
+
match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_evm_version')
|
|
1397
|
+
assert isinstance(match, (str, type(None))), f"Expected solc_evm_version to be string or None, got {type(match)}"
|
|
1398
|
+
return match
|
|
1399
|
+
|
|
1400
|
+
def get_solc_optimize_value(self, contract_file_path: Path) -> Optional[str]:
|
|
1401
|
+
match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_optimize')
|
|
1402
|
+
assert isinstance(match, (str, int, type(None))), f"Expected solc_optimize to be string, integer , got {type(match)}"
|
|
1403
|
+
if isinstance(match, int):
|
|
1404
|
+
match = str(match)
|
|
1405
|
+
return match
|
|
1406
|
+
|
|
1407
|
+
def _handle_venom(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
|
|
1408
|
+
if self.get_vyper_venom_value(contract_file_path):
|
|
1409
|
+
settings_dict["experimentalCodegen"] = True
|
|
1410
|
+
|
|
1411
|
+
def _handle_via_ir(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
|
|
1412
|
+
if self.get_solc_via_ir_value(contract_file_path):
|
|
1358
1413
|
settings_dict["viaIR"] = True
|
|
1359
1414
|
|
|
1360
|
-
def _handle_evm_version(self, contract_file_path:
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
settings_dict["evmVersion"] = match
|
|
1365
|
-
elif self.context.solc_evm_version:
|
|
1366
|
-
settings_dict["evmVersion"] = self.context.solc_evm_version
|
|
1415
|
+
def _handle_evm_version(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
|
|
1416
|
+
match = self.get_solc_evm_version_value(contract_file_path)
|
|
1417
|
+
if match:
|
|
1418
|
+
settings_dict["evmVersion"] = match
|
|
1367
1419
|
|
|
1368
|
-
def _handle_optimize(self, contract_file_path:
|
|
1420
|
+
def _handle_optimize(self, contract_file_path: Path, settings_dict: Dict[str, Any],
|
|
1369
1421
|
compiler_collector: CompilerCollector) -> None:
|
|
1370
1422
|
"""
|
|
1371
1423
|
@param contract_file_path: the contract that we are working on
|
|
1372
1424
|
@param settings_dict: data structure for sending to the solc compiler
|
|
1373
1425
|
"""
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
if match and int(match) > 0:
|
|
1377
|
-
settings_dict["optimizer"] = {"enabled": True}
|
|
1378
|
-
settings_dict["optimizer"]['runs'] = int(match)
|
|
1379
|
-
elif self.context.solc_optimize:
|
|
1426
|
+
match = self.get_solc_optimize_value(contract_file_path)
|
|
1427
|
+
if match:
|
|
1380
1428
|
settings_dict["optimizer"] = {"enabled": True}
|
|
1381
|
-
if int(
|
|
1382
|
-
settings_dict["optimizer"]['runs'] = int(
|
|
1429
|
+
if int(match) > 0:
|
|
1430
|
+
settings_dict["optimizer"]['runs'] = int(match)
|
|
1383
1431
|
|
|
1384
1432
|
# if optimizer is true, we should also define settings_dict["optimizer"]["details"]
|
|
1385
1433
|
# for both optimize map and optimize
|
|
1386
1434
|
optimizer = settings_dict.get("optimizer")
|
|
1387
1435
|
if optimizer and isinstance(optimizer, dict) and optimizer.get('enabled'):
|
|
1388
1436
|
# if we are not disabling finder friendly optimizer specifically, enable it whenever viaIR is also enabled
|
|
1389
|
-
if not self.context.strict_solc_optimizer and self.
|
|
1437
|
+
if not self.context.strict_solc_optimizer and self.get_solc_via_ir_value(contract_file_path):
|
|
1390
1438
|
# The default optimizer steps (taken from libsolidity/interface/OptimiserSettings.h) but with the
|
|
1391
1439
|
# full inliner step removed
|
|
1392
|
-
|
|
1440
|
+
solc0_8_26_to_0_8_30 = ("dhfoDgvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLCTUca[r]LSsTFOtfDnca[r]" +
|
|
1393
1441
|
"IulcscCTUtx[scCTUt]TOntnfDIuljmul[jul]VcTOculjmul")
|
|
1394
1442
|
solc0_8_13_to_0_8_25 = "dhfoDgvulfnTUtnIf[xa[r]EscLMcCTUtTOntnfDIulLculVcul[j]T" + \
|
|
1395
1443
|
"peulxa[rul]xa[r]cLgvifCTUca[r]LSsTFOtfDnca[r]Iulc]jmul[jul]VcTOculjmul"
|
|
@@ -1412,13 +1460,18 @@ class CertoraBuildGenerator:
|
|
|
1412
1460
|
compiler_version = compiler_collector.compiler_version
|
|
1413
1461
|
major, minor, patch = compiler_version
|
|
1414
1462
|
|
|
1415
|
-
err_msg =
|
|
1416
|
-
|
|
1463
|
+
err_msg = \
|
|
1464
|
+
f"Unsupported solc version {major}.{minor}.{patch} for `solc_via_ir`. " \
|
|
1465
|
+
f"Supported versions: 0.6.7 – 0.8.25.\n" \
|
|
1466
|
+
f"Use `solc_via_ir_map` to disable `solc_via_ir` for specific files with older compiler versions."
|
|
1467
|
+
|
|
1417
1468
|
yul_optimizer_steps = None
|
|
1418
1469
|
if major != 0:
|
|
1419
1470
|
raise Util.CertoraUserInputError(err_msg)
|
|
1420
1471
|
elif minor < 6 or (minor == 6 and patch < 7):
|
|
1421
1472
|
raise Util.CertoraUserInputError(err_msg)
|
|
1473
|
+
elif self.context.yul_optimizer_steps:
|
|
1474
|
+
yul_optimizer_steps = self.context.yul_optimizer_steps
|
|
1422
1475
|
elif (minor == 6 and patch >= 7) or (minor == 7 and 0 <= patch <= 1):
|
|
1423
1476
|
yul_optimizer_steps = solc0_6_7_to_0_7_1
|
|
1424
1477
|
elif minor == 7 and 2 <= patch <= 5:
|
|
@@ -1433,8 +1486,8 @@ class CertoraBuildGenerator:
|
|
|
1433
1486
|
yul_optimizer_steps = solc0_8_12
|
|
1434
1487
|
elif minor == 8 and 13 <= patch <= 25:
|
|
1435
1488
|
yul_optimizer_steps = solc0_8_13_to_0_8_25
|
|
1436
|
-
elif minor == 8 and 26 <= patch <=
|
|
1437
|
-
yul_optimizer_steps =
|
|
1489
|
+
elif minor == 8 and 26 <= patch <= 30:
|
|
1490
|
+
yul_optimizer_steps = solc0_8_26_to_0_8_30
|
|
1438
1491
|
assert yul_optimizer_steps is not None, \
|
|
1439
1492
|
'Yul Optimizer steps missing for requested Solidity version. Please contact Certora team.'
|
|
1440
1493
|
|
|
@@ -1456,7 +1509,7 @@ class CertoraBuildGenerator:
|
|
|
1456
1509
|
for opt_pass in self.context.disable_solc_optimizers:
|
|
1457
1510
|
settings_dict["optimizer"]["details"][opt_pass] = False
|
|
1458
1511
|
|
|
1459
|
-
def _fill_codegen_related_options(self, contract_file_path:
|
|
1512
|
+
def _fill_codegen_related_options(self, contract_file_path: Path, settings_dict: Dict[str, Any],
|
|
1460
1513
|
compiler_collector: CompilerCollector) -> None:
|
|
1461
1514
|
"""
|
|
1462
1515
|
Fills options that control how we call solc and affect the bytecode in some way
|
|
@@ -1467,6 +1520,9 @@ class CertoraBuildGenerator:
|
|
|
1467
1520
|
self._handle_via_ir(contract_file_path, settings_dict)
|
|
1468
1521
|
self._handle_evm_version(contract_file_path, settings_dict)
|
|
1469
1522
|
self._handle_optimize(contract_file_path, settings_dict, compiler_collector)
|
|
1523
|
+
compiler_lang = compiler_collector.smart_contract_lang
|
|
1524
|
+
if compiler_lang == CompilerLangVy():
|
|
1525
|
+
self._handle_venom(contract_file_path, settings_dict)
|
|
1470
1526
|
|
|
1471
1527
|
@staticmethod
|
|
1472
1528
|
def solc_setting_optimizer_runs(settings_dict: Dict[str, Any]) -> Tuple[bool, Optional[int]]:
|
|
@@ -1500,7 +1556,8 @@ class CertoraBuildGenerator:
|
|
|
1500
1556
|
contract_file_posix_abs: Path,
|
|
1501
1557
|
contract_file_as_provided: str,
|
|
1502
1558
|
remappings: List[str],
|
|
1503
|
-
compiler_collector: CompilerCollector
|
|
1559
|
+
compiler_collector: CompilerCollector,
|
|
1560
|
+
compile_wd: Path) -> Dict[str, Any]:
|
|
1504
1561
|
"""
|
|
1505
1562
|
when calling solc with the standard_json api, instead of passing it flags, we pass it json to request what we
|
|
1506
1563
|
want -- currently we only use this to retrieve storage layout as this is the only way to do that,
|
|
@@ -1511,18 +1568,22 @@ class CertoraBuildGenerator:
|
|
|
1511
1568
|
@param compiler_collector: Solidity or Vyper compiler collector
|
|
1512
1569
|
@return:
|
|
1513
1570
|
"""
|
|
1571
|
+
solc_json_contract_key = os.path.relpath(contract_file_as_provided, compile_wd) if self.context.use_relpaths_for_solc_json else contract_file_posix_abs
|
|
1514
1572
|
compiler_collector_lang = compiler_collector.smart_contract_lang
|
|
1515
1573
|
if compiler_collector_lang == CompilerLangSol() or compiler_collector_lang == CompilerLangYul():
|
|
1516
|
-
sources_dict = {str(
|
|
1574
|
+
sources_dict = {str(solc_json_contract_key): {
|
|
1517
1575
|
"urls": [str(contract_file_posix_abs)]}} # type: Dict[str, Dict[str, Any]]
|
|
1518
1576
|
output_selection = ["transientStorageLayout", "storageLayout", "abi", "evm.bytecode",
|
|
1519
|
-
"evm.deployedBytecode", "evm.methodIdentifiers", "evm.assembly"
|
|
1577
|
+
"evm.deployedBytecode", "evm.methodIdentifiers", "evm.assembly",
|
|
1578
|
+
"evm.bytecode.functionDebugData"]
|
|
1520
1579
|
ast_selection = ["id", "ast"]
|
|
1521
1580
|
elif compiler_collector_lang == CompilerLangVy():
|
|
1522
1581
|
with open(contract_file_posix_abs) as f:
|
|
1523
1582
|
contents = f.read()
|
|
1524
1583
|
sources_dict = {str(contract_file_posix_abs): {"content": contents}}
|
|
1525
1584
|
output_selection = ["abi", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers"]
|
|
1585
|
+
if compiler_collector.compiler_version >= (0, 4, 4):
|
|
1586
|
+
output_selection += ["metadata", "evm.deployedBytecode.symbolMap"]
|
|
1526
1587
|
ast_selection = ["ast"]
|
|
1527
1588
|
|
|
1528
1589
|
settings_dict: Dict[str, Any] = \
|
|
@@ -1536,7 +1597,7 @@ class CertoraBuildGenerator:
|
|
|
1536
1597
|
}
|
|
1537
1598
|
}
|
|
1538
1599
|
|
|
1539
|
-
self._fill_codegen_related_options(contract_file_as_provided, settings_dict, compiler_collector)
|
|
1600
|
+
self._fill_codegen_related_options(Path(contract_file_as_provided), settings_dict, compiler_collector)
|
|
1540
1601
|
|
|
1541
1602
|
result_dict = {"language": compiler_collector_lang.name, "sources": sources_dict, "settings": settings_dict}
|
|
1542
1603
|
# debug_print("Standard json input")
|
|
@@ -1739,6 +1800,10 @@ class CertoraBuildGenerator:
|
|
|
1739
1800
|
sdc_name = f"{Path(build_arg_contract_file).name}_{file_index}"
|
|
1740
1801
|
compilation_path = self.get_compilation_path(sdc_name)
|
|
1741
1802
|
self.file_to_sdc_name[Util.abs_norm_path(build_arg_contract_file)] = sdc_name
|
|
1803
|
+
|
|
1804
|
+
compiler_collector = self.compiler_coll_factory \
|
|
1805
|
+
.get_compiler_collector(Path(path_for_compiler_collector_file))
|
|
1806
|
+
|
|
1742
1807
|
# update remappings and collect_cmd:
|
|
1743
1808
|
if not is_vyper:
|
|
1744
1809
|
Util.safe_create_dir(compilation_path)
|
|
@@ -1786,8 +1851,10 @@ class CertoraBuildGenerator:
|
|
|
1786
1851
|
compiler_logger.debug(f"collect_cmd: {collect_cmd}\n")
|
|
1787
1852
|
else:
|
|
1788
1853
|
compiler_ver_to_run = get_relevant_compiler(Path(build_arg_contract_file), self.context)
|
|
1789
|
-
|
|
1790
|
-
|
|
1854
|
+
path_string = ""
|
|
1855
|
+
if compiler_collector.compiler_version[1] < 4:
|
|
1856
|
+
path_string = f' -p "{self.context.solc_allow_path}"'
|
|
1857
|
+
collect_cmd = f'{compiler_ver_to_run}{path_string} -o "{compilation_path}" ' \
|
|
1791
1858
|
f'--standard-json'
|
|
1792
1859
|
|
|
1793
1860
|
# Make sure compilation artifacts are always deleted
|
|
@@ -1800,13 +1867,10 @@ class CertoraBuildGenerator:
|
|
|
1800
1867
|
# (we do not try to save a big chain history of changes, just a previous and current)
|
|
1801
1868
|
self.backup_compiler_outputs(sdc_name, smart_contract_lang, "prev")
|
|
1802
1869
|
|
|
1803
|
-
compiler_collector = self.compiler_coll_factory \
|
|
1804
|
-
.get_compiler_collector(Path(path_for_compiler_collector_file))
|
|
1805
|
-
|
|
1806
1870
|
# Standard JSON
|
|
1807
1871
|
remappings = [] if isinstance(compiler_collector, CompilerCollectorYul) else self.context.remappings
|
|
1808
1872
|
input_for_solc = self.standard_json(Path(file_abs_path), build_arg_contract_file, remappings,
|
|
1809
|
-
compiler_collector)
|
|
1873
|
+
compiler_collector, compile_wd)
|
|
1810
1874
|
standard_json_input = json.dumps(input_for_solc).encode("utf-8")
|
|
1811
1875
|
compiler_logger.debug(f"about to run in {compile_wd} the command: {collect_cmd}")
|
|
1812
1876
|
compiler_logger.debug(f"solc input = {json.dumps(input_for_solc, indent=4)}")
|
|
@@ -1862,7 +1926,7 @@ class CertoraBuildGenerator:
|
|
|
1862
1926
|
contract_file_abs = str(Util.abs_norm_path(contract_file))
|
|
1863
1927
|
|
|
1864
1928
|
# using os.path.relpath because Path.relative_to cannot go up the directory tree (no ..)
|
|
1865
|
-
contract_file_rel = os.path.relpath(Path(contract_file_abs),
|
|
1929
|
+
contract_file_rel = os.path.relpath(Path(contract_file_abs), compile_wd)
|
|
1866
1930
|
|
|
1867
1931
|
build_logger.debug(f"available keys: {data['contracts'].keys()}")
|
|
1868
1932
|
if contract_file_rel in data[CONTRACTS]:
|
|
@@ -1961,7 +2025,15 @@ class CertoraBuildGenerator:
|
|
|
1961
2025
|
srclist = {"0": file_abs_path}
|
|
1962
2026
|
report_srclist = {"0": file_abs_path}
|
|
1963
2027
|
|
|
1964
|
-
|
|
2028
|
+
# Annoyingly, this is currently used just for... presentation?!
|
|
2029
|
+
# We should clean up report_source_file from all places...
|
|
2030
|
+
|
|
2031
|
+
build_logger.debug(f"Finding report source file, abs path {file_abs_path} relative to {compile_wd}")
|
|
2032
|
+
if self.context.use_relpaths_for_solc_json:
|
|
2033
|
+
orig_report_source_file = Util.abs_posix_path(os.path.relpath(file_abs_path, compile_wd))
|
|
2034
|
+
else:
|
|
2035
|
+
orig_report_source_file = file_abs_path
|
|
2036
|
+
report_source_file = report_srclist[[idx for idx in srclist if Util.abs_posix_path(srclist[idx]) == orig_report_source_file][0]]
|
|
1965
2037
|
|
|
1966
2038
|
# all "contracts in SDC" are the same for every primary contract of the compiled file.
|
|
1967
2039
|
# we can therefore compute those just once...
|
|
@@ -1992,8 +2064,9 @@ class CertoraBuildGenerator:
|
|
|
1992
2064
|
|
|
1993
2065
|
build_logger.debug(f"finding primary contract address of {file_compiler_path}:{primary_contract} in "
|
|
1994
2066
|
f"{contracts_with_chosen_addresses}")
|
|
2067
|
+
path_to_find = os.path.relpath(file_compiler_path, compile_wd) if self.context.use_relpaths_for_solc_json else file_compiler_path
|
|
1995
2068
|
primary_contract_address = \
|
|
1996
|
-
self.find_contract_address_str(
|
|
2069
|
+
self.find_contract_address_str(path_to_find,
|
|
1997
2070
|
primary_contract,
|
|
1998
2071
|
contracts_with_chosen_addresses)
|
|
1999
2072
|
build_logger.debug(f"Contracts in SDC {sdc_name}: {[contract.name for contract in contracts_in_sdc]}")
|
|
@@ -2039,7 +2112,7 @@ class CertoraBuildGenerator:
|
|
|
2039
2112
|
if new_path is None:
|
|
2040
2113
|
file_abs_path = Util.abs_posix_path(build_arg_contract_file)
|
|
2041
2114
|
# for vyper, because there are no autofinders, at least find the main file
|
|
2042
|
-
if (orig_file == file_abs_path and
|
|
2115
|
+
if (Util.abs_posix_path(orig_file) == file_abs_path and
|
|
2043
2116
|
((Util.get_certora_sources_dir() / build_arg_contract_file).exists() or
|
|
2044
2117
|
Path(build_arg_contract_file).exists())):
|
|
2045
2118
|
new_srclist_map[idx] = build_arg_contract_file
|
|
@@ -2054,6 +2127,7 @@ class CertoraBuildGenerator:
|
|
|
2054
2127
|
report_srclist = srclist
|
|
2055
2128
|
else:
|
|
2056
2129
|
report_srclist = srclist
|
|
2130
|
+
build_logger.debug(f"Report source list={report_srclist}")
|
|
2057
2131
|
return report_srclist
|
|
2058
2132
|
|
|
2059
2133
|
def get_bytecode(self,
|
|
@@ -2061,7 +2135,7 @@ class CertoraBuildGenerator:
|
|
|
2061
2135
|
contract_name: str,
|
|
2062
2136
|
primary_contracts: List[str],
|
|
2063
2137
|
contracts_with_chosen_addresses: List[Tuple[int, Any]],
|
|
2064
|
-
|
|
2138
|
+
is_deployed_bytecode: bool
|
|
2065
2139
|
) -> str:
|
|
2066
2140
|
"""
|
|
2067
2141
|
Computes the linked bytecode object from the Solidity compiler output.
|
|
@@ -2072,20 +2146,31 @@ class CertoraBuildGenerator:
|
|
|
2072
2146
|
@param primary_contracts - the names of the primary contracts we check to have a bytecode
|
|
2073
2147
|
@param contracts_with_chosen_addresses - a list of tuples of addresses and the
|
|
2074
2148
|
associated contract identifier
|
|
2075
|
-
@param
|
|
2149
|
+
@param is_deployed_bytecode - true if we deal with deployed (runtime) bytecode,
|
|
2150
|
+
thus the function should fail if bytecode object is missing,
|
|
2076
2151
|
false otherwise
|
|
2077
2152
|
@returns linked bytecode object
|
|
2078
2153
|
"""
|
|
2079
2154
|
# TODO: Only contract_name should be necessary. This requires a lot more test cases to make sure we're not
|
|
2080
2155
|
# missing any weird solidity outputs.
|
|
2081
2156
|
bytecode_ = bytecode_object["object"]
|
|
2082
|
-
|
|
2083
|
-
|
|
2157
|
+
try:
|
|
2158
|
+
bytecode = self.collect_and_link_bytecode(contract_name, contracts_with_chosen_addresses,
|
|
2159
|
+
bytecode_, bytecode_object.get("linkReferences", {}))
|
|
2160
|
+
except Util.CertoraUserInputError as e:
|
|
2161
|
+
if is_deployed_bytecode:
|
|
2162
|
+
raise e
|
|
2163
|
+
else:
|
|
2164
|
+
prefix_msg = f"Failed to find a dependency library while building the "\
|
|
2165
|
+
f"constructor bytecode of {contract_name}. "\
|
|
2166
|
+
f"If you need the contract, import the missing library directly and add a dummy usage:\n"
|
|
2167
|
+
orig_msg = str(e)
|
|
2168
|
+
raise Util.CertoraUserInputError(prefix_msg + orig_msg)
|
|
2084
2169
|
if contract_name in primary_contracts and len(bytecode) == 0:
|
|
2085
2170
|
msg = f"Contract {contract_name} has no bytecode. " \
|
|
2086
2171
|
f"It may be caused because the contract is abstract, " \
|
|
2087
2172
|
f"or is missing constructor code. Please check the output of the Solidity compiler."
|
|
2088
|
-
if
|
|
2173
|
+
if is_deployed_bytecode:
|
|
2089
2174
|
raise Util.CertoraUserInputError(msg)
|
|
2090
2175
|
else:
|
|
2091
2176
|
build_logger.warning(msg)
|
|
@@ -2155,6 +2240,40 @@ class CertoraBuildGenerator:
|
|
|
2155
2240
|
storage_slot['descriptor'] = type_descriptor.as_dict()
|
|
2156
2241
|
return storage_data
|
|
2157
2242
|
|
|
2243
|
+
@staticmethod
|
|
2244
|
+
def add_vyper_internal_function_data(internal_funcs: Set[Func], contract_data: Dict[str, Any]) -> None:
|
|
2245
|
+
metadata = contract_data["metadata"]
|
|
2246
|
+
symbol_map = contract_data["evm"]["deployedBytecode"]["symbolMap"]
|
|
2247
|
+
|
|
2248
|
+
for internal_func in internal_funcs:
|
|
2249
|
+
# find metadata entry
|
|
2250
|
+
func_name = internal_func.name
|
|
2251
|
+
func_info = metadata['function_info']
|
|
2252
|
+
metadata_func_info = None
|
|
2253
|
+
for value in func_info.values():
|
|
2254
|
+
if value.get("name") == func_name:
|
|
2255
|
+
metadata_func_info = value
|
|
2256
|
+
break
|
|
2257
|
+
assert metadata_func_info is not None, f"Could not find metadata for internal function {func_name}"
|
|
2258
|
+
vyper_metadata = VyperMetadata()
|
|
2259
|
+
if metadata_func_info.get('frame_info'):
|
|
2260
|
+
vyper_metadata.frame_size = metadata_func_info['frame_info']['frame_size']
|
|
2261
|
+
vyper_metadata.frame_start = metadata_func_info['frame_info']['frame_start']
|
|
2262
|
+
if metadata_func_info.get('venom_via_stack'):
|
|
2263
|
+
vyper_metadata.venom_via_stack = metadata_func_info['venom_via_stack']
|
|
2264
|
+
if metadata_func_info.get('venom_return_via_stack'):
|
|
2265
|
+
vyper_metadata.venom_return_via_stack = metadata_func_info['venom_return_via_stack']
|
|
2266
|
+
pattern_in_symbol_map = re.compile(fr"{func_name}\(.*\)_runtime$")
|
|
2267
|
+
matches = [k for k in symbol_map if pattern_in_symbol_map.search(k)]
|
|
2268
|
+
if len(matches) == 0:
|
|
2269
|
+
build_logger.warning(f"Could not find symbol map entry for {func_name} probably was inlined")
|
|
2270
|
+
continue
|
|
2271
|
+
elif len(matches) > 1:
|
|
2272
|
+
raise RuntimeError(f"Found multiple matches for {func_name} in symbol map: {matches}")
|
|
2273
|
+
else:
|
|
2274
|
+
vyper_metadata.runtime_start_pc = symbol_map[matches[0]]
|
|
2275
|
+
internal_func.vyper_metadata = vyper_metadata
|
|
2276
|
+
|
|
2158
2277
|
def get_contract_in_sdc(self,
|
|
2159
2278
|
source_code_file: str,
|
|
2160
2279
|
contract_name: str,
|
|
@@ -2190,14 +2309,17 @@ class CertoraBuildGenerator:
|
|
|
2190
2309
|
notpayable=x.notpayable,
|
|
2191
2310
|
fromLib=x.fromLib,
|
|
2192
2311
|
isConstructor=x.isConstructor,
|
|
2312
|
+
is_free_func=False,
|
|
2193
2313
|
stateMutability=x.stateMutability,
|
|
2194
2314
|
visibility=x.visibility,
|
|
2195
2315
|
implemented=x.implemented,
|
|
2196
2316
|
overrides=x.overrides,
|
|
2317
|
+
virtual=False,
|
|
2197
2318
|
contractName=x.contractName,
|
|
2198
2319
|
ast_id=x.ast_id,
|
|
2199
2320
|
source_bytes=x.source_bytes,
|
|
2200
2321
|
original_file=x.original_file,
|
|
2322
|
+
location=None,
|
|
2201
2323
|
body_location=x.body_location,
|
|
2202
2324
|
) for x in clfuncs]
|
|
2203
2325
|
elif compiler_lang == CompilerLangYul():
|
|
@@ -2246,6 +2368,14 @@ class CertoraBuildGenerator:
|
|
|
2246
2368
|
build_arg_contract_file)
|
|
2247
2369
|
immutables = self.collect_immutables(contract_data, build_arg_contract_file, compiler_lang)
|
|
2248
2370
|
|
|
2371
|
+
internal_function_entrypoints = set([])
|
|
2372
|
+
|
|
2373
|
+
if compiler_lang == CompilerLangSol() and "functionDebugData" in contract_data["evm"]["deployedBytecode"]:
|
|
2374
|
+
debug = contract_data["evm"]["deployedBytecode"]["functionDebugData"]
|
|
2375
|
+
for (_, v) in debug.items():
|
|
2376
|
+
if "entryPoint" in v and v["entryPoint"] is not None:
|
|
2377
|
+
internal_function_entrypoints.add(v["entryPoint"])
|
|
2378
|
+
|
|
2249
2379
|
if self.context.internal_funcs is not None:
|
|
2250
2380
|
all_internal_functions: Dict[str, Any] = \
|
|
2251
2381
|
Util.read_json_file(self.context.internal_funcs)
|
|
@@ -2260,7 +2390,7 @@ class CertoraBuildGenerator:
|
|
|
2260
2390
|
|
|
2261
2391
|
if compiler_lang == CompilerLangSol():
|
|
2262
2392
|
settings_dict: Dict[str, Any] = {}
|
|
2263
|
-
self._fill_codegen_related_options(build_arg_contract_file, settings_dict,
|
|
2393
|
+
self._fill_codegen_related_options(Path(build_arg_contract_file), settings_dict,
|
|
2264
2394
|
compiler_collector_for_contract_file)
|
|
2265
2395
|
solc_optimizer_on, solc_optimizer_runs = self.solc_setting_optimizer_runs(settings_dict)
|
|
2266
2396
|
solc_via_ir = self.solc_setting_via_ir(settings_dict)
|
|
@@ -2268,6 +2398,9 @@ class CertoraBuildGenerator:
|
|
|
2268
2398
|
else:
|
|
2269
2399
|
compiler_parameters = None
|
|
2270
2400
|
|
|
2401
|
+
if compiler_lang == CompilerLangVy() and compiler_collector_for_contract_file.compiler_version >= (0, 4, 4):
|
|
2402
|
+
self.add_vyper_internal_function_data(internal_funcs, contract_data)
|
|
2403
|
+
|
|
2271
2404
|
return ContractInSDC(contract_name,
|
|
2272
2405
|
# somehow make sure this is an absolute path which obeys the autofinder remappings
|
|
2273
2406
|
# (i.e. make sure this file doesn't start with autoFinder_)
|
|
@@ -2296,7 +2429,8 @@ class CertoraBuildGenerator:
|
|
|
2296
2429
|
extension_contracts=list(),
|
|
2297
2430
|
local_assignments={},
|
|
2298
2431
|
branches={},
|
|
2299
|
-
requires={}
|
|
2432
|
+
requires={},
|
|
2433
|
+
internal_starts=list(internal_function_entrypoints)
|
|
2300
2434
|
)
|
|
2301
2435
|
|
|
2302
2436
|
@staticmethod
|
|
@@ -2389,7 +2523,7 @@ class CertoraBuildGenerator:
|
|
|
2389
2523
|
# if no function finder mode set, determine based on viaIR enabled or not:
|
|
2390
2524
|
if self.context.function_finder_mode is None:
|
|
2391
2525
|
# in via-ir, should not compress
|
|
2392
|
-
if self.
|
|
2526
|
+
if self.get_solc_via_ir_value(Path(contract_file)):
|
|
2393
2527
|
should_compress = False
|
|
2394
2528
|
else:
|
|
2395
2529
|
should_compress = True
|
|
@@ -2398,6 +2532,19 @@ class CertoraBuildGenerator:
|
|
|
2398
2532
|
# (deprecate this option later)
|
|
2399
2533
|
should_compress = self.context.function_finder_mode == Vf.FunctionFinderMode.DEFAULT.name
|
|
2400
2534
|
|
|
2535
|
+
if self.context.solc_optimize and self.get_solc_via_ir_value(Path(contract_file)) and \
|
|
2536
|
+
any(param_name == "" for param_name in f.paramNames):
|
|
2537
|
+
# As reported in CERT-9399, we should not generate function finder for this edge case
|
|
2538
|
+
|
|
2539
|
+
instrumentation_logger.warning(f"Failed to generate auto finder for {f.name} @ {f.where()}.")
|
|
2540
|
+
instrumentation_logger.warning(
|
|
2541
|
+
"Cannot apply summaries for internal functions with unnamed argument when compiling "
|
|
2542
|
+
"using solc_optimize and solc_via_ir"
|
|
2543
|
+
)
|
|
2544
|
+
should_generate_inlining = False
|
|
2545
|
+
else:
|
|
2546
|
+
should_generate_inlining = True
|
|
2547
|
+
|
|
2401
2548
|
if len(mods) > 0:
|
|
2402
2549
|
# we need to add the instrumentation in a modifer because solidity modifiers will (potentially)
|
|
2403
2550
|
# appear before any instrumentation we add to the literal source body, which will tank the detection
|
|
@@ -2409,8 +2556,10 @@ class CertoraBuildGenerator:
|
|
|
2409
2556
|
# where in the source such modifiers will go. In order to insert a modifier, we have to have at
|
|
2410
2557
|
# least one modifier already present, and then insert before the first modifier's location in the
|
|
2411
2558
|
# source code
|
|
2412
|
-
mod_inst = generate_modifier_finder(
|
|
2413
|
-
|
|
2559
|
+
mod_inst = generate_modifier_finder(
|
|
2560
|
+
f, internal_id, function_symbol, sdc.compiler_collector, def_node,
|
|
2561
|
+
compress=should_compress, should_generate_inlining=should_generate_inlining
|
|
2562
|
+
)
|
|
2414
2563
|
if mod_inst is None:
|
|
2415
2564
|
instrumentation_logger.debug(f"Modifier generation for {f.name} @ {f.where()} failed")
|
|
2416
2565
|
return None
|
|
@@ -2444,8 +2593,8 @@ class CertoraBuildGenerator:
|
|
|
2444
2593
|
per_file_inst[first_mod_offset] = Instrumentation(expected=bytes(modifier_name[0:1], "utf-8"),
|
|
2445
2594
|
to_ins=modifier_invocation, mut=InsertBefore())
|
|
2446
2595
|
else:
|
|
2447
|
-
finder_res = generate_inline_finder(f, internal_id, function_symbol,
|
|
2448
|
-
|
|
2596
|
+
finder_res = generate_inline_finder(f, internal_id, function_symbol, sdc.compiler_collector,
|
|
2597
|
+
should_compress, should_generate_inlining)
|
|
2449
2598
|
if finder_res is None:
|
|
2450
2599
|
instrumentation_logger.debug(f"Generating auto finder for {f.name} @ {f.where()}"
|
|
2451
2600
|
f" failed, giving up generation")
|
|
@@ -2455,6 +2604,114 @@ class CertoraBuildGenerator:
|
|
|
2455
2604
|
mut=InsertAfter())
|
|
2456
2605
|
return function_finder_by_contract, function_finder_instrumentation
|
|
2457
2606
|
|
|
2607
|
+
def add_internal_func_harnesses(self, contract_file: str, sdc: SDC, spec_calls: List[str]) -> Optional[Tuple[Dict[str, str], Dict[str, Dict[int, Instrumentation]]]]:
|
|
2608
|
+
# contract file -> byte offset -> to insert
|
|
2609
|
+
harness_function_instrumentation: Dict[str, Dict[int, Instrumentation]] = defaultdict(dict)
|
|
2610
|
+
# internal function name -> harness fuction name
|
|
2611
|
+
harness_function_names: Dict[str, str] = {}
|
|
2612
|
+
|
|
2613
|
+
if not isinstance(sdc.compiler_collector, CompilerCollectorSol):
|
|
2614
|
+
raise Exception(f"Encountered a compiler collector that is not solc for file {contract_file}"
|
|
2615
|
+
" when trying to add function autofinders")
|
|
2616
|
+
instrumentation_logger.debug(f"Using {sdc.compiler_collector} compiler to "
|
|
2617
|
+
f"add external function harnesses to contract {sdc.primary_contract}")
|
|
2618
|
+
|
|
2619
|
+
for c in sdc.contracts:
|
|
2620
|
+
for f in c.internal_funcs:
|
|
2621
|
+
if f"{sdc.primary_contract}.{f.name}" not in spec_calls:
|
|
2622
|
+
continue
|
|
2623
|
+
|
|
2624
|
+
if f.fromLib:
|
|
2625
|
+
# Even external library functions can't be called directly from spec,
|
|
2626
|
+
# so skip harnessing internal ones.
|
|
2627
|
+
continue
|
|
2628
|
+
|
|
2629
|
+
if f.isConstructor:
|
|
2630
|
+
continue
|
|
2631
|
+
|
|
2632
|
+
if f.is_free_func:
|
|
2633
|
+
# Free functions (declared outside of any contract/library) don't have visibility modifiers
|
|
2634
|
+
continue
|
|
2635
|
+
|
|
2636
|
+
orig_file = f.original_file
|
|
2637
|
+
if not orig_file:
|
|
2638
|
+
instrumentation_logger.debug(f"missing file location for {f.name}")
|
|
2639
|
+
continue
|
|
2640
|
+
|
|
2641
|
+
loc = f.location
|
|
2642
|
+
if not loc:
|
|
2643
|
+
instrumentation_logger.debug(f"Found a function {f.name} in "
|
|
2644
|
+
f"{c.name} that doesn't have a location")
|
|
2645
|
+
continue
|
|
2646
|
+
|
|
2647
|
+
if f.implemented:
|
|
2648
|
+
expected_end_char = b"}"
|
|
2649
|
+
else:
|
|
2650
|
+
# This is a virtual function with no implementation. Declare also a virtual harness so that when
|
|
2651
|
+
# Implementing a harness for the override we can just add the `override` keyword to the harness
|
|
2652
|
+
# function as well without needing to have any extra logic at that point.
|
|
2653
|
+
expected_end_char = b";"
|
|
2654
|
+
|
|
2655
|
+
if len(f.fullArgs) != len(f.paramNames):
|
|
2656
|
+
instrumentation_logger.debug(f"Do not have argument names for {f.name} in"
|
|
2657
|
+
f" {c.name}, giving up internal function harnessing")
|
|
2658
|
+
continue
|
|
2659
|
+
|
|
2660
|
+
if any(ty.location == CT.TypeLocation.STORAGE for ty in f.fullArgs + f.returns):
|
|
2661
|
+
instrumentation_logger.debug(f"Function {f.name} has input arguments with 'storage' location - cannot harness it")
|
|
2662
|
+
continue
|
|
2663
|
+
|
|
2664
|
+
instrumentation_path = str(Util.abs_norm_path(orig_file))
|
|
2665
|
+
per_file_inst = harness_function_instrumentation[instrumentation_path]
|
|
2666
|
+
|
|
2667
|
+
start, size, _ = loc.split(":")
|
|
2668
|
+
body_end_byte = int(start) + int(size) - 1
|
|
2669
|
+
|
|
2670
|
+
def get_local_type_name(ty: CT.TypeInstance) -> str:
|
|
2671
|
+
# Handles imports that use 'as'. E.g. `import {A as B} from "A.sol";`
|
|
2672
|
+
ret = ty.get_source_str()
|
|
2673
|
+
assert orig_file
|
|
2674
|
+
for node in self.asts[sdc.sdc_origin_file][orig_file].values():
|
|
2675
|
+
if node["nodeType"] != "ImportDirective":
|
|
2676
|
+
continue
|
|
2677
|
+
for alias in node["symbolAliases"]:
|
|
2678
|
+
if alias["foreign"]["name"] == ty.get_source_str() and "local" in alias:
|
|
2679
|
+
ret = alias["local"]
|
|
2680
|
+
break
|
|
2681
|
+
|
|
2682
|
+
# Now add the location
|
|
2683
|
+
return ret + (f" {ty.location.value}" if ty.location != CT.TypeLocation.STACK else "")
|
|
2684
|
+
|
|
2685
|
+
harness_name = f"{f.name}_external_harness"
|
|
2686
|
+
harness_string = f" function {harness_name}({', '.join(f'{get_local_type_name(ty)} {n}' for ty, n in zip(f.fullArgs, f.paramNames))}) external"
|
|
2687
|
+
|
|
2688
|
+
if f.stateMutability in ["pure", "view"]:
|
|
2689
|
+
harness_string += f" {f.stateMutability}"
|
|
2690
|
+
|
|
2691
|
+
if f.virtual:
|
|
2692
|
+
harness_string += " virtual"
|
|
2693
|
+
|
|
2694
|
+
if f.overrides:
|
|
2695
|
+
harness_string += " override"
|
|
2696
|
+
|
|
2697
|
+
if f.returns:
|
|
2698
|
+
harness_string += f" returns ({', '.join(get_local_type_name(r) for r in f.returns)})"
|
|
2699
|
+
|
|
2700
|
+
if f.implemented:
|
|
2701
|
+
harness_call = f"{f.name}({', '.join(f.paramNames)})"
|
|
2702
|
+
if f.returns:
|
|
2703
|
+
harness_string += f" {{ return {harness_call}; }}"
|
|
2704
|
+
else:
|
|
2705
|
+
harness_string += f" {{ {harness_call}; }}"
|
|
2706
|
+
else:
|
|
2707
|
+
harness_string += ";"
|
|
2708
|
+
|
|
2709
|
+
per_file_inst[body_end_byte] = Instrumentation(expected=expected_end_char, to_ins=harness_string,
|
|
2710
|
+
mut=InsertAfter())
|
|
2711
|
+
harness_function_names[f.name] = harness_name
|
|
2712
|
+
|
|
2713
|
+
return harness_function_names, harness_function_instrumentation
|
|
2714
|
+
|
|
2458
2715
|
def cleanup(self) -> None:
|
|
2459
2716
|
for sdc_name, smart_contract_lang in self.__compiled_artifacts_to_clean:
|
|
2460
2717
|
self.cleanup_compiler_outputs(sdc_name, smart_contract_lang)
|
|
@@ -2510,6 +2767,16 @@ class CertoraBuildGenerator:
|
|
|
2510
2767
|
|
|
2511
2768
|
def build(self, certora_verify_generator: CertoraVerifyGenerator) -> None:
|
|
2512
2769
|
context = self.context
|
|
2770
|
+
|
|
2771
|
+
spec_calls: List[str] = []
|
|
2772
|
+
if context.verify and not context.disallow_internal_function_calls:
|
|
2773
|
+
with tempfile.NamedTemporaryFile("r", dir=Util.get_build_dir()) as tmp_file:
|
|
2774
|
+
try:
|
|
2775
|
+
Ctx.run_local_spec_check(False, context, ["-listCalls", tmp_file.name], print_errors=False)
|
|
2776
|
+
spec_calls = tmp_file.read().split("\n")
|
|
2777
|
+
except Exception as e:
|
|
2778
|
+
instrumentation_logger.debug(f"Failed to get calls from spec\n{e}")
|
|
2779
|
+
|
|
2513
2780
|
self.context.remappings = []
|
|
2514
2781
|
for i, build_arg_contract_file in enumerate(sorted(self.input_config.sorted_files)):
|
|
2515
2782
|
build_logger.debug(f"\nbuilding file {build_arg_contract_file}")
|
|
@@ -2519,7 +2786,6 @@ class CertoraBuildGenerator:
|
|
|
2519
2786
|
Util.print_progress_message(f"Compiling {orig_file_name}...")
|
|
2520
2787
|
sdc_pre_finders = self.collect_for_file(build_arg_contract_file, i, compiler_lang, Path(os.getcwd()),
|
|
2521
2788
|
path_for_compiler_collector_file, original_sdc=None)
|
|
2522
|
-
|
|
2523
2789
|
# Build sources tree
|
|
2524
2790
|
build_logger.debug("Building source tree")
|
|
2525
2791
|
sources_from_pre_finder_SDCs = set()
|
|
@@ -2527,7 +2793,7 @@ class CertoraBuildGenerator:
|
|
|
2527
2793
|
sources_from_pre_finder_SDCs |= sdc.all_contract_files
|
|
2528
2794
|
sources = self.collect_sources(context, certora_verify_generator, sources_from_pre_finder_SDCs)
|
|
2529
2795
|
try:
|
|
2530
|
-
|
|
2796
|
+
build_source_tree(sources, context)
|
|
2531
2797
|
except Exception as e:
|
|
2532
2798
|
build_logger.debug(f"build_source_tree failed. Sources: {sources}", exc_info=e)
|
|
2533
2799
|
raise
|
|
@@ -2562,7 +2828,7 @@ class CertoraBuildGenerator:
|
|
|
2562
2828
|
# We start by trying to instrument _all_ finders, both autofinders and source finders
|
|
2563
2829
|
added_finders, all_finders_success, src_finders_gen_success, post_backup_dir = self.finders_compilation_round(
|
|
2564
2830
|
build_arg_contract_file, i, ignore_patterns, path_for_compiler_collector_file, pre_backup_dir,
|
|
2565
|
-
sdc_pre_finders, not context.disable_source_finders)
|
|
2831
|
+
sdc_pre_finders, not context.disable_source_finders, spec_calls)
|
|
2566
2832
|
|
|
2567
2833
|
# we could have a case where source finders failed but regular finders succeeded.
|
|
2568
2834
|
# e.g. if we processed the AST wrong and skipped source finders generation
|
|
@@ -2574,21 +2840,21 @@ class CertoraBuildGenerator:
|
|
|
2574
2840
|
# let's try just the function autofinders
|
|
2575
2841
|
added_finders, function_autofinder_success, _, post_backup_dir = self.finders_compilation_round(
|
|
2576
2842
|
build_arg_contract_file, i, ignore_patterns, path_for_compiler_collector_file, pre_backup_dir,
|
|
2577
|
-
sdc_pre_finders, False)
|
|
2843
|
+
sdc_pre_finders, False, spec_calls)
|
|
2578
2844
|
|
|
2579
2845
|
if not function_autofinder_success:
|
|
2580
2846
|
self.auto_finders_failed = True
|
|
2581
2847
|
|
|
2582
2848
|
if not self.auto_finders_failed or not self.source_finders_failed:
|
|
2583
2849
|
# setup source_dir. note that post_backup_dir must include the finders in this case
|
|
2584
|
-
for _, _, sdc in added_finders:
|
|
2850
|
+
for _, _, _, sdc in added_finders:
|
|
2585
2851
|
sdc.source_dir = str(post_backup_dir.relative_to(Util.get_certora_sources_dir()))
|
|
2586
2852
|
sdc.orig_source_dir = str(pre_backup_dir.relative_to(Util.get_certora_sources_dir()))
|
|
2587
2853
|
else:
|
|
2588
2854
|
# no point in running autofinders in vyper right now
|
|
2589
|
-
added_finders = [({}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders]
|
|
2855
|
+
added_finders = [({}, {}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders]
|
|
2590
2856
|
|
|
2591
|
-
for added_func_finders, added_source_finders, sdc in added_finders:
|
|
2857
|
+
for added_func_finders, added_source_finders, added_internal_function_harnesses, sdc in added_finders:
|
|
2592
2858
|
for contract in sdc.contracts:
|
|
2593
2859
|
all_functions: List[Func] = list()
|
|
2594
2860
|
for k, v in added_func_finders.items():
|
|
@@ -2596,6 +2862,8 @@ class CertoraBuildGenerator:
|
|
|
2596
2862
|
contract.function_finders[k] = v
|
|
2597
2863
|
for source_key, source_value in added_source_finders.items():
|
|
2598
2864
|
contract.local_assignments[source_key] = source_value
|
|
2865
|
+
if contract.name == sdc.primary_contract:
|
|
2866
|
+
contract.internal_function_harnesses = added_internal_function_harnesses
|
|
2599
2867
|
all_functions.extend(contract.methods)
|
|
2600
2868
|
all_functions.extend(contract.internal_funcs)
|
|
2601
2869
|
functions_unique_by_internal_rep = list() # type: List[Func]
|
|
@@ -2649,11 +2917,182 @@ class CertoraBuildGenerator:
|
|
|
2649
2917
|
|
|
2650
2918
|
self.SDCs[self.get_sdc_key(sdc.primary_contract, sdc.primary_contract_address)] = sdc
|
|
2651
2919
|
|
|
2920
|
+
if self.context.dump_asts:
|
|
2921
|
+
self.dump_asts()
|
|
2652
2922
|
self.handle_links()
|
|
2653
2923
|
self.handle_struct_links()
|
|
2654
2924
|
self.handle_contract_extensions()
|
|
2925
|
+
if self.context.storage_extension_annotation:
|
|
2926
|
+
self.handle_erc7201_annotations()
|
|
2655
2927
|
self.handle_storage_extension_harnesses()
|
|
2656
2928
|
|
|
2929
|
+
def extract_slayout(self, original_file: str, ns_storage: Set[NameSpacedStorage], compiler_version: str, target_file: str) -> NewStorageInfo:
|
|
2930
|
+
"""
|
|
2931
|
+
Given a file containing a contract with namespaced storage, extract the storage information
|
|
2932
|
+
corresponding to the namespaced types.
|
|
2933
|
+
|
|
2934
|
+
Args:
|
|
2935
|
+
original_file: Path to the Solidity file containing namespaced storage declarations
|
|
2936
|
+
ns_storage: Set of tuples (type_name, namespace) for each namespaced storage
|
|
2937
|
+
|
|
2938
|
+
Returns:
|
|
2939
|
+
NewStorageInfo: A tuple (fields, types) where:
|
|
2940
|
+
- fields: List of new fields added by the namespaced storage
|
|
2941
|
+
- types: Dictionary of types referenced by the fields
|
|
2942
|
+
"""
|
|
2943
|
+
file_dir = Path(original_file).parent
|
|
2944
|
+
|
|
2945
|
+
# Generate a unique name for the harness contract based on the contract names in the file
|
|
2946
|
+
harness_name = storageExtension.generate_harness_name(original_file)
|
|
2947
|
+
|
|
2948
|
+
with tempfile.NamedTemporaryFile(mode="w+t", suffix=".sol", dir=file_dir, delete=True) as tmp_file:
|
|
2949
|
+
# Import the original file.
|
|
2950
|
+
# Note we import and don't inline the file's contents since that's how the
|
|
2951
|
+
# original file is accessed also in the actual code, and compiling it
|
|
2952
|
+
# directly can cause issues.
|
|
2953
|
+
tmp_file.write(f"import \"{original_file}\";\n\n")
|
|
2954
|
+
rel_path = os.path.relpath(tmp_file.name, Path.cwd())
|
|
2955
|
+
if self.context.compiler_map:
|
|
2956
|
+
self.context.compiler_map.update({rel_path: compiler_version}, last=False)
|
|
2957
|
+
# Write the harness contract with dummy fields for each namespaced storage
|
|
2958
|
+
var_to_slot = storageExtension.write_harness_contract(tmp_file, harness_name, ns_storage)
|
|
2959
|
+
tmp_file.flush()
|
|
2960
|
+
|
|
2961
|
+
if self.context.extract_storage_extension_annotation:
|
|
2962
|
+
# If the flag is set, save the storage extension contract
|
|
2963
|
+
build_dir = Util.get_build_dir()
|
|
2964
|
+
shutil.copyfile(
|
|
2965
|
+
Path(tmp_file.name),
|
|
2966
|
+
build_dir / f"{Path(original_file).stem}_storage_extension.sol"
|
|
2967
|
+
)
|
|
2968
|
+
|
|
2969
|
+
# normalize the path exactly the way collect_for_file expects it:
|
|
2970
|
+
abs_path = Util.abs_posix_path(tmp_file.name)
|
|
2971
|
+
self.context.file_to_contract[abs_path] = {harness_name}
|
|
2972
|
+
|
|
2973
|
+
def attempt_compilation() -> NewStorageInfo:
|
|
2974
|
+
"""Helper function to compile and extract layout"""
|
|
2975
|
+
compile_idx = storageExtension.get_next_file_index(self.file_to_sdc_name)
|
|
2976
|
+
sdcs = self.collect_for_file(tmp_file.name, compile_idx, CompilerLangSol(), Path.cwd(), Util.abs_posix_path(tmp_file.name), None)
|
|
2977
|
+
if not sdcs:
|
|
2978
|
+
raise RuntimeError(f"Failed to compile harness contract for {tmp_file}")
|
|
2979
|
+
layout = storageExtension.extract_harness_contract_layout(sdcs, harness_name)
|
|
2980
|
+
|
|
2981
|
+
# Remap each slot according to the ERC-7201 namespace
|
|
2982
|
+
remapped_fields = storageExtension.remapped_fields_from_layout(layout, var_to_slot)
|
|
2983
|
+
return (remapped_fields, layout.get('types', {}))
|
|
2984
|
+
|
|
2985
|
+
# First attempt with original content
|
|
2986
|
+
try:
|
|
2987
|
+
return attempt_compilation()
|
|
2988
|
+
except Exception as e:
|
|
2989
|
+
if not target_file:
|
|
2990
|
+
build_logger.error(f"Error extracting storage layout for {original_file}: {str(e)}")
|
|
2991
|
+
raise
|
|
2992
|
+
|
|
2993
|
+
# Retry with target file import
|
|
2994
|
+
build_logger.info("First attempt failed, retrying with import of target file")
|
|
2995
|
+
|
|
2996
|
+
# Read current content
|
|
2997
|
+
tmp_file.seek(0)
|
|
2998
|
+
current_content = tmp_file.read()
|
|
2999
|
+
|
|
3000
|
+
# Prepare modified content with target import at the beginning
|
|
3001
|
+
rel_target_path = os.path.relpath(target_file, Path.cwd())
|
|
3002
|
+
modified_content = f'import "{rel_target_path}";\n{current_content}'
|
|
3003
|
+
|
|
3004
|
+
# Write modified content
|
|
3005
|
+
tmp_file.seek(0)
|
|
3006
|
+
tmp_file.truncate()
|
|
3007
|
+
tmp_file.write(modified_content)
|
|
3008
|
+
tmp_file.flush()
|
|
3009
|
+
|
|
3010
|
+
try:
|
|
3011
|
+
return attempt_compilation()
|
|
3012
|
+
except Exception as retry_e:
|
|
3013
|
+
build_logger.error(f"Retry also failed for {original_file}: {str(retry_e)}")
|
|
3014
|
+
raise retry_e
|
|
3015
|
+
finally:
|
|
3016
|
+
# Delete the key from the context
|
|
3017
|
+
self.context.file_to_contract.pop(abs_path, None)
|
|
3018
|
+
|
|
3019
|
+
def handle_erc7201_annotations(self) -> None:
|
|
3020
|
+
"""
|
|
3021
|
+
Look for contracts that use erc-7201 namespaced storage layout
|
|
3022
|
+
(see https://eips.ethereum.org/EIPS/eip-7201).
|
|
3023
|
+
|
|
3024
|
+
Find contracts A s.t. A contain a type declaration with such an annotation, e.g.
|
|
3025
|
+
/** @custom:storage-location erc-7201:some.name.space */
|
|
3026
|
+
struct T { ... }
|
|
3027
|
+
|
|
3028
|
+
Then, for any contract C that has A as a base contract, _extend_ C's storage layout
|
|
3029
|
+
information such that it contains the information for a `T` at the slot
|
|
3030
|
+
erc-7201(some.name.space) as defined in the EIP.
|
|
3031
|
+
"""
|
|
3032
|
+
# Find all erc7201-like contracts, generate+compile a harness & extract layout information
|
|
3033
|
+
# maps (path,contract) -> new storage info added by (path,contract)
|
|
3034
|
+
slayouts: Dict[Tuple[str, str], NewStorageInfo] = {}
|
|
3035
|
+
|
|
3036
|
+
# Scan all of the contracts (including dependencies of targets) for namespaced storage
|
|
3037
|
+
# layout information
|
|
3038
|
+
for target_file in self.context.file_paths:
|
|
3039
|
+
if target_file not in self.asts:
|
|
3040
|
+
# No AST for this file, so we can't do anything
|
|
3041
|
+
continue
|
|
3042
|
+
for (imported_file, imported_file_ast) in self.asts[target_file].items():
|
|
3043
|
+
for def_node in imported_file_ast.values():
|
|
3044
|
+
if def_node.get("nodeType") != "ContractDefinition":
|
|
3045
|
+
continue
|
|
3046
|
+
|
|
3047
|
+
# Construct a key for the contract definition node
|
|
3048
|
+
contract_name = def_node.get("name")
|
|
3049
|
+
key = (imported_file, contract_name)
|
|
3050
|
+
if key in slayouts:
|
|
3051
|
+
# We already have this contract's storage layout information
|
|
3052
|
+
continue
|
|
3053
|
+
|
|
3054
|
+
# Collect any @custom:storage-location annotations
|
|
3055
|
+
ns_storage = storageExtension.get_namespace_storage_from_ast(def_node)
|
|
3056
|
+
|
|
3057
|
+
if not ns_storage:
|
|
3058
|
+
# No namespaced storage found in this contract
|
|
3059
|
+
continue
|
|
3060
|
+
|
|
3061
|
+
# Now that we have all the storage layout information, extract it once
|
|
3062
|
+
slayouts[key] = self.extract_slayout(imported_file, ns_storage,
|
|
3063
|
+
get_relevant_compiler(Path(target_file), self.context),
|
|
3064
|
+
target_file)
|
|
3065
|
+
|
|
3066
|
+
if self.context.test == str(Util.TestValue.STORAGE_EXTENSION_LAYOUT):
|
|
3067
|
+
raise Util.TestResultsReady(slayouts)
|
|
3068
|
+
|
|
3069
|
+
if not slayouts:
|
|
3070
|
+
# No contracts with namespaced storage found
|
|
3071
|
+
return
|
|
3072
|
+
|
|
3073
|
+
# Finally, extend each target contract with the storage layout info from
|
|
3074
|
+
# all of its base contracts
|
|
3075
|
+
for target in self.get_primary_contracts_from_sdcs():
|
|
3076
|
+
if target.name not in self.context.contract_to_file:
|
|
3077
|
+
# This is a contract that was not compiled, so we don't have a file for it
|
|
3078
|
+
continue
|
|
3079
|
+
target_file = self.context.contract_to_file[target.name]
|
|
3080
|
+
base_contracts = self.retrieve_base_contracts_list(
|
|
3081
|
+
target_file,
|
|
3082
|
+
target_file if self.context.use_relpaths_for_solc_json else Util.abs_posix_path(target_file),
|
|
3083
|
+
target.name
|
|
3084
|
+
)
|
|
3085
|
+
extensions: Set[str] = set()
|
|
3086
|
+
harnesses: Dict[str, NewStorageInfo] = {}
|
|
3087
|
+
for base in base_contracts:
|
|
3088
|
+
layout = slayouts.get((base[0], base[1]))
|
|
3089
|
+
if layout is not None:
|
|
3090
|
+
extensions.add(base[1])
|
|
3091
|
+
harnesses[base[1]] = layout
|
|
3092
|
+
else:
|
|
3093
|
+
build_logger.warning(f"Could not find storage layout for {base[1]} in {base[0]}")
|
|
3094
|
+
storageExtension.apply_extensions(target, extensions, harnesses)
|
|
3095
|
+
|
|
2657
3096
|
def handle_storage_extension_harnesses(self) -> None:
|
|
2658
3097
|
def new_field_of_node(ext_instance: Any, node: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
2659
3098
|
"""
|
|
@@ -2698,8 +3137,8 @@ class CertoraBuildGenerator:
|
|
|
2698
3137
|
return None
|
|
2699
3138
|
cloned_field = storage_field_info.copy()
|
|
2700
3139
|
cloned_field["slot"] = str(link_slot)
|
|
2701
|
-
#
|
|
2702
|
-
cloned_field["label"] =
|
|
3140
|
+
# Don't bother trying to uniquify the name
|
|
3141
|
+
cloned_field["label"] = var_name
|
|
2703
3142
|
return cloned_field
|
|
2704
3143
|
|
|
2705
3144
|
def handle_one_extension(storage_ext: str) -> tuple[Any, str, List[Dict[str, Any]]] :
|
|
@@ -2716,8 +3155,9 @@ class CertoraBuildGenerator:
|
|
|
2716
3155
|
|
|
2717
3156
|
# let's try to find the AST now
|
|
2718
3157
|
build_file_ast = None
|
|
3158
|
+
orig_path = os.path.relpath(ext_instance.original_file, Path.cwd()) if self.context.use_relpaths_for_solc_json else ext_instance.original_file
|
|
2719
3159
|
for (k, v) in self.asts.items():
|
|
2720
|
-
if
|
|
3160
|
+
if orig_path in v:
|
|
2721
3161
|
build_file_ast = k
|
|
2722
3162
|
break
|
|
2723
3163
|
if build_file_ast is None:
|
|
@@ -2725,10 +3165,10 @@ class CertoraBuildGenerator:
|
|
|
2725
3165
|
|
|
2726
3166
|
contract_def_node = self.get_contract_def_node_ref(
|
|
2727
3167
|
build_file_ast,
|
|
2728
|
-
|
|
3168
|
+
orig_path,
|
|
2729
3169
|
storage_ext
|
|
2730
3170
|
)
|
|
2731
|
-
def_node = self.asts[build_file_ast][
|
|
3171
|
+
def_node = self.asts[build_file_ast][orig_path][contract_def_node]
|
|
2732
3172
|
nodes = def_node.get("nodes")
|
|
2733
3173
|
if not isinstance(nodes, list):
|
|
2734
3174
|
raise RuntimeError(f"Couldn't find nodes for body of {storage_ext}")
|
|
@@ -2740,41 +3180,6 @@ class CertoraBuildGenerator:
|
|
|
2740
3180
|
new_fields.append(new_field)
|
|
2741
3181
|
|
|
2742
3182
|
return (ext_instance, extension_sdc_name, new_fields)
|
|
2743
|
-
|
|
2744
|
-
def apply_extensions(target_contract: Any, extensions: Set[str], to_add: Dict[str, Tuple[List[Dict[str, Any]], Dict[str, Any]]]) -> None:
|
|
2745
|
-
"""
|
|
2746
|
-
Apply the fields from each extension to the target contract,
|
|
2747
|
-
"""
|
|
2748
|
-
if target_contract.storage_layout.get("storage") is None:
|
|
2749
|
-
target_contract.storage_layout["storage"] = []
|
|
2750
|
-
if target_contract.storage_layout.get("types") is None:
|
|
2751
|
-
target_contract.storage_layout["types"] = {}
|
|
2752
|
-
target_slots = {storage["slot"] for storage in target_contract.storage_layout["storage"]}
|
|
2753
|
-
# Keep track of slots we've added, and error if we
|
|
2754
|
-
# find two extensions extending the same slot
|
|
2755
|
-
added_slots: Dict[str, str] = {}
|
|
2756
|
-
for ext in extensions:
|
|
2757
|
-
(new_fields, new_types) = to_add[ext]
|
|
2758
|
-
|
|
2759
|
-
for f in new_fields:
|
|
2760
|
-
# See if any of the new fields is a slot we've already added
|
|
2761
|
-
slot = f["slot"]
|
|
2762
|
-
if slot in added_slots:
|
|
2763
|
-
seen = added_slots[slot]
|
|
2764
|
-
raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} already added by {seen}")
|
|
2765
|
-
|
|
2766
|
-
if slot in target_slots:
|
|
2767
|
-
raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} already mapped by {target_contract.name}")
|
|
2768
|
-
|
|
2769
|
-
added_slots[slot] = ext
|
|
2770
|
-
|
|
2771
|
-
target_contract.storage_layout["storage"].extend(new_fields)
|
|
2772
|
-
|
|
2773
|
-
for (new_id, new_ty) in new_types.items():
|
|
2774
|
-
if new_id in target_contract.storage_layout["types"]:
|
|
2775
|
-
continue
|
|
2776
|
-
target_contract.storage_layout["types"][new_id] = new_ty
|
|
2777
|
-
|
|
2778
3183
|
extension_contracts: Set[str] = set()
|
|
2779
3184
|
storage_extensions: Dict[str, Set[str]] = defaultdict(set)
|
|
2780
3185
|
storage_ext = self.context.storage_extension_harnesses
|
|
@@ -2803,7 +3208,7 @@ class CertoraBuildGenerator:
|
|
|
2803
3208
|
sdc = self.SDCs[target_sdc]
|
|
2804
3209
|
target_contract = sdc.find_contract(target)
|
|
2805
3210
|
assert target_contract is not None, f"could not find contract for {target}"
|
|
2806
|
-
apply_extensions(target_contract, extensions, extension_to_fields_and_types)
|
|
3211
|
+
storageExtension.apply_extensions(target_contract, extensions, extension_to_fields_and_types)
|
|
2807
3212
|
|
|
2808
3213
|
def finders_compilation_round(self,
|
|
2809
3214
|
build_arg_contract_file: str,
|
|
@@ -2812,12 +3217,13 @@ class CertoraBuildGenerator:
|
|
|
2812
3217
|
path_for_compiler_collector_file: str,
|
|
2813
3218
|
pre_backup_dir: Path,
|
|
2814
3219
|
sdc_pre_finders: List[SDC],
|
|
2815
|
-
with_source_finders: bool
|
|
2816
|
-
|
|
3220
|
+
with_source_finders: bool,
|
|
3221
|
+
spec_calls: List[str]) -> Tuple[
|
|
3222
|
+
List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]], bool, bool, Path]:
|
|
2817
3223
|
added_finders_to_sdc, finders_compilation_success, source_finders_gen_success = \
|
|
2818
3224
|
self.instrument_auto_finders(
|
|
2819
3225
|
build_arg_contract_file, i, sdc_pre_finders,
|
|
2820
|
-
path_for_compiler_collector_file, with_source_finders)
|
|
3226
|
+
path_for_compiler_collector_file, with_source_finders, spec_calls)
|
|
2821
3227
|
# successful or not, we backup current .certora_sources for either debuggability, or for availability
|
|
2822
3228
|
# of sources.
|
|
2823
3229
|
post_backup_dir = self.get_fresh_backupdir(Util.POST_AUTOFINDER_BACKUP_DIR)
|
|
@@ -2889,11 +3295,12 @@ class CertoraBuildGenerator:
|
|
|
2889
3295
|
def instrument_auto_finders(self, build_arg_contract_file: str, i: int,
|
|
2890
3296
|
sdc_pre_finders: List[SDC],
|
|
2891
3297
|
path_for_compiler_collector_file: str,
|
|
2892
|
-
instrument_source_finders: bool
|
|
2893
|
-
|
|
3298
|
+
instrument_source_finders: bool,
|
|
3299
|
+
spec_calls: List[str]) -> Tuple[
|
|
3300
|
+
List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]], bool, bool]:
|
|
2894
3301
|
|
|
2895
3302
|
# initialization
|
|
2896
|
-
ret
|
|
3303
|
+
ret: List[Tuple[Dict[str, InternalFunc], Dict[str, UnspecializedSourceFinder], Dict[str, str], SDC]] = []
|
|
2897
3304
|
instrumentation_logger.debug(f"Instrumenting auto finders in {build_arg_contract_file}")
|
|
2898
3305
|
# all of the [SDC]s inside [sdc_pre_finders] have the same list of [ContractInSDC]s
|
|
2899
3306
|
# (generated in the [collect_from_file] function).
|
|
@@ -2902,10 +3309,19 @@ class CertoraBuildGenerator:
|
|
|
2902
3309
|
if added_function_finders_tuple is None:
|
|
2903
3310
|
instrumentation_logger.warning(
|
|
2904
3311
|
f"Computing function finder instrumentation failed for {build_arg_contract_file}")
|
|
2905
|
-
return [({}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
3312
|
+
return [({}, {}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
2906
3313
|
|
|
2907
3314
|
(added_function_finders, function_instr) = added_function_finders_tuple
|
|
2908
3315
|
|
|
3316
|
+
instr = function_instr
|
|
3317
|
+
|
|
3318
|
+
added_internal_function_harnesses: Dict[str, str] = {}
|
|
3319
|
+
if not self.context.disallow_internal_function_calls:
|
|
3320
|
+
added_internal_func_harness_tuple = self.add_internal_func_harnesses(build_arg_contract_file, sdc_pre_finder, spec_calls)
|
|
3321
|
+
if added_internal_func_harness_tuple:
|
|
3322
|
+
instr = CertoraBuildGenerator.merge_dicts_instrumentation(function_instr, added_internal_func_harness_tuple[1])
|
|
3323
|
+
added_internal_function_harnesses = added_internal_func_harness_tuple[0]
|
|
3324
|
+
|
|
2909
3325
|
source_finders_gen_succeeded = False
|
|
2910
3326
|
if instrument_source_finders:
|
|
2911
3327
|
try:
|
|
@@ -2914,17 +3330,24 @@ class CertoraBuildGenerator:
|
|
|
2914
3330
|
(added_source_finders, source_instr) = added_source_finders_tuple
|
|
2915
3331
|
# Update instr with additional instrumentations. Recall it is a map file -> offset -> instr.
|
|
2916
3332
|
# Function finders take precedence
|
|
2917
|
-
instr = CertoraBuildGenerator.merge_dicts_instrumentation(
|
|
3333
|
+
instr = CertoraBuildGenerator.merge_dicts_instrumentation(instr, source_instr)
|
|
2918
3334
|
source_finders_gen_succeeded = True
|
|
2919
3335
|
except: # noqa: E722
|
|
2920
3336
|
instrumentation_logger.warning(
|
|
2921
3337
|
f"Computing source finder instrumentation failed for {build_arg_contract_file}")
|
|
2922
|
-
instr = function_instr
|
|
2923
3338
|
added_source_finders = {}
|
|
2924
3339
|
else:
|
|
2925
|
-
instr = function_instr
|
|
2926
3340
|
added_source_finders = {}
|
|
2927
3341
|
|
|
3342
|
+
try:
|
|
3343
|
+
casting_instrumentations, casting_types = generate_casting_instrumentation(self.asts, build_arg_contract_file, sdc_pre_finder)
|
|
3344
|
+
except Exception as e:
|
|
3345
|
+
instrumentation_logger.warning(
|
|
3346
|
+
f"Computing casting instrumentation failed for {build_arg_contract_file}: {e}", exc_info=True)
|
|
3347
|
+
casting_instrumentations, casting_types = {}, {}
|
|
3348
|
+
|
|
3349
|
+
instr = CertoraBuildGenerator.merge_dicts_instrumentation(instr, casting_instrumentations)
|
|
3350
|
+
|
|
2928
3351
|
abs_build_arg_contract_file = Util.abs_posix_path(build_arg_contract_file)
|
|
2929
3352
|
if abs_build_arg_contract_file not in instr:
|
|
2930
3353
|
instrumentation_logger.debug(
|
|
@@ -2942,7 +3365,7 @@ class CertoraBuildGenerator:
|
|
|
2942
3365
|
# instrumentation should be keyed only using absolute paths
|
|
2943
3366
|
instrumentation_logger.warning(f"Already generated autofinder for {new_name}, "
|
|
2944
3367
|
f"cannot instrument again for {contract_file}")
|
|
2945
|
-
return [({}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
3368
|
+
return [({}, {}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
2946
3369
|
|
|
2947
3370
|
autofinder_remappings[new_name] = contract_file
|
|
2948
3371
|
|
|
@@ -2971,19 +3394,26 @@ class CertoraBuildGenerator:
|
|
|
2971
3394
|
instrumentation_logger.warning("Skipping source finder generation!")
|
|
2972
3395
|
else:
|
|
2973
3396
|
instrumentation_logger.warning("Skipping internal function finder generation!")
|
|
2974
|
-
return [({}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
3397
|
+
return [({}, {}, {}, old_sdc) for old_sdc in sdc_pre_finders], False, False
|
|
2975
3398
|
to_skip = to_insert.mut.insert(to_insert.to_ins, to_insert.expected, output)
|
|
2976
3399
|
if to_skip != 0:
|
|
2977
3400
|
in_file.read(to_skip)
|
|
2978
3401
|
read_so_far += amt + 1 + to_skip
|
|
2979
3402
|
output.write(in_file.read(-1))
|
|
2980
3403
|
|
|
3404
|
+
library_name, funcs = casting_types.get(contract_file, ("", list()))
|
|
3405
|
+
if len(funcs) > 0:
|
|
3406
|
+
output.write(bytes(f"\nlibrary {library_name}" + "{\n", "utf8"))
|
|
3407
|
+
for f in funcs:
|
|
3408
|
+
output.write(bytes(f, "utf8"))
|
|
3409
|
+
output.write(bytes("}\n", "utf8"))
|
|
3410
|
+
|
|
2981
3411
|
new_file = self.to_autofinder_file(build_arg_contract_file)
|
|
2982
3412
|
self.context.file_to_contract[new_file] = self.context.file_to_contract[
|
|
2983
3413
|
build_arg_contract_file]
|
|
2984
3414
|
|
|
2985
3415
|
# add generated file to map attributes
|
|
2986
|
-
for map_attr in
|
|
3416
|
+
for map_attr in self.context.app.attr_class.all_map_attrs():
|
|
2987
3417
|
map_attr_value = getattr(self.context, map_attr)
|
|
2988
3418
|
if map_attr_value and build_arg_contract_file in map_attr_value:
|
|
2989
3419
|
map_attr_value[new_file] = map_attr_value[build_arg_contract_file]
|
|
@@ -3003,13 +3433,13 @@ class CertoraBuildGenerator:
|
|
|
3003
3433
|
for k, v in autofinder_remappings.items():
|
|
3004
3434
|
self.function_finder_file_remappings[Util.abs_posix_path(k)] = Util.abs_posix_path(v)
|
|
3005
3435
|
new_sdcs = self.collect_for_file(new_file, i, get_compiler_lang(build_arg_contract_file),
|
|
3006
|
-
Util.get_certora_sources_dir() / self.cwd_rel_in_sources,
|
|
3436
|
+
Util.get_certora_sources_dir() / self.context.cwd_rel_in_sources,
|
|
3007
3437
|
path_for_compiler_collector_file,
|
|
3008
3438
|
sdc_pre_finder,
|
|
3009
3439
|
fail_on_compilation_error=False,
|
|
3010
3440
|
reroute_main_path=True)
|
|
3011
3441
|
for new_sdc in new_sdcs:
|
|
3012
|
-
ret.append((added_function_finders, added_source_finders, new_sdc))
|
|
3442
|
+
ret.append((added_function_finders, added_source_finders, added_internal_function_harnesses, new_sdc))
|
|
3013
3443
|
|
|
3014
3444
|
except Util.SolcCompilationException as e:
|
|
3015
3445
|
print(f"Encountered an exception generating autofinder {new_file} ({e}), falling back to original "
|
|
@@ -3018,7 +3448,7 @@ class CertoraBuildGenerator:
|
|
|
3018
3448
|
f"falling back to the original file {Path(build_arg_contract_file).name}", exc_info=e)
|
|
3019
3449
|
# clean up mutation
|
|
3020
3450
|
self.function_finder_file_remappings = {}
|
|
3021
|
-
return [({}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders], False, False
|
|
3451
|
+
return [({}, {}, {}, sdc_pre_finder) for sdc_pre_finder in sdc_pre_finders], False, False
|
|
3022
3452
|
return ret, True, source_finders_gen_succeeded
|
|
3023
3453
|
|
|
3024
3454
|
def to_autofinder_file(self, contract_file: str) -> str:
|
|
@@ -3031,7 +3461,7 @@ class CertoraBuildGenerator:
|
|
|
3031
3461
|
contract_path = Util.abs_posix_path_obj(contract_file)
|
|
3032
3462
|
rel_directory = Path(os.path.relpath(contract_file, '.')).parent
|
|
3033
3463
|
contract_filename = contract_path.name
|
|
3034
|
-
new_path = Util.get_certora_sources_dir() / self.cwd_rel_in_sources / rel_directory / contract_filename
|
|
3464
|
+
new_path = Util.get_certora_sources_dir() / self.context.cwd_rel_in_sources / rel_directory / contract_filename
|
|
3035
3465
|
new_path.parent.mkdir(parents=True, exist_ok=True)
|
|
3036
3466
|
return str(new_path)
|
|
3037
3467
|
|
|
@@ -3041,7 +3471,7 @@ class CertoraBuildGenerator:
|
|
|
3041
3471
|
This assumes those paths can be related to cwd.
|
|
3042
3472
|
"""
|
|
3043
3473
|
rel_to_cwd_path = Path(os.path.relpath(path, '.'))
|
|
3044
|
-
new_path = Util.get_certora_sources_dir() / self.cwd_rel_in_sources / rel_to_cwd_path
|
|
3474
|
+
new_path = Util.get_certora_sources_dir() / self.context.cwd_rel_in_sources / rel_to_cwd_path
|
|
3045
3475
|
return str(new_path.absolute())
|
|
3046
3476
|
|
|
3047
3477
|
def handle_links(self) -> None:
|
|
@@ -3405,13 +3835,23 @@ class CertoraBuildGenerator:
|
|
|
3405
3835
|
raise Util.CertoraUserInputError(f"collect_sources: {path_to_file} does not exist cwd - {Path.cwd()}."
|
|
3406
3836
|
f"abs - {os.path.normpath(Path.cwd() / path_to_file)}")
|
|
3407
3837
|
|
|
3408
|
-
sources = set()
|
|
3409
|
-
|
|
3838
|
+
sources = set(sources_from_SDCs)
|
|
3839
|
+
if context.files:
|
|
3840
|
+
sources.update(Path(p) for p in context.files) # all files in "files" attribute are uploaded
|
|
3410
3841
|
sources |= certora_verify_generator.get_spec_files()
|
|
3411
3842
|
if Util.PACKAGE_FILE.exists():
|
|
3412
3843
|
add_to_sources(Util.PACKAGE_FILE)
|
|
3413
3844
|
if Util.REMAPPINGS_FILE.exists():
|
|
3414
3845
|
add_to_sources(Util.REMAPPINGS_FILE)
|
|
3846
|
+
foundry_toml = Util.find_nearest_foundry_toml()
|
|
3847
|
+
if foundry_toml:
|
|
3848
|
+
# if we find foundry.toml we add it to source tree and, if exists, the remappings.txt file from the
|
|
3849
|
+
# root directory
|
|
3850
|
+
foundry_root = foundry_toml.parent
|
|
3851
|
+
add_to_sources(foundry_root / Util.FOUNDRY_TOML_FILE)
|
|
3852
|
+
remappings_file_in_root = foundry_root / Util.REMAPPINGS_FILE
|
|
3853
|
+
if remappings_file_in_root.exists():
|
|
3854
|
+
add_to_sources(remappings_file_in_root)
|
|
3415
3855
|
if context.bytecode_jsons:
|
|
3416
3856
|
for file in context.bytecode_jsons:
|
|
3417
3857
|
add_to_sources(Path(file))
|
|
@@ -3437,7 +3877,21 @@ class CertoraBuildGenerator:
|
|
|
3437
3877
|
return sources
|
|
3438
3878
|
|
|
3439
3879
|
def __del__(self) -> None:
|
|
3440
|
-
|
|
3880
|
+
try:
|
|
3881
|
+
self.cleanup()
|
|
3882
|
+
except ImportError:
|
|
3883
|
+
# Avoiding Python interpreter shutdown exceptions which are safe to ignore
|
|
3884
|
+
pass
|
|
3885
|
+
|
|
3886
|
+
def dump_asts(self) -> None:
|
|
3887
|
+
asts_dump_file = Util.get_asts_file()
|
|
3888
|
+
filtered_asts = {k: v for k, v in self.asts.items() if not str(k).startswith(f"{Util.get_build_dir()}")}
|
|
3889
|
+
with asts_dump_file.open("w+") as output_file:
|
|
3890
|
+
try:
|
|
3891
|
+
json.dump(filtered_asts, output_file, indent=4, sort_keys=True)
|
|
3892
|
+
except Exception as e:
|
|
3893
|
+
ast_logger.debug(f"Couldn't dump ASTs to {asts_dump_file}", exc_info=e)
|
|
3894
|
+
raise
|
|
3441
3895
|
|
|
3442
3896
|
|
|
3443
3897
|
# make sure each source file exists and its path is in absolute format
|
|
@@ -3449,20 +3903,19 @@ def sources_to_abs(sources: Set[Path]) -> Set[Path]:
|
|
|
3449
3903
|
return result
|
|
3450
3904
|
|
|
3451
3905
|
|
|
3452
|
-
def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bool = False) ->
|
|
3906
|
+
def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bool = False) -> None:
|
|
3453
3907
|
"""
|
|
3454
3908
|
Copies files to .certora_sources
|
|
3455
|
-
@returns the cwd relative in sources
|
|
3456
3909
|
"""
|
|
3457
3910
|
sources = sources_to_abs(sources)
|
|
3458
|
-
cwd_rel_in_sources, common_path = CertoraBuildGenerator.get_cwd_rel_in_sources(sources)
|
|
3911
|
+
context.cwd_rel_in_sources, context.common_path = CertoraBuildGenerator.get_cwd_rel_in_sources(sources)
|
|
3459
3912
|
|
|
3460
3913
|
for source_path in sources:
|
|
3461
3914
|
is_dir = source_path.is_dir()
|
|
3462
3915
|
# copy file to the path of the file from the common root under the sources directory
|
|
3463
3916
|
|
|
3464
3917
|
# make sure directory exists
|
|
3465
|
-
target_path = Util.get_certora_sources_dir() / source_path.relative_to(common_path)
|
|
3918
|
+
target_path = Util.get_certora_sources_dir() / source_path.relative_to(context.common_path)
|
|
3466
3919
|
target_directory = target_path if is_dir else target_path.parent
|
|
3467
3920
|
try:
|
|
3468
3921
|
target_directory.mkdir(parents=True, exist_ok=True)
|
|
@@ -3477,7 +3930,7 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
|
|
|
3477
3930
|
|
|
3478
3931
|
try:
|
|
3479
3932
|
if overwrite:
|
|
3480
|
-
# expecting target path to exist.
|
|
3933
|
+
# expecting the target path to exist.
|
|
3481
3934
|
if target_path.exists():
|
|
3482
3935
|
build_logger.debug(f"Overwriting {target_path} by copying from {source_path}")
|
|
3483
3936
|
else:
|
|
@@ -3491,16 +3944,24 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
|
|
|
3491
3944
|
raise
|
|
3492
3945
|
|
|
3493
3946
|
# the empty file .cwd is written in the source tree to denote the current working directory
|
|
3494
|
-
cwd_file_path = Util.get_certora_sources_dir() / cwd_rel_in_sources / Util.CWD_FILE
|
|
3947
|
+
cwd_file_path = Util.get_certora_sources_dir() / context.cwd_rel_in_sources / Util.CWD_FILE
|
|
3495
3948
|
cwd_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
3496
3949
|
cwd_file_path.touch()
|
|
3497
3950
|
|
|
3498
|
-
#
|
|
3951
|
+
# copy context.forge_remappings to remappings.txt in the source tree, overwriting any existing remappings.txt
|
|
3952
|
+
forge_remappings = getattr(context, 'forge_remappings', None)
|
|
3953
|
+
if forge_remappings:
|
|
3954
|
+
remappings_file_path = Util.get_certora_sources_dir() / context.cwd_rel_in_sources / Util.REMAPPINGS_FILE
|
|
3955
|
+
with remappings_file_path.open("w") as remap_file:
|
|
3956
|
+
for remap in context.forge_remappings:
|
|
3957
|
+
remap_file.write(remap + "\n")
|
|
3958
|
+
|
|
3959
|
+
# the empty file .project_directory is written in the source tree to denote the project directory
|
|
3499
3960
|
rust_proj_dir = getattr(context, 'rust_project_directory', None)
|
|
3500
3961
|
if rust_proj_dir:
|
|
3501
3962
|
proj_dir_parent_relative = os.path.relpath(rust_proj_dir, os.getcwd())
|
|
3502
3963
|
assert Path(rust_proj_dir).is_dir(), f"build_source_tree: not a directory {rust_proj_dir}"
|
|
3503
|
-
proj_dir_parent_file = (Util.get_certora_sources_dir() / cwd_rel_in_sources / proj_dir_parent_relative /
|
|
3964
|
+
proj_dir_parent_file = (Util.get_certora_sources_dir() / context.cwd_rel_in_sources / proj_dir_parent_relative /
|
|
3504
3965
|
Util.PROJECT_DIR_FILE)
|
|
3505
3966
|
proj_dir_parent_file.parent.mkdir(parents=True, exist_ok=True)
|
|
3506
3967
|
proj_dir_parent_file.touch()
|
|
@@ -3516,10 +3977,8 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
|
|
|
3516
3977
|
build_logger.debug("Couldn't copy repro conf to certora sources.", exc_info=e)
|
|
3517
3978
|
raise
|
|
3518
3979
|
|
|
3519
|
-
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
|
|
3980
|
+
def build_from_scratch(context: CertoraContext,
|
|
3981
|
+
certora_build_generator: CertoraBuildGenerator,
|
|
3523
3982
|
certora_verify_generator: CertoraVerifyGenerator,
|
|
3524
3983
|
build_cache_enabled: bool) -> CachedFiles:
|
|
3525
3984
|
"""
|
|
@@ -3545,10 +4004,17 @@ def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
|
|
|
3545
4004
|
may_store_in_build_cache = True
|
|
3546
4005
|
absolute_sources_dir = Util.get_certora_sources_dir().absolute()
|
|
3547
4006
|
for sdc in certora_build_generator.SDCs.values():
|
|
4007
|
+
|
|
4008
|
+
# add to cache also source files that were found in the SDCs (e.g., storage extensions)
|
|
4009
|
+
paths_set = sdc.all_contract_files
|
|
4010
|
+
for f in context.files:
|
|
4011
|
+
path = f.split(':')[0] # `f` is either 'path/to/file.sol' or 'path/to/file.sol:ContractName'
|
|
4012
|
+
paths_set.add(Path(path).absolute())
|
|
4013
|
+
|
|
3548
4014
|
# the contract files in SDCs are relative to .certora_sources. Which isn't good for us here.
|
|
3549
4015
|
# Need to be relative to original paths
|
|
3550
|
-
for f in
|
|
3551
|
-
if is_relative_to(
|
|
4016
|
+
for f in paths_set:
|
|
4017
|
+
if f.is_relative_to(absolute_sources_dir):
|
|
3552
4018
|
rel_f = f.relative_to(absolute_sources_dir)
|
|
3553
4019
|
else:
|
|
3554
4020
|
# may be an absolute path already outside .certora_sources, so we can keep it.
|
|
@@ -3583,61 +4049,84 @@ def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
|
|
|
3583
4049
|
|
|
3584
4050
|
def build_from_cache_or_scratch(context: CertoraContext,
|
|
3585
4051
|
certora_build_generator: CertoraBuildGenerator,
|
|
3586
|
-
certora_verify_generator: CertoraVerifyGenerator
|
|
3587
|
-
|
|
3588
|
-
-> Tuple[bool, bool, CachedFiles]:
|
|
4052
|
+
certora_verify_generator: CertoraVerifyGenerator) \
|
|
4053
|
+
-> Tuple[bool, CachedFiles]:
|
|
3589
4054
|
"""
|
|
3590
4055
|
Builds either from cache (fast path) or from scratch (slow path)
|
|
3591
|
-
@returns 1st tuple element whether
|
|
3592
|
-
@returns 2nd tuple element
|
|
3593
|
-
@returns 3rd tuple element the artifacts of the build to potentially be cached
|
|
4056
|
+
@returns 1st tuple element whether the cache should be saved
|
|
4057
|
+
@returns 2nd tuple element the artifacts of the build to potentially be cached
|
|
3594
4058
|
"""
|
|
3595
|
-
cache_hit = False
|
|
3596
4059
|
cached_files: Optional[CachedFiles] = None
|
|
3597
4060
|
|
|
3598
4061
|
if not context.build_cache:
|
|
3599
|
-
cached_files = build_from_scratch(certora_build_generator,
|
|
4062
|
+
cached_files = build_from_scratch(context, certora_build_generator,
|
|
3600
4063
|
certora_verify_generator,
|
|
3601
4064
|
False)
|
|
3602
|
-
return
|
|
4065
|
+
return False, cached_files
|
|
3603
4066
|
|
|
3604
|
-
build_cache_applicable =
|
|
4067
|
+
build_cache_applicable = CertoraBuildCacheManager.cache_is_applicable(context)
|
|
3605
4068
|
|
|
3606
4069
|
if not build_cache_applicable:
|
|
3607
|
-
build_cache_disabling_options =
|
|
4070
|
+
build_cache_disabling_options = CertoraBuildCacheManager.cache_disabling_options(context)
|
|
3608
4071
|
build_logger.warning("Requested to enable the build cache, but the build cache is not applicable "
|
|
3609
4072
|
f"to this run because of the given options: {build_cache_disabling_options}")
|
|
3610
|
-
cached_files = build_from_scratch(certora_build_generator,
|
|
4073
|
+
cached_files = build_from_scratch(context, certora_build_generator,
|
|
3611
4074
|
certora_verify_generator,
|
|
3612
4075
|
False)
|
|
3613
|
-
return
|
|
3614
|
-
|
|
3615
|
-
cached_files =
|
|
3616
|
-
|
|
3617
|
-
if
|
|
3618
|
-
#
|
|
3619
|
-
|
|
3620
|
-
# write build_output_props file
|
|
3621
|
-
shutil.copyfile(cached_files.build_output_props_file, Util.get_built_output_props_file())
|
|
3622
|
-
# write build_cache indicator file
|
|
3623
|
-
with open(Util.get_build_cache_indicator_file(), "w+") as indicator_handle:
|
|
3624
|
-
json.dump({"build_cache_hit": True}, indicator_handle)
|
|
3625
|
-
# write in sources all the additional paths found
|
|
3626
|
-
for p in cached_files.path_with_additional_included_files.glob("*"):
|
|
3627
|
-
if p.is_dir():
|
|
3628
|
-
Util.safe_copy_folder(p,
|
|
3629
|
-
Util.get_certora_sources_dir() / p.name,
|
|
3630
|
-
shutil.ignore_patterns())
|
|
3631
|
-
else:
|
|
3632
|
-
shutil.copyfile(p, Util.get_certora_sources_dir() / p.name)
|
|
3633
|
-
cache_hit = True
|
|
3634
|
-
else:
|
|
3635
|
-
# rebuild
|
|
3636
|
-
cached_files = build_from_scratch(certora_build_generator,
|
|
4076
|
+
return False, cached_files
|
|
4077
|
+
|
|
4078
|
+
cached_files = CertoraBuildCacheManager.build_from_cache(context)
|
|
4079
|
+
|
|
4080
|
+
if not cached_files:
|
|
4081
|
+
# No match, rebuild
|
|
4082
|
+
cached_files = build_from_scratch(context, certora_build_generator,
|
|
3637
4083
|
certora_verify_generator,
|
|
3638
4084
|
True)
|
|
4085
|
+
return True, cached_files
|
|
4086
|
+
|
|
4087
|
+
# Cache hit!
|
|
4088
|
+
|
|
4089
|
+
# write to .certora_build.json
|
|
4090
|
+
# This must happen _before_ searching for new internal function calls - the typechecker needs this file.
|
|
4091
|
+
shutil.copyfile(cached_files.certora_build_file, Util.get_certora_build_file())
|
|
4092
|
+
|
|
4093
|
+
# Check whether there are any new internal function calls in the spec file - if there are then we need to build
|
|
4094
|
+
# from scratch in order to create the relevant harness function.
|
|
4095
|
+
if context.verify and not context.disallow_internal_function_calls:
|
|
4096
|
+
with tempfile.NamedTemporaryFile("r", dir=Util.get_build_dir()) as tmp_file:
|
|
4097
|
+
internal_calls = []
|
|
4098
|
+
try:
|
|
4099
|
+
Ctx.run_local_spec_check(True, context, ["-listCalls", tmp_file.name], print_errors=False)
|
|
4100
|
+
output = tmp_file.read().strip()
|
|
4101
|
+
if output:
|
|
4102
|
+
internal_calls = output.split("\n")
|
|
4103
|
+
except Exception as e:
|
|
4104
|
+
instrumentation_logger.debug(f"Failed to get calls from spec\n{e}")
|
|
4105
|
+
|
|
4106
|
+
if internal_calls:
|
|
4107
|
+
build_logger.info("Found new internal calls in the spec file, need to recompile anyway")
|
|
4108
|
+
# There are new internal calls in the spec, we need to rebuild in order to generate the
|
|
4109
|
+
# external harness functions for them.
|
|
4110
|
+
cached_files = build_from_scratch(context, certora_build_generator,
|
|
4111
|
+
certora_verify_generator,
|
|
4112
|
+
True)
|
|
4113
|
+
return True, cached_files
|
|
4114
|
+
|
|
4115
|
+
# write build_output_props file
|
|
4116
|
+
shutil.copyfile(cached_files.build_output_props_file, Util.get_built_output_props_file())
|
|
4117
|
+
# write build_cache indicator file
|
|
4118
|
+
with open(Util.get_build_cache_indicator_file(), "w+") as indicator_handle:
|
|
4119
|
+
json.dump({"build_cache_hit": True}, indicator_handle)
|
|
4120
|
+
# write in sources all the additional paths found
|
|
4121
|
+
for p in cached_files.path_with_additional_included_files.glob("*"):
|
|
4122
|
+
if p.is_dir():
|
|
4123
|
+
Util.safe_copy_folder(p,
|
|
4124
|
+
Util.get_certora_sources_dir() / p.name,
|
|
4125
|
+
shutil.ignore_patterns())
|
|
4126
|
+
else:
|
|
4127
|
+
shutil.copyfile(p, Util.get_certora_sources_dir() / p.name)
|
|
3639
4128
|
|
|
3640
|
-
return
|
|
4129
|
+
return False, cached_files
|
|
3641
4130
|
|
|
3642
4131
|
|
|
3643
4132
|
def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> None:
|
|
@@ -3651,7 +4140,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
|
|
|
3651
4140
|
|
|
3652
4141
|
try:
|
|
3653
4142
|
input_config = InputConfig(context)
|
|
3654
|
-
|
|
4143
|
+
context.main_cache_key = CertoraBuildCacheManager.get_main_cache_key(context)
|
|
3655
4144
|
# Create generators
|
|
3656
4145
|
certora_build_generator = CertoraBuildGenerator(input_config, context)
|
|
3657
4146
|
|
|
@@ -3661,19 +4150,17 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
|
|
|
3661
4150
|
|
|
3662
4151
|
# Start by syntax checking, if we're in the right mode
|
|
3663
4152
|
if Cv.mode_has_spec_file(context) and not context.build_only and not ignore_spec_syntax_check:
|
|
3664
|
-
|
|
3665
|
-
if attr:
|
|
3666
|
-
build_logger.warning(
|
|
3667
|
-
"Local checks of CVL specification files disabled. It is recommended to enable the checks.")
|
|
3668
|
-
else:
|
|
4153
|
+
if Ctx.should_run_local_speck_check(context):
|
|
3669
4154
|
Ctx.run_local_spec_check(False, context)
|
|
3670
4155
|
|
|
3671
|
-
|
|
4156
|
+
should_save_cache, cached_files = build_from_cache_or_scratch(context,
|
|
4157
|
+
certora_build_generator,
|
|
4158
|
+
certora_verify_generator)
|
|
3672
4159
|
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
4160
|
+
# avoid running the same test over and over again for each split run, context.split_rules is true only for
|
|
4161
|
+
# the first run and is set to [] for split runs
|
|
4162
|
+
if Ctx.should_run_local_speck_check(context) and context.split_rules:
|
|
4163
|
+
Ctx.run_local_spec_check(True, context)
|
|
3677
4164
|
|
|
3678
4165
|
# .certora_verify.json is always constructed even if build cache is enabled
|
|
3679
4166
|
# Sources construction should only happen when rebuilding
|
|
@@ -3687,8 +4174,8 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
|
|
|
3687
4174
|
build_logger.debug("build_source_tree failed", exc_info=e)
|
|
3688
4175
|
|
|
3689
4176
|
# save in build cache
|
|
3690
|
-
if
|
|
3691
|
-
|
|
4177
|
+
if should_save_cache and cached_files.may_store_in_build_cache:
|
|
4178
|
+
CertoraBuildCacheManager.save_build_cache(context, cached_files)
|
|
3692
4179
|
|
|
3693
4180
|
certora_verify_generator.update_certora_verify_struct(True)
|
|
3694
4181
|
certora_verify_generator.dump() # second dump with properly rooted specs
|