certora-cli-beta-mirror 8.6.3__py3-none-any.whl → 8.8.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.
@@ -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, smart_contract_lang: CompilerLang) -> None:
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(smart_contract_lang.normalize_file_compiler_path_name(file_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
- standard_json_data = self.get_standard_json_data(sdc_name, smart_contract_lang)
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 = self.get_compilation_path(sdc_name)
1167
- standard_json_data = self.get_standard_json_data(sdc_name, smart_contract_lang)
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, smart_contract_lang)
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
- contents = f.read()
1583
- sources_dict = {str(contract_file_posix_abs): {"content": contents}}
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"{error['formattedMessage']}"
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
- data = \
1904
- smart_contract_lang.collect_storage_layout_info(file_abs_path, compilation_path, compiler_ver_to_run,
1905
- compiler_collector.compiler_version,
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 = smart_contract_lang.normalize_file_compiler_path_name(file_abs_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
- 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")
2316
+ symbol_map_name = metadata_func_info["_ir_identifier"] + "_runtime"
2317
+ if symbol_map_name not in symbol_map:
2270
2318
  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]]
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,
@@ -3292,6 +3337,9 @@ class CertoraBuildGenerator:
3292
3337
  })
3293
3338
  return result
3294
3339
 
3340
+ def _needs_casting_instrumentation(self) -> bool:
3341
+ return self.context.safe_casting_builtin or self.context.assume_no_casting_overflow
3342
+
3295
3343
  def instrument_auto_finders(self, build_arg_contract_file: str, i: int,
3296
3344
  sdc_pre_finders: List[SDC],
3297
3345
  path_for_compiler_collector_file: str,
@@ -3339,14 +3387,35 @@ class CertoraBuildGenerator:
3339
3387
  else:
3340
3388
  added_source_finders = {}
3341
3389
 
3342
- if self.context.safe_casting_builtin:
3390
+ offset_converters = generate_offset_converters(sdc_pre_finder)
3391
+
3392
+ if self._needs_casting_instrumentation():
3343
3393
  try:
3344
- casting_instrumentations, casting_types = generate_casting_instrumentation(self.asts, build_arg_contract_file, sdc_pre_finder)
3394
+ casting_instrumentations, casting_types = generate_casting_instrumentation(self.asts, build_arg_contract_file, sdc_pre_finder, offset_converters)
3345
3395
  except Exception as e:
3346
- instrumentation_logger.warning(
3347
- f"Computing casting instrumentation failed for {build_arg_contract_file}: {e}", exc_info=True)
3348
3396
  casting_instrumentations, casting_types = {}, {}
3349
- instr = CertoraBuildGenerator.merge_dicts_instrumentation(instr, casting_instrumentations)
3397
+ instrumentation_logger.warning(f"Computing casting instrumentation failed for {build_arg_contract_file}: {e}", exc_info=True)
3398
+ else:
3399
+ casting_instrumentations, casting_types = {}, {}
3400
+
3401
+ if self.context.unchecked_overflow_builtin:
3402
+ try:
3403
+ overflow_instrumentations, op_funcs = generate_overflow_instrumentation(self.asts, build_arg_contract_file, sdc_pre_finder, offset_converters)
3404
+ except Exception as e:
3405
+ overflow_instrumentations, op_funcs = {}, {}
3406
+ instrumentation_logger.warning(
3407
+ f"Computing overflow instrumenstation failed for {build_arg_contract_file}: {e}", exc_info=True)
3408
+ else:
3409
+ overflow_instrumentations, op_funcs = {}, {}
3410
+
3411
+ for file_name, inst_dict in casting_instrumentations.items():
3412
+ if file_name not in overflow_instrumentations:
3413
+ overflow_instrumentations[file_name] = dict()
3414
+ d = overflow_instrumentations[file_name]
3415
+ for offset, inst in inst_dict.items():
3416
+ add_instrumentation(d, offset, inst)
3417
+
3418
+ instr = CertoraBuildGenerator.merge_dicts_instrumentation(instr, overflow_instrumentations)
3350
3419
 
3351
3420
  abs_build_arg_contract_file = Util.abs_posix_path(build_arg_contract_file)
3352
3421
  if abs_build_arg_contract_file not in instr:
@@ -3401,7 +3470,7 @@ class CertoraBuildGenerator:
3401
3470
  read_so_far += amt + 1 + to_skip
3402
3471
  output.write(in_file.read(-1))
3403
3472
 
3404
- if self.context.safe_casting_builtin:
3473
+ if self._needs_casting_instrumentation():
3405
3474
  library_name, funcs = casting_types.get(contract_file, ("", list()))
3406
3475
  if len(funcs) > 0:
3407
3476
  output.write(bytes(f"\nlibrary {library_name}" + "{\n", "utf8"))
@@ -3409,6 +3478,13 @@ class CertoraBuildGenerator:
3409
3478
  output.write(bytes(f, "utf8"))
3410
3479
  output.write(bytes("}\n", "utf8"))
3411
3480
 
3481
+ library_name, funcs = op_funcs.get(contract_file, ("", list()))
3482
+ if len(funcs) > 0:
3483
+ output.write(bytes(f"\nlibrary {library_name}" + "{\n", "utf8"))
3484
+ for f in funcs:
3485
+ output.write(bytes(f, "utf8"))
3486
+ output.write(bytes("}\n", "utf8"))
3487
+
3412
3488
  new_file = self.to_autofinder_file(build_arg_contract_file)
3413
3489
  self.context.file_to_contract[new_file] = self.context.file_to_contract[
3414
3490
  build_arg_contract_file]
@@ -3429,6 +3505,7 @@ class CertoraBuildGenerator:
3429
3505
  f"Compiling {orig_file_name} to expose internal function information and local variables...")
