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.
- certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +10 -3
- certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +51 -16
- certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +3 -0
- certora_cli/CertoraProver/castingInstrumenter.py +192 -0
- certora_cli/CertoraProver/certoraApp.py +52 -0
- certora_cli/CertoraProver/certoraBuild.py +694 -207
- certora_cli/CertoraProver/certoraBuildCacheManager.py +21 -17
- certora_cli/CertoraProver/certoraBuildDataClasses.py +8 -2
- certora_cli/CertoraProver/certoraBuildRust.py +88 -54
- certora_cli/CertoraProver/certoraBuildSui.py +112 -0
- certora_cli/CertoraProver/certoraCloudIO.py +97 -96
- certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +230 -84
- certora_cli/CertoraProver/certoraCollectRunMetadata.py +52 -6
- certora_cli/CertoraProver/certoraCompilerParameters.py +11 -0
- certora_cli/CertoraProver/certoraConfigIO.py +43 -35
- certora_cli/CertoraProver/certoraContext.py +128 -54
- certora_cli/CertoraProver/certoraContextAttributes.py +415 -234
- certora_cli/CertoraProver/certoraContextValidator.py +152 -105
- certora_cli/CertoraProver/certoraContractFuncs.py +34 -1
- certora_cli/CertoraProver/certoraParseBuildScript.py +8 -10
- certora_cli/CertoraProver/certoraType.py +10 -1
- certora_cli/CertoraProver/certoraVerifyGenerator.py +22 -4
- certora_cli/CertoraProver/erc7201.py +45 -0
- certora_cli/CertoraProver/splitRules.py +23 -18
- certora_cli/CertoraProver/storageExtension.py +351 -0
- certora_cli/EquivalenceCheck/Eq_default.conf +0 -1
- certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -1
- certora_cli/EquivalenceCheck/equivCheck.py +2 -1
- certora_cli/Mutate/mutateApp.py +41 -22
- certora_cli/Mutate/mutateAttributes.py +11 -0
- certora_cli/Mutate/mutateValidate.py +42 -2
- certora_cli/Shared/certoraAttrUtil.py +21 -5
- certora_cli/Shared/certoraUtils.py +180 -60
- certora_cli/Shared/certoraValidateFuncs.py +68 -26
- certora_cli/Shared/proverCommon.py +308 -0
- certora_cli/certoraCVLFormatter.py +76 -0
- certora_cli/certoraConcord.py +39 -0
- certora_cli/certoraEVMProver.py +4 -3
- certora_cli/certoraRanger.py +39 -0
- certora_cli/certoraRun.py +83 -223
- certora_cli/certoraSolanaProver.py +40 -128
- certora_cli/certoraSorobanProver.py +59 -4
- certora_cli/certoraSuiProver.py +93 -0
- certora_cli_beta_mirror-8.5.0.dist-info/LICENSE +15 -0
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/METADATA +21 -5
- certora_cli_beta_mirror-8.5.0.dist-info/RECORD +81 -0
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/WHEEL +1 -1
- {certora_cli_beta_mirror-7.28.0.dist-info → certora_cli_beta_mirror-8.5.0.dist-info}/entry_points.txt +3 -0
- certora_jars/ASTExtraction.jar +0 -0
- certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -1
- certora_jars/Typechecker.jar +0 -0
- certora_cli_beta_mirror-7.28.0.dist-info/LICENSE +0 -22
- certora_cli_beta_mirror-7.28.0.dist-info/RECORD +0 -70
- {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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
29
|
-
|
|
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
|
-
|
|
35
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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 = [
|
|
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",
|
|
118
|
+
patterns = ["*.rs", "*.so", "*.wasm", Util.CARGO_TOML_FILE, "Cargo.lock", "justfile"]
|
|
80
119
|
exclude_dirs = [".certora_internal"]
|
|
81
120
|
|
|
82
|
-
|
|
121
|
+
if hasattr(context, 'rust_project_directory'):
|
|
122
|
+
project_directory = Path(context.rust_project_directory)
|
|
83
123
|
|
|
84
|
-
|
|
85
|
-
|
|
124
|
+
if not project_directory.is_dir():
|
|
125
|
+
raise ValueError(f"The given directory '{project_directory}' is not valid.")
|
|
86
126
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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}")
|