certora-cli 7.21.1__tar.gz → 7.22.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {certora_cli-7.21.1/certora_cli.egg-info → certora_cli-7.22.2}/PKG-INFO +3 -2
  2. certora_cli-7.22.2/README.md +1 -0
  3. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraBuild.py +92 -82
  4. certora_cli-7.22.2/certora_cli/EVMVerifier/certoraBuildRust.py +78 -0
  5. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraCloudIO.py +81 -54
  6. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraConfigIO.py +10 -4
  7. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraContext.py +11 -68
  8. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraContextAttributes.py +88 -46
  9. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraContextValidator.py +16 -19
  10. certora_cli-7.22.2/certora_cli/EVMVerifier/certoraParseBuildScript.py +58 -0
  11. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraType.py +1 -0
  12. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Mutate/mutateApp.py +22 -10
  13. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Shared/certoraAttrUtil.py +2 -2
  14. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Shared/certoraUtils.py +12 -11
  15. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Shared/certoraValidateFuncs.py +25 -7
  16. certora_cli-7.22.2/certora_cli/certoraEVMProver.py +21 -0
  17. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/certoraRun.py +4 -5
  18. certora_cli-7.22.2/certora_cli/certoraSolanaProver.py +152 -0
  19. certora_cli-7.22.2/certora_cli/certoraSorobanProver.py +21 -0
  20. certora_cli-7.22.2/certora_cli/rustMutator.py +401 -0
  21. {certora_cli-7.21.1 → certora_cli-7.22.2/certora_cli.egg-info}/PKG-INFO +3 -2
  22. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli.egg-info/SOURCES.txt +5 -0
  23. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli.egg-info/entry_points.txt +3 -0
  24. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli.egg-info/requires.txt +1 -0
  25. certora_cli-7.22.2/certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -0
  26. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_jars/Typechecker.jar +0 -0
  27. {certora_cli-7.21.1 → certora_cli-7.22.2}/setup.py +7 -4
  28. certora_cli-7.21.1/README.md +0 -1
  29. certora_cli-7.21.1/certora_cli/rustMutator.py +0 -286
  30. certora_cli-7.21.1/certora_jars/CERTORA-CLI-VERSION-METADATA.json +0 -1
  31. {certora_cli-7.21.1 → certora_cli-7.22.2}/LICENSE +0 -0
  32. {certora_cli-7.21.1 → certora_cli-7.22.2}/MANIFEST.in +0 -0
  33. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_bins/__init__.py +0 -0
  34. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollector.py +0 -0
  35. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollectorFactory.py +0 -0
  36. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollectorSol.py +0 -0
  37. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollectorSolBased.py +0 -0
  38. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollectorVy.py +0 -0
  39. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/CompilerCollectorYul.py +0 -0
  40. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/Compiler/__init__.py +0 -0
  41. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/__init__.py +0 -0
  42. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraBuildCacheManager.py +0 -0
  43. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraBuildDataClasses.py +0 -0
  44. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraCollectRunMetadata.py +0 -0
  45. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraCompilerParameters.py +0 -0
  46. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraContextClass.py +0 -0
  47. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraContractFuncs.py +0 -0
  48. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraExtensionInfo.py +0 -0
  49. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraJobList.py +0 -0
  50. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraMiniSpecParser.py +0 -0
  51. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraNodeFilters.py +0 -0
  52. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraSourceFinders.py +0 -0
  53. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EVMVerifier/certoraVerifyGenerator.py +0 -0
  54. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/Eq_default.conf +0 -0
  55. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/Eq_mc_no_out_template.spec +0 -0
  56. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/Eq_mc_template.spec +0 -0
  57. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -0
  58. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/Eq_template.spec +0 -0
  59. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/__init__.py +0 -0
  60. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/equivCheck.py +0 -0
  61. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/EquivalenceCheck/sanity.spec +0 -0
  62. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Mutate/__init__.py +0 -0
  63. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Mutate/mutateAttributes.py +0 -0
  64. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Mutate/mutateConstants.py +0 -0
  65. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Mutate/mutateUtil.py +0 -0
  66. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Mutate/mutateValidate.py +0 -0
  67. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Shared/ExpectedComparator.py +0 -0
  68. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Shared/__init__.py +0 -0
  69. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/Shared/certoraLogging.py +0 -0
  70. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/__init__.py +0 -0
  71. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/certoraEqCheck.py +0 -0
  72. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli/certoraMutate.py +0 -0
  73. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli.egg-info/dependency_links.txt +0 -0
  74. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_cli.egg-info/top_level.txt +0 -0
  75. {certora_cli-7.21.1 → certora_cli-7.22.2}/certora_jars/__init__.py +0 -0
  76. {certora_cli-7.21.1 → certora_cli-7.22.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: certora-cli
3
- Version: 7.21.1
3
+ Version: 7.22.2
4
4
  Summary: Runner for the Certora Prover
5
5
  Home-page: https://pypi.org/project/certora-cli
6
6
  Author: Certora
@@ -22,5 +22,6 @@ Requires-Dist: tqdm
22
22
  Requires-Dist: StrEnum
23
23
  Requires-Dist: tomli
24
24
  Requires-Dist: universalmutator
25
+ Requires-Dist: jinja2
25
26
 
26
- Commit 03eae31. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
27
+ Commit 31a4e5d. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -0,0 +1 @@
1
+ Commit 31a4e5d. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -1577,7 +1577,17 @@ class CertoraBuildGenerator:
1577
1577
  while queue:
1578
1578
  pop = queue.pop(0)
1579
1579
  if isinstance(pop, dict) and node_id_attrb in pop:
1580
- container[int(pop[node_id_attrb])] = pop
1580
+ idAttr = pop[node_id_attrb]
1581
+ if isinstance(idAttr, int):
1582
+ container[int(idAttr)] = pop
1583
+ elif isinstance(idAttr, list) and len(idAttr) == 1 and isinstance(idAttr[0], int):
1584
+ # In the bug reported in https://certora.atlassian.net/browse/CERT-7643 the id field is a list
1585
+ # instead of a single integer - we except that the first element of the list is the actual id
1586
+ # and unpack it.
1587
+ container[int(idAttr[0])] = pop
1588
+ else:
1589
+ raise Exception(f"Unexpected type of attribute `{node_id_attrb}`, was {idAttr}, expected an integer or an int-typed list of length 1")
1590
+
1581
1591
  for key, value in pop.items():
1582
1592
  if (node_type_attrb in pop and
1583
1593
  pop[node_type_attrb] == "InlineAssembly" and key == "externalReferences"):
@@ -2620,7 +2630,7 @@ class CertoraBuildGenerator:
2620
2630
  sources_from_pre_finder_SDCs |= sdc.all_contract_files
2621
2631
  sources = self.collect_sources(context, certora_verify_generator, sources_from_pre_finder_SDCs)
2622
2632
  try:
2623
- self.cwd_rel_in_sources = self.build_source_tree(sources, context)
2633
+ self.cwd_rel_in_sources = build_source_tree(sources, context)
2624
2634
  except Exception as e:
2625
2635
  build_logger.debug(f"build_source_tree failed. Sources: {sources}", exc_info=e)
2626
2636
  raise
@@ -2787,85 +2797,6 @@ class CertoraBuildGenerator:
2787
2797
  common_path = Path(os.path.commonpath(list(sources.union({cwd}))))
2788
2798
  return cwd.relative_to(common_path), common_path
2789
2799
 
2790
- @staticmethod
2791
- def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bool = False) -> Path:
2792
- """
2793
- Copies files to .certora_sources
2794
- @returns the cwd relative in sources
2795
- """
2796
- sources = sources_to_abs(sources)
2797
- cwd_rel_in_sources, common_path = CertoraBuildGenerator.get_cwd_rel_in_sources(sources)
2798
-
2799
- for source_path in sources:
2800
- is_dir = source_path.is_dir()
2801
- # copy file to the path of the file from the common root under the sources directory
2802
-
2803
- # make sure directory exists
2804
- target_path = Util.get_certora_sources_dir() / source_path.relative_to(common_path)
2805
- target_directory = target_path if is_dir else target_path.parent
2806
- try:
2807
- target_directory.mkdir(parents=True, exist_ok=True)
2808
- except OSError as e:
2809
- build_logger.debug(f"Failed to create directory {target_directory}", exc_info=e)
2810
- raise
2811
-
2812
- # copy files. if we got a directory, nothing to do
2813
- if is_dir:
2814
- build_logger.debug(f"Skipping directory {source_path}")
2815
- continue
2816
-
2817
- try:
2818
- if overwrite:
2819
- # expecting target path to exist.
2820
- if target_path.exists():
2821
- build_logger.debug(f"Overwriting {target_path} by copying from {source_path}")
2822
- else:
2823
- build_logger.warning(f"Supposed to overwrite {target_path} by copying from {source_path}" +
2824
- " but it does not exist... this may indicate bad things happen")
2825
- if overwrite or not target_path.exists():
2826
- build_logger.debug(f"Copying {source_path} to {target_path}")
2827
- shutil.copyfile(source_path, target_path)
2828
- except OSError as e:
2829
- build_logger.debug(f"Couldn't copy {source_path} to {target_path}", exc_info=e)
2830
- raise
2831
-
2832
- # the empty file .cwd is written in the source tree to denote the current working directory
2833
- if cwd_rel_in_sources != '.':
2834
- file_path = Util.get_certora_sources_dir() / cwd_rel_in_sources / Util.CWD_FILE
2835
- file_path.parent.mkdir(parents=True, exist_ok=True)
2836
- file_path.touch()
2837
-
2838
- """
2839
- Once the resource files are copied to the source tree, the paths in the value of the 'prover_resource_files'
2840
- attribute are replaced with the relative path of the resource file from the source tree root.
2841
- This way The server can easily find the resource files in the source tree. The path from the source tree is the
2842
- relative path of cwd from source tree root (most cases '.') concatenated with the relative path of the resource
2843
- file from cwd
2844
- """
2845
- if context.prover_resource_files:
2846
- new_value = []
2847
- len_orig = len(context.prover_resource_files)
2848
- for value in context.prover_resource_files:
2849
- label, file_path = value.split(':')
2850
- rel_path = Path(os.path.relpath(file_path, '.'))
2851
- new_value.append(':'.join([label, os.path.normpath(rel_path)]))
2852
- if len_orig != len(new_value):
2853
- raise RuntimeError(f"fail to process prover_resource_files {len_orig} out of {len(new_value)}")
2854
- context.prover_resource_files = new_value
2855
-
2856
- # Copy the repro conf file to the sources as the overall way to reproduce this run easily.
2857
- # The file is being copied in here and not added through collect_sources, because we want
2858
- # to avoid building the whole tree up to the common path for this file. We simply want to copy
2859
- # the repro file from .certora_internal to .certora_sources so it will be uploaded as a run
2860
- # resource.
2861
- try:
2862
- shutil.copy(Util.get_last_conf_file(), Util.get_certora_sources_dir() / Util.LAST_CONF_FILE)
2863
- except OSError as e:
2864
- build_logger.debug("Couldn't copy repro conf to certora sources.", exc_info=e)
2865
- raise
2866
-
2867
- return cwd_rel_in_sources
2868
-
2869
2800
  @staticmethod
