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
@@ -13,13 +13,12 @@
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
- import json5
17
16
  import logging
18
17
  from pathlib import Path
19
- from typing import Dict, Any
18
+ from typing import Dict, Any, cast
20
19
 
21
20
  import CertoraProver.certoraContext as Ctx
22
- import CertoraProver.certoraContextAttributes as Attrs
21
+ import CertoraProver.certoraApp as App
23
22
  from CertoraProver.certoraContextClass import CertoraContext
24
23
  from Shared import certoraUtils as Util
25
24
 
@@ -46,11 +45,12 @@ def current_conf_to_file(context: CertoraContext) -> Dict[str, Any]:
46
45
  4. Parsing the .conf file is simpler, as we can ignore the null case
47
46
  """
48
47
  def input_arg_with_value(k: Any, v: Any) -> Any:
49
- all_conf_names = Attrs.get_attribute_class().all_conf_names()
48
+ all_conf_names = context.app.attr_class.all_conf_names()
50
49
  return v is not None and v is not False and k in all_conf_names
51
50
 
52
51
  context_to_save = {k: v for k, v in vars(context).items() if input_arg_with_value(k, v)}
53
- all_conf_names = Attrs.get_attribute_class().all_conf_names()
52
+ context_to_save = cast(Dict[str, Any], Util.convert_str_ints(context_to_save))
53
+ all_conf_names = context.app.attr_class.all_conf_names()
54
54
  context_to_save = dict(sorted(context_to_save.items(), key=lambda x: all_conf_names.index(x[0])))
55
55
  context_to_save.pop('build_dir', None) # build dir should not be saved, each run should define its own build_dir
56
56
  context_to_save.pop('mutation_test_id', None) # mutation_test_id should be recreated for every run
@@ -75,11 +75,11 @@ def read_from_conf_file(context: CertoraContext) -> None:
75
75
 
76
76
  try:
77
77
  with conf_file_path.open() as conf_file:
78
- conf_file_attr = json5.load(conf_file, allow_duplicate_keys=False)
78
+ context.conf_file_attr = Util.read_conf_file(conf_file)
79
79
  try:
80
- check_conf_content(conf_file_attr, context)
80
+ check_conf_content(context)
81
81
  except Util.CertoraUserInputError as e:
82
- raise Util.CertoraUserInputError(f"Error when reading {conf_file_path}: {str(e)}", e) from None
82
+ raise Util.CertoraUserInputError(f"Error when reading {conf_file_path}: {e}") from None
83
83
  context.conf_file = str(conf_file_path)
84
84
  except FileNotFoundError:
85
85
  raise Util.CertoraUserInputError(f"read_from_conf_file: {conf_file_path}: not found") from None
@@ -96,24 +96,34 @@ def handle_override_base_config(context: CertoraContext) -> None:
96
96
  """
97
97
 
98
98
  if context.override_base_config:
99
- with Path(context.override_base_config).open() as conf_file:
100
- try:
101
- override_base_config_attrs = json5.load(conf_file, allow_duplicate_keys=False)
99
+ try:
100
+ with (Path(context.override_base_config).open() as conf_file):
101
+ override_base_config_attrs = Util.read_conf_file(conf_file)
102
+ context.attrs_set_in_main_conf = context.conf_file_attr.copy()
103
+ context.conf_file_attr = {**override_base_config_attrs, **context.conf_file_attr}
102
104
 
103
105
  if 'override_base_config' in override_base_config_attrs:
104
106
  raise Util.CertoraUserInputError("base config cannot include 'override_base_config'")