3430
3506
  else:
3431
3507
  Util.print_progress_message(f"Compiling {orig_file_name} to expose internal function information...")
3508
+
3432
3509
  # record what aliases we have created (for the purposes of type canonicalization, the generated autofinder
3433
3510
  # is an alias of the original file)
3434
3511
  for k, v in autofinder_remappings.items():
@@ -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="This needs to be set to true for the safeCasting builtin to work",
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,45 @@ class EvmAttributes(AttrUtil.Attributes):
1226
1243
  disables_build_cache=False,
1227
1244
  )
1228
1245
 
1246
+ ASSUME_NO_CASTING_OVERFLOW = AttrUtil.AttributeDefinition(
1247
+ arg_type=AttrUtil.AttrArgType.BOOLEAN,
1248
+ help_msg="Will Assume solidity casting expressions never overflow",
1249
+ default_desc="Solidity casting expressions may overflow",
1250
+ jar_flag='-assumeNoCastingOverflow',
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
+
1259
+ UNCHECKED_OVERFLOW_BUILTIN = AttrUtil.AttributeDefinition(
1260
+ arg_type=AttrUtil.AttrArgType.BOOLEAN,
1261
+ help_msg="This needs to be set to true for the uncheckedOverflow builtin to work",
1262
+ default_desc="uncheckedOverflow builtin will not run",
1263
+ jar_flag='-uncheckedOverflowBuiltin',
1264
+ argparse_args={
1265
+ 'action': AttrUtil.STORE_TRUE,
1266
+ 'default': False
1267
+ },
1268
+ affects_build_cache_key=True,
1269
+ disables_build_cache=False,
1270
+ )
1271
+
1272
+ CONTRACT_EXTENSIONS_OVERRIDE = AttrUtil.AttributeDefinition(
1273
+ arg_type=AttrUtil.AttrArgType.BOOLEAN,
1274
+ help_msg="Set this flag if you are using `contract_extensions` and an extending contract has a method that should override a method with the same name in the extension contract",
1275
+ default_desc="Prover will fail if an extending contract has a method with the same name as one in the extended contract that wasn't excluded.",
1276
+ jar_flag='-overrideExtendedContractFunctions',
1277
+ argparse_args={
1278
+ 'action': AttrUtil.STORE_TRUE,
1279
+ 'default': False
1280
+ },
1281
+ affects_build_cache_key=False,
1282
+ disables_build_cache=False,
1283
+ )
1284
+
1229
1285
  @classmethod
1230
1286
  def hide_attributes(cls) -> List[str]:
1231
1287
  # 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
- raise Util.CertoraUserInputError(f"The following files are not matched in {map_attr_name}: {none_keys}")
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 = False,
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 find_semicolon(filepath: str, offset: int) -> Optional[int]:
294
+ def find_char(filepath: str, offset: int, c : str) -> Optional[int]:
295
295
  """
296
- From given offset, skip whitespace until finding a semicolon.
297
- Returns None if any non-whitespace character other than ';' is encountered.
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 ';' or whitespace
301
+ offset: Byte offset where we expect to find 'c' or whitespace
302
302
 
303
303
  Returns:
304
- Offset of the semicolon if found after only whitespace
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 = find_semicolon(solfile, end_offset)
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