2870
2801
  def normalize_path(path: str) -> str:
2871
2802
  """
@@ -3462,6 +3393,85 @@ def sources_to_abs(sources: Set[Path]) -> Set[Path]:
3462
3393
  return result
3463
3394
 
3464
3395
 
3396
+ def build_source_tree(sources: Set[Path], context: CertoraContext, overwrite: bool = False) -> Path:
3397
+ """
3398
+ Copies files to .certora_sources
3399
+ @returns the cwd relative in sources
3400
+ """
3401
+ sources = sources_to_abs(sources)
3402
+ cwd_rel_in_sources, common_path = CertoraBuildGenerator.get_cwd_rel_in_sources(sources)
3403
+
3404
+ for source_path in sources:
3405
+ is_dir = source_path.is_dir()
3406
+ # copy file to the path of the file from the common root under the sources directory
3407
+
3408
+ # make sure directory exists
3409
+ target_path = Util.get_certora_sources_dir() / source_path.relative_to(common_path)
3410
+ target_directory = target_path if is_dir else target_path.parent
3411
+ try:
3412
+ target_directory.mkdir(parents=True, exist_ok=True)
3413
+ except OSError as e:
3414
+ build_logger.debug(f"Failed to create directory {target_directory}", exc_info=e)
3415
+ raise
3416
+
3417
+ # copy files. if we got a directory, nothing to do
3418
+ if is_dir:
3419
+ build_logger.debug(f"Skipping directory {source_path}")
3420
+ continue
3421
+
3422
+ try:
3423
+ if overwrite:
3424
+ # expecting target path to exist.
3425
+ if target_path.exists():
3426
+ build_logger.debug(f"Overwriting {target_path} by copying from {source_path}")
3427
+ else:
3428
+ build_logger.warning(f"Supposed to overwrite {target_path} by copying from {source_path}" +
3429
+ " but it does not exist... this may indicate bad things happen")
3430
+ if overwrite or not target_path.exists():
3431
+ build_logger.debug(f"Copying {source_path} to {target_path}")
3432
+ shutil.copyfile(source_path, target_path)
3433
+ except OSError as e:
3434
+ build_logger.debug(f"Couldn't copy {source_path} to {target_path}", exc_info=e)
3435
+ raise
3436
+
3437
+ # the empty file .cwd is written in the source tree to denote the current working directory
3438
+ if cwd_rel_in_sources != '.':
3439
+ file_path = Util.get_certora_sources_dir() / cwd_rel_in_sources / Util.CWD_FILE
3440
+ file_path.parent.mkdir(parents=True, exist_ok=True)
3441
+ file_path.touch()
3442
+
3443
+ """
3444
+ Once the resource files are copied to the source tree, the paths in the value of the 'prover_resource_files'
3445
+ attribute are replaced with the relative path of the resource file from the source tree root.
3446
+ This way The server can easily find the resource files in the source tree. The path from the source tree is the
3447
+ relative path of cwd from source tree root (most cases '.') concatenated with the relative path of the resource
3448
+ file from cwd
3449
+ """
3450
+ if context.prover_resource_files:
3451
+ new_value = []
3452
+ len_orig = len(context.prover_resource_files)
3453
+ for value in context.prover_resource_files:
3454
+ label, file_path = value.split(':')
3455
+ rel_path = Path(os.path.relpath(file_path, '.'))
3456
+ new_value.append(':'.join([label, os.path.normpath(rel_path)]))
3457
+ if len_orig != len(new_value):
3458
+ raise RuntimeError(f"fail to process prover_resource_files {len_orig} out of {len(new_value)}")
3459
+ context.prover_resource_files = new_value
3460
+
3461
+ # Copy the repro conf file to the sources as the overall way to reproduce this run easily.
3462
+ # The file is being copied in here and not added through collect_sources, because we want
3463
+ # to avoid building the whole tree up to the common path for this file. We simply want to copy
3464
+ # the repro file from .certora_internal to .certora_sources so it will be uploaded as a run
3465
+ # resource.
3466
+ try:
3467
+ shutil.copy(Util.get_last_conf_file(), Util.get_certora_sources_dir() / Util.LAST_CONF_FILE)
3468
+ except OSError as e:
3469
+ build_logger.debug("Couldn't copy repro conf to certora sources.", exc_info=e)
3470
+ raise
3471
+
3472
+ return cwd_rel_in_sources
3473
+
3474
+
3465
3475
  def build_from_scratch(certora_build_generator: CertoraBuildGenerator,
3466
3476
  certora_verify_generator: CertoraVerifyGenerator,
3467
3477
  build_cache_enabled: bool) -> CachedFiles:
@@ -3625,7 +3635,7 @@ def build(context: CertoraContext, ignore_spec_syntax_check: bool = False) -> No
3625
3635
  cached_files.all_contract_files)
3626
3636
  try:
3627
3637
  # Copies files, not updating state
3628
- certora_build_generator.build_source_tree(sources, context)
3638
+ build_source_tree(sources, context)
3629
3639
  except Exception as e:
3630
3640
  build_logger.debug("build_source_tree failed", exc_info=e)
3631
3641
 
@@ -0,0 +1,78 @@
1
+ import glob
2
+ import shutil
3
+ from pathlib import Path
4
+ from typing import Set
5
+
6
+ from EVMVerifier.certoraBuild import build_source_tree
7
+ from EVMVerifier.certoraContextClass import CertoraContext
8
+ from EVMVerifier.certoraParseBuildScript import run_script_and_parse_json
9
+ from Shared import certoraUtils as Util
10
+
11
+
12
+ def build_rust_app(context: CertoraContext) -> None:
13
+ if context.build_script:
14
+ run_script_and_parse_json(context)
15
+ if not context.rust_executables:
16
+ raise Util.CertoraUserInputError("failed to get target executable")
17
+
18
+ sources: Set[Path] = set()
19
+ root_directory = Path(context.rust_project_directory)
20
+ collect_files_from_rust_sources(context, sources, root_directory)
21
+
22
+ try:
23
+ # Create generators
24
+ build_source_tree(sources, context)
25
+
26
+ copy_files_to_build_dir(context)
27
+
28
+ except Exception as e:
29
+ raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
30
+ else:
31
+ if not context.files:
32
+ raise Util.CertoraUserInputError("'files' or 'build_script' must be set for Rust projects")
33
+ if len(context.files) > 1:
34
+ raise Util.CertoraUserInputError("Rust projects must specify exactly one executable in 'files'.")
35
+ context.rust_executables = context.files[0]
36
+
37
+
38
+ def collect_files_from_rust_sources(context: CertoraContext, sources: Set[Path], root_directory: Path) -> None:
39
+ patterns = ["*.rs", "*.so", "*.wasm", "Cargo.toml", "Cargo.lock", "justfile"]
40
+ exclude_dirs = [".certora_internal"]
41
+
42
+ if not root_directory.is_dir():
43
+ raise ValueError(f"The given directory {root_directory} is not valid.")
44
+
45
+ for source in context.rust_sources:
46
+ for file in glob.glob(f'{root_directory.joinpath(source)}', recursive=True):
47
+ file_path = Path(file)
48
+ if any(excluded in file_path.parts for excluded in exclude_dirs):
49
+ continue
50
+ if file_path.is_file() and any(file_path.match(pattern) for pattern in patterns):
51
+ sources.add(file_path)
52
+
53
+ if Path(context.build_script).exists():
54
+ sources.add(Path(context.build_script).resolve())
55
+ if context.conf_file and Path(context.conf_file).exists():
56
+ sources.add(Path(context.conf_file).absolute())
57
+
58
+ additional_files = (getattr(context, 'solana_inlining', None) or []) + \
59
+ (getattr(context, 'solana_summaries', None) or [])
60
+ for file in additional_files:
61
+ sources.add(Path(file))
62
+
63
+
64
+ def copy_files_to_build_dir(context: CertoraContext) -> None:
65
+ rust_executable = Path(context.rust_project_directory) / context.rust_executables
66
+ shutil.copyfile(rust_executable, Util.get_build_dir() / rust_executable.name)
67
+
68
+ additional_files = (getattr(context, 'solana_inlining', None) or []) + \
69
+ (getattr(context, 'solana_summaries', None) or [])
70
+
71
+ for file in additional_files:
72
+ file_path = Path(file).resolve()
73
+ shutil.copy(file_path, Util.get_build_dir() / file_path.name)
74
+
75
+ if rust_logs := getattr(context, 'rust_logs_stdout', None):
76
+ shutil.copy(Path(rust_logs), Util.get_build_dir() / Path(rust_logs).name)
77
+ if rust_logs := getattr(context, 'rust_logs_stderr', None):
78
+ shutil.copy(Path(rust_logs), Util.get_build_dir() / Path(rust_logs).name)
@@ -190,25 +190,6 @@ def parse_json(response: Response) -> Dict[str, Any]:
190
190
  return json_response
191
191
 
192
192
 
193
- def compress_files_wasm(zip_file_path: Path, context: CertoraContext) -> bool:
194
- with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zip_obj:
195
- cargo_directory = context.cargo_dir
196
- for path in cargo_directory.rglob('*.rs'):
197
- if cargo_directory / 'target' in path.parents or cargo_directory / '.certora_internal' in path.parents:
198
- continue
199
- zip_obj.write(path, Util.CERTORA_SOURCES / path.relative_to(cargo_directory))
200
- for path in [cargo_directory / 'Cargo.toml', cargo_directory / 'Cargo.lock']:
201
- if path.exists():
202
- zip_obj.write(path, path.relative_to(cargo_directory))
203
- if context.wasm_file.exists():
204
- zip_obj.write(context.wasm_file, context.wasm_file.relative_to(context.wasm_file.parent))
205
-
206
- if zip_file_path.stat().st_size > MAX_FILE_SIZE:
207
- cloud_logger.error(f"{GENERAL_ERR_PREFIX} Max 25MB file size exceeded.")
208
- return False
209
- return True
210
-
211
-
212
193
  def compress_files(zip_file_path: Path, *resource_paths: Path, short_output: bool = False) -> bool:
213
194
  with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED) as zip_obj:
214
195
 
@@ -600,8 +581,12 @@ class CloudVerification:
600
581
  # We need to strip "../" path component from all file paths because
601
582
  # unzip will also do that.
602
583
  solana_jar_settings = []
603
- for file in self.context.files:
604
- solana_jar_settings.append(file.split('../')[-1])
584
+ if hasattr(self.context, 'build_script') and self.context.build_script:
585
+ solana_jar_settings.append(Path(self.context.rust_executables).name)
586
+
587
+ else:
588
+ for file in self.context.files:
589
+ solana_jar_settings.append(file.split('../')[-1])
605
590
 
606
591
  is_file = False
607
592
  for arg in jar_settings:
@@ -616,18 +601,19 @@ class CloudVerification:
616
601
  elif arg == '-solanaSummaries':
617
602
  is_file = True
618
603
  auth_data["jarSettings"] = solana_jar_settings
619
- elif Attrs.is_wasm_app():
604
+ elif Attrs.is_soroban_app():
620
605
  # We need to strip "../" path component from all file paths because
621
606
  # unzip will also do that.
622
- wasm_jar_settings = []
623
- if hasattr(self.context, 'wasm_file') and self.context.wasm_file:
624
- wasm_jar_settings.append(self.context.wasm_file.name) # assuming root is cargo_directory
607
+ soroban_jar_settings = []
608
+ # not needed - should be in files
609
+ if hasattr(self.context, 'build_script') and self.context.build_script:
610
+ soroban_jar_settings.append(Path(self.context.rust_executables).name)
625
611
  else:
626
612
  for file in self.context.files:
627
- wasm_jar_settings.append(file.split('../')[-1])
613
+ soroban_jar_settings.append(file.split('../')[-1])
628
614
  for arg in jar_settings:
629
- wasm_jar_settings.append(arg)
630
- auth_data["jarSettings"] = wasm_jar_settings
615
+ soroban_jar_settings.append(arg)
616
+ auth_data["jarSettings"] = soroban_jar_settings
631
617
  else:
632
618
  auth_data["jarSettings"] = jar_settings
633
619
 
@@ -738,40 +724,81 @@ class CloudVerification:
738
724
  short_output=Ctx.is_minimal_cli_output(self.context))
739
725
  elif Attrs.is_evm_app() and self.context.is_bytecode:
740
726
  # We zip the bytecode jsons and the spec
741
- paths = []
727
+ paths = [Util.get_certora_build_file(), Util.get_certora_verify_file(),
728
+ Util.get_certora_metadata_file()]
729
+ if Util.get_certora_sources_dir().exists():
730
+ paths.append(Util.get_certora_sources_dir())
731
+
742
732
  for bytecode_json in self.context.bytecode_jsons:
743
733
  paths.append(Path(bytecode_json))
744
734
  paths.append(Path(self.context.bytecode_spec))
745
735
  result = compress_files(self.ZipFilePath, *paths,
746
736
  short_output=Ctx.is_minimal_cli_output(self.context))
747
- elif Attrs.is_solana_app():
748
- # We zip the ELF files and the two configuration files
749
- jar_args = Ctx.collect_jar_args(self.context)
750
- paths = []
751
- for file in self.context.files:
752
- paths.append(Path(file))
753
- is_file = False
754
- for arg in jar_args:
755
- if is_file:
756
- paths.append(Path(arg))
757
- is_file = False
737
+ elif Attrs.is_rust_app():
738
+ files_list = [Util.get_certora_metadata_file()]
739
+
740
+ if hasattr(self.context, 'build_script') and self.context.build_script:
741
+ result = compress_files(self.logZipFilePath, Util.get_debug_log_file(),
742
+ short_output=Ctx.is_minimal_cli_output(self.context))
743
+
744
+ if not result:
745
+ return False
746
+ files_list.append(self.logZipFilePath)
747
+ if Util.get_certora_sources_dir().exists():
748
+ files_list.append(Util.get_certora_sources_dir())
749
+
750
+ files_list.append(Util.get_build_dir() / Path(self.context.rust_executables).name)
751
+
752
+ # Create a .RustExecution file to classify zipInput as a rust source code
753
+ rust_execution_file = Util.get_build_dir() / ".RustExecution"
754
+ rust_execution_file.touch(exist_ok=True)
755
+ files_list.append(rust_execution_file)
756
+
757
+ additional_files = (getattr(self.context, 'solana_inlining', None) or []) + \
758
+ (getattr(self.context, 'solana_summaries', None) or [])
759
+ for file in additional_files:
760
+ files_list.append(Util.get_build_dir() / Path(file).name)
761
+
762
+ if attr_file := getattr(self.context, 'rust_logs_stdout', None):
763
+ files_list.append(Util.get_build_dir() / Path(attr_file).name)
764
+ if attr_file := getattr(self.context, 'rust_logs_stderr', None):
765
+ files_list.append(Util.get_build_dir() / Path(attr_file).name)
766
+
767
+ result = compress_files(self.ZipFilePath, *files_list,
768
+ short_output=Ctx.is_minimal_cli_output(self.context))
769
+
770
+ elif Attrs.is_solana_app():
771
+ # We zip the ELF files and the two configuration files
772
+ jar_args = Ctx.collect_jar_args(self.context)
758
773
 
759
- if arg == '-solanaInlining':
760
- is_file = True
761
- elif arg == '-solanaSummaries':
762
- is_file = True
763
- result = compress_files(self.ZipFilePath, *paths,
764
- short_output=Ctx.is_minimal_cli_output(self.context))
765
- elif Attrs.is_wasm_app():
766
- # We zip the wat file
767
- paths = []
768
- if hasattr(self.context, 'wasm_file') and self.context.wasm_file:
769
- result = compress_files_wasm(self.ZipFilePath, self.context)
770
- else:
771
774
  for file in self.context.files:
772
- paths.append(Path(file))
773
- result = compress_files(self.ZipFilePath, *paths,
775
+ files_list.append(Path(file))
776
+ is_file = False
777
+ for arg in jar_args:
778
+ if is_file:
779
+ files_list.append(Path(arg))
780
+ is_file = False
781
+
782
+ if arg == '-solanaInlining':
783
+ is_file = True
784
+ elif arg == '-solanaSummaries':
785
+ is_file = True
786
+ result = compress_files(self.ZipFilePath, *files_list,
774
787
  short_output=Ctx.is_minimal_cli_output(self.context))
788
+
789
+ elif Attrs.is_soroban_app():
790
+ # We zip the wat file
791
+ for file in self.context.files:
792
+ files_list.append(Path(file))
793
+ result = compress_files(self.ZipFilePath, *files_list,
794
+ short_output=Ctx.is_minimal_cli_output(self.context))
795
+ else:
796
+ raise Util.CertoraUserInputError(
797
+ 'When running rust application, context should either have attribute "rust_executables" '
798
+ 'provided by build_script execution, '
799
+ 'or either is_solana_app(), is_soroban_app() should be set to True'
800
+ )
801
+
775
802
  else:
776
803
  # Zip the log file first separately and again with the rest of the files, so it will not be decompressed
777
804
  # on each run in order to save space
@@ -53,7 +53,6 @@ def read_from_conf_file(context: CertoraContext) -> None:
53
53
  context namespace if the key was not set in the command line (command line shadows conf data).
54
54
  @param context: A namespace containing options from the command line
55
55
  """
