certora-cli 7.26.0__tar.gz → 7.29.1__tar.gz

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 (82) hide show
  1. certora_cli-7.29.1/LICENSE +15 -0
  2. {certora_cli-7.26.0/certora_cli.egg-info → certora_cli-7.29.1}/PKG-INFO +8 -5
  3. certora_cli-7.29.1/README.md +1 -0
  4. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +6 -3
  5. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +48 -13
  6. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraBuild.py +227 -51
  7. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraBuildDataClasses.py +19 -4
  8. certora_cli-7.29.1/certora_cli/CertoraProver/certoraBuildRust.py +139 -0
  9. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraCloudIO.py +57 -67
  10. certora_cli-7.29.1/certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +377 -0
  11. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraCollectRunMetadata.py +13 -1
  12. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraConfigIO.py +55 -20
  13. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraContext.py +18 -6
  14. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraContextAttributes.py +235 -67
  15. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraContextValidator.py +114 -50
  16. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraParseBuildScript.py +15 -12
  17. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraVerifyGenerator.py +12 -0
  18. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/splitRules.py +3 -1
  19. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/EquivalenceCheck/equivCheck.py +5 -4
  20. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Mutate/mutateApp.py +10 -10
  21. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Mutate/mutateConstants.py +0 -1
  22. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Shared/certoraAttrUtil.py +14 -2
  23. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Shared/certoraUtils.py +32 -8
  24. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Shared/certoraValidateFuncs.py +38 -14
  25. certora_cli-7.29.1/certora_cli/certoraRanger.py +71 -0
  26. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/certoraRun.py +21 -16
  27. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/certoraSolanaProver.py +17 -3
  28. certora_cli-7.29.1/certora_cli/certoraSorobanProver.py +285 -0
  29. {certora_cli-7.26.0 → certora_cli-7.29.1/certora_cli.egg-info}/PKG-INFO +8 -5
  30. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli.egg-info/SOURCES.txt +2 -0
  31. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli.egg-info/entry_points.txt +1 -0
  32. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli.egg-info/requires.txt +0 -2
  33. certora_cli-7.29.1/certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -0
  34. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_jars/Typechecker.jar +0 -0
  35. {certora_cli-7.26.0 → certora_cli-7.29.1}/setup.py +11 -5
  36. certora_cli-7.26.0/LICENSE +0 -22
  37. certora_cli-7.26.0/README.md +0 -1
  38. certora_cli-7.26.0/certora_cli/CertoraProver/certoraBuildRust.py +0 -99
  39. certora_cli-7.26.0/certora_cli/certoraSorobanProver.py +0 -36
  40. certora_cli-7.26.0/certora_jars/CERTORA-CLI-VERSION-METADATA.json +0 -1
  41. {certora_cli-7.26.0 → certora_cli-7.29.1}/MANIFEST.in +0 -0
  42. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_bins/__init__.py +0 -0
  43. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/Compiler/CompilerCollector.py +0 -0
  44. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/Compiler/CompilerCollectorSol.py +0 -0
  45. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/Compiler/CompilerCollectorSolBased.py +0 -0
  46. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +0 -0
  47. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/Compiler/__init__.py +0 -0
  48. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/__init__.py +0 -0
  49. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraBuildCacheManager.py +0 -0
  50. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraCompilerParameters.py +0 -0
  51. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraContextClass.py +0 -0
  52. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraContractFuncs.py +0 -0
  53. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraExtensionInfo.py +0 -0
  54. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraJobList.py +0 -0
  55. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraMiniSpecParser.py +0 -0
  56. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraNodeFilters.py +0 -0
  57. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraProjectScanner.py +0 -0
  58. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraSourceFinders.py +0 -0
  59. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/CertoraProver/certoraType.py +0 -0
  60. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/EquivalenceCheck/Eq_default.conf +0 -0
  61. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/EquivalenceCheck/Eq_mc_no_out_template.spec +0 -0
  62. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/EquivalenceCheck/Eq_mc_template.spec +0 -0
  63. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -0
  64. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/EquivalenceCheck/Eq_template.spec +0 -0
  65. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/EquivalenceCheck/__init__.py +0 -0
  66. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/EquivalenceCheck/sanity.spec +0 -0
  67. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Mutate/__init__.py +0 -0
  68. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Mutate/mutateAttributes.py +0 -0
  69. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Mutate/mutateUtil.py +0 -0
  70. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Mutate/mutateValidate.py +0 -0
  71. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Shared/ExpectedComparator.py +0 -0
  72. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Shared/__init__.py +0 -0
  73. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/Shared/certoraLogging.py +0 -0
  74. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/__init__.py +0 -0
  75. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/certoraEVMProver.py +0 -0
  76. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/certoraEqCheck.py +0 -0
  77. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/certoraMutate.py +0 -0
  78. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli/rustMutator.py +0 -0
  79. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli.egg-info/dependency_links.txt +0 -0
  80. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_cli.egg-info/top_level.txt +0 -0
  81. {certora_cli-7.26.0 → certora_cli-7.29.1}/certora_jars/__init__.py +0 -0
  82. {certora_cli-7.26.0 → certora_cli-7.29.1}/setup.cfg +0 -0
@@ -0,0 +1,15 @@
1
+
2
+ The Certora Prover
3
+ Copyright (C) 2025 Certora Ltd.
4
+
5
+ This program is free software: you can redistribute it and/or modify
6
+ it under the terms of the GNU General Public License as published by
7
+ the Free Software Foundation, version 3 of the License.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY, without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR a PARTICULAR PURPOSE. See the
12
+ GNU General Public License for more details.
13
+
14
+ You should have received a copy of the GNU General Public License
15
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
@@ -1,12 +1,15 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: certora-cli
3
- Version: 7.26.0
3
+ Version: 7.29.1
4
4
  Summary: Runner for the Certora Prover
5
5
  Home-page: https://pypi.org/project/certora-cli
6
6
  Author: Certora
7
7
  Author-email: support@certora.com
8
+ License: GPL-3.0-only
9
+ Project-URL: Documentation, https://docs.certora.com/en/latest/
10
+ Project-URL: Source, https://github.com/Certora/CertoraProver
8
11
  Classifier: Programming Language :: Python :: 3
9
- Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
10
13
  Classifier: Operating System :: OS Independent
11
14
  Requires-Python: >=3.8
12
15
  Description-Content-Type: text/markdown
@@ -20,18 +23,18 @@ Requires-Dist: sly
20
23
  Requires-Dist: tabulate
21
24
  Requires-Dist: tqdm
22
25
  Requires-Dist: StrEnum
23
- Requires-Dist: tomli
24
26
  Requires-Dist: universalmutator
25
27
  Requires-Dist: jinja2
26
- Requires-Dist: setuptools==75.8.2
27
28
  Dynamic: author
28
29
  Dynamic: author-email
29
30
  Dynamic: classifier
30
31
  Dynamic: description
31
32
  Dynamic: description-content-type
32
33
  Dynamic: home-page
34
+ Dynamic: license
35
+ Dynamic: project-url
33
36
  Dynamic: requires-dist
34
37
  Dynamic: requires-python
35
38
  Dynamic: summary
36
39
 
37
- Commit 4799cf7. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
40
+ Commit b8a319d. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -0,0 +1 @@
1
+ Commit b8a319d. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -39,12 +39,15 @@ def get_relevant_compiler(contract_file_path: Path, context: CertoraContext) ->
39
39
  @return: the name of the compiler executable we want to run on this file (as a string, could be a path
40
40
  or a resolvable executable name)
41
41
  """
42
- if context.solc_map:
43
- match = match_path_to_mapping_key(contract_file_path, context.solc_map)
42
+
43
+ if contract_file_path.is_absolute():
44
+ contract_file_path = Path(os.path.relpath(contract_file_path, Path.cwd()))
45
+ if context.compiler_map:
46
+ match = match_path_to_mapping_key(contract_file_path, context.compiler_map)
44
47
  if match:
45
48
  return match
46
49
  else:
47
- raise RuntimeError(f'cannot match compiler to {contract_file_path} from solc_map')
50
+ raise RuntimeError(f'cannot match compiler to {contract_file_path} from compiler_map')
48
51
 
49
52
  if Util.is_vyper_file(str(contract_file_path)):
50
53
  if context.vyper:
@@ -556,43 +556,78 @@ class CompilerLangVy(CompilerLang, metaclass=Singleton):
556
556
  else:
557
557
  raise Exception(f"Unexpected ast_node {ast_node}, cannot evaluate constant")
558
558
 
559
+ @staticmethod
560
+ def subscript_node(ast_node: Dict[str, Any]) -> Optional[Dict[str, Any]]:
561
+ ty = ast_node.get("ast_type")
562
+ # pre-vyper 0.4.0, look deeper within
563
+ if ty == "Index":
564
+ nested = ast_node.get("value")
565
+ if nested is None or not isinstance(nested, dict):
566
+ return None
567
+ return nested
568
+ else:
569
+ return ast_node
570
+
571
+ @staticmethod
572
+ def int_subscript(ast_node: Dict[str, Any], named_constants: Dict[str, int]) -> Optional[int]:
573
+ node = CompilerLangVy.subscript_node(ast_node)
574
+ if node is None:
575
+ return None
576
+
577
+ if "id" in node and node["id"] in named_constants:
578
+ return named_constants[node["id"]]
579
+ elif node.get("ast_type") == "Int":
580
+ return node.get("value")
581
+ else:
582
+ return None
583
+
584
+ @staticmethod
585
+ def get_tuple_elements(ast_node: Dict[str, Any]) -> List[Dict[str, Any]]:
586
+ node = CompilerLangVy.subscript_node(ast_node)
587
+ if node is None or node.get("ast_type") != "Tuple":
588
+ raise Exception("Couldn't get tuple elements from ast node")
589
+ return node["elements"]
590
+
559
591
  @staticmethod
560
592
  def extract_type_from_subscript_node(ast_subscript_node: Dict[str, Any],
561
593
  named_constants: Dict[str, int]) -> VyperType:
562
594
  value_id = ast_subscript_node['value'].get('id', None)
563
595
  if value_id == 'String':
564
- max_bytes = ast_subscript_node['slice']['value']['value']
596
+ max_bytes = CompilerLangVy.int_subscript(ast_subscript_node['slice'], named_constants)
597
+ if max_bytes is None:
598
+ raise Exception("Failed to find max length for string type declaration")
565
599
  return CompilerLangVy.VyperTypeString(max_bytes)
566
600
  elif value_id == 'Bytes':
567
- max_bytes = ast_subscript_node['slice']['value']['value']
601
+ max_bytes = CompilerLangVy.int_subscript(ast_subscript_node['slice'], named_constants)
602
+ if max_bytes is None:
603
+ raise Exception("Failed to find max length for bytes type declaration")
568
604
  return CompilerLangVy.VyperTypeBytes(max_bytes)
569
605
  elif value_id == 'DynArray':
570
- elem_type = CompilerLangVy.extract_type_from_type_annotation_node(
571
- ast_subscript_node['slice']['value']['elements'][0], named_constants)
572
- max_elements = CompilerLangVy.extract_constant(ast_subscript_node['slice']['value']['elements'][1],
573
- named_constants)
606
+ tup_elems = CompilerLangVy.get_tuple_elements(ast_subscript_node["slice"])
607
+ elem_type = CompilerLangVy.extract_type_from_type_annotation_node(tup_elems[0], named_constants)
608
+ max_elements = CompilerLangVy.extract_constant(tup_elems[1], named_constants)
574
609
  return CompilerLangVy.VyperTypeDynArray(elem_type, max_elements)
575
610
  elif value_id == 'HashMap':
576
- elements_node = ast_subscript_node['slice']['value']['elements']
611
+ elements_node = CompilerLangVy.get_tuple_elements(ast_subscript_node['slice'])
577
612
  key_type = CompilerLangVy.extract_type_from_type_annotation_node(elements_node[0], named_constants)
578
613
  value_type = CompilerLangVy.extract_type_from_type_annotation_node(elements_node[1], named_constants)
579
614
  return CompilerLangVy.VyperTypeHashMap(key_type, value_type)
580
615
  else: # StaticArray key_type[size]
581
616
  key_type = CompilerLangVy.extract_type_from_type_annotation_node(ast_subscript_node['value'],
582
617
  named_constants)
583
- max_elements_node = ast_subscript_node['slice']['value']
584
- if 'id' in max_elements_node and max_elements_node['id'] in named_constants:
585
- return CompilerLangVy.VyperTypeStaticArray(key_type, named_constants[max_elements_node['id']])
618
+ max_elem_value = CompilerLangVy.int_subscript(ast_subscript_node['slice'], named_constants)
619
+ if max_elem_value is not None:
620
+ return CompilerLangVy.VyperTypeStaticArray(key_type, max_elem_value)
586
621
  else:
587
622
  # this is very specific to curve code which has uint256[CONST/2] static array declaration.
623
+ max_elements_node = CompilerLangVy.subscript_node(ast_subscript_node["slice"])
624
+ if max_elements_node is None:
625
+ raise Exception("Couldn't find subscript node for array type")
588
626
  if 'ast_type' in max_elements_node:
589
627
  if max_elements_node['ast_type'] == 'BinOp' or max_elements_node['ast_type'] in ('Int', 'Name'):
590
628
  # good chance this will succeed
591
629
  static_array_len = CompilerLangVy.extract_constant(max_elements_node, named_constants)
592
630
  return CompilerLangVy.VyperTypeStaticArray(key_type, static_array_len)
593
- elif 'value' in max_elements_node:
594
- return CompilerLangVy.VyperTypeStaticArray(key_type, max_elements_node['value'])
595
-
596
631
  raise Exception(
597
632
  f"Don't know how to deal with vyper static array declaration with length {max_elements_node}")
598
633
 
@@ -21,7 +21,7 @@ import re
21
21
  import shutil
22
22
  import sys
23
23
  import typing
24
- from collections import OrderedDict
24
+ from collections import OrderedDict, defaultdict
25
25
  from enum import Enum
26
26
  from functools import lru_cache
27
27
  from pathlib import Path
@@ -411,6 +411,8 @@ def generate_modifier_finder(f: Func, internal_id: int, sym: int,
411
411
  formal_strings = []
412
412
  arg_strings = []
413
413
  for (logged_ty, logged_name) in zip(loggable_types, loggable_names):
414
+ if logged_name == "":
415
+ continue
414
416
  arg_strings.append(logged_name)
415
417
  formal_strings.append(f"{logged_ty} {logged_name}")
416
418
  modifier_body = f"modifier {modifier_name}"
@@ -825,7 +827,7 @@ class CertoraBuildGenerator:
825
827
  ast_logger.debug(f"No body for {func_def} but ast claims it is implemented")
826
828
 
827
829
  if original_contract is not None:
828
- if method := original_contract.find_method(func_name):
830
+ if method := original_contract.find_method(func_name, solidity_type_args):
829
831
  source_bytes = method.source_bytes
830
832
  original_file = method.original_file
831
833
  else:
@@ -1198,10 +1200,15 @@ class CertoraBuildGenerator:
1198
1200
  return x[TYPE] == FUNCTION or x[TYPE] == CertoraBuildGenerator.CONSTRUCTOR_STRING
1199
1201
 
1200
1202
  @staticmethod
1201
- def collect_srcmap(data: Dict[str, Any]) -> Any:
1203
+ def collect_srcmap(data: Dict[str, Any]) -> Tuple[str, str]:
1202
1204
  # no source map object in vyper
1203
- return (data["evm"]["deployedBytecode"].get("sourceMap", ""),
1204
- data["evm"]["bytecode"].get("sourceMap", ""))
1205
+ deployed = data["evm"]["deployedBytecode"].get("sourceMap", "")
1206
+ if isinstance(deployed, dict):
1207
+ deployed = deployed.get("pc_pos_map_compressed", "")
1208
+ regular = data["evm"]["bytecode"].get("sourceMap", "")
1209
+ if isinstance(regular, dict):
1210
+ regular = regular.get("pc_pos_map_compressed", "")
1211
+ return deployed, regular
1205
1212
 
1206
1213
  @staticmethod
1207
1214
  def collect_varmap(contract: str, data: Dict[str, Any]) -> Any:
@@ -1389,7 +1396,7 @@ class CertoraBuildGenerator:
1389
1396
  if not self.context.strict_solc_optimizer and self.context.solc_via_ir:
1390
1397
  # The default optimizer steps (taken from libsolidity/interface/OptimiserSettings.h) but with the
1391
1398
  # full inliner step removed
1392
- solc0_8_26_to_0_8_28 = ("dhfoDgvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLCTUca[r]LSsTFOtfDnca[r]" +
1399
+ solc0_8_26_to_0_8_29 = ("dhfoDgvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLCTUca[r]LSsTFOtfDnca[r]" +
1393
1400
  "IulcscCTUtx[scCTUt]TOntnfDIuljmul[jul]VcTOculjmul")
1394
1401
  solc0_8_13_to_0_8_25 = "dhfoDgvulfnTUtnIf[xa[r]EscLMcCTUtTOntnfDIulLculVcul[j]T" + \
1395
1402
  "peulxa[rul]xa[r]cLgvifCTUca[r]LSsTFOtfDnca[r]Iulc]jmul[jul]VcTOculjmul"
@@ -1433,8 +1440,8 @@ class CertoraBuildGenerator:
1433
1440
  yul_optimizer_steps = solc0_8_12
1434
1441
  elif minor == 8 and 13 <= patch <= 25:
1435
1442
  yul_optimizer_steps = solc0_8_13_to_0_8_25
1436
- elif minor == 8 and 26 <= patch <= 28:
1437
- yul_optimizer_steps = solc0_8_26_to_0_8_28
1443
+ elif minor == 8 and 26 <= patch <= 29:
1444
+ yul_optimizer_steps = solc0_8_26_to_0_8_29
1438
1445
  assert yul_optimizer_steps is not None, \
1439
1446
  'Yul Optimizer steps missing for requested Solidity version. Please contact Certora team.'
1440
1447
 
@@ -1516,7 +1523,8 @@ class CertoraBuildGenerator:
1516
1523
  sources_dict = {str(contract_file_posix_abs): {
1517
1524
  "urls": [str(contract_file_posix_abs)]}} # type: Dict[str, Dict[str, Any]]
1518
1525
  output_selection = ["transientStorageLayout", "storageLayout", "abi", "evm.bytecode",
1519
- "evm.deployedBytecode", "evm.methodIdentifiers", "evm.assembly"]
1526
+ "evm.deployedBytecode", "evm.methodIdentifiers", "evm.assembly",
1527
+ "evm.bytecode.functionDebugData"]
1520
1528
  ast_selection = ["id", "ast"]
1521
1529
  elif compiler_collector_lang == CompilerLangVy():
1522
1530
  with open(contract_file_posix_abs) as f:
@@ -1739,6 +1747,10 @@ class CertoraBuildGenerator:
1739
1747
  sdc_name = f"{Path(build_arg_contract_file).name}_{file_index}"
1740
1748
  compilation_path = self.get_compilation_path(sdc_name)
1741
1749
  self.file_to_sdc_name[Util.abs_norm_path(build_arg_contract_file)] = sdc_name
1750
+
1751
+ compiler_collector = self.compiler_coll_factory \
1752
+ .get_compiler_collector(Path(path_for_compiler_collector_file))
1753
+
1742
1754
  # update remappings and collect_cmd:
1743
1755
  if not is_vyper:
1744
1756
  Util.safe_create_dir(compilation_path)
@@ -1786,8 +1798,10 @@ class CertoraBuildGenerator:
1786
1798
  compiler_logger.debug(f"collect_cmd: {collect_cmd}\n")
1787
1799
  else:
1788
1800
  compiler_ver_to_run = get_relevant_compiler(Path(build_arg_contract_file), self.context)
1789
-
1790
- collect_cmd = f'{compiler_ver_to_run} -p "{self.context.solc_allow_path}" -o "{compilation_path}" ' \
1801
+ path_string = ""
1802
+ if compiler_collector.compiler_version[1] < 4:
1803
+ path_string = f' -p "{self.context.solc_allow_path}"'
1804
+ collect_cmd = f'{compiler_ver_to_run}{path_string} -o "{compilation_path}" ' \
1791
1805
  f'--standard-json'
1792
1806
 
1793
1807
  # Make sure compilation artifacts are always deleted
@@ -1800,9 +1814,6 @@ class CertoraBuildGenerator:
1800
1814
  # (we do not try to save a big chain history of changes, just a previous and current)
1801
1815
  self.backup_compiler_outputs(sdc_name, smart_contract_lang, "prev")
1802
1816
 
1803
- compiler_collector = self.compiler_coll_factory \
1804
- .get_compiler_collector(Path(path_for_compiler_collector_file))
1805
-
1806
1817
  # Standard JSON
1807
1818
  remappings = [] if isinstance(compiler_collector, CompilerCollectorYul) else self.context.remappings
1808
1819
  input_for_solc = self.standard_json(Path(file_abs_path), build_arg_contract_file, remappings,
@@ -2246,6 +2257,14 @@ class CertoraBuildGenerator:
2246
2257
  build_arg_contract_file)
2247
2258
  immutables = self.collect_immutables(contract_data, build_arg_contract_file, compiler_lang)
2248
2259
 
2260
+ internal_function_entrypoints = set([])
2261
+
2262
+ if compiler_lang == CompilerLangSol() and "functionDebugData" in contract_data["evm"]["deployedBytecode"]:
2263
+ debug = contract_data["evm"]["deployedBytecode"]["functionDebugData"]
2264
+ for (_, v) in debug.items():
2265
+ if "entryPoint" in v and v["entryPoint"] is not None:
2266
+ internal_function_entrypoints.add(v["entryPoint"])
2267
+
2249
2268
  if self.context.internal_funcs is not None:
2250
2269
  all_internal_functions: Dict[str, Any] = \
2251
2270
  Util.read_json_file(self.context.internal_funcs)
@@ -2296,7 +2315,8 @@ class CertoraBuildGenerator:
2296
2315
  extension_contracts=list(),
2297
2316
  local_assignments={},
2298
2317
  branches={},
2299
- requires={}
2318
+ requires={},
2319
+ internal_starts=list(internal_function_entrypoints)
2300
2320
  )
2301
2321
 
2302
2322
  @staticmethod
@@ -2527,7 +2547,7 @@ class CertoraBuildGenerator:
2527
2547
  sources_from_pre_finder_SDCs |= sdc.all_contract_files
2528
2548
  sources = self.collect_sources(context, certora_verify_generator, sources_from_pre_finder_SDCs)
2529
2549
  try:
2530
- self.cwd_rel_in_sources = build_source_tree(sources, context)
2550
+ build_source_tree(sources, context)
2531
2551
  except Exception as e:
2532
2552
  build_logger.debug(f"build_source_tree failed. Sources: {sources}", exc_info=e)
2533
2553
  raise
@@ -2652,6 +2672,169 @@ class CertoraBuildGenerator:
2652
2672
  self.handle_links()
2653
2673
  self.handle_struct_links()
2654
2674
  self.handle_contract_extensions()
2675
+ self.handle_storage_extension_harnesses()
2676
+
2677
+ def handle_storage_extension_harnesses(self) -> None:
2678
+ def new_field_of_node(ext_instance: Any, node: Dict[str, Any]) -> Optional[Dict[str, Any]]:
2679
+ """
2680
+ If this node is a storage variable declaration like the following:
2681
+
2682
+ /** @custom:certoralink 0x12345 */
2683
+ T x;
2684
+
2685
+ and if so, return a storage field matching the type of x but using 0x12345 as the slot
2686
+ """
2687
+
2688
+ if node.get("nodeType") != "VariableDeclaration":
2689
+ return None
2690
+ if not node.get("stateVariable"):
2691
+ return None
2692
+ if "documentation" not in node:
2693
+ return None
2694
+ if node["documentation"].get("nodeType") != "StructuredDocumentation":
2695
+ return None
2696
+ annot_link = node["documentation"].get("text")
2697
+ var_name = node.get("name")
2698
+ if var_name is None or not isinstance(annot_link, str):
2699
+ return None
2700
+
2701
+ link_regex = r'@custom:certoralink\s+(0x[0-9a-fA-F]+)\s*'
2702
+ link_match = re.search(link_regex, annot_link)
2703
+
2704
+ if link_match is None:
2705
+ raise Util.CertoraUserInputError(f"Couldn't parse annotation in on "
2706
+ f"{ext_instance.original_file_name}.{var_name}: {annot_link}")
2707
+ try:
2708
+ link_slot = int(link_match.group(1), 16)
2709
+ except ValueError:
2710
+ raise Util.CertoraUserInputError(f"Invalid certoralink slot value in annotation on"
2711
+ f"{ext_instance.original_file_name}.{var_name}: {annot_link}")
2712
+ storage_field_info = None
2713
+ for field in ext_instance.storage_layout["storage"]:
2714
+ if field["label"] == var_name:
2715
+ storage_field_info = field
2716
+ break
2717
+ if storage_field_info is None:
2718
+ return None
2719
+ cloned_field = storage_field_info.copy()
2720
+ cloned_field["slot"] = str(link_slot)
2721
+ # Don't bother trying to uniquify the name
2722
+ cloned_field["label"] = var_name
2723
+ return cloned_field
2724
+
2725
+ def handle_one_extension(storage_ext: str) -> tuple[Any, str, List[Dict[str, Any]]] :
2726
+ """
2727
+ Get the extension contract plus the new fields
2728
+ """
2729
+ extension_sdc_cands = self.get_matching_sdc_names_from_SDCs(storage_ext)
2730
+ assert len(extension_sdc_cands) == 1, f"Zero or multiple possible contracts for {storage_ext}"
2731
+ extension_sdc_name = extension_sdc_cands[0]
2732
+ extension_sdc = self.SDCs.get(extension_sdc_name)
2733
+ assert extension_sdc and extension_sdc.primary_contract == storage_ext, f"Could not find SDC for {extension_sdc_name}"
2734
+ ext_instance = extension_sdc.find_contract(storage_ext)
2735
+ assert ext_instance is not None, f"Could not find extension contract {storage_ext}"
2736
+
2737
+ # let's try to find the AST now
2738
+ build_file_ast = None
2739
+ for (k, v) in self.asts.items():
2740
+ if ext_instance.original_file in v:
2741
+ build_file_ast = k
2742
+ break
2743
+ if build_file_ast is None:
2744
+ raise RuntimeError(f"Couldn't find file for storage extension file {storage_ext}")
2745
+
2746
+ contract_def_node = self.get_contract_def_node_ref(
2747
+ build_file_ast,
2748
+ ext_instance.original_file,
2749
+ storage_ext
2750
+ )
2751
+ def_node = self.asts[build_file_ast][ext_instance.original_file][contract_def_node]
2752
+ nodes = def_node.get("nodes")
2753
+ if not isinstance(nodes, list):
2754
+ raise RuntimeError(f"Couldn't find nodes for body of {storage_ext}")
2755
+
2756
+ new_fields = []
2757
+ for node in nodes:
2758
+ new_field = new_field_of_node(ext_instance, node)
2759
+ if new_field is not None:
2760
+ new_fields.append(new_field)
2761
+
2762
+ return (ext_instance, extension_sdc_name, new_fields)
2763
+
2764
+ def apply_extensions(target_contract: Any, extensions: Set[str], to_add: Dict[str, Tuple[List[Dict[str, Any]], Dict[str, Any]]]) -> None:
2765
+ """
2766
+ Apply the fields from each extension to the target contract,
2767
+ """
2768
+ if target_contract.storage_layout.get("storage") is None:
2769
+ target_contract.storage_layout["storage"] = []
2770
+ if target_contract.storage_layout.get("types") is None:
2771
+ target_contract.storage_layout["types"] = {}
2772
+ target_slots = {storage["slot"] for storage in target_contract.storage_layout["storage"]}
2773
+ target_vars = {storage["label"] for storage in target_contract.storage_layout["storage"]}
2774
+ # Keep track of slots we've added, and error if we
2775
+ # find two extensions extending the same slot
2776
+ added_slots: Dict[str, str] = {}
2777
+ added_vars: Dict[str, str] = {}
2778
+ for ext in extensions:
2779
+ (new_fields, new_types) = to_add[ext]
2780
+
2781
+ for f in new_fields:
2782
+ # See if any of the new fields is a slot or variable name we've already added
2783
+ slot = f["slot"]
2784
+ var = f["label"]
2785
+ if slot in added_slots:
2786
+ seen = added_slots[slot]
2787
+ raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} was already added by {seen}")
2788
+
2789
+ if var in added_vars:
2790
+ seen = added_vars[var]
2791
+ raise Util.CertoraUserInputError(f"Var '{var}' added to {target_contract.name} by {ext} was already added by {seen}")
2792
+
2793
+ if slot in target_slots:
2794
+ raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} is already mapped by {target_contract.name}")
2795
+
2796
+ if var in target_vars:
2797
+ raise Util.CertoraUserInputError(f"Var '{var}' added to {target_contract.name} by {ext} is already declared by {target_contract.name}")
2798
+
2799
+ added_slots[slot] = ext
2800
+ added_vars[var] = ext
2801
+
2802
+ target_contract.storage_layout["storage"].extend(new_fields)
2803
+
2804
+ for (new_id, new_ty) in new_types.items():
2805
+ if new_id in target_contract.storage_layout["types"]:
2806
+ continue
2807
+ target_contract.storage_layout["types"][new_id] = new_ty
2808
+
2809
+ extension_contracts: Set[str] = set()
2810
+ storage_extensions: Dict[str, Set[str]] = defaultdict(set)
2811
+ storage_ext = self.context.storage_extension_harnesses
2812
+
2813
+ if storage_ext is None:
2814
+ return
2815
+
2816
+ for i in storage_ext:
2817
+ target_contract, extension_contract = i.split("=")
2818
+ extension_contracts.add(extension_contract)
2819
+ storage_extensions[target_contract].add(extension_contract)
2820
+
2821
+ # These are the new fields to add for each contract
2822
+ extension_to_fields_and_types: Dict[str, Tuple[List[Dict[str, Any]], Dict[str, Any]]] = {}
2823
+
2824
+ for storage_ext in extension_contracts:
2825
+ (ext_instance, extension_sdc_name, new_fields) = handle_one_extension(storage_ext)
2826
+ new_types = ext_instance.storage_layout["types"]
2827
+ extension_to_fields_and_types[storage_ext] = (new_fields, {} if new_types is None else new_types)
2828
+ del self.SDCs[extension_sdc_name]
2829
+
2830
+ for (target, extensions) in storage_extensions.items():
2831
+ target_sdc_names = self.get_matching_sdc_names_from_SDCs(target)
2832
+ assert len(target_sdc_names) == 1, f"no matching sdc for {target}"
2833
+ target_sdc = target_sdc_names[0]
2834
+ sdc = self.SDCs[target_sdc]
2835
+ target_contract = sdc.find_contract(target)
2836
+ assert target_contract is not None, f"could not find contract for {target}"
2837
+ apply_extensions(target_contract, extensions, extension_to_fields_and_types)
2655
2838
 
2656
2839
  def finders_compilation_round(self,
2657
2840
  build_arg_contract_file: str,
@@ -2851,7 +3034,7 @@ class CertoraBuildGenerator:
2851
3034
  for k, v in autofinder_remappings.items():
2852
3035
  self.function_finder_file_remappings[Util.abs_posix_path(k)] = Util.abs_posix_path(v)
2853
3036
  new_sdcs = self.collect_for_file(new_file, i, get_compiler_lang(build_arg_contract_file),
2854
- Util.get_certora_sources_dir() / self.cwd_rel_in_sources,
3037
+ Util.get_certora_sources_dir() / self.context.cwd_rel_in_sources,
2855
3038
  path_for_compiler_collector_file,
2856
3039
  sdc_pre_finder,
2857
3040
  fail_on_compilation_error=False,
@@ -2879,7 +3062,7 @@ class CertoraBuildGenerator:
2879
3062
  contract_path = Util.abs_posix_path_obj(contract_file)
2880
3063
  rel_directory = Path(os.path.relpath(contract_file, '.')).parent
2881
3064
  contract_filename = contract_path.name
2882
- new_path = Util.get_certora_sources_dir() / self.cwd_rel_in_sources / rel_directory / contract_filename
3065
+ new_path = Util.get_certora_sources_dir() / self.context.cwd_rel_in_sources / rel_directory / contract_filename
2883
3066
  new_path.parent.mkdir(parents=True, exist_ok=True)
2884
3067
  return str(new_path)
2885
3068
 
@@ -2889,7 +3072,7 @@ class CertoraBuildGenerator:
2889
3072
  This assumes those paths can be related to cwd.
2890
3073
  """