105
- except Exception as e:
106
- raise Util.CertoraUserInputError(f"Cannot load base config: {context.override_base_config}\n{e}")
107
- for attr in override_base_config_attrs:
108
- if hasattr(context, attr):
109
- value = getattr(context, attr, False)
110
- if not value:
111
- setattr(context, attr, override_base_config_attrs.get(attr))
112
- else:
113
- raise Util.CertoraUserInputError(f"{attr} appears in the base conf file {context.override_base_config} but is not a known attribute.")
114
-
115
-
116
- def check_conf_content(conf_file_attr: Dict[str, Any], context: CertoraContext) -> None:
107
+ except Exception as e:
108
+ raise Util.CertoraUserInputError(f"Cannot load base config: {context.override_base_config}\n{e}")
109
+
110
+ for attr in override_base_config_attrs:
111
+ if hasattr(context, attr):
112
+ value = getattr(context, attr, False)
113
+ if not value:
114
+ # if boolean attribute does not appear in main conf but is True in the base conf, set it to True
115
+ if override_base_config_attrs[attr] is True and attr not in context.attrs_set_in_main_conf:
116
+ setattr(context, attr, True)
117
+ continue
118
+ # if boolean attribute does appear in main conf and is False, do not override it
119
+ elif attr in context.conf_file_attr and value is False:
120
+ continue # skip override if a boolean attribute was explicitly set to False in the conf file
121
+ setattr(context, attr, override_base_config_attrs.get(attr))
122
+ else:
123
+ raise Util.CertoraUserInputError(f"{attr} appears in the base conf file {context.override_base_config} but is not a known attribute.")
124
+
125
+
126
+ def check_conf_content(context: CertoraContext) -> None:
117
127
  """
118
128
  validating content read from the conf file
119
129
  Note: a command line definition trumps the definition in the file.
@@ -122,15 +132,15 @@ def check_conf_content(conf_file_attr: Dict[str, Any], context: CertoraContext)
122
132
  @param context: A namespace containing options from the command line, if any
123
133
  """
124
134
 
125
- for option in conf_file_attr:
135
+ for option in context.conf_file_attr:
126
136
  if hasattr(context, option):
127
137
  val = getattr(context, option)
128
138
  if val is None or val is False:
129
- setattr(context, option, conf_file_attr[option])
130
- elif option != Attrs.EvmProverAttributes.FILES.get_conf_key() and val != conf_file_attr[option]:
139
+ setattr(context, option, context.conf_file_attr[option])
140
+ elif option != 'files' and val != context.conf_file_attr[option]:
131
141
  cli_val = ' '.join(val) if isinstance(val, list) else str(val)
132
- conf_val = ' '.join(conf_file_attr[option]) \
133
- if isinstance(conf_file_attr[option], list) else str(conf_file_attr[option])
142
+ conf_val = ' '.join(context.conf_file_attr[option]) \
143
+ if isinstance(context.conf_file_attr[option], list) else str(context.conf_file_attr[option])
134
144
  run_logger.warning(f"Note: attribute {option} value in CLI ({cli_val}) overrides value stored in conf"
135
145
  f" file ({conf_val})")
136
146
  else:
@@ -138,12 +148,10 @@ def check_conf_content(conf_file_attr: Dict[str, Any], context: CertoraContext)
138
148
 
139
149
  handle_override_base_config(context)
140
150
 
141
- if Attrs.is_evm_app() and 'files' not in conf_file_attr and not context.project_sanity and not context.foundry:
151
+ if Ctx.is_evm_app_class(context) and not context.files and not context.project_sanity and not context.foundry:
142
152
  raise Util.CertoraUserInputError("Mandatory 'files' attribute is missing from the configuration")
153
+ context.files = context.conf_file_attr.get('files')
154
+ if context.app == App.SorobanApp and not context.files and not context.build_script:
155
+ raise Util.CertoraUserInputError("'files' or 'build script' must be set for Soroban runs")
143
156
 
144
- if Attrs.is_rust_app():
145
- has_build_script = getattr(context, 'build_script', False)
146
- if not has_build_script and 'files' not in conf_file_attr:
147
- raise Util.CertoraUserInputError("Mandatory 'build_script' or 'files' attribute is missing from the configuration")
148
-
149
- context.files = conf_file_attr.get('files')
157
+ context.files = context.conf_file_attr.get('files')
@@ -20,10 +20,12 @@ import os
20
20
  import re
21
21
  import sys
22
22
  import logging
23
+ import fnmatch
24
+ from wcmatch import glob
23
25
 
24
26
 
25
27
  from pathlib import Path