56
-
57
56
  conf_file_path = Path(context.files[0])
58
57
  assert conf_file_path.suffix == ".conf", f"conf file must be of type .conf, instead got {conf_file_path}"
59
58
 
@@ -62,7 +61,7 @@ def read_from_conf_file(context: CertoraContext) -> None:
62
61
  try:
63
62
  check_conf_content(configuration, context)
64
63
  except Util.CertoraUserInputError as e:
65
- raise Util.CertoraUserInputError(f"Error when reading {conf_file_path}", e) from None
64
+ raise Util.CertoraUserInputError(f"Error when reading {conf_file_path}: {str(e)}", e) from None
66
65
  context.conf_file = str(conf_file_path)
67
66
 
68
67
 
@@ -86,6 +85,13 @@ def check_conf_content(conf: Dict[str, Any], context: CertoraContext) -> None:
86
85
  f" file ({conf_val})")
87
86
  else:
88
87
  raise Util.CertoraUserInputError(f"{option} appears in the conf file but is not a known attribute. ")
89
- if 'files' not in conf:
88
+
89
+ if Attrs.is_evm_app() and 'files' not in conf:
90
90
  raise Util.CertoraUserInputError("Mandatory 'files' attribute is missing from the configuration")
91
- context.files = conf['files'] # Override the current .conf file
91
+
92
+ if Attrs.is_rust_app():
93
+ has_build_script = getattr(context, 'build_script', False)
94
+ if not has_build_script and 'files' not in conf:
95
+ raise Util.CertoraUserInputError("Mandatory 'build_script' or 'files' attribute is missing from the configuration")
96
+
97
+ context.files = conf.get('files')
@@ -1,24 +1,16 @@
1
1
  import argparse
