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
@@ -18,11 +18,14 @@ import sys
18
18
  import re
19
19
  from pathlib import Path
20
20
  import subprocess
21
+ import tempfile
21
22
  import uuid
22
23
 
23
24
  from typing import List, Set, Optional
24
25
 
26
+ import CertoraProver.certoraContext as Ctx
25
27
  import CertoraProver.certoraContextAttributes as Attrs
28
+ import CertoraProver.certoraApp as App
26
29
  from CertoraProver.certoraContextClass import CertoraContext
27
30
  from Shared import certoraUtils as Util
28
31
 
@@ -71,31 +74,33 @@ class SplitRulesHandler():
71
74
  def jar_list_value(list_attr: List[str]) -> str:
72
75
  return ','.join(list_attr)
73
76
 
74
- path_to_typechecker = Util.find_jar("Typechecker.jar")
77
+ with tempfile.NamedTemporaryFile("r", dir=Util.get_build_dir()) as tmp_file:
78
+ args = ["-listRules", tmp_file.name]
75
79
 
76
- command = ["java", "-jar", str(path_to_typechecker), "-listRules", "-buildDirectory", str(Util.get_build_dir())]
80
+ if self.context.exclude_rule:
81
+ args += ['-excludeRule', jar_list_value(self.context.exclude_rule)]
77
82
 
78
- if self.context.exclude_rule:
79
- command += ['-excludeRule', jar_list_value(self.context.exclude_rule)]
83
+ if not split_rules and self.context.rule:
84
+ args += ['-rule', jar_list_value(self.context.rule)]
85
+ elif split_rules and self.context.split_rules:
86
+ args += ['-rule', jar_list_value(self.context.split_rules)]
80
87
 
81
- if not split_rules and self.context.rule:
82
- command += ['-rule', jar_list_value(self.context.rule)]
83
- elif split_rules and self.context.split_rules:
84
- command += ['-rule', jar_list_value(self.context.split_rules)]
85
- try:
88
+ try:
89
+ Ctx.run_local_spec_check(False, self.context, args, print_errors=False)
90
+ lines = tmp_file.read().split("\n")
91
+ return set(lines)
86
92
 
87
- result = subprocess.run(command, capture_output=True, text=True, check=True)
88
- lines = result.stdout.strip().split("\n")
89
- filtered_lines = [s for s in lines if not (s.startswith("Warning:") or " " in s)]
90
- return set(filtered_lines)
91
-
92
- except subprocess.CalledProcessError as e:
93
- raise Util.CertoraUserInputError(f"Failed to get {'split ' if split_rules else ''}rules\ncommand: {command}\n{e}")
93
+ except Exception as e:
94
+ raise Util.CertoraUserInputError(f"Failed to get {'split ' if split_rules else ''}rules\n{e}")
94
95
 
95
96
  def run_commands(self) -> int:
96
97
  rule_flag = Attrs.EvmProverAttributes.RULE.get_flag()
97
98
  split_rules_flag = Attrs.EvmProverAttributes.SPLIT_RULES.get_flag()
98
99
  msg_flag = Attrs.CommonAttributes.MSG.get_flag()
100
+
101
+ # it is important to use the cache, when the difference between the runs is only the rules that apply
102
+ build_cache_flag = Attrs.EvmProverAttributes.BUILD_CACHE.get_flag()
103
+
99
104
  group_id_flag = Attrs.EvmProverAttributes.GROUP_ID.get_flag()
100
105
  disable_local_typechecking_flag = Attrs.EvmProverAttributes.DISABLE_LOCAL_TYPECHECKING.get_flag()
101
106
 
@@ -118,7 +123,7 @@ class SplitRulesHandler():
118
123
  if called as library then if running in local mode we use certoraRun.py otherwise certoraRun (from package)
119
124
  :return:
120
125
  """
121
- assert Attrs.is_evm_app(), "Split rules is supported only for EVM apps"
126
+ assert self.context.app == App.EvmApp, "Split rules is supported only for EVM apps"
122
127
  if hasattr(self.context, 'prover_cmd'):
123
128
  return self.context.prover_cmd
124
129
  if self.context.local:
@@ -137,7 +142,7 @@ class SplitRulesHandler():
137
142
  self.context.msg = ''
138
143
 
139
144
  cmd = [get_cmd()] + args + [group_id_flag, self.context.group_id, disable_local_typechecking_flag,
140
- split_rules_flag]
145
+ build_cache_flag, split_rules_flag]
141
146
 
142
147
  if self.split_rules:
143
148
  for rule in self.split_rules:
@@ -0,0 +1,351 @@
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
+ import re
17
+ import time
18
+ import hashlib
19
+ import sys
20
+ from pathlib import Path
21
+ import logging
22
+ from typing import Any, Dict, List, Optional, Set, Tuple
23
+ try:
24
+ from typing import TypeAlias
25
+ except ImportError:
26
+ from typing_extensions import TypeAlias
27
+
28
+ scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
29
+ sys.path.insert(0, str(scripts_dir_path))
30
+
31
+ from CertoraProver.certoraBuildDataClasses import ContractInSDC
32
+ from CertoraProver import erc7201
33
+ from Shared import certoraUtils as Util
34
+ from CertoraProver.certoraBuildDataClasses import SDC
35
+
36
+ NameSpacedStorage: TypeAlias = Tuple[str, str]
37
+ NewStorageFields = List[Dict[str, Any]]
38
+ NewStorageTypes = Dict[str, Any]
39
+ NewStorageInfo = Tuple[NewStorageFields, NewStorageTypes]
40
+
41
+ storage_extension_logger = logging.getLogger("storage_extension")
42
+
43
+
44
+ def erc7201_of_node(n: Dict[str, Any]) -> Optional[NameSpacedStorage]:
45
+ """
46
+ If n is a StructDefinition node, try and extract the @custom:storage-location
47
+ structured documentation, if it exists.
48
+
49
+ @returns (type, namespace) where 'type' is the name of the annotated type and
50
+ 'namespace' is the namespace string
51
+ """
52
+ if n.get("nodeType") != "StructDefinition":
53
+ return None
54
+ typeName = n.get("canonicalName")
55
+ doc = n.get("documentation")
56
+ if doc is None or doc.get("nodeType") != "StructuredDocumentation" or typeName is None:
57
+ return None
58
+ storage_location_regex = r'@custom:storage-location erc7201:([a-zA-Z.0-9]+)'
59
+ match = re.search(storage_location_regex, doc.get("text"))
60
+ if match is None:
61
+ return None
62
+ ns = match.group(1)
63
+ return (typeName, ns)
64
+
65
+
66
+ def generate_harness_name(original_file: str) -> str:
67
+ """
68
+ Generate a unique name for the harness contract based on the original file name.
69
+ The name is generated by hashing the original file name and appending a suffix to ensure uniqueness.
70
+ Args:
71
+ original_file (str): The path to the original file used to generate the harness name.
72
+ Returns:
73
+ str: A unique name for the harness contract.
74
+ """
75
+ stem = Path(original_file).stem
76
+ # 1) Compute an 8-hex salt from path+time
77
+ digest = hashlib.sha1(f"{original_file}{time.time()}".encode()).hexdigest()[:8]
78
+ suffix = f"_{digest}_Harness"
79
+ # 2) Reserve space for suffix so whole name ≤31 chars
80
+ max_stem = 31 - len(suffix)
81
+ if len(stem) > max_stem:
82
+ stem = stem[:max_stem]
83
+ # 3) Build, sanitize, and ensure start‐with‐letter
84
+ raw = f"{stem}{suffix}"
85
+ name = re.sub(r'[^A-Za-z0-9_]', '_', raw)
86
+ if not name[0].isalpha():
87
+ name = f"H{name[1:]}"
88
+ return name
89
+
90
+
91
+ def get_next_file_index(self_file_to_sdc_name: Dict[Path, str], max_index: int = 1000) -> int:
92
+ """
93
+ Gets the next available file index for temporary files to avoid naming conflicts.
94
+
95
+ This method examines the existing SDC names in `file_to_sdc_name` dictionary and
96
+ extracts numeric indices from them. It identifies the highest index currently in use
97
+ and returns that value plus one. If no valid indices are found or if an error occurs,
98
+ a default value of 1000 is returned.
99
+
100
+ The method assumes SDC names follow the format "some_prefix_NUMBER", where NUMBER
101
+ is an integer that can be extracted from the last segment after splitting by underscore.
102
+ Args:
103
+ self_file_to_sdc_name (Dict[Path, str]): A dictionary mapping file paths to their SDC names.
104
+ max_index (int): The maximum index to return if no valid indices are found. Default is 1000.
105
+ Returns:
106
+ int: The next available file index, or a default value of 1000 if no valid indices are found.
107
+ """
108
+ try:
109
+ # If file_to_sdc_name is empty, return a default starting index
110
+ if not self_file_to_sdc_name:
111
+ return max_index
112
+
113
+ indices = []
114
+ for sdc_name in self_file_to_sdc_name.values():
115
+ parts = sdc_name.split("_")
116
+ if len(parts) > 1: # Make sure there's at least one underscore
117
+ try:
118
+ # Try to convert the last part to an integer
119
+ index = int(parts[-1])
120
+ indices.append(index)
121
+ except ValueError:
122
+ # Skip if the last part isn't a number
123
+ continue
124
+
125
+ # If we found no valid indices, return a default value
126
+ if not indices:
127
+ storage_extension_logger.debug(f"No valid indices found in file_to_sdc_name, using default value of {max_index}")
128
+ return max_index
129
+
130
+ return max(indices) + 1
131
+ except Exception:
132
+ # Fallback in case of any unexpected errors
133
+ storage_extension_logger.debug(f"Error determining next file index, using default value of {max_index}")
134
+ return max_index
135
+
136
+
137
+ def write_harness_contract(tmp_file: Any,
138
+ harness_name: str,
139
+ ns_storage: Set[NameSpacedStorage]) -> Dict[str, str]:
140
+ """
141
+ Write the harness contract with dummy fields to the temporary file.
142
+
143
+ Args:
144
+ tmp_file: The temporary file to write to
145
+ harness_name: Name of the harness contract
146
+ ns_storage: Set of namespace storage declarations
147
+
148
+ Returns:
149
+ Dict[str, str]: Mapping from variable names to their slots
150
+ """
151
+ tmp_file.write(f"contract {harness_name} {{\n")
152
+
153
+ # Map from variable names to their slots
154
+ var_to_slot = {}
155
+
156
+ # Add dummy fields for each namespaced storage
157
+ for type_name, namespace in ns_storage:
158
+ # Create a variable name by replacing dots with underscores and appending the hash
159
+ # Add a prefix to ensure the variable name is valid in Solidity (e.g., no leading digits)
160
+ var_name = f"ext_{namespace.replace('.', '_')}"
161
+
162
+ # Calculate the slot using ERC-7201 formula
163
+ # UTF-8 is the standard encoding for Ethereum and Solidity
164
+ slot = str(erc7201.erc7201(namespace.encode('utf-8')))
165
+
166
+ var_to_slot[var_name] = slot
167
+ tmp_file.write(f"\t{type_name} {var_name};\n")
168
+
169
+ tmp_file.write("}\n")
170
+ return var_to_slot
171
+
172
+
173
+ def extract_harness_contract_layout(sdcs: List[SDC], harness_name: str) -> Dict[str, Any]:
174
+ """
175
+ Extract the storage layout of the harness contract.
176
+
177
+ Args:
178
+ sdcs: List of SDCs containing the compiled contracts
179
+ harness_name: Name of the harness contract
180
+ Returns:
181
+ Dict[str, Any]: The storage layout of the harness contract
182
+ """
183
+
184
+ # Search through all SDC's to find the correct contract
185
+ harness_contract = None
186
+ for sdc in sdcs:
187
+ harness_contract = sdc.find_contract(harness_name)
188
+ if harness_contract:
189
+ break
190
+
191
+ if not harness_contract:
192
+ raise RuntimeError(f"Could not find harness contract {harness_name} in compiled output")
193
+ # Extract the storage layout
194
+ layout = harness_contract.storage_layout
195
+ if not layout or 'storage' not in layout:
196
+ raise RuntimeError(f"Invalid storage layout for harness contract {harness_name}")
197
+ return layout
198
+
199
+
200
+ def remapped_fields_from_layout(layout: Dict[str, Any], var_to_slot: Dict[str, str]) -> NewStorageFields:
201
+ """
202
+ Remap the fields in the storage layout according to the variable to slot mapping.
203
+
204
+ Args:
205
+ layout: The storage layout of the harness contract
206
+ var_to_slot: Mapping from variable names to their slots
207
+
208
+ Returns:
209
+ List[Dict[str, Any]]: A list of remapped fields with updated slot information
210
+ """
211
+ remapped_fields = []
212
+ for storage_item in layout['storage']:
213
+ cloned_item = storage_item.copy()
214
+ var_name = cloned_item["label"]
215
+ if var_name in var_to_slot:
216
+ cloned_item["slot"] = var_to_slot[var_name]
217
+ remapped_fields.append(cloned_item)
218
+ else:
219
+ storage_extension_logger.warning(f"Skipping adding variable {var_name} not found in variable to slot mapping")
220
+
221
+ remapped_fields.sort(key=lambda f: int(f["slot"]))
222
+ return remapped_fields
223
+
224
+
225
+ def get_namespace_storage_from_ast(def_node: Dict[str, Any]) -> Set[NameSpacedStorage]:
226
+ """
227
+ Extracts namespaced storage information from the AST nodes.
228
+
229
+ Args:
230
+ def_node: The AST node representing the contract definition.
231
+
232
+ Returns:
233
+ Set[NameSpacedStorage]: A set of namespaced storage information.
234
+ """
235
+ ns_storage: Set[NameSpacedStorage] = set()
236
+ nodes = def_node.get("nodes")
237
+ if not nodes:
238
+ # No nodes found in the contract definition
239
+ return ns_storage
240
+ for n in nodes:
241
+ sinfo = erc7201_of_node(n)
242
+ if sinfo is not None:
243
+ storage_extension_logger.debug(f"Found namespaced storage: {sinfo}")
244
+ ns_storage.add(sinfo)
245
+ return ns_storage
246
+
247
+
248
+ def apply_extensions(target_contract: ContractInSDC,
249
+ extensions: Set[str],
250
+ to_add: Dict[str, NewStorageInfo]) -> None:
251
+ """
252
+ Apply the fields from each extension to the target contract,
253
+ @param target_contract contract to which to apply extensions
254
+ @param extensions set of extension contract names
255
+ @param to_add maps extension name in extensions to (storage layouts, new types)
256
+ """
257
+ storage_layout = target_contract.storage_layout
258
+ # Check if the target contract has a storage layout
259
+ if not storage_layout:
260
+ storage_extension_logger.warning(f"Target contract {target_contract.name} has no storage layout")
261
+ return
262
+
263
+ # Check if the target contract has a storage layout with 'storage' key
264
+ if "storage" not in storage_layout:
265
+ storage_extension_logger.warning(f"Target contract {target_contract.name} storage layout does not contain 'storage' key")
266
+ storage_layout["storage"] = []
267
+ elif not isinstance(storage_layout["storage"], list):
268
+ storage_extension_logger.warning(f"Target contract {target_contract.name} 'storage' is not a list but {type(storage_layout['storage']).__name__}: {storage_layout['storage']}")
269
+ storage_layout["storage"] = []
270
+
271
+ # Check if the target contract has a storage layout with 'types' key
272
+ if "types" not in storage_layout:
273
+ storage_extension_logger.warning(f"Target contract {target_contract.name} storage layout does not contain 'types' key")
274
+ storage_layout["types"] = {}
275
+ elif not isinstance(storage_layout["types"], dict):
276
+ storage_extension_logger.warning(f"Target contract {target_contract.name} 'types' is not a dict but {type(storage_layout['types']).__name__}: {storage_layout['types']}")
277
+ storage_layout["types"] = {}
278
+
279
+ target_slots = {storage["slot"] for storage in storage_layout["storage"]}
280
+ target_vars = {storage["label"] for storage in storage_layout["storage"]}
281
+ # Keep track of slots we've added, and error if we
282
+ # find two extensions extending the same slot
283
+ added_slots: Dict[str, str] = {}
284
+ added_vars: Dict[str, str] = {}
285
+ for ext in extensions:
286
+ # Check if the extension is in the to_add mapping
287
+ if ext not in to_add:
288
+ storage_extension_logger.warning(f"Extension {ext} not found in to_add mapping")
289
+ continue
290
+ (new_fields, new_types) = to_add[ext]
291
+ for field in new_fields:
292
+ # See if any of the new fields is a slot or variable name we've already added
293
+ slot = field["slot"]
294
+ var = field["label"]
295
+ validate_new_fields(
296
+ target_contract,
297
+ ext,
298
+ slot,
299
+ var,
300
+ added_slots,
301
+ added_vars,
302
+ target_slots,
303
+ target_vars
304
+ )
305
+
306
+ added_slots[slot] = ext
307
+ added_vars[var] = ext
308
+
309
+ # Add the fields to the storage layout
310
+ storage_layout["storage"].extend(new_fields)
311
+ storage_extension_logger.debug(f"Added {len(new_fields)} fields from extension {ext} to contract {target_contract.name}: {[field['label'] for field in new_fields]}")
312
+ for (new_id, new_ty) in new_types.items():
313
+ if new_id in storage_layout["types"]:
314
+ continue
315
+ storage_layout["types"][new_id] = new_ty
316
+
317
+
318
+ def validate_new_fields(
319
+ target_contract: ContractInSDC,
320
+ ext: str,
321
+ slot: str,
322
+ var: str,
323
+ added_slots: Dict[str, str],
324
+ added_vars: Dict[str, str],
325
+ target_slots: Set[str],
326
+ target_vars: Set[str]) -> None:
327
+ """
328
+ Validate that the new fields being added to the target contract
329
+ do not conflict with existing fields or variables.
330
+
331
+ Args:
332
+ target_contract: The target contract to which the fields are being added
333
+ ext: The name of the extension contract
334
+ slot: The slot being added
335
+ var: The variable being added
336
+ added_slots: Dictionary of slots already added
337
+ added_vars: Dictionary of variables already added
338
+ target_slots: Set of slots in the target contract
339
+ target_vars: Set of variables in the target contract
340
+ """
341
+
342
+ if slot in added_slots:
343
+ seen = added_slots[slot]
344
+ raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} was already added by {seen}")
345
+ if var in added_vars:
346
+ seen = added_vars[var]
347
+ raise Util.CertoraUserInputError(f"Var '{var}' added to {target_contract.name} by {ext} was already added by {seen}")
348
+ if slot in target_slots:
349
+ raise Util.CertoraUserInputError(f"Slot {slot} added to {target_contract.name} by {ext} is already mapped by {target_contract.name}")
350
+ if var in target_vars:
351
+ raise Util.CertoraUserInputError(f"Var '{var}' added to {target_contract.name} by {ext} is already declared by {target_contract.name}")
@@ -3,7 +3,6 @@
3
3
  "msg": "Equivalence Check",
4
4
  "optimistic_loop": true,
5
5
  "loop_iter": "4",
6
- "process": "emv",
7
6
  "rule_sanity": "basic",
8
7
  "verify": "",
9
8
  "msg": ""
@@ -4,6 +4,5 @@
4
4
  "msg": "Equivalence Check",
5
5
  "optimistic_loop": true,
6
6
  "loop_iter": "4",
7
- "process": "emv",
8
7
  "verify": ""
9
8
  }
@@ -30,6 +30,7 @@ from CertoraProver.certoraBuild import InputConfig, CertoraBuildGenerator, Funct
30
30
  from CertoraProver.certoraVerifyGenerator import CertoraVerifyGenerator
31
31
  from CertoraProver.certoraContext import get_args
32
32
  from certoraRun import run_certora
33
+ import CertoraProver.certoraApp as App
33
34
 
34
35
  LOG_PATH = Path('EqCheck_log.log')
35
36
  SANITY_PATH = Path('sanity.spec')
@@ -140,7 +141,7 @@ class EquivalenceChecker:
140
141
  tmp_conf = self.path / TMP_SANITY_CONF
141
142
  with tmp_conf.open('w') as temp_sanity:
142
143
  json.dump(contents, temp_sanity, indent=4)
143
- context = get_args([str(tmp_conf)])
144
+ context = get_args([str(tmp_conf)], App.EvmApp)
144
145
  config = InputConfig(context)
145
146
  cfb = CertoraBuildGenerator(config, context)
146
147
  certora_verify_generator = CertoraVerifyGenerator(context)
@@ -28,7 +28,6 @@ from typing import Optional, Any, List, Dict, Tuple, Set, Union
28
28
  import logging
29
29
  from types import SimpleNamespace
30
30
  import tarfile
31
- import json5
32
31
  import csv
33
32
  import time
34
33
  import requests
@@ -44,7 +43,9 @@ from CertoraProver.certoraContextValidator import KEY_ENV_VAR
44
43
  from Mutate import mutateConstants as MConstants
45
44
  from Shared import certoraUtils as Util
46
45
  from Shared.certoraLogging import LoggingManager
47
- from certoraRun import run_certora, CertoraRunResult, CertoraFoundViolations
46
+ from certoraRun import run_certora
47
+ from Shared.proverCommon import CertoraRunResult, CertoraFoundViolations
48
+ from certoraSorobanProver import run_soroban_prover
48
49
  from Shared import certoraValidateFuncs as Vf
49
50
  from CertoraProver.Compiler.CompilerCollectorFactory import get_relevant_compiler
50
51
  from Mutate import mutateUtil as MutUtil
@@ -52,6 +53,7 @@ from Mutate import mutateAttributes as MutAttrs
52
53
  import CertoraProver.certoraContextAttributes as Attrs
53
54
  from CertoraProver.certoraContextClass import CertoraContext
54
55
  from rustMutator import run_universal_mutator
56
+ from CertoraProver import certoraContextValidator as Cv
55
57
 
56
58
  class RunTimedout(Exception):
57
59
  pass
@@ -496,7 +498,7 @@ class WebUtils:
496
498
  mutation_test_domain = MConstants.MUTATION_TEST_REPORT_DEV
497
499
  else:
498
500
  raise Util.CertoraUserInputError(f"Invalid server name {args.server}")
499
- self.mutation_test_id_url = f"https://{domain}/mutationTesting/initiate/"
501
+ self.mutation_test_id_url = f"https://{domain}/v1/domain/mutation-tests/initiate/"
500
502
  self.mutation_test_submit_final_result_url = f"https://{domain}/mutationTesting/getUploadInfo/"
501
503
  self.mutation_test_final_result_url = f"https://{mutation_test_domain}"
502
504
  mutation_logger.debug(f"Using server {args.server} with mutation_test_id_url {self.mutation_test_id_url}")
@@ -542,8 +544,8 @@ class WebUtils:
542
544
  try:
543
545
  return requests.put(url, json=body, timeout=self.request_timeout,
544
546
  headers=headers)
545
- except Exception:
546
- mutation_logger.debug(f"attempt {i} failed to put url {url}.")
547
+ except Exception as e:
548
+ mutation_logger.debug(f"attempt {i} failed to put url {url}. {e}")
547
549
  return None
548
550
 
549
551
  def get_response_with_timeout(self, url: str,
@@ -708,6 +710,7 @@ class MutateApp:
708
710
  dump_csv: Optional[Path]
709
711
  sync: bool
710
712
  wait_for_original_run: bool
713
+ url_visibility: Vf.UrlVisibilityOptions
711
714
  collect_file: Optional[Path]
712
715
  poll_timeout: Optional[int]
713
716
  max_timeout_attempts_count: Optional[int]
@@ -720,7 +723,7 @@ class MutateApp:
720
723
  def __init__(self, args_list: List[str]) -> None:
721
724
  self.mutation_test_id = ''
722
725
  self.numb_of_jobs: int = 0
723
- self.backup_paths: List[Path] = []
726
+ self.backup_path: Optional[Path] = None
724
727
  self.sources_dir: Optional[Path] = None
725
728
  self.with_split_stats_data = False
726
729
  self.manual_mutants_list: List[Mutant] = list()
@@ -879,9 +882,9 @@ class MutateApp:
879
882
  " verification results. Please remove or replace the mutation")
880
883
 
881
884
  def restore_backups(self) -> None:
882
- for backup_path in self.backup_paths:
883
- Util.restore_backup(backup_path)
884
- self.backup_paths = []
885
+ if self.backup_path:
886
+ Util.restore_backup(self.backup_path)
887
+ self.backup_path = None
885
888
 
886
889
  def submit_soroban(self) -> None:
887
890
 
@@ -928,12 +931,14 @@ class MutateApp:
928
931
  # run mutants
929
932
  mutant_runs = []
930
933
  try:
931
- self.backup_paths = []
932
934
  for mutant in all_mutants:
933
935
  # call certoraRun for each mutant we found
934
936
  mutant_runs.append(self.run_mutant_soroban(mutant))
937
+ self.restore_backups()
935
938
  finally:
936
- self.restore_backups()
939
+ if self.backup_path:
940
+ mutation_logger.warning("Not empty backup path")
941
+ self.restore_backups()
937
942
 
938
943
  # wrap it all up and make the input for the 2nd step: the collector
939
944
  assert self.collect_file, "submit_soroban: no collect_file"
@@ -1141,10 +1146,11 @@ class MutateApp:
1141
1146
  def run_mutant_soroban(self, mutant: Mutant) -> MutantJob:
1142
1147
  file_to_mutate = Path(mutant.original_filename)
1143
1148
  # create a backup copy (if needed) by adding '.backup' to the end of the file
1144
- if Util.get_backup_path(file_to_mutate) not in self.backup_paths:
1145
- backup_file = Util.create_backup(file_to_mutate)
1146
- assert backup_file, f"run_mutant_soroban: create_backup for {file_to_mutate} failed"
1147
- self.backup_paths.append(backup_file)
1149
+ if self.backup_path and Util.get_backup_path(file_to_mutate) != self.backup_path:
1150
+ raise Util.ImplementationError("backup_path is set but does not match the file to mutate")
1151
+ if not self.backup_path:
1152
+ self.backup_path = Util.create_backup(file_to_mutate)
1153
+ assert self.backup_path, f"run_mutant_soroban: create_backup for {file_to_mutate} failed"
1148
1154
  shutil.copy(mutant.filename, file_to_mutate)
1149
1155
 
1150
1156
  try:
@@ -1180,6 +1186,7 @@ class MutateApp:
1180
1186
  if solc:
1181
1187
  compiler = solc
1182
1188
  elif compiler_map:
1189
+ Cv.check_contract_name_arg_inputs(self.prover_context)
1183
1190
  compiler = get_relevant_compiler(path_to_file, self.prover_context)
1184
1191
  if not compiler:
1185
1192
  raise Util.CertoraUserInputError(f"Cannot resolve Solidity compiler for {path_to_file}: "
@@ -1255,6 +1262,8 @@ class MutateApp:
1255
1262
  for gambit_obj in self.gambit:
1256
1263
  if MConstants.NUM_MUTANTS not in gambit_obj:
1257
1264
  gambit_obj[MConstants.NUM_MUTANTS] = DEFAULT_NUM_MUTANTS
1265
+ else:
1266
+ gambit_obj[MConstants.NUM_MUTANTS] = int(gambit_obj[MConstants.NUM_MUTANTS])
1258
1267
  gambit_obj[MConstants.SOLC] = self.get_solc_version(Path(gambit_obj[MConstants.FILENAME]))
1259
1268
  gambit_obj.update(shared_attributes)
1260
1269
  with MConstants.TMP_GAMBIT_PATH.open('w') as f:
@@ -1373,7 +1382,7 @@ class MutateApp:
1373
1382
  for mutant in self.universal_mutator:
1374
1383
  file_to_mutate = Path(os.path.normpath(mutant[MConstants.FILE_TO_MUTATE]))
1375
1384
  mutants_location = Path(mutant[MConstants.MUTANTS_LOCATION])
1376
- num_of_mutants = mutant[MConstants.NUM_MUTANTS]
1385
+ num_of_mutants = int(mutant[MConstants.NUM_MUTANTS])
1377
1386
  run_universal_mutator(file_to_mutate, self.prover_context.build_script, mutants_location, num_of_mutants)
1378
1387
  self.add_dir_to_mutants(ret_mutants, mutants_location, file_to_mutate)
1379
1388
  return ret_mutants
@@ -1385,6 +1394,8 @@ class MutateApp:
1385
1394
  args = ["--compilation_steps_only"]
1386
1395
  if self.prover_version:
1387
1396
  args += ['--prover_version', self.prover_version]
1397
+ if self.server:
1398
+ args += ['--server', self.server]
1388
1399
  run_certora([str(self.conf)] + args)
1389
1400
  except CertoraFoundViolations: # violations should not stop execution
1390
1401
  pass
@@ -1401,6 +1412,8 @@ class MutateApp:
1401
1412
  certora_args = [str(conf_file), "--run_source", "MUTATION", "--msg", msg]
1402
1413
  if self.wait_for_original_run:
1403
1414
  certora_args.append("--wait_for_results")
1415
+ if self.url_visibility:
1416
+ certora_args.extend(["--url_visibility", str(self.url_visibility)])
1404
1417
  if self.with_split_stats_data and os.environ.get("WITH_AUTOCONFING", False) == '1':
1405
1418
  certora_args += ['--prover_resource_files', f"ac:{MConstants.SPLIT_STATS_DATA}"]
1406
1419
  if mutation_test_id:
@@ -1416,7 +1429,10 @@ class MutateApp:
1416
1429
  certora_args.extend(["--disable_local_typechecking"])
1417
1430
  mutation_logger.debug(f"Running the Prover: {certora_args}")
1418
1431
  try:
1419
- certora_run_result = run_certora(certora_args)
1432
+ if self.is_soroban_run():
1433
+ certora_run_result = run_soroban_prover(certora_args)
1434
+ else:
1435
+ certora_run_result = run_certora(certora_args)
1420
1436
  except CertoraFoundViolations as e:
1421
1437
  assert e.results, "expect e.results not to be None"
1422
1438
  certora_run_result = e.results
@@ -1865,7 +1881,7 @@ class MutateApp:
1865
1881
 
1866
1882
  with open(self.conf, 'r') as conf_file:
1867
1883
  try:
1868
- self.prover_context = CertoraContext(**json5.load(conf_file, allow_duplicate_keys=False))
1884
+ self.prover_context = CertoraContext(**Util.read_conf_file(conf_file))
1869
1885
  self.check_prover_context()
1870
1886
  except Exception as e:
1871
1887
  raise Util.CertoraUserInputError(f"Failed to parse {self.conf} as JSON", e)
@@ -1893,7 +1909,7 @@ class MutateApp:
1893
1909
  common_flags.extend(['--optimize'])
1894
1910
 
1895
1911
  if hasattr(self.prover_context, MConstants.SOLC_ALLOW_PATH):
1896
- common_flags.extend([f'--allow-paths, {self.prover_context.solc_allow_path}'])
1912
+ common_flags.extend(['--allow-paths', '/'])
1897
1913
 
1898
1914
  if hasattr(self.prover_context, MConstants.SOLC_EVM_VERSION):
1899
1915
  common_flags.extend(['--evm-version', self.prover_context.solc_evm_version])
@@ -1908,7 +1924,9 @@ class MutateApp:
1908
1924
  if not self.manual_mutants:
1909
1925
  return
1910
1926
 
1911
- solc = self.get_solc_version(trg_dir)
1927
+ solc = self.get_solc_version(Path(mutant.original_filename))
1928
+ if not solc:
1929
+ raise Util.CertoraUserInputError(f"Unable to find a compiler for manual mutant {mutant.original_filename}")
1912
1930
 
1913
1931
  via_ir_flag = []
1914
1932
  if getattr(self.prover_context, MConstants.SOLC_VIA_IR, '') or \
@@ -1919,10 +1937,11 @@ class MutateApp:
1919
1937
  if self.test == str(Util.TestValue.CHECK_MANUAL_COMPILATION):
1920
1938
  raise Util.TestResultsReady(' '.join(args))
1921
1939
  with Util.change_working_directory(find_cwd(trg_dir)):
1922
- result = subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)
1940
+ result = subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)
1923
1941
  if result.returncode:
1924
1942
  mutation_logger.debug(f"mutation compilation failed: cmd: {' '.join(args)}\n cwd: {os.getcwd()}")
1925
- raise Util.CertoraUserInputError(f"mutation file {mutant.filename} failed to compile")
1943
+ raise Util.CertoraUserInputError(f"mutation file {mutant.filename} failed to compile\n\n"
1944
+ f"{result.stderr.decode()}")
1926
1945
 
1927
1946
 
1928
1947
  def rec_collect_statuses_children(rule: Dict[str, Any], statuses: List[str]) -> None: