certora-cli-beta-mirror 8.6.2__py3-none-any.whl → 8.7.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/CompilerCollector.py +9 -8
- certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +126 -73
- certora_cli/CertoraProver/castingInstrumenter.py +23 -16
- certora_cli/CertoraProver/certoraApp.py +8 -0
- certora_cli/CertoraProver/certoraBuild.py +106 -32
- certora_cli/CertoraProver/certoraCloudIO.py +2 -27
- certora_cli/CertoraProver/certoraCollectRunMetadata.py +12 -5
- certora_cli/CertoraProver/certoraContextAttributes.py +31 -1
- certora_cli/CertoraProver/certoraContextValidator.py +8 -1
- certora_cli/CertoraProver/certoraContractFuncs.py +1 -1
- certora_cli/CertoraProver/certoraOffsetConverter.py +54 -0
- certora_cli/CertoraProver/certoraSourceFinders.py +7 -7
- certora_cli/CertoraProver/uncheckedOverflowInstrumenter.py +152 -0
- certora_cli/Shared/certoraValidateFuncs.py +31 -0
- {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/METADATA +2 -2
- {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/RECORD +23 -21
- certora_jars/ASTExtraction.jar +0 -0
- certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
- certora_jars/Typechecker.jar +0 -0
- {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/LICENSE +0 -0
- {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/WHEEL +0 -0
- {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/entry_points.txt +0 -0
- {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/top_level.txt +0 -0
|
@@ -36,15 +36,17 @@ from CertoraProver.certoraBuildDataClasses import CONTRACTS, ImmutableReference,
|
|
|
36
36
|
Instrumentation, InsertBefore, InsertAfter, UnspecializedSourceFinder, instrumentation_logger
|
|
37
37
|
from CertoraProver.certoraCompilerParameters import SolcParameters
|
|
38
38
|
from CertoraProver.certoraContractFuncs import Func, InternalFunc, STATEMUT, SourceBytes, VyperMetadata
|
|
39
|
+
from CertoraProver.certoraOffsetConverter import generate_offset_converters
|
|
39
40
|
from CertoraProver.certoraSourceFinders import add_source_finders
|
|
40
41
|
from CertoraProver.certoraVerifyGenerator import CertoraVerifyGenerator
|
|
42
|
+
from CertoraProver.uncheckedOverflowInstrumenter import generate_overflow_instrumentation, add_instrumentation
|
|
41
43
|
|
|
42
44
|
scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
|
|
43
45
|
sys.path.insert(0, str(scripts_dir_path))
|
|
44
46
|
from CertoraProver.Compiler.CompilerCollector import CompilerLang, CompilerCollector
|
|
45
47
|
from CertoraProver.Compiler.CompilerCollectorSol import CompilerCollectorSol, CompilerLangSol
|
|
46
48
|
from CertoraProver.Compiler.CompilerCollectorYul import CompilerLangYul, CompilerCollectorYul
|
|
47
|
-
from CertoraProver.Compiler.CompilerCollectorVy import CompilerLangVy
|
|
49
|
+
from CertoraProver.Compiler.CompilerCollectorVy import CompilerLangVy, CompilerCollectorVy
|
|
48
50
|
from CertoraProver.Compiler.CompilerCollectorFactory import CompilerCollectorFactory, \
|
|
49
51
|
get_relevant_compiler, get_compiler_lang
|
|
50
52
|
from CertoraProver.certoraNodeFilters import NodeFilters as Nf
|
|
@@ -444,7 +446,7 @@ def generate_inline_finder(f: Func, internal_id: int, sym: int, compiler_collect
|
|
|
444
446
|
return finder[1]
|
|
445
447
|
|
|
446
448
|
|
|
447
|
-
def convert_pathname_to_posix(json_dict: Dict[str, Any], entry: str,
|
|
449
|
+
def convert_pathname_to_posix(json_dict: Dict[str, Any], entry: str, compiler_collector: CompilerCollector) -> None:
|
|
448
450
|
"""
|
|
449
451
|
assuming the values kept in the entry [entry] inside [json_dict] are path names
|
|
450
452
|
:param json_dict: dict to iterate on
|
|
@@ -453,7 +455,7 @@ def convert_pathname_to_posix(json_dict: Dict[str, Any], entry: str, smart_contr
|
|
|
453
455
|
if entry in json_dict:
|
|
454
456
|
json_dict_posix_paths = {}
|
|
455
457
|
for file_path in json_dict[entry]:
|
|
456
|
-
path_obj = Path(
|
|
458
|
+
path_obj = Path(compiler_collector.normalize_file_compiler_path_name(file_path))
|
|
457
459
|
if path_obj.is_file():
|
|
458
460
|
json_dict_posix_paths[path_obj.as_posix()] = json_dict[entry][file_path]
|
|
459
461
|
else:
|
|
@@ -461,7 +463,7 @@ def convert_pathname_to_posix(json_dict: Dict[str, Any], entry: str, smart_contr
|
|
|
461
463
|
# protecting against long strings
|
|
462
464
|
if len(json_dict_str) > 200:
|
|
463
465
|
json_dict_str = json_dict_str[:200] + '...'
|
|
464
|
-
fatal_error(compiler_logger, f"The path of the source file {file_path} "
|
|
466
|
+
fatal_error(compiler_logger, f"The path of the source file {file_path} ({path_obj})"
|
|
465
467
|
f"in the standard json file does not exist!\n{json_dict_str} ")
|
|
466
468
|
json_dict[entry] = json_dict_posix_paths
|
|
467
469
|
|
|
@@ -1152,7 +1154,12 @@ class CertoraBuildGenerator:
|
|
|
1152
1154
|
if file_abs_path.suffix == VY:
|
|
1153
1155
|
smart_contract_lang: CompilerLang = CompilerLangVy()
|
|
1154
1156
|
sdc_name = self.file_to_sdc_name[Path(contract_file).absolute()]
|
|
1155
|
-
|
|
1157
|
+
"""
|
|
1158
|
+
maintain backward-compatibility,
|
|
1159
|
+
but in reality this equiavlence checker (equivChecker.py) should be removed
|
|
1160
|
+
"""
|
|
1161
|
+
dummyCompilerCollectorVy = CompilerCollectorVy((0, 3, 10))
|
|
1162
|
+
standard_json_data = self.get_standard_json_data(sdc_name, smart_contract_lang, dummyCompilerCollectorVy)
|
|
1156
1163
|
abi = standard_json_data[CONTRACTS][str(Path(contract_file).absolute())][contract_name]['abi']
|
|
1157
1164
|
ast_logger.debug(f"abi is: \n{abi}")
|
|
1158
1165
|
for f in filter(lambda x: self.is_imported_abi_entry(x), abi):
|
|
@@ -1163,11 +1170,14 @@ class CertoraBuildGenerator:
|
|
|
1163
1170
|
elif file_abs_path.suffix == SOL:
|
|
1164
1171
|
smart_contract_lang = CompilerLangSol()
|
|
1165
1172
|
sdc_name = self.file_to_sdc_name[file_abs_path]
|
|
1166
|
-
compilation_path =
|
|
1167
|
-
|
|
1173
|
+
compilation_path = Path(Util.abs_posix_path(contract_file))
|
|
1174
|
+
compilerCollectorSol = self.compiler_coll_factory.get_compiler_collector(compilation_path)
|
|
1175
|
+
standard_json_data = self.get_standard_json_data(sdc_name, smart_contract_lang, compilerCollectorSol)
|
|
1168
1176
|
storage_data = smart_contract_lang.collect_storage_layout_info(str(file_abs_path), compilation_path,
|
|
1169
1177
|
solc, None,
|
|
1170
|
-
standard_json_data
|
|
1178
|
+
standard_json_data,
|
|
1179
|
+
{}, # dummy ast, not used in solc
|
|
1180
|
+
str(contract_file))
|
|
1171
1181
|
abi = storage_data[CONTRACTS][str(file_abs_path)][contract_name]["abi"]
|
|
1172
1182
|
ast_logger.debug(f"abi is: \n{abi}")
|
|
1173
1183
|
for f in filter(lambda x: self.is_imported_abi_entry(x), abi):
|
|
@@ -1270,7 +1280,7 @@ class CertoraBuildGenerator:
|
|
|
1270
1280
|
else:
|
|
1271
1281
|
raise RuntimeError(f"failed to get contract bytes for {contract_name} in file {contract_file}")
|
|
1272
1282
|
|
|
1273
|
-
def get_standard_json_data(self, sdc_name: str, smart_contract_lang: CompilerLang) -> Dict[str, Any]:
|
|
1283
|
+
def get_standard_json_data(self, sdc_name: str, smart_contract_lang: CompilerLang, compiler_collector : CompilerCollector) -> Dict[str, Any]:
|
|
1274
1284
|
json_file = smart_contract_lang.compilation_output_path(sdc_name)
|
|
1275
1285
|
process_logger.debug(f"reading standard json data from {json_file}")
|
|
1276
1286
|
# jira CER_927 - under windows it happens the solc generate wrong
|
|
@@ -1278,7 +1288,7 @@ class CertoraBuildGenerator:
|
|
|
1278
1288
|
json_dict = Util.read_json_file(json_file)
|
|
1279
1289
|
entries = [CONTRACTS, "sources"]
|
|
1280
1290
|
for ent in entries:
|
|
1281
|
-
convert_pathname_to_posix(json_dict, ent,
|
|
1291
|
+
convert_pathname_to_posix(json_dict, ent, compiler_collector)
|
|
1282
1292
|
return json_dict
|
|
1283
1293
|
|
|
1284
1294
|
def cleanup_compiler_outputs(self, sdc_name: str, smart_contract_lang: CompilerLang) -> None:
|
|
@@ -1570,6 +1580,9 @@ class CertoraBuildGenerator:
|
|
|
1570
1580
|
"""
|
|
1571
1581
|
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
|
|
1572
1582
|
compiler_collector_lang = compiler_collector.smart_contract_lang
|
|
1583
|
+
main_contract_for_output_selection = "*"
|
|
1584
|
+
search_paths_arr = None
|
|
1585
|
+
additional_asts = None
|
|
1573
1586
|
if compiler_collector_lang == CompilerLangSol() or compiler_collector_lang == CompilerLangYul():
|
|
1574
1587
|
sources_dict = {str(solc_json_contract_key): {
|
|
1575
1588
|
"urls": [str(contract_file_posix_abs)]}} # type: Dict[str, Dict[str, Any]]
|
|
@@ -1578,28 +1591,59 @@ class CertoraBuildGenerator:
|
|
|
1578
1591
|
"evm.bytecode.functionDebugData"]
|
|
1579
1592
|
ast_selection = ["id", "ast"]
|
|
1580
1593
|
elif compiler_collector_lang == CompilerLangVy():
|
|
1594
|
+
main_contract_for_output_selection = "*"
|
|
1595
|
+
sources_dict = {}
|
|
1581
1596
|
with open(contract_file_posix_abs) as f:
|
|
1582
|
-
|
|
1583
|
-
|
|
1597
|
+
if self.context.vyper_custom_std_json_in_map and contract_file_as_provided in self.context.vyper_custom_std_json_in_map:
|
|
1598
|
+
"""
|
|
1599
|
+
If we're given a custom standard_json, we'll take from it the sources and
|
|
1600
|
+
the search paths.
|
|
1601
|
+
In particular, we will separate between the interfaces (*.vyi) and regular files,
|
|
1602
|
+
so that we could get the ASTs for the regular files (as those are compilation units).
|
|
1603
|
+
This was tested ONLY on a single contract
|
|
1604
|
+
(ask Shelly)
|
|
1605
|
+
and may require refinement
|
|
1606
|
+
if we get more projects.
|
|
1607
|
+
"""
|
|
1608
|
+
vyper_custom_std_json_in = self.context.vyper_custom_std_json_in_map[contract_file_as_provided]
|
|
1609
|
+
with open(vyper_custom_std_json_in) as custom:
|
|
1610
|
+
custom_json = json.load(custom)
|
|
1611
|
+
sources_dict = custom_json.get("sources", None)
|
|
1612
|
+
search_paths_arr = custom_json.get("settings", {}).get("search_paths", None)
|
|
1613
|
+
additional_asts = [x for x, _ in sources_dict.items() if x.endswith(".vy")]
|
|
1614
|
+
if not sources_dict:
|
|
1615
|
+
contents = f.read()
|
|
1616
|
+
sources_dict = {str(contract_file_posix_abs): {"content": contents}}
|
|
1584
1617
|
output_selection = ["abi", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers"]
|
|
1585
1618
|
if compiler_collector.compiler_version >= (0, 4, 4):
|
|
1586
1619
|
output_selection += ["metadata", "evm.deployedBytecode.symbolMap"]
|
|
1587
1620
|
ast_selection = ["ast"]
|
|
1621
|
+
else:
|
|
1622
|
+
# "non-compilable" language so no need to deal with it
|
|
1623
|
+
fatal_error(compiler_logger, "Expected only Solidity and Vyper as "
|
|
1624
|
+
"languages for which we build a standard-json")
|
|
1588
1625
|
|
|
1589
1626
|
settings_dict: Dict[str, Any] = \
|
|
1590
1627
|
{
|
|
1591
1628
|
"remappings": remappings,
|
|
1592
1629
|
"outputSelection": {
|
|
1593
|
-
|
|
1630
|
+
main_contract_for_output_selection: {
|
|
1594
1631
|
"*": output_selection,
|
|
1595
1632
|
"": ast_selection
|
|
1596
1633
|
}
|
|
1597
1634
|
}
|
|
1598
1635
|
}
|
|
1636
|
+
if search_paths_arr:
|
|
1637
|
+
settings_dict["search_paths"] = search_paths_arr
|
|
1638
|
+
if additional_asts:
|
|
1639
|
+
for p in additional_asts:
|
|
1640
|
+
if p != main_contract_for_output_selection:
|
|
1641
|
+
settings_dict["outputSelection"][p] = {"": ["ast"]}
|
|
1599
1642
|
|
|
1600
1643
|
self._fill_codegen_related_options(Path(contract_file_as_provided), settings_dict, compiler_collector)
|
|
1601
1644
|
|
|
1602
1645
|
result_dict = {"language": compiler_collector_lang.name, "sources": sources_dict, "settings": settings_dict}
|
|
1646
|
+
|
|
1603
1647
|
# debug_print("Standard json input")
|
|
1604
1648
|
# debug_print(json.dumps(result_dict, indent=4))
|
|
1605
1649
|
return result_dict
|
|
@@ -1881,7 +1925,7 @@ class CertoraBuildGenerator:
|
|
|
1881
1925
|
compiler_input=standard_json_input)
|
|
1882
1926
|
|
|
1883
1927
|
compiler_logger.debug(f"Collecting standard json: {collect_cmd}")
|
|
1884
|
-
standard_json_data = self.get_standard_json_data(sdc_name, smart_contract_lang)
|
|
1928
|
+
standard_json_data = self.get_standard_json_data(sdc_name, smart_contract_lang, compiler_collector)
|
|
1885
1929
|
|
|
1886
1930
|
for error in standard_json_data.get("errors", []):
|
|
1887
1931
|
# is an error not a warning
|
|
@@ -1890,8 +1934,10 @@ class CertoraBuildGenerator:
|
|
|
1890
1934
|
# 6275 the error code of solc compiler for missing file
|
|
1891
1935
|
if 'errorCode' in error and error['errorCode'] == '6275':
|
|
1892
1936
|
print_package_file_note()
|
|
1937
|
+
|
|
1938
|
+
error_msg = error.get("formattedMessage", error.get("message", "[no msg]"))
|
|
1893
1939
|
friendly_message = f"{compiler_ver_to_run} had an error:\n" \
|
|
1894
|
-
f"{
|
|
1940
|
+
f"{error_msg}"
|
|
1895
1941
|
if fail_on_compilation_error:
|
|
1896
1942
|
raise Util.CertoraUserInputError(friendly_message)
|
|
1897
1943
|
else:
|
|
@@ -1900,16 +1946,20 @@ class CertoraBuildGenerator:
|
|
|
1900
1946
|
raise Util.SolcCompilationException(friendly_message)
|
|
1901
1947
|
|
|
1902
1948
|
# load data
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
standard_json_data) # Note we collected for just ONE file
|
|
1949
|
+
# In vyper, we first need the ASTs. Then collect_storage_layout_info will add
|
|
1950
|
+
# storage layout keys for vyper and do nothing for solidity,
|
|
1951
|
+
data = standard_json_data
|
|
1907
1952
|
self.check_for_errors_and_warnings(data, fail_on_compilation_error)
|
|
1908
1953
|
if smart_contract_lang.supports_ast_output:
|
|
1909
1954
|
self.collect_asts(build_arg_contract_file, data["sources"])
|
|
1955
|
+
data = \
|
|
1956
|
+
smart_contract_lang.collect_storage_layout_info(file_abs_path, compilation_path, compiler_ver_to_run,
|
|
1957
|
+
compiler_collector.compiler_version,
|
|
1958
|
+
data, self.asts.get(build_arg_contract_file, {}),
|
|
1959
|
+
build_arg_contract_file) # Note we collected for just ONE file
|
|
1910
1960
|
|
|
1911
1961
|
contracts_with_libraries = {}
|
|
1912
|
-
file_compiler_path =
|
|
1962
|
+
file_compiler_path = compiler_collector.normalize_file_compiler_path_name(file_abs_path)
|
|
1913
1963
|
|
|
1914
1964
|
# But apparently this heavily depends on the Solidity AST format anyway
|
|
1915
1965
|
|
|
@@ -2263,15 +2313,10 @@ class CertoraBuildGenerator:
|
|
|
2263
2313
|
vyper_metadata.venom_via_stack = metadata_func_info['venom_via_stack']
|
|
2264
2314
|
if metadata_func_info.get('venom_return_via_stack'):
|
|
2265
2315
|
vyper_metadata.venom_return_via_stack = metadata_func_info['venom_return_via_stack']
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
if len(matches) == 0:
|
|
2269
|
-
build_logger.warning(f"Could not find symbol map entry for {func_name} probably was inlined")
|
|
2316
|
+
symbol_map_name = metadata_func_info["_ir_identifier"] + "_runtime"
|
|
2317
|
+
if symbol_map_name not in symbol_map:
|
|
2270
2318
|
continue
|
|
2271
|
-
|
|
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]]
|
|
2319
|
+
vyper_metadata.runtime_start_pc = symbol_map[symbol_map_name]
|
|
2275
2320
|
internal_func.vyper_metadata = vyper_metadata
|
|
2276
2321
|
|
|
2277
2322
|
def get_contract_in_sdc(self,
|
|
@@ -3339,14 +3384,35 @@ class CertoraBuildGenerator:
|
|
|
3339
3384
|
else:
|
|
3340
3385
|
added_source_finders = {}
|
|
3341
3386
|
|
|
3387
|
+
offset_converters = generate_offset_converters(sdc_pre_finder)
|
|
3388
|
+
|
|
3342
3389
|
if self.context.safe_casting_builtin:
|
|
3343
3390
|
try:
|
|
3344
|
-
casting_instrumentations, casting_types = generate_casting_instrumentation(self.asts, build_arg_contract_file, sdc_pre_finder)
|
|
3391
|
+
casting_instrumentations, casting_types = generate_casting_instrumentation(self.asts, build_arg_contract_file, sdc_pre_finder, offset_converters)
|
|
3345
3392
|
except Exception as e:
|
|
3346
|
-
instrumentation_logger.warning(
|
|
3347
|
-
f"Computing casting instrumentation failed for {build_arg_contract_file}: {e}", exc_info=True)
|
|
3348
3393
|
casting_instrumentations, casting_types = {}, {}
|
|
3349
|
-
|
|
3394
|
+
instrumentation_logger.warning(f"Computing casting instrumentation failed for {build_arg_contract_file}: {e}", exc_info=True)
|
|
3395
|
+
else:
|
|
3396
|
+
casting_instrumentations, casting_types = {}, {}
|
|
3397
|
+
|
|
3398
|
+
if self.context.unchecked_overflow_builtin:
|
|
3399
|
+
try:
|
|
3400
|
+
overflow_instrumentations, op_funcs = generate_overflow_instrumentation(self.asts, build_arg_contract_file, sdc_pre_finder, offset_converters)
|
|
3401
|
+
except Exception as e:
|
|
3402
|
+
overflow_instrumentations, op_funcs = {}, {}
|
|
3403
|
+
instrumentation_logger.warning(
|
|
3404
|
+
f"Computing overflow instrumenstation failed for {build_arg_contract_file}: {e}", exc_info=True)
|
|
3405
|
+
else:
|
|
3406
|
+
overflow_instrumentations, op_funcs = {}, {}
|
|
3407
|
+
|
|
3408
|
+
for file_name, inst_dict in casting_instrumentations.items():
|
|
3409
|
+
if file_name not in overflow_instrumentations:
|
|
3410
|
+
overflow_instrumentations[file_name] = dict()
|
|
3411
|
+
d = overflow_instrumentations[file_name]
|
|
3412
|
+
for offset, inst in inst_dict.items():
|
|
3413
|
+
add_instrumentation(d, offset, inst)
|
|
3414
|
+
|
|
3415
|
+
instr = CertoraBuildGenerator.merge_dicts_instrumentation(instr, overflow_instrumentations)
|
|
3350
3416
|
|
|
3351
3417
|
abs_build_arg_contract_file = Util.abs_posix_path(build_arg_contract_file)
|
|
3352
3418
|
if abs_build_arg_contract_file not in instr:
|
|
@@ -3409,6 +3475,13 @@ class CertoraBuildGenerator:
|
|
|
3409
3475
|
output.write(bytes(f, "utf8"))
|
|
3410
3476
|
output.write(bytes("}\n", "utf8"))
|
|
3411
3477
|
|
|
3478
|
+
library_name, funcs = op_funcs.get(contract_file, ("", list()))
|
|
3479
|
+
if len(funcs) > 0:
|
|
3480
|
+
output.write(bytes(f"\nlibrary {library_name}" + "{\n", "utf8"))
|
|
3481
|
+
for f in funcs:
|
|
3482
|
+
output.write(bytes(f, "utf8"))
|
|
3483
|
+
output.write(bytes("}\n", "utf8"))
|
|
3484
|
+
|
|
3412
3485
|
new_file = self.to_autofinder_file(build_arg_contract_file)
|
|
3413
3486
|
self.context.file_to_contract[new_file] = self.context.file_to_contract[
|
|
3414
3487
|
build_arg_contract_file]
|
|
@@ -3429,6 +3502,7 @@ class CertoraBuildGenerator:
|
|
|
3429
3502
|
f"Compiling {orig_file_name} to expose internal function information and local variables...")
|
|
3430
3503
|
else:
|
|
3431
3504
|
Util.print_progress_message(f"Compiling {orig_file_name} to expose internal function information...")
|
|
3505
|
+
|
|
3432
3506
|
# record what aliases we have created (for the purposes of type canonicalization, the generated autofinder
|
|
3433
3507
|
# is an alias of the original file)
|
|
3434
3508
|
for k, v in autofinder_remappings.items():
|
|
@@ -69,18 +69,6 @@ Response = requests.models.Response
|
|
|
69
69
|
|
|
70
70
|
FEATURES_REPORT_FILE = Path("featuresReport.json")
|
|
71
71
|
|
|
72
|
-
class EcoEnum(Util.NoValEnum):
|
|
73
|
-
EVM = Util.auto()
|
|
74
|
-
SOROBAN = Util.auto()
|
|
75
|
-
SOLANA = Util.auto()
|
|
76
|
-
SUI = Util.auto()
|
|
77
|
-
|
|
78
|
-
class ProductEnum(Util.NoValEnum):
|
|
79
|
-
PROVER = Util.auto()
|
|
80
|
-
RANGER = Util.auto()
|
|
81
|
-
CONCORD = Util.auto()
|
|
82
|
-
|
|
83
|
-
|
|
84
72
|
class TimeError(Exception):
|
|
85
73
|
"""A custom exception used to report on time elapsed errors"""
|
|
86
74
|
|
|
@@ -648,21 +636,8 @@ class CloudVerification:
|
|
|
648
636
|
|
|
649
637
|
auth_data["useLatestFe"] = self.context.fe_version == str(Util.FeValue.LATEST)
|
|
650
638
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
elif Attrs.is_soroban_app():
|
|
654
|
-
auth_data["ecosystem"] = EcoEnum.SOROBAN.name
|
|
655
|
-
elif Attrs.is_sui_app():
|
|
656
|
-
auth_data["ecosystem"] = EcoEnum.SUI.name
|
|
657
|
-
else:
|
|
658
|
-
auth_data["ecosystem"] = EcoEnum.EVM.name
|
|
659
|
-
|
|
660
|
-
if Attrs.is_ranger_app():
|
|
661
|
-
auth_data["product"] = ProductEnum.RANGER.name
|
|
662
|
-
elif Attrs.is_concord_app():
|
|
663
|
-
auth_data["product"] = ProductEnum.CONCORD.name
|
|
664
|
-
else:
|
|
665
|
-
auth_data["product"] = ProductEnum.PROVER.name
|
|
639
|
+
auth_data["ecosystem"] = self.context.app.ecosystem
|
|
640
|
+
auth_data["product"] = self.context.app.product
|
|
666
641
|
|
|
667
642
|
if Attrs.is_evm_app() and self.context.cache is not None:
|
|
668
643
|
auth_data["toolSceneCacheKey"] = self.context.cache
|
|
@@ -74,6 +74,7 @@ class RunMetaData:
|
|
|
74
74
|
conf_path -- the relative path form the cwd_relative to the configuration file
|
|
75
75
|
group_id -- optional identifier for grouping this run
|
|
76
76
|
java_version -- version of Java used during the run, if available
|
|
77
|
+
ecosystem -- the ecosystem of the current run (EVM, SOLANA, SOROBAN, SUI, etc)
|
|
77
78
|
default_solc_version -- version of default solc version on current machine, if available
|
|
78
79
|
python_version -- version of Python running the process
|
|
79
80
|
certora_ci_client -- name of the CI client if available, derived from environment
|
|
@@ -84,7 +85,7 @@ class RunMetaData:
|
|
|
84
85
|
"""
|
|
85
86
|
def __init__(self, raw_args: List[str], conf: Dict[str, Any], origin: str, revision: str,
|
|
86
87
|
branch: str, cwd_relative: Path, dirty: bool, main_spec: Optional[str],
|
|
87
|
-
conf_path: Optional[Path], group_id: Optional[str], java_version: str):
|
|
88
|
+
conf_path: Optional[Path], group_id: Optional[str], java_version: str, ecosystem: str):
|
|
88
89
|
self.raw_args = raw_args
|
|
89
90
|
self.conf = conf
|
|
90
91
|
self.origin = origin
|
|
@@ -97,6 +98,7 @@ class RunMetaData:
|
|
|
97
98
|
self.group_id = group_id
|
|
98
99
|
self.python_version = ".".join(str(x) for x in sys.version_info[:3])
|
|
99
100
|
self.java_version = java_version
|
|
101
|
+
self.ecosystem = ecosystem
|
|
100
102
|
self.default_solc_version = get_solc_version(self.conf)
|
|
101
103
|
self.certora_ci_client = Utils.get_certora_ci_name()
|
|
102
104
|
self.timestamp = str(datetime.now(timezone.utc).timestamp())
|
|
@@ -119,6 +121,7 @@ class RunMetaData:
|
|
|
119
121
|
f" group_id: {self.group_id}\n"
|
|
120
122
|
f" python_version: {self.python_version}\n"
|
|
121
123
|
f" java_version: {self.java_version}\n"
|
|
124
|
+
f" ecosystem: {self.ecosystem}\n"
|
|
122
125
|
f" default_solc_version: {self.default_solc_version}\n"
|
|
123
126
|
f" CertoraCI client: {self.certora_ci_client}\n"
|
|
124
127
|
f" jar_flag_info: {self.jar_flag_info}\n"
|
|
@@ -198,7 +201,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
198
201
|
main_spec=None,
|
|
199
202
|
conf_path=None,
|
|
200
203
|
group_id=None,
|
|
201
|
-
java_version=context.java_version
|
|
204
|
+
java_version=context.java_version,
|
|
205
|
+
ecosystem=context.app.ecosystem)
|
|
202
206
|
|
|
203
207
|
# collect information about current git snapshot
|
|
204
208
|
cwd_abs = wd.absolute()
|
|
@@ -227,7 +231,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
227
231
|
main_spec=get_main_spec(context),
|
|
228
232
|
conf_path=conf_path,
|
|
229
233
|
group_id=context.group_id,
|
|
230
|
-
java_version=context.java_version
|
|
234
|
+
java_version=context.java_version,
|
|
235
|
+
ecosystem=context.app.ecosystem)
|
|
231
236
|
|
|
232
237
|
try:
|
|
233
238
|
sha_out = subprocess.run(['git', 'rev-parse', 'HEAD'], cwd=wd,
|
|
@@ -263,7 +268,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
263
268
|
main_spec=get_main_spec(context),
|
|
264
269
|
conf_path=conf_path,
|
|
265
270
|
group_id=context.group_id,
|
|
266
|
-
java_version=context.java_version
|
|
271
|
+
java_version=context.java_version,
|
|
272
|
+
ecosystem=context.app.ecosystem)
|
|
267
273
|
|
|
268
274
|
metadata_logger.debug(f' collected data:\n{str(data)}')
|
|
269
275
|
|
|
@@ -282,7 +288,8 @@ def collect_run_metadata(wd: Path, raw_args: List[str], context: CertoraContext)
|
|
|
282
288
|
main_spec=get_main_spec(context),
|
|
283
289
|
conf_path=conf_path,
|
|
284
290
|
group_id=context.group_id,
|
|
285
|
-
java_version=context.java_version
|
|
291
|
+
java_version=context.java_version,
|
|
292
|
+
ecosystem=context.app.ecosystem)
|
|
286
293
|
|
|
287
294
|
|
|
288
295
|
def get_solc_version(conf: Dict[str, Any]) -> Optional[str]:
|
|
@@ -326,6 +326,23 @@ class EvmAttributes(AttrUtil.Attributes):
|
|
|
326
326
|
)
|
|
327
327
|
)
|
|
328
328
|
|
|
329
|
+
VYPER_CUSTOM_STD_JSON_IN_MAP = AttrUtil.AttributeDefinition(
|
|
330
|
+
arg_type=AttrUtil.AttrArgType.MAP,
|
|
331
|
+
attr_validation_func=Vf.validate_vyper_custom_std_json_in_map,
|
|
332
|
+
help_msg="Supply a base json for getting vyper compiler output, generated by `vyper -f solc_json`, on a per"
|
|
333
|
+
"contract basis",
|
|
334
|
+
default_desc="It is assumed the standard-json generated by certora-cli will be able to compile the contracts",
|
|
335
|
+
argparse_args={
|
|
336
|
+
"action": AttrUtil.UniqueStore,
|
|
337
|
+
"type": lambda value: Vf.parse_ordered_dict("validate_vyper_custom_std_json_in_map", value)
|
|
338
|
+
},
|
|
339
|
+
affects_build_cache_key=True,
|
|
340
|
+
disables_build_cache=False,
|
|
341
|
+
config_data=AttributeJobConfigData(
|
|
342
|
+
main_section=MainSection.SOLIDITY_COMPILER
|
|
343
|
+
)
|
|
344
|
+
)
|
|
345
|
+
|
|
329
346
|
SOLC_VIA_IR = AttrUtil.AttributeDefinition(
|
|
330
347
|
arg_type=AttrUtil.AttrArgType.BOOLEAN,
|
|
331
348
|
help_msg="Pass the `--via-ir` flag to the Solidity compiler",
|
|
@@ -1216,7 +1233,7 @@ class EvmAttributes(AttrUtil.Attributes):
|
|
|
1216
1233
|
SAFE_CASTING_BUILTIN = AttrUtil.AttributeDefinition(
|
|
1217
1234
|
arg_type=AttrUtil.AttrArgType.BOOLEAN,
|
|
1218
1235
|
help_msg="This needs to be set to true for the safeCasting builtin to work",
|
|
1219
|
-
default_desc="
|
|
1236
|
+
default_desc="safeCasting builtin will not run",
|
|
1220
1237
|
jar_flag='-safeCastingBuiltin',
|
|
1221
1238
|
argparse_args={
|
|
1222
1239
|
'action': AttrUtil.STORE_TRUE,
|
|
@@ -1226,6 +1243,19 @@ class EvmAttributes(AttrUtil.Attributes):
|
|
|
1226
1243
|
disables_build_cache=False,
|
|
1227
1244
|
)
|
|
1228
1245
|
|
|
1246
|
+
UNCHECKED_OVERFLOW_BUILTIN = AttrUtil.AttributeDefinition(
|
|
1247
|
+
arg_type=AttrUtil.AttrArgType.BOOLEAN,
|
|
1248
|
+
help_msg="This needs to be set to true for the uncheckedOverflow builtin to work",
|
|
1249
|
+
default_desc="uncheckedOverflow builtin will not run",
|
|
1250
|
+
jar_flag='-uncheckedOverflowBuiltin',
|
|
1251
|
+
argparse_args={
|
|
1252
|
+
'action': AttrUtil.STORE_TRUE,
|
|
1253
|
+
'default': False
|
|
1254
|
+
},
|
|
1255
|
+
affects_build_cache_key=True,
|
|
1256
|
+
disables_build_cache=False,
|
|
1257
|
+
)
|
|
1258
|
+
|
|
1229
1259
|
@classmethod
|
|
1230
1260
|
def hide_attributes(cls) -> List[str]:
|
|
1231
1261
|
# do not show these attributes in the help message
|
|
@@ -750,7 +750,14 @@ def check_map_attributes(context: CertoraContext) -> None:
|
|
|
750
750
|
|
|
751
751
|
none_keys = [k for k, v in file_list.items() if v is False]
|
|
752
752
|
if none_keys:
|
|
753
|
-
|
|
753
|
+
if map_attr_name == Attrs.EvmAttributes.VYPER_CUSTOM_STD_JSON_IN_MAP.name.lower():
|
|
754
|
+
# this new attribute requires special handling in case we combine solidity files with vyper0.4 files
|
|
755
|
+
vy_files_unmatched = [k for k in none_keys if Util.is_vyper_file(k)]
|
|
756
|
+
if vy_files_unmatched:
|
|
757
|
+
raise Util.CertoraUserInputError(
|
|
758
|
+
f"The following vyper files are not matched in {map_attr_name}: {none_keys}")
|
|
759
|
+
else:
|
|
760
|
+
raise Util.CertoraUserInputError(f"The following files are not matched in {map_attr_name}: {none_keys}")
|
|
754
761
|
|
|
755
762
|
|
|
756
763
|
def check_parametric_contracts(context: CertoraContext) -> None:
|
|
@@ -52,7 +52,7 @@ class VyperMetadata:
|
|
|
52
52
|
frame_size: Optional[int] = None,
|
|
53
53
|
frame_start: Optional[int] = None,
|
|
54
54
|
venom_via_stack: Optional[List[str]] = None,
|
|
55
|
-
venom_return_via_stack: bool =
|
|
55
|
+
venom_return_via_stack: Optional[bool] = None,
|
|
56
56
|
runtime_start_pc: Optional[int] = None,
|
|
57
57
|
):
|
|
58
58
|
self.frame_size = frame_size
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# The Certora Prover
|
|
2
|
+
# Copyright (C) 2025 Certora Ltd.
|
|
3
|
+
#
|
|
4
|
+
# This program is free software: you can redistribute it and/or modify
|
|
5
|
+
# it under the terms of the GNU General Public License as published by
|
|
6
|
+
# the Free Software Foundation, version 3 of the License.
|
|
7
|
+
#
|
|
8
|
+
# This program is distributed in the hope that it will be useful,
|
|
9
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
11
|
+
# GNU General Public License for more details.
|
|
12
|
+
#
|
|
13
|
+
# You should have received a copy of the GNU General Public License
|
|
14
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
import bisect
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from CertoraProver.certoraBuildDataClasses import SDC
|
|
21
|
+
from Shared import certoraUtils as Util
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class OffsetConverter:
|
|
25
|
+
"""Holds newline positions for a file to enable offset-to-line-column conversion."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, file: str):
|
|
28
|
+
"""Initialize OffsetConverter by reading newline positions from a file."""
|
|
29
|
+
with Path(file).open('rb') as f:
|
|
30
|
+
content = f.read()
|
|
31
|
+
self.newline_positions = [i for i, byte in enumerate(content) if byte == ord(b'\n')]
|
|
32
|
+
|
|
33
|
+
def offset_to_line_column(self, offset: int) -> tuple[int, int]:
|
|
34
|
+
"""
|
|
35
|
+
Convert a file offset to line and column number.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
offset: Byte offset in the file
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Tuple of (line_number, column_number), both 1-indexed
|
|
42
|
+
"""
|
|
43
|
+
line = bisect.bisect_left(self.newline_positions, offset)
|
|
44
|
+
# Calculate column based on previous newline position
|
|
45
|
+
if line == 0:
|
|
46
|
+
column = offset + 1 # 1-indexed, no previous newline
|
|
47
|
+
else:
|
|
48
|
+
column = offset - self.newline_positions[line - 1] # 1-indexed from newline
|
|
49
|
+
return line, column
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def generate_offset_converters(sdc: SDC) -> dict[str, OffsetConverter]:
|
|
53
|
+
original_files = {Util.convert_path_for_solc_import(c.original_file) for c in sdc.contracts}
|
|
54
|
+
return {file: OffsetConverter(file) for file in original_files}
|
|
@@ -291,17 +291,17 @@ def is_unsupported_type(type_string: str) -> bool:
|
|
|
291
291
|
return type_string not in PrimitiveType.allowed_primitive_type_names
|
|
292
292
|
|
|
293
293
|
|
|
294
|
-
def
|
|
294
|
+
def find_char(filepath: str, offset: int, c : str) -> Optional[int]:
|
|
295
295
|
"""
|
|
296
|
-
|
|
297
|
-
Returns None if any non-whitespace character other than
|
|
296
|
+
given offset, skip whitespace until finding the input character `c`.
|
|
297
|
+
Returns None if any non-whitespace character other than `c` is encountered.
|
|
298
298
|
|
|
299
299
|
Args:
|
|
300
300
|
filepath: Path to the file
|
|
301
|
-
offset: Byte offset where we expect to find '
|
|
301
|
+
offset: Byte offset where we expect to find 'c' or whitespace
|
|
302
302
|
|
|
303
303
|
Returns:
|
|
304
|
-
Offset of
|
|
304
|
+
Offset of 'c' if found after only whitespace
|
|
305
305
|
None if any other non-whitespace character is encountered
|
|
306
306
|
"""
|
|
307
307
|
try:
|
|
@@ -312,7 +312,7 @@ def find_semicolon(filepath: str, offset: int) -> Optional[int]:
|
|
|
312
312
|
if not char: # EOF
|
|
313
313
|
return None
|
|
314
314
|
|
|
315
|
-
if char ==
|
|
315
|
+
if char == c:
|
|
316
316
|
return f.tell() - 1
|
|
317
317
|
|
|
318
318
|
if char not in ' \t\n\r': # not whitespace
|
|
@@ -366,7 +366,7 @@ def add_source_finders(asts: Dict[str, Dict[str, Dict[int, Any]]], contract_file
|
|
|
366
366
|
# no need to -1 as the source mapping does not include the ';', and we want to find it...
|
|
367
367
|
# i.e., the end_offset should point at ';'
|
|
368
368
|
end_offset = int(start_offset) + int(src_len) # this is the original end offset
|
|
369
|
-
end_offset_with_semicolon =
|
|
369
|
+
end_offset_with_semicolon = find_char(solfile, end_offset, ";")
|
|
370
370
|
if end_offset_with_semicolon is None:
|
|
371
371
|
# we are dealing with Solidity code with unexpected format, let's skip this assignment
|
|
372
372
|
continue
|