2891
3074
  rel_to_cwd_path = Path(os.path.relpath(path, '.'))
2892
- new_path = Util.get_certora_sources_dir() / self.cwd_rel_in_sources / rel_to_cwd_path
3075
+ new_path = Util.get_certora_sources_dir() / self.context.cwd_rel_in_sources / rel_to_cwd_path
2893
3076
  return str(new_path.absolute())
2894
3077
 
2895
3078
  def handle_links(self) -> None:
@@ -2985,7 +3168,7 @@ class CertoraBuildGenerator:
2985
3168
  f"for contract extension")
2986
3169
  extension_contract = contracts_by_name[extension]
2987
3170
  for f in ext["exclude"]:
2988
- if extension_contract.find_method(f) is None:
3171
+ if not extension_contract.has_method_with_name(f):
2989
3172
  raise Util.CertoraUserInputError(f"Can't find a method named {f} in contract "
2990
3173
  f"{extension_contract.name}")
2991
3174
  extended_contract.add_extension(ContractExtension(extension_contract, ext["exclude"]))
@@ -3267,6 +3450,8 @@ class CertoraBuildGenerator:
3267
3450
  sources.add(Path(context.bytecode_spec))
3268
3451
  if context.yul_abi:
3269
3452
  sources.add(Path(context.yul_abi))
