certora-cli 7.29.2__tar.gz → 7.31.0__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 (87) hide show
  1. {certora_cli-7.29.2/certora_cli.egg-info → certora_cli-7.31.0}/PKG-INFO +3 -2
  2. certora_cli-7.31.0/README.md +1 -0
  3. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +11 -2
  4. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraBuild.py +224 -105
  5. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraBuildCacheManager.py +15 -16
  6. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraBuildRust.py +2 -1
  7. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraCloudIO.py +7 -27
  8. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +62 -51
  9. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraConfigIO.py +2 -1
  10. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraContext.py +29 -2
  11. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraContextAttributes.py +53 -9
  12. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraContextValidator.py +39 -61
  13. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraParseBuildScript.py +4 -3
  14. certora_cli-7.31.0/certora_cli/CertoraProver/erc7201.py +45 -0
  15. certora_cli-7.31.0/certora_cli/CertoraProver/storageExtension.py +386 -0
  16. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/EquivalenceCheck/Eq_default.conf +0 -1
  17. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -1
  18. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Mutate/mutateApp.py +10 -3
  19. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Shared/certoraUtils.py +22 -19
  20. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Shared/certoraValidateFuncs.py +5 -3
  21. certora_cli-7.31.0/certora_cli/Shared/proverCommon.py +302 -0
  22. certora_cli-7.31.0/certora_cli/Shared/rustProverCommon.py +62 -0
  23. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/certoraEVMProver.py +2 -1
  24. certora_cli-7.31.0/certora_cli/certoraRanger.py +39 -0
  25. certora_cli-7.31.0/certora_cli/certoraRun.py +218 -0
  26. certora_cli-7.31.0/certora_cli/certoraSolanaProver.py +98 -0
  27. certora_cli-7.31.0/certora_cli/certoraSorobanProver.py +92 -0
  28. {certora_cli-7.29.2 → certora_cli-7.31.0/certora_cli.egg-info}/PKG-INFO +3 -2
  29. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli.egg-info/SOURCES.txt +4 -0
  30. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli.egg-info/requires.txt +1 -0
  31. certora_cli-7.31.0/certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -0
  32. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_jars/Typechecker.jar +0 -0
  33. {certora_cli-7.29.2 → certora_cli-7.31.0}/setup.py +3 -3
  34. certora_cli-7.29.2/README.md +0 -1
  35. certora_cli-7.29.2/certora_cli/certoraRanger.py +0 -71
  36. certora_cli-7.29.2/certora_cli/certoraRun.py +0 -320
  37. certora_cli-7.29.2/certora_cli/certoraSolanaProver.py +0 -193
  38. certora_cli-7.29.2/certora_cli/certoraSorobanProver.py +0 -285
  39. certora_cli-7.29.2/certora_jars/CERTORA-CLI-VERSION-METADATA.json +0 -1
  40. {certora_cli-7.29.2 → certora_cli-7.31.0}/LICENSE +0 -0
  41. {certora_cli-7.29.2 → certora_cli-7.31.0}/MANIFEST.in +0 -0
  42. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_bins/__init__.py +0 -0
  43. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/Compiler/CompilerCollector.py +0 -0
  44. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorSol.py +0 -0
  45. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorSolBased.py +0 -0
  46. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +0 -0
  47. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +0 -0
  48. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/Compiler/__init__.py +0 -0
  49. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/__init__.py +0 -0
  50. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraBuildDataClasses.py +0 -0
  51. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraCollectRunMetadata.py +0 -0
  52. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraCompilerParameters.py +0 -0
  53. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraContextClass.py +0 -0
  54. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraContractFuncs.py +0 -0
  55. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraExtensionInfo.py +0 -0
  56. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraJobList.py +0 -0
  57. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraMiniSpecParser.py +0 -0
  58. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraNodeFilters.py +0 -0
  59. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraProjectScanner.py +0 -0
  60. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraSourceFinders.py +0 -0
  61. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraType.py +0 -0
  62. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/certoraVerifyGenerator.py +0 -0
  63. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/CertoraProver/splitRules.py +0 -0
  64. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/EquivalenceCheck/Eq_mc_no_out_template.spec +0 -0
  65. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/EquivalenceCheck/Eq_mc_template.spec +0 -0
  66. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/EquivalenceCheck/Eq_template.spec +0 -0
  67. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/EquivalenceCheck/__init__.py +0 -0
  68. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/EquivalenceCheck/equivCheck.py +0 -0
  69. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/EquivalenceCheck/sanity.spec +0 -0
  70. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Mutate/__init__.py +0 -0
  71. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Mutate/mutateAttributes.py +0 -0
  72. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Mutate/mutateConstants.py +0 -0
  73. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Mutate/mutateUtil.py +0 -0
  74. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Mutate/mutateValidate.py +0 -0
  75. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Shared/ExpectedComparator.py +0 -0
  76. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Shared/__init__.py +0 -0
  77. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Shared/certoraAttrUtil.py +0 -0
  78. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/Shared/certoraLogging.py +0 -0
  79. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/__init__.py +0 -0
  80. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/certoraEqCheck.py +0 -0
  81. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/certoraMutate.py +0 -0
  82. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli/rustMutator.py +0 -0
  83. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli.egg-info/dependency_links.txt +0 -0
  84. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli.egg-info/entry_points.txt +0 -0
  85. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_cli.egg-info/top_level.txt +0 -0
  86. {certora_cli-7.29.2 → certora_cli-7.31.0}/certora_jars/__init__.py +0 -0
  87. {certora_cli-7.29.2 → certora_cli-7.31.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: certora-cli
3
- Version: 7.29.2
3
+ Version: 7.31.0
4
4
  Summary: Runner for the Certora Prover
5
5
  Home-page: https://pypi.org/project/certora-cli
6
6
  Author: Certora
@@ -25,6 +25,7 @@ Requires-Dist: tqdm
25
25
  Requires-Dist: StrEnum
26
26
  Requires-Dist: universalmutator
27
27
  Requires-Dist: jinja2
28
+ Requires-Dist: wcmatch
28
29
  Dynamic: author
29
30
  Dynamic: author-email
30
31
  Dynamic: classifier
@@ -37,4 +38,4 @@ Dynamic: requires-dist
37
38
  Dynamic: requires-python
38
39
  Dynamic: summary
39
40
 
40
- Commit 58a3aa6. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
41
+ Commit 89159ec. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -0,0 +1 @@
1
+ Commit 89159ec. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -25,8 +25,9 @@ from CertoraProver.Compiler.CompilerCollector import CompilerLang, CompilerColle
25
25
  from CertoraProver.Compiler.CompilerCollectorSol import CompilerCollectorSol, CompilerLangSol
26
26
  from CertoraProver.Compiler.CompilerCollectorYul import CompilerLangYul, CompilerCollectorYul
27
27
  from CertoraProver.Compiler.CompilerCollectorVy import CompilerCollectorVy, CompilerLangVy
28
- from Shared.certoraUtils import match_path_to_mapping_key, remove_file, get_certora_config_dir
28
+ from Shared.certoraUtils import remove_file, get_certora_config_dir
29
29
  from CertoraProver.certoraContextClass import CertoraContext
30
+ import CertoraProver.certoraContext as Ctx
30
31
 
31
32
  # logger for running the Solidity compiler and reporting any errors it emits
32
33
  compiler_logger = logging.getLogger("compiler")
@@ -42,8 +43,16 @@ def get_relevant_compiler(contract_file_path: Path, context: CertoraContext) ->
42
43
 
43
44
  if contract_file_path.is_absolute():
44
45
  contract_file_path = Path(os.path.relpath(contract_file_path, Path.cwd()))
46
+
47
+ # If the contract file is in the sources directory, we want to use the relative path from the "cwd" in the source tree.
48
+ if Util.get_certora_sources_dir() in contract_file_path.parents:
49
+ cwd_in_source = Util.get_certora_sources_dir() / context.cwd_rel_in_sources
50
+ contract_file_path = Path(os.path.relpath(contract_file_path, cwd_in_source))
51
+
45
52
  if context.compiler_map:
46
- match = match_path_to_mapping_key(contract_file_path, context.compiler_map)
53
+ match = Ctx.get_map_attribute_value(context, contract_file_path, 'compiler')
54
+ assert isinstance(match, str), (f"In the attribute compiler_map, {contract_file_path} expected to be matched "
55
+ f"to a string, {match} is of type {type(match)} not a string")
47
56
  if match:
48
57
  return match
49
58
  else:
@@ -25,8 +25,11 @@ from collections import OrderedDict, defaultdict
25
25
  from enum import Enum
26
26
  from functools import lru_cache
27
27
  from pathlib import Path
28
- from typing import Any, Dict, List, Tuple, Optional, Set, Iterator, NoReturn
29
28
  from Crypto.Hash import keccak
29
+ import tempfile
30
+
31
+ from typing import Any, Dict, List, Tuple, Optional, Set, Iterator, NoReturn
32
+
30
33
 
31
34
  from CertoraProver.certoraBuildCacheManager import CertoraBuildCacheManager, CachedFiles
32
35
  from CertoraProver.certoraBuildDataClasses import CONTRACTS, ImmutableReference, ContractExtension, ContractInSDC, SDC, \
@@ -35,6 +38,7 @@ from CertoraProver.certoraCompilerParameters import SolcParameters
35
38
  from CertoraProver.certoraSourceFinders import add_source_finders
36
39
  from CertoraProver.certoraVerifyGenerator import CertoraVerifyGenerator
37
40
  from CertoraProver.certoraContractFuncs import Func, InternalFunc, STATEMUT, SourceBytes
41
+
38
42
  from Shared.certoraUtils import is_relative_to
39
43
 
40
44
  scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
@@ -54,7 +58,11 @@ from Shared import certoraValidateFuncs as Vf
54
58
  from CertoraProver import certoraContextValidator as Cv
55
59
  from Shared import certoraUtils as Util
56
60
  import CertoraProver.certoraContext as Ctx
57
-
61
+ from CertoraProver import storageExtension
62
+ from CertoraProver.storageExtension import (
63
+ NameSpacedStorage,
64
+ NewStorageInfo,
65
+ )
58
66
 
59
67
  BUILD_IS_LIBRARY = False
60
68
  AUTO_FINDER_PREFIX = "autoFinder_"
@@ -72,6 +80,7 @@ MUTANTS_LOCATION = "mutants_location"
72
80
 
73
81
  FunctionSig = Tuple[str, List[str], List[str], str]
74
82
 
83
+
75
84
  # logger for building the abstract syntax tree
76
85
  ast_logger = logging.getLogger("ast")
77
86
  # logger for issues calling/shelling out to external functions
@@ -84,12 +93,10 @@ build_logger = logging.getLogger("build_conf")
84
93
  # logger of the build cache
85
94
  build_cache_logger = logging.getLogger("build_cache")
86
95
 
87
-
88
96
  def fatal_error(logger: logging.Logger, msg: str) -> NoReturn:
89
97
  logger.fatal(msg)
90
98
  raise Exception(msg)
91
99
 
92
-
93
100
  class InputConfig:
94
101
  def __init__(self, context: CertoraContext) -> None:
95
102
  """
@@ -443,8 +450,12 @@ def convert_pathname_to_posix(json_dict: Dict[str, Any], entry: str, smart_contr
443
450
  if path_obj.is_file():
444
451
  json_dict_posix_paths[path_obj.as_posix()] = json_dict[entry][file_path]
445
452
  else:
453
+ json_dict_str = str(json_dict)
454
+ # protecting against long strings
455
+ if len(json_dict_str) > 200:
456
+ json_dict_str = json_dict_str[:200] + '...'
446
457
  fatal_error(compiler_logger, f"The path of the source file {file_path}"
447
- f"in the standard json file {json_dict} does not exist")
458
+ f"in the standard json file {json_dict_str} does not exist")
448
459
  json_dict[entry] = json_dict_posix_paths
449
460
 
450
461
 
@@ -1354,49 +1365,51 @@ class CertoraBuildGenerator:
1354
1365
 
1355
1366
  return bytecode
1356
1367
 
1357
- def _handle_via_ir(self, contract_file_path: str, settings_dict: Dict[str, Any]) -> None:
1358
- if not self.context.solc_via_ir_map and not self.context.solc_via_ir:
1359
- return
1360
- if self.context.solc_via_ir_map:
1361
- match = Util.match_path_to_mapping_key(Path(contract_file_path), self.context.solc_via_ir_map)
1362
- if match:
1363
- settings_dict["viaIR"] = match
1364
- elif self.context.solc_via_ir:
1368
+ def get_solc_via_ir_value(self, contract_file_path: Path) -> bool:
1369
+ match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_via_ir')
1370
+ assert isinstance(match, (bool, type(None))), f"Expected solc_via_ir to be bool or None, got {type(match)}"
1371
+ return bool(match)
1372
+
1373
+ def get_solc_evm_version_value(self, contract_file_path: Path) -> Optional[str]:
1374
+ match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_evm_version')
1375
+ assert isinstance(match, (str, type(None))), f"Expected solc_evm_version to be string or None, got {type(match)}"
1376
+ return match
1377
+
1378
+ def get_solc_optimize_value(self, contract_file_path: Path) -> Optional[str]:
1379
+ match = Ctx.get_map_attribute_value(self.context, contract_file_path, 'solc_optimize')
1380
+ assert isinstance(match, (str, type(None))), f"Expected solc_optimize to be string or None, got {type(match)}"
1381
+ return match
1382
+
1383
+ def _handle_via_ir(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
1384
+ if self.get_solc_via_ir_value(contract_file_path):
1365
1385
  settings_dict["viaIR"] = True
1366
1386
 
1367
- def _handle_evm_version(self, contract_file_path: str, settings_dict: Dict[str, Any]) -> None:
1368
- if self.context.solc_evm_version_map:
1369
- match = Util.match_path_to_mapping_key(Path(contract_file_path), self.context.solc_evm_version_map)
1370
- if match:
1371
- settings_dict["evmVersion"] = match
1372
- elif self.context.solc_evm_version:
1373
- settings_dict["evmVersion"] = self.context.solc_evm_version
1387
+ def _handle_evm_version(self, contract_file_path: Path, settings_dict: Dict[str, Any]) -> None:
1388
+ match = self.get_solc_evm_version_value(contract_file_path)
1389
+ if match:
1390
+ settings_dict["evmVersion"] = match
1374
1391
 
1375
- def _handle_optimize(self, contract_file_path: str, settings_dict: Dict[str, Any],
1392
+ def _handle_optimize(self, contract_file_path: Path, settings_dict: Dict[str, Any],
1376
1393
  compiler_collector: CompilerCollector) -> None:
1377
1394
  """
1378
1395
  @param contract_file_path: the contract that we are working on
1379
1396
  @param settings_dict: data structure for sending to the solc compiler
1380
1397
  """
1381
- if self.context.solc_optimize_map:
1382
- match = Util.match_path_to_mapping_key(Path(contract_file_path), self.context.solc_optimize_map)
1383
- if match and int(match) > 0:
1384
- settings_dict["optimizer"] = {"enabled": True}
1385
- settings_dict["optimizer"]['runs'] = int(match)
1386
- elif self.context.solc_optimize:
1398
+ match = self.get_solc_optimize_value(contract_file_path)
1399
+ if match:
1387
1400
  settings_dict["optimizer"] = {"enabled": True}
1388
- if int(self.context.solc_optimize) > 0:
1389
- settings_dict["optimizer"]['runs'] = int(self.context.solc_optimize)
1401
+ if int(match) > 0:
1402
+ settings_dict["optimizer"]['runs'] = int(match)
1390
1403
 
1391
1404
  # if optimizer is true, we should also define settings_dict["optimizer"]["details"]
1392
1405
  # for both optimize map and optimize
1393
1406
  optimizer = settings_dict.get("optimizer")
1394
1407
  if optimizer and isinstance(optimizer, dict) and optimizer.get('enabled'):
1395
1408
  # if we are not disabling finder friendly optimizer specifically, enable it whenever viaIR is also enabled
1396
- if not self.context.strict_solc_optimizer and self.context.solc_via_ir:
1409
+ if not self.context.strict_solc_optimizer and self.get_solc_via_ir_value(contract_file_path):
1397
1410
  # The default optimizer steps (taken from libsolidity/interface/OptimiserSettings.h) but with the
1398
1411
  # full inliner step removed
1399
- solc0_8_26_to_0_8_29 = ("dhfoDgvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLCTUca[r]LSsTFOtfDnca[r]" +
1412
+ solc0_8_26_to_0_8_30 = ("dhfoDgvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLCTUca[r]LSsTFOtfDnca[r]" +
1400
1413
  "IulcscCTUtx[scCTUt]TOntnfDIuljmul[jul]VcTOculjmul")
1401
1414
  solc0_8_13_to_0_8_25 = "dhfoDgvulfnTUtnIf[xa[r]EscLMcCTUtTOntnfDIulLculVcul[j]T" + \
1402
1415
  "peulxa[rul]xa[r]cLgvifCTUca[r]LSsTFOtfDnca[r]Iulc]jmul[jul]VcTOculjmul"
@@ -1426,6 +1439,8 @@ class CertoraBuildGenerator:
1426
1439
  raise Util.CertoraUserInputError(err_msg)
1427
1440
  elif minor < 6 or (minor == 6 and patch < 7):
1428
1441
  raise Util.CertoraUserInputError(err_msg)
1442
+ elif self.context.yul_optimizer_steps:
1443
+ yul_optimizer_steps = self.context.yul_optimizer_steps
1429
1444
  elif (minor == 6 and patch >= 7) or (minor == 7 and 0 <= patch <= 1):
1430
1445
  yul_optimizer_steps = solc0_6_7_to_0_7_1
1431
1446
  elif minor == 7 and 2 <= patch <= 5:
@@ -1440,8 +1455,8 @@ class CertoraBuildGenerator:
1440
1455
  yul_optimizer_steps = solc0_8_12
1441
1456
  elif minor == 8 and 13 <= patch <= 25:
1442
1457
  yul_optimizer_steps = solc0_8_13_to_0_8_25
1443
- elif minor == 8 and 26 <= patch <= 29:
1444
- yul_optimizer_steps = solc0_8_26_to_0_8_29
1458
+ elif minor == 8 and 26 <= patch <= 30:
1459
+ yul_optimizer_steps = solc0_8_26_to_0_8_30
1445
1460
  assert yul_optimizer_steps is not None, \
1446
1461
  'Yul Optimizer steps missing for requested Solidity version. Please contact Certora team.'
1447
1462
 
@@ -1463,7 +1478,7 @@ class CertoraBuildGenerator:
1463
1478
  for opt_pass in self.context.disable_solc_optimizers:
1464
1479
  settings_dict["optimizer"]["details"][opt_pass] = False
1465
1480
 
1466
- def _fill_codegen_related_options(self, contract_file_path: str, settings_dict: Dict[str, Any],
1481
+ def _fill_codegen_related_options(self, contract_file_path: Path, settings_dict: Dict[str, Any],
1467
1482
  compiler_collector: CompilerCollector) -> None:
1468
1483
  """
1469
1484
  Fills options that control how we call solc and affect the bytecode in some way
@@ -1544,7 +1559,7 @@ class CertoraBuildGenerator:
1544
1559
  }