26
- from typing import Dict, List, Optional, Any, Union
28
+ from typing import Dict, List, Optional, Any, Union, Type
27
29
  from rich.console import Console
28
30
 
29
31
  scripts_dir_path = Path(__file__).parent.resolve() # containing directory
@@ -37,6 +39,8 @@ import CertoraProver.certoraContextAttributes as Attrs
37
39
 
38
40
  from Shared import certoraValidateFuncs as Vf
39
41
  from Shared import certoraAttrUtil as AttrUtil
42
+ import CertoraProver.certoraApp as App
43
+
40
44
 
41
45
  context_logger = logging.getLogger("context")
42
46
 
@@ -53,6 +57,11 @@ def escape_string(string: str) -> str:
53
57
  else:
54
58
  return f"\"{string}\""
55
59
 
60
+ def is_evm_app_class(context: CertoraContext) -> bool:
61
+ return issubclass(context.app, App.EvmAppClass)
62
+
63
+ def is_rust_app_class(context: CertoraContext) -> bool:
64
+ return issubclass(context.app, App.RustAppClass)
56
65
 
57
66
  def get_attr_value(context: CertoraContext, attr: AttrUtil.AttributeDefinition) -> Any:
58
67
  conf_key = attr.get_conf_key()
@@ -60,18 +69,18 @@ def get_attr_value(context: CertoraContext, attr: AttrUtil.AttributeDefinition)
60
69
 
61
70
 
62
71
  def collect_args_with_jar_flags(context: CertoraContext) -> List[AttrUtil.AttributeDefinition]:
63
- attribute_list = Attrs.get_attribute_class().attribute_list()
72
+ attribute_list = context.app.attr_class.attribute_list()
64
73
  return [attr for attr in attribute_list if get_attr_value(context, attr) and attr.jar_flag]
65
74
 
66
75
 
67
76
  def collect_args_build_cache_affecting(context: CertoraContext) -> List[AttrUtil.AttributeDefinition]:
68
- attribute_list = Attrs.get_attribute_class().attribute_list()
77
+ attribute_list = context.app.attr_class.attribute_list()
69
78
  return [attr for attr in attribute_list if get_attr_value(context, attr) and
70
79
  attr.affects_build_cache_key]
71
80
 
72
81
 
73
82
  def collect_args_build_cache_disabling(context: CertoraContext) -> List[AttrUtil.AttributeDefinition]:
74
- attribute_list = Attrs.get_attribute_class().attribute_list()
83
+ attribute_list = context.app.attr_class.attribute_list()
75
84
  return [attr for attr in attribute_list if get_attr_value(context, attr) and
76
85
  attr.disables_build_cache]
77
86
 