3453
+ if context.override_base_config:
3454
+ sources.add(Path(context.override_base_config))
3270
3455
 
3271
3456
  if hasattr(context, Attrs.EvmProverAttributes.PROVER_RESOURCE_FILES.get_conf_key()) \
3272
3457
  and context.prover_resource_files:
@@ -3295,20 +3480,19 @@ def sources_to_abs(sources: Set[Path]) -> Set[Path]:
3295
3480
  return result
3296
3481
 
3297
3482
 
3298
- def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bool = False) -> Path:
3483
+ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bool = False) -> None:
3299
3484
  """
3300
3485
  Copies files to .certora_sources
3301
- @returns the cwd relative in sources
3302
3486
  """
3303
3487
  sources = sources_to_abs(sources)
3304
- cwd_rel_in_sources, common_path = CertoraBuildGenerator.get_cwd_rel_in_sources(sources)
3488
+ context.cwd_rel_in_sources, context.common_path = CertoraBuildGenerator.get_cwd_rel_in_sources(sources)
3305
3489
 
3306
3490
  for source_path in sources:
3307
3491
  is_dir = source_path.is_dir()
3308
3492
  # copy file to the path of the file from the common root under the sources directory
3309
3493
 
3310
3494
  # make sure directory exists
3311
- target_path = Util.get_certora_sources_dir() / source_path.relative_to(common_path)
3495
+ target_path = Util.get_certora_sources_dir() / source_path.relative_to(context.common_path)
3312
3496
  target_directory = target_path if is_dir else target_path.parent
3313
3497
  try:
3314
3498
  target_directory.mkdir(parents=True, exist_ok=True)
@@ -3337,28 +3521,19 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3337
3521
  raise
3338
3522
 
3339
3523
  # the empty file .cwd is written in the source tree to denote the current working directory
3340
- if cwd_rel_in_sources != '.':
3341
- file_path = Util.get_certora_sources_dir() / cwd_rel_in_sources / Util.CWD_FILE
3342
- file_path.parent.mkdir(parents=True, exist_ok=True)
3343
- file_path.touch()
3344
-
3345
- """
3346
- Once the resource files are copied to the source tree, the paths in the value of the 'prover_resource_files'
3347
- attribute are replaced with the relative path of the resource file from the source tree root.
3348
- This way The server can easily find the resource files in the source tree. The path from the source tree is the
3349
- relative path of cwd from source tree root (most cases '.') concatenated with the relative path of the resource
3350
- file from cwd
3351
- """
3352
- if context.prover_resource_files:
3353
- new_value = []
3354
- len_orig = len(context.prover_resource_files)
3355
- for value in context.prover_resource_files:
3356
- label, file_path = value.split(':')
3357
- rel_path = Path(os.path.relpath(file_path, '.'))
3358
- new_value.append(':'.join([label, os.path.normpath(rel_path)]))
3359
- if len_orig != len(new_value):
3360
- raise RuntimeError(f"fail to process prover_resource_files {len_orig} out of {len(new_value)}")
3361
- context.prover_resource_files = new_value
3524
+ cwd_file_path = Util.get_certora_sources_dir() / context.cwd_rel_in_sources / Util.CWD_FILE
3525
+ cwd_file_path.parent.mkdir(parents=True, exist_ok=True)
3526
+ cwd_file_path.touch()
3527
+
3528
+ # the empty file .project_directory is written in the source tree to denote the projecct directory
3529
+ rust_proj_dir = getattr(context, 'rust_project_directory', None)
3530
+ if rust_proj_dir:
3531
+ proj_dir_parent_relative = os.path.relpath(rust_proj_dir, os.getcwd())
3532
+ assert Path(rust_proj_dir).is_dir(), f"build_source_tree: not a directory {rust_proj_dir}"
3533
+ proj_dir_parent_file = (Util.get_certora_sources_dir() / context.cwd_rel_in_sources / proj_dir_parent_relative /
3534
+ Util.PROJECT_DIR_FILE)
3535
+ proj_dir_parent_file.parent.mkdir(parents=True, exist_ok=True)
3536
+ proj_dir_parent_file.touch()
3362
3537
 