2
2
  import hashlib
3
3
  import json
4
- import logging
5
4
  import os
6
5
  import re
7
6
  import sys
8
- import shutil
9
- import platform
7
+ import logging
10
8
 
11
- # tomllib was added to python 3.11 this import is for supporting older versions
12
- if sys.version_info >= (3, 11):
13
- import tomllib
14
- else:
15
- import tomli as tomllib
16
9
 
17
10
  from pathlib import Path
18
11
  from typing import Dict, List, Optional, Any
19
12
  from rich.console import Console
20
13
 
21
-
22
14
  scripts_dir_path = Path(__file__).parent.resolve() # containing directory
23
15
  sys.path.insert(0, str(scripts_dir_path))
24
16
 
@@ -112,14 +104,15 @@ def get_local_run_cmd(context: CertoraContext) -> str:
112
104
  @return: A command for running the prover locally
113
105
  """
114
106
  run_args = []
115
- if context.is_tac or Attrs.is_solana_app():
116
- run_args.append(context.files[0])
117
107
 
118
- if Attrs.is_wasm_app():
119
- if hasattr(context, 'wasm_file') and context.wasm_file:
120
- run_args.append(str(context.wasm_file))
121
- else:
108
+ if hasattr(context, 'rust_executables') and hasattr(context, 'rust_project_directory'):
109
+ run_args.append(os.path.join(context.rust_project_directory, context.rust_executables))
110
+ elif context.is_tac or Attrs.is_rust_app():
111
+ # For Rust app we assume the files holds the executable for the prover, currently we support a single file
112
+ try:
122
113
  run_args.append(context.files[0])
114
+ except Exception:
115
+ raise RuntimeError("get_local_run_cmd: cannot find context.files[0]")
123
116
 
124
117
  if Attrs.is_evm_app() and context.cache is not None:
125
118
  run_args.extend(['-cache', context.cache])
@@ -158,7 +151,7 @@ class ProverParser(AttrUtil.ContextAttributeParser):
158
151
  console = Console()
159
152
  console.print("\n\nThe Certora Prover - A formal verification tool for smart contracts")
160
153
  # Using sys.stdout.write() as print() would color some of the strings here
161
- sys.stdout.write("\n\nUsage: certoraRun <Files> <Flags>\n\n")
154
+ sys.stdout.write(f"\n\nUsage: {sys.argv[0]} <Files> <Flags>\n\n")
162
155
  sys.stdout.write("Files are Solidity, Vyper contract files, a shared object Solana contract file, "
163
156
  "or a conf file. Solidity contracts are denoted as file:contract "
164
157
  "e.g. f.sol:A. If the contract name is identical to the file name, "
@@ -171,7 +164,7 @@ class ProverParser(AttrUtil.ContextAttributeParser):
171
164
  console.print("3. list (L): gets multiple strings as a value, flags may also appear multiple times")
172
165
  console.print("4. map (M): collection of key, value pairs\n\n")
173
166
 
174
- Attrs.EvmProverAttributes.print_attr_help()
167
+ Attrs.get_attribute_class().print_attr_help()
175
168
  console.print("\n\nYou can find detailed documentation of the supported flags here: "
176
169
  f"{Util.print_rich_link(CLI_DOCUMENTATION_URL)}\n\n")
177
170
 
@@ -242,7 +235,7 @@ def get_args(args_list: Optional[List[str]] = None) -> CertoraContext:
242
235
 
243
236
  validator = Cv.CertoraContextValidator(context)
244
237
  validator.validate()
245
- if Attrs.is_evm_app():
238
+ if Attrs.is_evm_app() or Attrs.is_rust_app():
246
239
  current_build_directory = Util.get_build_dir()
247
240
  if context.build_dir is not None and current_build_directory != context.build_dir:
248
241
  Util.reset_certora_internal_dir(context.build_dir)
@@ -526,53 +519,3 @@ def run_local_spec_check(with_typechecking: bool, context: CertoraContext) -> No
526
519
  else:
527
520
  raise Util.CertoraUserInputError("Cannot run local checks because of missing a suitable java installation. "
528
521
  "To skip local checks run with the --disable_local_typechecking flag")
529
-
530
- def set_cargo_name(context: CertoraContext) -> None:
531
- mode = "r" if platform.system().lower() == 'windows' else "rb"
532
- with open(context.cargo_file, mode) as f:
533
- cargo_data = tomllib.load(f)
534
-
535
- if 'package' in cargo_data and 'name' in cargo_data['package']:
536
- context.cargo_name = cargo_data['package']['name'].replace('-', '_')
537
-
538
- def build_wasm(context: CertoraContext) -> None:
539
- if len(context.files) != 1:
540
- raise Util.CertoraUserInputError(f"expected a single wasm file ({Util.CARGO_FILE} or .wat file)."
541
- f" Got: {context.files}")
542
- file_path = Path(context.files[0])
543
- if file_path.name != Util.CARGO_FILE:
544
- return
545
- context.cargo_dir = file_path.parent
546
- context.cargo_file = file_path
547
- set_cargo_name(context)
548
- run_wasm_compiler(context)
549
-
550
-
551
- def run_wasm_compiler(context: CertoraContext) -> None:
552
- env = os.environ.copy()
553
- env['RUSTFLAGS'] = "-C strip=none --emit=llvm-ir -C debuginfo=2"
554
- try:
555
- context_logger.info("Executing build process...")
556
-
557
- cmd = f"cargo build --target={context.wasm_target} --release --features certora"
558
- Util.run_shell_command(cmd, env=env, cwd=context.cargo_dir)
559
-
560
- context.wasm_file = Path(f"{context.cargo_dir}/target/{context.wasm_target}/release/{context.cargo_name}.wasm")
561
- intermediate_wasm = Path("tmpwasm.wasm")
562
-
563
- wat_file = context.wasm_file.with_suffix(".wat")
564
-
565
- cmd = f"wasm2wat {context.wasm_file} --generate-names -o {wat_file}"
566
- Util.run_shell_command(cmd, cwd=context.cargo_dir, env=env)
567
-
568
- cmd = f"wat2wasm {wat_file} --debug-names -o {intermediate_wasm}"
569
- Util.run_shell_command(cmd, cwd=context.cargo_dir, env=env)
570
-
571
- cmd = f"wasm2wat {intermediate_wasm} -o {wat_file}"
572
- Util.run_shell_command(cmd, cwd=context.cargo_dir, env=env)
573
-
574
- intermediate_wasm.unlink()
575
- shutil.copy(context.wasm_file, context.cargo_dir)
576
-
577
- except Exception as e:
578
- raise RuntimeError(f"Failed to build wasm file\n{e}")