certora-cli-beta-mirror 7.28.0__py3-none-any.whl → 8.5.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 (54) hide show
  1. certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +10 -3
  2. certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +51 -16
  3. certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +3 -0
  4. certora_cli/CertoraProver/castingInstrumenter.py +192 -0
  5. certora_cli/CertoraProver/certoraApp.py +52 -0
  6. certora_cli/CertoraProver/certoraBuild.py +694 -207
  7. certora_cli/CertoraProver/certoraBuildCacheManager.py +21 -17
  8. certora_cli/CertoraProver/certoraBuildDataClasses.py +8 -2
  9. certora_cli/CertoraProver/certoraBuildRust.py +88 -54
  10. certora_cli/CertoraProver/certoraBuildSui.py +112 -0
  11. certora_cli/CertoraProver/certoraCloudIO.py +97 -96
  12. certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +230 -84
  13. certora_cli/CertoraProver/certoraCollectRunMetadata.py +52 -6
  14. certora_cli/CertoraProver/certoraCompilerParameters.py +11 -0
  15. certora_cli/CertoraProver/certoraConfigIO.py +43 -35
  16. certora_cli/CertoraProver/certoraContext.py +128 -54
  17. certora_cli/CertoraProver/certoraContextAttributes.py +415 -234
  18. certora_cli/CertoraProver/certoraContextValidator.py +152 -105
  19. certora_cli/CertoraProver/certoraContractFuncs.py +34 -1
  20. certora_cli/CertoraProver/certoraParseBuildScript.py +8 -10
  21. certora_cli/CertoraProver/certoraType.py +10 -1
  22. certora_cli/CertoraProver/certoraVerifyGenerator.py +22 -4
  23. certora_cli/CertoraProver/erc7201.py +45 -0
  24. certora_cli/CertoraProver/splitRules.py +23 -18
  25. certora_cli/CertoraProver/storageExtension.py +351 -0
  26. certora_cli/EquivalenceCheck/Eq_default.conf +0 -1
  27. certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -1
  28. certora_cli/EquivalenceCheck/equivCheck.py +2 -1
  29. certora_cli/Mutate/mutateApp.py +41 -22
  30. certora_cli/Mutate/mutateAttributes.py +11 -0
  31. certora_cli/Mutate/mutateValidate.py +42 -2
  32. certora_cli/Shared/certoraAttrUtil.py +21 -5
  33. certora_cli/Shared/certoraUtils.py +180 -60
  34. certora_cli/Shared/certoraValidateFuncs.py +68 -26
  35. certora_cli/Shared/proverCommon.py +308 -0
  36. certora_cli/certoraCVLFormatter.py +76 -0
  37. certora_cli/certoraConcord.py +39 -0
  38. certora_cli/certoraEVMProver.py +4 -3
  39. certora_cli/certoraRanger.py +39 -0
  40. certora_cli/certoraRun.py +83 -223
  41. certora_cli/certoraSolanaProver.py +40 -128
  42. certora_cli/certoraSorobanProver.py +59 -4
  43. certora_cli/certoraSuiProver.py +93 -0
  44. certora_cli_beta_mirror-8.5.0.dist-info/LICENSE +15 -0
  45. {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/METADATA +21 -5
  46. certora_cli_beta_mirror-8.5.0.dist-info/RECORD +81 -0
  47. {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/WHEEL +1 -1
  48. {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/entry_points.txt +3 -0
  49. certora_jars/ASTExtraction.jar +0 -0
  50. certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
  51. certora_jars/Typechecker.jar +0 -0
  52. certora_cli_beta_mirror-7.28.0.dist-info/LICENSE +0 -22
  53. certora_cli_beta_mirror-7.28.0.dist-info/RECORD +0 -70
  54. {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/top_level.txt +0 -0
@@ -66,10 +66,9 @@ class CertoraBuildCacheManager:
66
66
  @returns None if no cache hit, otherwise - the matching `.certora_build.json` file, and a set of source files
67
67
  """
68
68
  build_cache_dir = Util.get_certora_build_cache_dir()
69
- main_cache_key = CertoraBuildCacheManager.get_main_cache_key(context)
70
- main_cache_entry_dir = build_cache_dir / main_cache_key
69
+ main_cache_entry_dir = build_cache_dir / context.main_cache_key
71
70
  if not main_cache_entry_dir.exists():
72
- build_cache_logger.info(f"cache miss on build cache key {main_cache_key}")
71
+ build_cache_logger.info(f"cache miss on build cache key {context.main_cache_key}")
73
72
  return None
74
73
 
75
74
  # here's the tricky matching part:
@@ -92,7 +91,7 @@ class CertoraBuildCacheManager:
92
91
  build_cache_logger.debug(f"Current sub build cache key computed for {file_list_file} is invalid")
93
92
  continue
94
93
  elif sub_cache_key_current_for_list == sub_cache_key_from_saved_entry:
95
- build_cache_logger.info(f"We have a match on build cache key {main_cache_key} and "
94
+ build_cache_logger.info(f"We have a match on build cache key {context.main_cache_key} and "
96
95
  f"{sub_cache_key_current_for_list}")
97
96
  sub_cache_key = sub_cache_key_current_for_list
98
97
  all_contract_files = set([Path(f) for f in file_list])
@@ -103,24 +102,24 @@ class CertoraBuildCacheManager:
103
102
 
104
103
  if sub_cache_key is None:
105
104
  build_cache_logger.info("All sub-cache-key file list files missed, cache miss on build cache key "
106
- f"{main_cache_key}")
105
+ f"{context.main_cache_key}")
107
106
  return None
108
107
 
109
108
  # cache hit
110
109
  certora_build_file = main_cache_entry_dir / f"{sub_cache_key}.{CachedFiles.certora_build_suffix}"
111
110
  if not certora_build_file.exists():
112
- build_cache_logger.warning(f"Got a cache-hit on build cache key {main_cache_key} and {sub_cache_key} "
111
+ build_cache_logger.warning(f"Got a cache-hit on build cache key {context.main_cache_key} and {sub_cache_key} "
113
112
  "but .certora_build.json file does not exist")
114
113
  return None
115
114
 
116
115
  if all_contract_files is None: # should not be feasible
117
- build_cache_logger.warning(f"Got a cache-hit on build cache key {main_cache_key} and {sub_cache_key} "
118
- "but file list file does not exist")
116
+ build_cache_logger.warning(f"Got a cache-hit on build cache key {context.main_cache_key} and "
117
+ f"{sub_cache_key} but file list file does not exist")
119
118
  return None
120
119
 
121
120
  build_output_props_file = main_cache_entry_dir / f"{sub_cache_key}.{CachedFiles.build_output_props_suffix}"
122
121
  if not build_output_props_file.exists():
123
- build_cache_logger.warning(f"Got a cache-hit on build cache key {main_cache_key} and {sub_cache_key} "
122
+ build_cache_logger.warning(f"Got a cache-hit on build cache key {context.main_cache_key} and {sub_cache_key} "
124
123
  f"but {CachedFiles.build_output_props_suffix} file does not exist")
125
124
  return None
126
125
 
@@ -131,26 +130,26 @@ class CertoraBuildCacheManager:
131
130
  @staticmethod
132
131
  def save_build_cache(context: CertoraContext, cached_files: CachedFiles) -> None:
133
132
  build_cache_dir = Util.get_certora_build_cache_dir()
134
- main_cache_key = CertoraBuildCacheManager.get_main_cache_key(context)
135
- main_cache_entry_dir = build_cache_dir / main_cache_key
133
+ main_cache_entry_dir = build_cache_dir / context.main_cache_key
136
134
  sub_cache_key = CertoraBuildCacheManager.get_sub_cache_key(cached_files.all_contract_files)
137
135
  if sub_cache_key is None:
138
- build_cache_logger.warning(f"Cannot save cache for main build cache key {main_cache_key} "
136
+ build_cache_logger.warning(f"Cannot save cache for main build cache key {context.main_cache_key} "
139
137
  "as sub-cache-key could not be computed")
140
138
  return
141
139
 
142
140
  if main_cache_entry_dir.exists():
143
- build_cache_logger.info(f"main build cache key already exists {main_cache_key}, "
141
+ build_cache_logger.info(f"main build cache key already exists {context.main_cache_key}, "
144
142
  f"saving sub build cache key {sub_cache_key}")
145
143
  if cached_files.all_exist(main_cache_entry_dir, sub_cache_key):
146
144
  build_cache_logger.debug("cache already saved under this build cache key, override")
147
145
  else:
148
- build_cache_logger.debug(f"cache was corrupted, need to re-save build cache key {main_cache_key} "
149
- f"and sub cache key {sub_cache_key}")
146
+ build_cache_logger.debug(f"cache was corrupted, need to re-save build cache key "
147
+ f"{context.main_cache_key} and sub cache key {sub_cache_key}")
150
148
  CertoraBuildCacheManager.save_files(cached_files, main_cache_entry_dir,
151
149
  sub_cache_key)
152
150
  else:
153
- build_cache_logger.info(f"saving main build cache key {main_cache_key} and sub cache key {sub_cache_key}")
151
+ build_cache_logger.info(f"saving main build cache key {context.main_cache_key} and sub cache key "
152
+ f"{sub_cache_key}")
154
153
  safe_create_dir(main_cache_entry_dir)
155
154
  CertoraBuildCacheManager.save_files(cached_files, main_cache_entry_dir,
156
155
  sub_cache_key)
@@ -180,7 +179,12 @@ class CertoraBuildCacheManager:
180
179
  trg = trg_path_with_additional_included_files / post_autofinder_dir.name
181
180
  if post_autofinder_dir != trg:
182
181
  if post_autofinder_dir.is_dir():
183
- Util.safe_copy_folder(post_autofinder_dir, trg, shutil.ignore_patterns())
182
+ # if we match on the cache, and we run a few certoraRun-s in parallel,
183
+ # it could be this folder already exists. But the exact contents do not matter and are likely to
184
+ # agree with the exception of spec files. So let's merge the folders so that the
185
+ # sources are in any case runnable
186
+ if not trg.exists():
187
+ Util.safe_merge_folder(post_autofinder_dir, trg, shutil.ignore_patterns())
184
188
  else:
185
189
  # highly unlikely .post_autofinder.[digit] will be a file and not a directory,
186
190
  # but would rather not crash and future-proof instead
@@ -107,7 +107,8 @@ class ContractInSDC:
107
107
  extension_contracts: List[ContractExtension],
108
108
  local_assignments: Dict[str, UnspecializedSourceFinder],
109
109
  branches: Dict[str, UnspecializedSourceFinder],
110
- requires: Dict[str, UnspecializedSourceFinder]
110
+ requires: Dict[str, UnspecializedSourceFinder],
111
+ internal_starts: List[int]
111
112
  ):
112
113
  self.name = name
113
114
  self.original_file = source_file
@@ -139,6 +140,7 @@ class ContractInSDC:
139
140
  self.original_file_name = Path(source_file).name
140
141
  self.compiler_collector = compiler_collector
141
142
  self.compiler_parameters = compiler_parameters
143
+ self.internal_starts = internal_starts
142
144
 
143
145
  if not self.compiler_collector:
144
146
  compiler_version = ""
@@ -161,6 +163,7 @@ class ContractInSDC:
161
163
  self.local_assignments = local_assignments
162
164
  self.branches = branches
163
165
  self.requires = requires
166
+ self.internal_function_harnesses: Dict[str, str] = {}
164
167
 
165
168
  def as_dict(self) -> Dict[str, Any]:
166
169
  """
@@ -186,6 +189,7 @@ class ContractInSDC:
186
189
  # why are we using this and not the normal list of functions?
187
190
  "internalFunctions": {key: method.as_dict() for key, method in self.function_finders.items()},
188
191
  # this doesn't even have all functions
192
+ "internalFuncs": [f.as_dict() for f in self.internal_funcs],
189
193
  "allMethods": [f.as_dict() for f in self.all_funcs],
190
194
  "solidityTypes": [x.as_dict() for x in self.types],
191
195
  "compilerName": "" if not self.compiler_collector else self.compiler_collector.compiler_name,
@@ -193,7 +197,9 @@ class ContractInSDC:
193
197
  "compilerParameters": None if not self.compiler_parameters else self.compiler_parameters.as_dict(),
194
198
  "sourceBytes": None if self.source_bytes is None else self.source_bytes.as_dict(),
195
199
  "extensionContracts": [e.as_dict() for e in self.extension_contracts],
196
- "localAssignments": {k: v.as_dict() for k, v in self.local_assignments.items()}
200
+ "localAssignments": {k: v.as_dict() for k, v in self.local_assignments.items()},
201
+ "internalFunctionStarts": self.internal_starts,
202
+ "internalFunctionHarnesses": self.internal_function_harnesses
197
203
  }
198
204
  # "sourceHints": {"localAssignments": {k: v.as_dict() for k, v in self.local_assignments.items()},
199
205
  # "branches": {k: v.as_dict() for k, v in self.branches.items()},
@@ -13,58 +13,97 @@
13
13
  # You should have received a copy of the GNU General Public License
14
14
  # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
15
 
16
+ from __future__ import annotations
17
+
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
22
+ sys.path.insert(0, str(scripts_dir_path))
23
+
16
24
  import glob
17
25
  import shutil
26
+ import time
27
+ import logging
18
28
  from pathlib import Path
19
- from typing import Set
29
+ from typing import Set, Dict
20
30
 
21
31
  from CertoraProver.certoraBuild import build_source_tree
22
32
  from CertoraProver.certoraContextClass import CertoraContext
23
- from CertoraProver.certoraParseBuildScript import run_script_and_parse_json
33
+ from CertoraProver.certoraParseBuildScript import run_rust_build
24
34
  import CertoraProver.certoraContextAttributes as Attrs
25
35
  from Shared import certoraUtils as Util
26
36
 
27
37
 
28
- def build_rust_app(context: CertoraContext) -> None:
29
- if context.build_script:
30
- run_script_and_parse_json(context)
31
- if not context.rust_executables:
32
- raise Util.CertoraUserInputError("failed to get target executable")
38
+ log = logging.getLogger(__name__)
39
+
33
40
 
34
- sources: Set[Path] = set()
35
- collect_files_from_rust_sources(context, sources)
41
+ def build_rust_project(context: CertoraContext, timings: Dict) -> None:
42
+ """
43
+ Compile the Rust artefact and record elapsed time in *timings*.
36
44
 
37
- try:
38
- # Create generators
39
- build_source_tree(sources, context)
45
+ Args:
46
+ context: The CertoraContext object containing the configuration.
47
+ timings: A dictionary to store timing information.
48
+ """
49
+ log.debug("Build Rust target")
50
+ start = time.perf_counter()
51
+ set_rust_build_directory(context)
52
+ timings["buildTime"] = round(time.perf_counter() - start, 4)
53
+ if context.test == str(Util.TestValue.AFTER_BUILD):
54
+ raise Util.TestResultsReady(context)
40
55
 
41
- copy_files_to_build_dir(context)
42
56
 
43
- except Exception as e:
44
- raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
45
- else:
46
- if not context.files:
47
- raise Util.CertoraUserInputError("'files' or 'build_script' must be set for Rust projects")
48
- if len(context.files) > 1:
49
- raise Util.CertoraUserInputError("Rust projects must specify exactly one executable in 'files'.")
57
+ def set_rust_build_directory(context: CertoraContext) -> None:
58
+ if not context.files:
59
+ build_rust_app(context)
50
60
 
51
- try:
52
- Util.get_certora_sources_dir().mkdir(parents=True, exist_ok=True)
53
- shutil.copy(Util.get_last_conf_file(), Util.get_certora_sources_dir() / Util.LAST_CONF_FILE)
54
- except Exception as e:
55
- raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
61
+ copy_files_to_build_dir(context)
56
62
 
57
- context.rust_executables = context.files[0]
63
+ sources: Set[Path] = set()
64
+ collect_files_from_rust_sources(context, sources)
58
65
 
66
+ try:
67
+ # Create generators
68
+ build_source_tree(sources, context)
59
69
 
60
- def add_solana_files(context: CertoraContext, sources: Set[Path]) -> None:
61
- for attr in [Attrs.SolanaProverAttributes.SOLANA_INLINING, Attrs.SolanaProverAttributes.SOLANA_SUMMARIES]:
70
+ except Exception as e:
71
+ raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
72
+
73
+
74
+ def build_rust_app(context: CertoraContext) -> None:
75
+ assert not context.files, "build_rust_app: expecting files to be empty"
76
+ if context.build_script:
77
+ build_command = [context.build_script, '--json', '-l']
78
+ feature_flag = '--cargo_features'
79
+ else: # cargo
80
+ build_command = ["cargo", "certora-sbf", '--json']
81
+ feature_flag = '--features'
82
+ if context.cargo_tools_version:
83
+ build_command.extend(["--tools-version", context.cargo_tools_version])
84
+ context.rust_project_directory = Util.find_nearest_cargo_toml()
85
+
86
+ if context.cargo_features is not None:
87
+ build_command.append(feature_flag)
88
+ build_command.append(' '.join(context.cargo_features))
89
+
90
+ if context.test == str(Util.TestValue.SOLANA_BUILD_CMD):
91
+ raise Util.TestResultsReady(build_command)
92
+
93
+ run_rust_build(context, build_command)
94
+
95
+
96
+ def add_solana_files_to_sources(context: CertoraContext, sources: Set[Path]) -> None:
97
+ for attr in [Attrs.SolanaProverAttributes.SOLANA_INLINING,
98
+ Attrs.SolanaProverAttributes.SOLANA_SUMMARIES,
99
+ Attrs.SolanaProverAttributes.BUILD_SCRIPT,
100
+ Attrs.SolanaProverAttributes.FILES]:
62
101
  attr_name = attr.get_conf_key()
63
102
  attr_value = getattr(context, attr_name, None)
64
103
  if not attr_value:
65
104
  continue
66
105
  if isinstance(attr_value, str):
67
- attr_value = [str]
106
+ attr_value = [attr_value]
68
107
  if not isinstance(attr_value, list):
69
108
  raise Util.CertoraUserInputError(f"{attr_value} is not a valid value for {attr_name} {attr_value}. Value "
70
109
  f"must be a string or a llist ")
@@ -72,44 +111,39 @@ def add_solana_files(context: CertoraContext, sources: Set[Path]) -> None:
72
111
  for file_path in file_paths:
73
112
  if not file_path.exists():
74
113
  raise Util.CertoraUserInputError(f"in {attr_name} file {file_path} does not exist")
75
- sources.add(file_path.absolute())
114
+ sources.add(file_path.absolute().resolve())
76
115
 
77
116
 
78
117
  def collect_files_from_rust_sources(context: CertoraContext, sources: Set[Path]) -> None:
79
- patterns = ["*.rs", "*.so", "*.wasm", "Cargo.toml", "Cargo.lock", "justfile"]
118
+ patterns = ["*.rs", "*.so", "*.wasm", Util.CARGO_TOML_FILE, "Cargo.lock", "justfile"]
80
119
  exclude_dirs = [".certora_internal"]
81
120
 
82
- root_directory = Path(context.rust_project_directory)
121
+ if hasattr(context, 'rust_project_directory'):
122
+ project_directory = Path(context.rust_project_directory)
83
123
 
84
- if not root_directory.is_dir():
85
- raise ValueError(f"The given directory '{root_directory}' is not valid.")
124
+ if not project_directory.is_dir():
125
+ raise ValueError(f"The given directory '{project_directory}' is not valid.")
86
126
 
87
- for source in context.rust_sources:
88
- for file in glob.glob(f'{root_directory.joinpath(source)}', recursive=True):
89
- file_path = Path(file)
90
- if any(excluded in file_path.parts for excluded in exclude_dirs):
91
- continue
92
- if file_path.is_file() and any(file_path.match(pattern) for pattern in patterns):
93
- sources.add(file_path)
127
+ for source in context.rust_sources:
128
+ for file in glob.glob(f'{project_directory.joinpath(source)}', recursive=True):
129
+ file_path = Path(file)
130
+ if any(excluded in file_path.parts for excluded in exclude_dirs):
131
+ continue
132
+ if file_path.is_file() and any(file_path.match(pattern) for pattern in patterns):
133
+ sources.add(file_path)
94
134
 
95
- sources.add(Path(context.rust_project_directory).absolute())
96
- if Path(context.build_script).exists():
97
- sources.add(Path(context.build_script).resolve())
135
+ sources.add(project_directory.absolute())
136
+ if context.build_script:
137
+ sources.add(Path(context.build_script).resolve())
98
138
  if getattr(context, 'conf_file', None) and Path(context.conf_file).exists():
99
139
  sources.add(Path(context.conf_file).absolute())
100
- add_solana_files(context, sources)
101
140
 
141
+ add_solana_files_to_sources(context, sources)
102
142
 
103
- def copy_files_to_build_dir(context: CertoraContext) -> None:
104
- rust_executable = Path(context.rust_project_directory) / context.rust_executables
105
- shutil.copyfile(rust_executable, Util.get_build_dir() / rust_executable.name)
106
-
107
- additional_files = (getattr(context, 'solana_inlining', None) or []) + \
108
- (getattr(context, 'solana_summaries', None) or [])
109
143
 
110
- for file in additional_files:
111
- file_path = Path(file).resolve()
112
- shutil.copy(file_path, Util.get_build_dir() / file_path.name)
144
+ def copy_files_to_build_dir(context: CertoraContext) -> None:
145
+ assert context.files, "copy_files_to_build_dir: expecting files to be non-empty"
146
+ shutil.copyfile(context.files[0], Util.get_build_dir() / Path(context.files[0]).name)
113
147
 
114
148
  if rust_logs := getattr(context, 'rust_logs_stdout', None):
115
149
  shutil.copy(Path(rust_logs), Util.get_build_dir() / Path(rust_logs).name)
@@ -0,0 +1,112 @@
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
+ from __future__ import annotations
17
+
18
+ import subprocess
19
+ import sys
20
+ from pathlib import Path
21
+
22
+ scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
23
+ sys.path.insert(0, str(scripts_dir_path))
24
+
25
+ import shutil
26
+ import time
27
+ import logging
28
+ from pathlib import Path
29
+ from typing import Set, Dict
30
+
31
+ from CertoraProver.certoraBuild import build_source_tree
32
+ from CertoraProver.certoraContextClass import CertoraContext
33
+ from Shared import certoraUtils as Util
34
+
35
+
36
+ log = logging.getLogger(__name__)
37
+
38
+
39
+ def build_sui_project(context: CertoraContext, timings: Dict) -> None:
40
+ """
41
+ Compile the Sui artefact and record elapsed time in *timings*.
42
+
43
+ Args:
44
+ context: The CertoraContext object containing the configuration.
45
+ timings: A dictionary to store timing information.
46
+ """
47
+ log.debug("Build Sui target")
48
+ start = time.perf_counter()
49
+ set_sui_build_directory(context)
50
+ timings["buildTime"] = round(time.perf_counter() - start, 4)
51
+ if context.test == str(Util.TestValue.AFTER_BUILD):
52
+ raise Util.TestResultsReady(context)
53
+
54
+
55
+ def set_sui_build_directory(context: CertoraContext) -> None:
56
+ sources: Set[Path] = set()
57
+
58
+ # If no move_path was specified, try to build the package
59
+ if not context.move_path:
60
+ move_toml_file = Util.find_file_in_parents("Move.toml")
61
+ if not move_toml_file:
62
+ raise Util.CertoraUserInputError("Could not find Move.toml, and no move_path was specified.")
63
+ sources.add(move_toml_file.absolute())
64
+ run_sui_build(context, move_toml_file.parent)
65
+
66
+ assert context.move_path, "expecting move_path to be set after build"
67
+ move_dir = Path(context.move_path)
68
+ assert move_dir.exists(), f"Output path '{move_dir}' does not exist"
69
+ assert move_dir.is_dir(), f"Output path '{move_dir}' is not a directory"
70
+
71
+ # Add all source files. We get these from the Sui build output, because it includes dependencies as well, and is
72
+ # available even if we didn't run the build ourselves.
73
+ sources.update(move_dir.rglob("*.move"))
74
+
75
+ # Add conf file if it exists
76
+ if getattr(context, 'conf_file', None) and Path(context.conf_file).exists():
77
+ sources.add(Path(context.conf_file).absolute())
78
+
79
+ # Copy the binary modules and source maps
80
+ shutil.copytree(move_dir,
81
+ Util.get_build_dir() / move_dir.name,
82
+ ignore=shutil.ignore_patterns('*.move'))
83
+
84
+ try:
85
+ # Create generators
86
+ build_source_tree(sources, context)
87
+
88
+ except Exception as e:
89
+ raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
90
+
91
+ def run_sui_build(context: CertoraContext, package_dir: Path) -> None:
92
+ assert not context.move_path, "run_sui_build: expecting move_path to be empty"
93
+
94
+ build_cmd = ["sui", "move", "build", "--test", "--path", str(package_dir)]
95
+
96
+ try:
97
+ build_cmd_text = ' '.join(build_cmd)
98
+ log.info(f"Building by calling `{build_cmd_text}`")
99
+ result = subprocess.run(build_cmd, capture_output=False)
100
+
101
+ # Check if the script executed successfully
102
+ if result.returncode != 0:
103
+ raise Util.CertoraUserInputError(f"Error running `{build_cmd_text}`")
104
+
105
+ context.move_path = str(package_dir / "build")
106
+
107
+ except Util.TestResultsReady as e:
108
+ raise e
109
+ except Util.CertoraUserInputError as e:
110
+ raise e
111
+ except Exception as e:
112
+ raise Util.CertoraUserInputError(f"An unexpected error occurred: {e}")