3363
3538
  # Copy the repro conf file to the sources as the overall way to reproduce this run easily.
3364
3539
  # The file is being copied in here and not added through collect_sources, because we want
@@ -3371,9 +3546,6 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3371
3546
  build_logger.debug("Couldn't copy repro conf to certora sources.", exc_info=e)
3372
3547
  raise
3373
3548
 
3374
- return cwd_rel_in_sources
3375
-
3376
-
3377
3549
  def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
3378
3550
  certora_verify_generator: CertoraVerifyGenerator,
3379
3551
  build_cache_enabled: bool) -> CachedFiles:
@@ -3516,8 +3688,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3516
3688
 
3517
3689
  # Start by syntax checking, if we're in the right mode
3518
3690
  if Cv.mode_has_spec_file(context) and not context.build_only and not ignore_spec_syntax_check:
3519
- attr = context.disable_local_typechecking
3520
- if attr:
3691
+ if context.disable_local_typechecking:
3521
3692
  build_logger.warning(
3522
3693
  "Local checks of CVL specification files disabled. It is recommended to enable the checks.")
3523
3694
  else:
@@ -3530,6 +3701,11 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3530
3701
  certora_verify_generator,
3531
3702
  certora_build_cache_manager)
3532
3703
 
3704
+ # avoid running the same test over and over again for each split run, context.split_rules is true only for
3705
+ # the first run and is set to [] for split runs
3706
+ if context.split_rules:
3707
+ Ctx.run_local_spec_check(True, context)
3708
+
3533
3709
  # .certora_verify.json is always constructed even if build cache is enabled
3534
3710
  # Sources construction should only happen when rebuilding
3535
3711
  # Build sources tree