@@ -120,18 +129,25 @@ def get_local_run_cmd(context: CertoraContext) -> List[str]:
120
129
  """
121
130
  run_args = []
122
131
 
123
- if hasattr(context, 'rust_executables') and hasattr(context, 'rust_project_directory'):
124
- run_args.append(os.path.join(context.rust_project_directory, context.rust_executables))
125
- elif context.is_tac or Attrs.is_rust_app():
132
+ if context.app == App.SuiApp:
133
+ # For local runs, we want path to be relative to cwd instead of zip root.
134
+ move_rel_path = os.path.relpath(Path(context.move_path), os.getcwd())
135
+ run_args.extend(['-movePath', move_rel_path])
136
+ elif is_rust_app_class(context):
137
+ # For local runs, we want path to be relative to cwd instead of zip root.
138
+ rust_rel_path = os.path.relpath(Path(context.files[0]), os.getcwd())
139
+ run_args.append(rust_rel_path)
140
+ elif context.is_tac:
126
141
  # For Rust app we assume the files holds the executable for the prover, currently we support a single file
127
142
  try:
128
143
  run_args.append(context.files[0])
129
144
  except Exception:
130
145
  raise RuntimeError("get_local_run_cmd: cannot find context.files[0]")
131
146
 
132
- if Attrs.is_evm_app() and context.cache is not None:
147
+ if is_evm_app_class(context) and context.cache is not None:
133
148
  run_args.extend(['-cache', context.cache])
134
-
149
+ if context.app == App.ConcordApp:
150
+ run_args.extend(['-equivalenceCheck', 'true'])
135
151
  jar_args = collect_jar_args(context)
136
152
  run_args.extend(jar_args)
137
153
 
@@ -148,27 +164,37 @@ def get_local_run_cmd(context: CertoraContext) -> List[str]:
148
164
  java_cmd.extend(context.java_args.strip().split(' '))
149
165
 
150
166
  cmd = java_cmd + ["-jar", jar_path] + run_args
151
- if context.test == str(Util.TestValue.LOCAL_JAR):
152
- raise Util.TestResultsReady(' '.join(cmd))
153
167
  return cmd
154
168
 
155
169
 
156
170
  class ProverParser(AttrUtil.ContextAttributeParser):
157
- def __init__(self, *args: Any, **kwargs: Any) -> None:
171
+ def __init__(self, app: Type[App.CertoraApp], *args: Any, **kwargs: Any) -> None:
158
172
  super().__init__(*args, **kwargs)
173
+ self.app = app
159
174
 
160
175
  def format_help(self) -> str:
161
176
  console = Console()
162
- console.print("\n\nThe Certora Prover - A formal verification tool for smart contracts")
177
+ if self.app == App.ConcordApp:
178
+ console.print("\n\nConcord - Certora’s equivalence checker for smart contracts")
179
+ elif self.app == App.RangerApp:
180
+ console.print("\n\nRanger - Certora’s bounded model checker for smart contracts")
181
+ else:
182
+ console.print("\n\nThe Certora Prover - A formal verification tool for smart contracts")
163
183
  # Using sys.stdout.write() as print() would color some of the strings here
164
184
  sys.stdout.write(f"\n\nUsage: {sys.argv[0]} <Files> <Flags>\n\n")
165
- if Attrs.is_evm_app():
185
+ if self.app == App.ConcordApp:
186
+ sys.stdout.write("Concord supports only Solidity (.sol/.yul) and configuration (.conf) files.\n"
187
+ "Rust and Vyper contracts are not currently supported.\n\n")
188
+ if self.app == App.RangerApp:
189
+ sys.stdout.write("Ranger supports only Solidity (.sol) and configuration (.conf) files.\n"
190
+ "Rust and Vyper contracts are not currently supported.\n\n")
191
+ elif isinstance(self.app, App.EvmAppClass):
166
192
  sys.stdout.write("Acceptable files for EVM projects are Solidity files (.sol suffix), "
167
193
  "Vyper files (.vy suffix), or conf files (.conf suffix)\n\n")
168
- elif Attrs.is_solana_app():
194
+ elif self.app == App.SolanaApp:
169
195
  sys.stdout.write("Acceptable files for Solana projects are RUST executables ('.so' suffix) or conf "
170
196
  "files ('.conf' suffix')\n\n")
171
- elif Attrs.is_soroban_app():
197
+ elif self.app == App.SorobanApp:
172
198
  sys.stdout.write("Acceptable files for Soroban projects are WASM executables ('.wasm' suffix) or conf "
173
199
  "files ('.conf' suffix')\n\n")
174
200
  else:
@@ -187,24 +213,23 @@ class ProverParser(AttrUtil.ContextAttributeParser):
187
213
 
188
214
  return ''
189
215
 
190
- def __get_argparser() -> argparse.ArgumentParser:
216
+ def __get_argparser(app: Type[App.CertoraApp]) -> argparse.ArgumentParser:
191
217
  def formatter(prog: Any) -> argparse.HelpFormatter:
192
218
  return argparse.HelpFormatter(prog, max_help_position=100, width=200)
193
219
 
194
- parser = ProverParser(prog="certora-cli arguments and options", allow_abbrev=False,
220
+ parser = ProverParser(app, prog="certora-cli arguments and options", allow_abbrev=False,
195
221
  formatter_class=formatter,
196
222
  epilog=" -*-*-* You can find detailed documentation of the supported options in "
197
223
  f"{CLI_DOCUMENTATION_URL} -*-*-*")
198
224
 
199
- for arg in Attrs.get_attribute_class().attribute_list():
225
+ for arg in app.attr_class.attribute_list():
200
226
  flag = arg.get_flag()
201
227
  parser.add_argument(flag, help=arg.help_msg, **arg.argparse_args)
202
228
  return parser
203
229
 
204
-
205
- def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
230
+ def get_args(args_list: List[str], app: Type[App.CertoraApp]) -> CertoraContext:
206
231
  """
207
- Compiles an argparse.Namespace from the given list of command line arguments.
232
+ Compiles an CertoraContext object from the given list of command line arguments.
208
233
 
209
234
  Why do we handle --version before argparse?
210
235
  Because on some platforms, mainly CI tests, we cannot fetch the installed distribution package version of
@@ -212,15 +237,13 @@ def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
212
237
  We do it pre-argparse, because we do not care bout the input validity of anything else if we have a --version flag
213
238
  """
214
239
 
215
- if not Attrs.ATTRIBUTES_CLASS:
216
- Attrs.set_attribute_class(Attrs.EvmProverAttributes) # for scripts that call get_args directly
217
240
  if args_list is None:
218
241
  args_list = sys.argv
219
242
 
220
243
  handle_version_flag(args_list)
221
244
 
222
245
  pre_arg_fetching_checks(args_list)
223
- parser = __get_argparser()
246
+ parser = __get_argparser(app)
224
247
 
225
248
  # if there is a --help flag, we want to ignore all parsing errors, even those before it:
226
249
  if any(string in [arg.strip() for arg in args_list] for string in ['--help', '-h']):
@@ -235,6 +258,7 @@ def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
235
258
 
236
259
  args = parser.parse_args(args_list)
237
260
  context = CertoraContext(**vars(args))
261
+ context.app = app
238
262
  context.args_list = args_list
239
263
 
240
264
  __remove_parsing_whitespace(args_list)
@@ -243,17 +267,18 @@ def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
243
267
 
244
268
  if context.is_conf:
245
269
  read_from_conf_file(context)
246
-
270
+ context.process = None
247
271
  context.local = Util.is_local(context)
248
272
  context.is_tac = context.files and context.files[0].endswith('.tac')
249
273
  context.is_vyper = context.files and context.files[0].endswith('.vy')
250
274
 
251
- if Attrs.is_evm_app():
275
+ if is_evm_app_class(context):
252
276
  Cv.check_mode_of_operation(context) # Here boolean run characteristics are set
253
277
 
254
278
  validator = Cv.CertoraContextValidator(context)
279
+
255
280
  validator.validate()
256
- if Attrs.is_evm_app() or Attrs.is_rust_app():
281
+ if is_evm_app_class(context) or is_rust_app_class(context):
257
282
  current_build_directory = Util.get_build_dir()
258
283
  if context.build_dir is not None and current_build_directory != context.build_dir:
259
284
  Util.reset_certora_internal_dir(context.build_dir)
@@ -265,14 +290,19 @@ def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
265
290
  if context.java_args is not None:
266
291
  context.java_args = ' '.join(context.java_args).replace('"', '').replace("'", '')
267
292
 
268
- if Attrs.is_evm_app():
293
+ if is_evm_app_class(context) or context.app == App.ConcordApp:
269
294
  validator.check_args_post_argparse()
270
295
  setup_cache(context) # Here context.cache, context.user_defined_cache are set
296
+ validator.handle_ranger_attrs()
297
+ validator.handle_concord_attrs()
298
+ if is_rust_app_class(context):
299
+ validator.check_rust_args_post_argparse()
271
300
 
272
301
  attrs_to_relative(context)
273
302
  # Setup defaults (defaults are not recorded in conf file)
274
303
  context.expected_file = context.expected_file or "expected.json"
275
304
  context.run_source = context.run_source or Vf.RunSources.COMMAND.name.upper()
305
+ context.java_version = Util.get_java_version()
276
306
 
277
307
  context_logger.debug("parsed args successfully.")
278
308
  context_logger.debug(f"args= {context}")
@@ -346,7 +376,7 @@ def format_input(context: CertoraContext) -> None:
346
376
  :param context: Namespace containing all command line arguments, generated by get_args()
347
377
  """
348
378
  flatten_arg_lists(context)
349
- if Attrs.is_evm_app():
379
+ if is_evm_app_class(context):
350
380
  __dedup_link(context)
351
381
 
352
382
 
@@ -391,7 +421,7 @@ def setup_cache(context: CertoraContext) -> None:
391
421
  # we have a user defined cache key if the user provided a cache key
392
422
  context.user_defined_cache = context.cache is not None
393
423
  if not context.disable_auto_cache_key_gen and not os.environ.get("CERTORA_DISABLE_AUTO_CACHE") is not None:
394
- if context.is_verify or context.is_assert or context.is_conf:
424
+ if context.is_verify or context.is_conf:
395
425
  # in local mode we don't want to create a cache key if not such is given
396
426
  if (context.cache is None) and (not context.local):
397
427
  optimistic_loop = context.optimistic_loop
@@ -425,7 +455,7 @@ def write_output_conf_to_path(json_content: Dict[str, Any], path: Path) -> None:
425
455
  json.dump(json_content, out_file, indent=4, sort_keys=True)
426
456
 
427
457
 
428
- def handle_flags_in_args(args: List[str]) -> None:
458
+ def handle_flags_in_args(args: List[str], app: Type[App.CertoraApp]) -> None:
429
459
  """
430
460
  For argparse flags are strings that start with a dash. Some arguments get flags as value.
431
461
  The problem is that argparse will not treat the string as a value but rather as a new flag. There are different ways
@@ -441,7 +471,7 @@ def handle_flags_in_args(args: List[str]) -> None:
441
471
  Will all be converted to " -d"
442
472
 
443
473
  """
444
- all_flags = list(map(lambda member: member.get_flag(), Attrs.get_attribute_class().attribute_list()))
474
+ all_flags = list(map(lambda member: member.get_flag(), app.attr_class.attribute_list()))
445
475
 
446
476
  def surrounded(string: str, char: str) -> bool:
447
477
  if len(string) < 2:
@@ -492,14 +522,27 @@ def __rename_key(context: CertoraContext, old_key: str, new_key: str) -> None:
492
522
  context.delete_key(old_key)
493
523
 
494
524
 
495
- def run_typechecker(typechecker_name: str, with_typechecking: bool, args: List[str]) -> None:
525
+ def should_run_local_speck_check(context: CertoraContext) -> bool:
526
+ output = context.compilation_steps_only or not (context.disable_local_typechecking or Util.is_ci_or_git_action())
527
+ if not output:
528
+ if context.disable_local_typechecking:
529
+ context_logger.warning(
530
+ "Local checks of CVL specification files disabled. It is recommended to enable the checks."
531
+ )
532
+ else:
533
+ context_logger.info("Local checks of CVL specification files skipped in CI and will run remotely.")
534
+ return output
535
+
536
+
537
+ def run_typechecker(typechecker_name: str, with_typechecking: bool, args: List[str], print_errors: bool) -> None:
496
538
  """
497
539
  Runs a spec typechecker or syntax checker
498
- @param typechecker_name - the name of the jar that we should run for running typechecking
499
- @param with_typechecking - True if we want full typechecking including build (Solidity outputs) file,
540
+
541
+ @param typechecker_name: the name of the jar that we should run for running typechecking
542
+ @param with_typechecking: True if we want full typechecking including build (Solidity outputs) file,
500
543
  False if we run only the leaner syntax checking.
501
544
  @param args: A list of strings of additional arguments to the typechecker jar.
502
-
545
+ @param print_errors: Toggles printing the typechecker's errors to stderr.
503
546
  """
504
547
  # Find path to typechecker jar
505
548
  path_to_typechecker = Util.find_jar(typechecker_name)
@@ -512,31 +555,35 @@ def run_typechecker(typechecker_name: str, with_typechecking: bool, args: List[s
512
555
  if with_typechecking:
513
556
  cmd_str_list.append('-typeCheck')
514
557
 
558
+ context_logger.debug(f"typechecking cmd: {' '.join(cmd_str_list)}")
559
+
515
560
  exit_code = Util.run_jar_cmd(cmd_str_list, False,
516
- custom_error_message="Failed to run Certora Prover locally. Please check the errors "
517
- "below for problems in the specifications (.spec files) or the "
518
- "prover_args defined in the .conf file.",
519
- logger_topic="type_check")
561
+ custom_error_message="Failed to run Certora Prover typechecker locally.\n"
562
+ "Please check the errors below for problems in the specifications"
563
+ " (.spec files) or the prover_args defined in the .conf file.",
564
+ logger_topic="type_check",
565
+ print_err=print_errors)
520
566
  if exit_code != 0:
521
- raise Util.CertoraUserInputError(
522
- "CVL syntax or type check failed.\n Please fix the issue. "
523
- "Using --disable_local_typechecking to skip this check is strongly discouraged, "
524
- "as simple syntax errors will only be detected during the cloud run."
525
- "with --disable_local_typechecking is not recommended is not recommended as "
526
- "simple syntax failures will be visible only on the cloud run.")
567
+ raise Util.CertoraUserInputError("CVL syntax or type check failed, please fix the issue.")
527
568
 
528
569
 
529
- def run_local_spec_check(with_typechecking: bool, context: CertoraContext) -> None:
570
+ def run_local_spec_check(with_typechecking: bool, context: CertoraContext, extra_args: List[str] = list(), print_errors: bool = True) -> None:
530
571
  """
531
- Runs the local type checker in one of two modes: (1) syntax only,
532
- and (2) including full typechecking after building the contracts
572
+ Runs the local type checker
573
+
574
+ @param with_typechecking: If `True` will perform a full typecheck pass which requires Solidity build information.
575
+ Otherwise performs only a syntax check.
576
+
577
+ @param context: The `CertoraContext`.
578
+
579
+ @param extra_args: a list of additional arguments that should be sent to the typechecker.
580
+
581
+ @param print_errors: Toggles printing the typechecker's errors to stderr. Default: True
533
582
  """
534
583
 
535
- if context.disable_local_typechecking or Util.is_ci_or_git_action():
536
- return
537
584
  args = collect_jar_args(context)
538
- if Util.is_java_installed():
539
- run_typechecker("Typechecker.jar", with_typechecking, args)
585
+ if Util.is_java_installed(context.java_version):
586
+ run_typechecker("Typechecker.jar", with_typechecking, args + extra_args, print_errors)
540
587
  else:
541
588
  raise Util.CertoraUserInputError("Cannot run local checks because of missing a suitable java installation. "
542
589
  "To skip local checks run with the --disable_local_typechecking flag")
@@ -603,3 +650,30 @@ def attrs_to_relative(context: CertoraContext) -> None:
603
650
  packages_to_relative()
604
651
  prover_resource_file_to_relative()
605
652
  verify_to_relative()
653
+
654
+ def get_map_attribute_value(context: CertoraContext, path: Path, attr_name: str) -> Optional[Union[str, bool]]:
655
+
656
+ value = getattr(context, attr_name, None)
657
+ if value:
658
+ return value
659
+
660
+ map_value = getattr(context, f"{attr_name}_map", None)
661
+ if not map_value:
662
+ return None # No map value defined
663
+ for key, entry_value in map_value.items():
664
+ # Split key to handle contract:field syntax
665
+ key_parts = key.split(':')
666
+ pattern = key_parts[0]
667
+
668
+ if Path(pattern).suffix == "": # This is a contract pattern
669
+ # Find contracts that match the pattern
670
+ for contract_name, contract_file_path in context.contract_to_file.items():
671
+ if fnmatch.fnmatch(contract_name, pattern):
672
+ # Check if this contract's file matches our target path
673
+ if str(path).endswith(contract_file_path):
674
+ return entry_value
675
+ else: # This is a file pattern
676
+ # Match the file pattern against the path
677
+ if glob.globmatch(str(path), pattern, flags=glob.GLOBSTAR):
678
+ return entry_value
679
+ raise RuntimeError(f"cannot match {attr_name} to {path} from {attr_name}_map")