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.
Files changed (23) hide show
  1. certora_cli/CertoraProver/Compiler/CompilerCollector.py +9 -8
  2. certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +126 -73
  3. certora_cli/CertoraProver/castingInstrumenter.py +23 -16
  4. certora_cli/CertoraProver/certoraApp.py +8 -0
  5. certora_cli/CertoraProver/certoraBuild.py +106 -32
  6. certora_cli/CertoraProver/certoraCloudIO.py +2 -27
  7. certora_cli/CertoraProver/certoraCollectRunMetadata.py +12 -5
  8. certora_cli/CertoraProver/certoraContextAttributes.py +31 -1
  9. certora_cli/CertoraProver/certoraContextValidator.py +8 -1
  10. certora_cli/CertoraProver/certoraContractFuncs.py +1 -1
  11. certora_cli/CertoraProver/certoraOffsetConverter.py +54 -0
  12. certora_cli/CertoraProver/certoraSourceFinders.py +7 -7
  13. certora_cli/CertoraProver/uncheckedOverflowInstrumenter.py +152 -0
  14. certora_cli/Shared/certoraValidateFuncs.py +31 -0
  15. {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/METADATA +2 -2
  16. {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/RECORD +23 -21
  17. certora_jars/ASTExtraction.jar +0 -0
  18. certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
  19. certora_jars/Typechecker.jar +0 -0
  20. {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/LICENSE +0 -0
  21. {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/WHEEL +0 -0
  22. {certora_cli_beta_mirror-8.6.2.dist-info → certora_cli_beta_mirror-8.7.0.dist-info}/entry_points.txt +0 -0
  23. {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, 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,
@@ -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
- instr = CertoraBuildGenerator.merge_dicts_instrumentation(instr, casting_instrumentations)
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
- if Attrs.is_solana_app():
652
- auth_data["ecosystem"] = EcoEnum.SOLANA.name
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="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,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
- 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