1545
1560
  }
1546
1561
 
1547
- self._fill_codegen_related_options(contract_file_as_provided, settings_dict, compiler_collector)
1562
+ self._fill_codegen_related_options(Path(contract_file_as_provided), settings_dict, compiler_collector)
1548
1563
 
1549
1564
  result_dict = {"language": compiler_collector_lang.name, "sources": sources_dict, "settings": settings_dict}
1550
1565
  # debug_print("Standard json input")
@@ -2279,7 +2294,7 @@ class CertoraBuildGenerator:
2279
2294
 
2280
2295
  if compiler_lang == CompilerLangSol():
2281
2296
  settings_dict: Dict[str, Any] = {}
2282
- self._fill_codegen_related_options(build_arg_contract_file, settings_dict,
2297
+ self._fill_codegen_related_options(Path(build_arg_contract_file), settings_dict,
2283
2298
  compiler_collector_for_contract_file)
2284
2299
  solc_optimizer_on, solc_optimizer_runs = self.solc_setting_optimizer_runs(settings_dict)
2285
2300
  solc_via_ir = self.solc_setting_via_ir(settings_dict)
@@ -2409,7 +2424,7 @@ class CertoraBuildGenerator:
2409
2424
  # if no function finder mode set, determine based on viaIR enabled or not:
2410
2425
  if self.context.function_finder_mode is None:
2411
2426
  # in via-ir, should not compress
2412
- if self.context.solc_via_ir:
2427
+ if self.get_solc_via_ir_value(Path(contract_file)):
2413
2428
  should_compress = False
2414
2429
  else:
2415
2430
  should_compress = True
@@ -2539,7 +2554,6 @@ class CertoraBuildGenerator:
2539
2554
  Util.print_progress_message(f"Compiling {orig_file_name}...")
2540
2555
  sdc_pre_finders = self.collect_for_file(build_arg_contract_file, i, compiler_lang, Path(os.getcwd()),
2541
2556
  path_for_compiler_collector_file, original_sdc=None)
2542
-
2543
2557
  # Build sources tree
2544
2558
  build_logger.debug("Building source tree")
2545
2559
  sources_from_pre_finder_SDCs = set()
@@ -2672,8 +2686,154 @@ class CertoraBuildGenerator:
2672
2686
  self.handle_links()
2673
2687
  self.handle_struct_links()
2674
2688
  self.handle_contract_extensions()
2689
+ if self.context.storage_extension_annotation:
2690
+ self.handle_erc7201_annotations()
2675
2691
  self.handle_storage_extension_harnesses()
2676
2692
 
2693
+ def extract_slayout(self, original_file: str, ns_storage: Set[NameSpacedStorage]) -> NewStorageInfo:
2694
+ """
2695
+ Given a file containing a contract with namespaced storage, extract the storage information
2696
+ corresponding to the namespaced types.
2697
+
2698
+ Args:
2699
+ original_file: Path to the Solidity file containing namespaced storage declarations
2700
+ ns_storage: Set of tuples (type_name, namespace) for each namespaced storage
2701
+
2702
+ Returns:
2703
+ NewStorageInfo: A tuple (fields, types) where:
2704
+ - fields: List of new fields added by the namespaced storage
2705
+ - types: Dictionary of types referenced by the fields
2706
+ """
2707
+ file_dir = Path(original_file).parent
2708
+
2709
+ # Generate a unique name for the harness contract based on the contract names in the file
2710
+ harness_name = storageExtension.generate_harness_name(original_file)
2711
+
2712
+ with tempfile.NamedTemporaryFile(mode="w+t", suffix=".sol", dir=file_dir, delete=True) as tmp_file:
2713
+ # Import the original file.
2714
+ # Note we import and don't inline the file's contents since that's how the
2715
+ # original file is accessed also in the actual code, and compiling it
2716
+ # directly can cause issues.
2717
+ tmp_file.write(f"import \"{original_file}\";\n\n")
2718
+
2719
+ # Write the harness contract with dummy fields for each namespaced storage
2720
+ var_to_slot = storageExtension.write_harness_contract(tmp_file, harness_name, ns_storage)
2721
+ tmp_file.flush()
2722
+
2723
+ if self.context.extract_storage_extension_annotation:
2724
+ # If the flag is set, save the storage extension contract
2725
+ build_dir = Util.get_build_dir()
2726
+ shutil.copyfile(
2727
+ Path(tmp_file.name),
2728
+ build_dir / f"{Path(original_file).stem}_storage_extension.sol"
2729
+ )
2730
+
2731
+ # Add harness to compiler map
2732
+ storageExtension.add_harness_to_compiler_map(
2733
+ original_file,
2734
+ tmp_file,
2735
+ self.context
2736
+ )
2737
+
2738
+ # normalize the path exactly the way collect_for_file expects it:
2739
+ abs_path = Util.abs_posix_path(tmp_file.name)
2740
+ self.context.file_to_contract[abs_path] = {harness_name}
2741
+
2742
+ try:
2743
+ # Compile & fetch the raw storage_layout
2744
+ compile_idx = storageExtension.get_next_file_index(self.file_to_sdc_name)
2745
+ sdcs = self.collect_for_file(tmp_file.name, compile_idx, CompilerLangSol(), Path.cwd(), Util.abs_posix_path(tmp_file.name), None)
2746
+ if not sdcs:
2747
+ raise RuntimeError(f"Failed to compile harness contract for {tmp_file}")
2748
+ layout = storageExtension.extract_harness_contract_layout(sdcs, harness_name)
2749
+
2750
+ # Remap each slot according to the ERC-7201 namespace
2751
+ remapped_fields = storageExtension.remapped_fields_from_layout(layout, var_to_slot)
2752
+
2753
+ return (remapped_fields, layout.get('types', {}))
2754
+
2755
+ except Exception as e:
2756
+ build_logger.error(f"Error extracting storage layout for {original_file}: {str(e)}")
2757
+ raise
2758
+ finally:
2759
+ # Delete the key from the context
2760
+ self.context.file_to_contract.pop(abs_path, None)
2761
+
2762
+ def handle_erc7201_annotations(self) -> None:
2763
+ """
2764
+ Look for contracts that use erc-7201 namespaced storage layout
2765
+ (see https://eips.ethereum.org/EIPS/eip-7201).
2766
+
2767
+ Find contracts A s.t. A contain a type declaration with such an annotation, e.g.
2768
+ /** @custom:storage-location erc-7201:some.name.space */
2769
+ struct T { ... }
2770
+
2771
+ Then, for any contract C that has A as a base contract, _extend_ C's storage layout
2772
+ information such that it contains the information for a `T` at the slot
2773
+ erc-7201(some.name.space) as defined in the EIP.
2774
+ """
2775
+ # Find all erc7201-like contracts, generate+compile a harness & extract layout information
2776
+ # maps (path,contract) -> new storage info added by (path,contract)
2777
+ slayouts: Dict[Tuple[str, str], NewStorageInfo] = {}
2778
+
2779
+ # Scan all of the contracts (including dependencies of targets) for namespaced storage
2780
+ # layout information
2781
+ for target_file in self.context.file_paths:
2782
+ if target_file not in self.asts:
2783
+ # No AST for this file, so we can't do anything
2784
+ continue
2785
+ for (imported_file, imported_file_ast) in self.asts[target_file].items():
2786
+ for def_node in imported_file_ast.values():
2787
+ if def_node.get("nodeType") != "ContractDefinition":
2788
+ continue
2789
+
2790
+ # Construct a key for the contract definition node
2791
+ contract_name = def_node.get("name")
2792
+ key = (imported_file, contract_name)
2793
+ if key in slayouts:
2794
+ # We already have this contract's storage layout information
2795
+ continue
2796
+
2797
+ # Collect any @custom:storage-location annotations
2798
+ ns_storage = storageExtension.get_namespace_storage_from_ast(def_node)
2799
+
2800
+ if not ns_storage:
2801
+ # No namespaced storage found in this contract
2802
+ continue
2803
+
2804
+ # Now that we have all the storage layout information, extract it once
2805
+ slayouts[key] = self.extract_slayout(imported_file, ns_storage)
2806
+
2807
+ if self.context.test == str(Util.TestValue.STORAGE_EXTENSION_LAYOUT):
2808
+ raise Util.TestResultsReady(slayouts)
2809
+
2810
+ if not slayouts:
2811
+ # No contracts with namespaced storage found
2812
+ return
2813
+
2814
+ # Finally, extend each target contract with the storage layout info from
2815
+ # all of its base contracts
2816
+ for target in self.get_primary_contracts_from_sdcs():
2817
+ if target.name not in self.context.contract_to_file:
2818
+ # This is a contract that was not compiled, so we don't have a file for it
2819
+ continue
2820
+ target_file = self.context.contract_to_file[target.name]
2821
+ base_contracts = self.retrieve_base_contracts_list(
2822
+ target_file,
2823
+ Util.abs_posix_path(target_file),
2824
+ target.name
2825
+ )
2826
+ extensions: Set[str] = set()
2827
+ harnesses: Dict[str, NewStorageInfo] = {}
2828
+ for base in base_contracts:
2829
+ layout = slayouts.get((base[0], base[1]))
2830
+ if layout is not None:
2831
+ extensions.add(base[1])
2832
+ harnesses[base[1]] = layout
2833
+ else:
2834
+ build_logger.warning(f"Could not find storage layout for {base[1]} in {base[0]}")
2835
+ storageExtension.apply_extensions(target, extensions, harnesses)
2836
+
2677
2837
  def handle_storage_extension_harnesses(self) -> None:
2678
2838
  def new_field_of_node(ext_instance: Any, node: Dict[str, Any]) -> Optional[Dict[str, Any]]:
2679
2839
  """
@@ -2760,52 +2920,6 @@ class CertoraBuildGenerator:
2760
2920
  new_fields.append(new_field)
2761
2921
 
2762
2922
  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
2923
  extension_contracts: Set[str] = set()
2810
2924
  storage_extensions: Dict[str, Set[str]] = defaultdict(set)
2811
2925
  storage_ext = self.context.storage_extension_harnesses
@@ -2834,7 +2948,7 @@ class CertoraBuildGenerator:
2834
2948
  sdc = self.SDCs[target_sdc]
2835
2949
  target_contract = sdc.find_contract(target)
2836
2950
  assert target_contract is not None, f"could not find contract for {target}"
2837
- apply_extensions(target_contract, extensions, extension_to_fields_and_types)
2951
+ storageExtension.apply_extensions(target_contract, extensions, extension_to_fields_and_types)
2838
2952
 
2839
2953
  def finders_compilation_round(self,
2840
2954
  build_arg_contract_file: str,
@@ -3436,8 +3550,9 @@ class CertoraBuildGenerator:
3436
3550
  raise Util.CertoraUserInputError(f"collect_sources: {path_to_file} does not exist cwd - {Path.cwd()}."
3437
3551
  f"abs - {os.path.normpath(Path.cwd() / path_to_file)}")
3438
3552
 
3439
- sources = set()
3440
- sources |= sources_from_SDCs
3553
+ sources = set(sources_from_SDCs)
3554
+ if context.files:
3555
+ sources.update(Path(p) for p in context.files) # all files in "files" attribute are uploaded
3441
3556
  sources |= certora_verify_generator.get_spec_files()
3442
3557
  if Util.PACKAGE_FILE.exists():
3443
3558
  add_to_sources(Util.PACKAGE_FILE)
@@ -3507,7 +3622,7 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3507
3622
 
3508
3623
  try:
3509
3624
  if overwrite:
3510
- # expecting target path to exist.
3625
+ # expecting the target path to exist.
3511
3626
  if target_path.exists():
3512
3627
  build_logger.debug(f"Overwriting {target_path} by copying from {source_path}")
3513
3628
  else:
@@ -3525,7 +3640,7 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3525
3640
  cwd_file_path.parent.mkdir(parents=True, exist_ok=True)
3526
3641
  cwd_file_path.touch()
3527
3642
 
3528
- # the empty file .project_directory is written in the source tree to denote the projecct directory
3643
+ # the empty file .project_directory is written in the source tree to denote the project directory
3529
3644
  rust_proj_dir = getattr(context, 'rust_project_directory', None)
3530
3645
  if rust_proj_dir:
3531
3646
  proj_dir_parent_relative = os.path.relpath(rust_proj_dir, os.getcwd())
@@ -3546,7 +3661,8 @@ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bo
3546
3661
  build_logger.debug("Couldn't copy repro conf to certora sources.", exc_info=e)
3547
3662
  raise
3548
3663
 
3549
- def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
3664
+ def build_from_scratch(context: CertoraContext,
3665
+ certora_build_generator: CertoraBuildGenerator,
3550
3666
  certora_verify_generator: CertoraVerifyGenerator,
3551
3667
  build_cache_enabled: bool) -> CachedFiles:
3552
3668
  """
@@ -3572,9 +3688,16 @@ def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
3572
3688
  may_store_in_build_cache = True
3573
3689
  absolute_sources_dir = Util.get_certora_sources_dir().absolute()
3574
3690
  for sdc in certora_build_generator.SDCs.values():
3691
+
3692
+ # add to cache also source files that were found in the SDCs (e.g., storage extensions)
3693
+ paths_set = sdc.all_contract_files
3694
+ for f in context.files:
3695
+ path = f.split(':')[0] # `f` is either 'path/to/file.sol' or 'path/to/file.sol:ContractName'
3696
+ paths_set.add(Path(path).absolute())
3697
+
3575
3698
  # the contract files in SDCs are relative to .certora_sources. Which isn't good for us here.
3576
3699
  # Need to be relative to original paths
3577
- for f in sdc.all_contract_files:
3700
+ for f in paths_set:
3578
3701
  if is_relative_to(f, absolute_sources_dir):
3579
3702
  rel_f = f.relative_to(absolute_sources_dir)
3580
3703
  else:
@@ -3610,8 +3733,7 @@ def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
3610
3733
 
3611
3734
  def build_from_cache_or_scratch(context: CertoraContext,
3612
3735
  certora_build_generator: CertoraBuildGenerator,
3613
- certora_verify_generator: CertoraVerifyGenerator,
3614
- certora_build_cache_manager: CertoraBuildCacheManager) \
3736
+ certora_verify_generator: CertoraVerifyGenerator) \
3615
3737
  -> Tuple[bool, bool, CachedFiles]:
3616
3738
  """
3617
3739
  Builds either from cache (fast path) or from scratch (slow path)
@@ -3623,23 +3745,23 @@ def build_from_cache_or_scratch(context: CertoraContext,
3623
3745
  cached_files: Optional[CachedFiles] = None
3624
3746
 
3625
3747
  if not context.build_cache:
3626
- cached_files = build_from_scratch(certora_build_generator,
3748
+ cached_files = build_from_scratch(context, certora_build_generator,
3627
3749
  certora_verify_generator,
3628
3750
  False)
3629
3751
  return cache_hit, False, cached_files
3630
3752
 
3631
- build_cache_applicable = certora_build_cache_manager.cache_is_applicable(context)
3753
+ build_cache_applicable = CertoraBuildCacheManager.cache_is_applicable(context)
3632
3754
 
3633
3755
  if not build_cache_applicable:
3634
- build_cache_disabling_options = certora_build_cache_manager.cache_disabling_options(context)
3756
+ build_cache_disabling_options = CertoraBuildCacheManager.cache_disabling_options(context)
3635
3757
  build_logger.warning("Requested to enable the build cache, but the build cache is not applicable "
3636
3758
  f"to this run because of the given options: {build_cache_disabling_options}")
3637
- cached_files = build_from_scratch(certora_build_generator,
3759
+ cached_files = build_from_scratch(context, certora_build_generator,
3638
3760
  certora_verify_generator,
3639
3761
  False)
3640
3762
  return cache_hit, False, cached_files
3641
3763
 
3642
- cached_files = certora_build_cache_manager.build_from_cache(context)
3764
+ cached_files = CertoraBuildCacheManager.build_from_cache(context)
3643
3765
  # if no match, will rebuild from scratch
3644
3766
  if cached_files is not None:
3645
3767
  # write to .certora_build.json
@@ -3660,7 +3782,7 @@ def build_from_cache_or_scratch(context: CertoraContext,
3660
3782
  cache_hit = True
3661
3783
  else:
3662
3784
  # rebuild
3663
- cached_files = build_from_scratch(certora_build_generator,
3785
+ cached_files = build_from_scratch(context, certora_build_generator,
3664
3786
  certora_verify_generator,
3665
3787
  True)
3666
3788
 
@@ -3678,7 +3800,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3678
3800
 
3679
3801
  try:
3680
3802
  input_config = InputConfig(context)
3681
-
3803
+ context.main_cache_key = CertoraBuildCacheManager.get_main_cache_key(context)
3682
3804
  # Create generators
3683
3805
  certora_build_generator = CertoraBuildGenerator(input_config, context)
3684
3806
 
@@ -3694,12 +3816,9 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3694
3816
  else:
3695
3817
  Ctx.run_local_spec_check(False, context)
3696
3818
 
3697
- certora_build_cache_manager = CertoraBuildCacheManager()
3698
-
3699
3819
  cache_hit, build_cache_enabled, cached_files = build_from_cache_or_scratch(context,
3700
3820
  certora_build_generator,
3701
- certora_verify_generator,
3702
- certora_build_cache_manager)
3821
+ certora_verify_generator)
3703
3822
 
3704
3823
  # avoid running the same test over and over again for each split run, context.split_rules is true only for
3705
3824
  # the first run and is set to [] for split runs
@@ -3719,7 +3838,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3719
3838
 
3720
3839
  # save in build cache
3721
3840
  if not cache_hit and build_cache_enabled and cached_files.may_store_in_build_cache:
3722
- certora_build_cache_manager.save_build_cache(context, cached_files)
3841
+ CertoraBuildCacheManager.save_build_cache(context, cached_files)
3723
3842
 
3724
3843
  certora_verify_generator.update_certora_verify_struct(True)
3725
3844
  certora_verify_generator.dump() # second dump with properly rooted specs