certora-cli 8.13.1__tar.gz → 8.16.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 (91) hide show
  1. {certora_cli-8.13.1/certora_cli.egg-info → certora_cli-8.16.2}/PKG-INFO +2 -2
  2. certora_cli-8.16.2/README.md +1 -0
  3. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraBuild.py +78 -36
  4. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraCloudIO.py +25 -10
  5. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraSourceFinders.py +33 -0
  6. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Mutate/mutateApp.py +5 -4
  7. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Shared/certoraValidateFuncs.py +2 -0
  8. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Shared/proverCommon.py +10 -3
  9. {certora_cli-8.13.1 → certora_cli-8.16.2/certora_cli.egg-info}/PKG-INFO +2 -2
  10. certora_cli-8.13.1/certora_jars/Typechecker.jar → certora_cli-8.16.2/certora_jars/ASTExtraction.jar +0 -0
  11. certora_cli-8.16.2/certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -0
  12. certora_cli-8.13.1/certora_jars/ASTExtraction.jar → certora_cli-8.16.2/certora_jars/Typechecker.jar +0 -0
  13. {certora_cli-8.13.1 → certora_cli-8.16.2}/setup.py +2 -2
  14. certora_cli-8.13.1/README.md +0 -1
  15. certora_cli-8.13.1/certora_jars/CERTORA-CLI-VERSION-METADATA.json +0 -1
  16. {certora_cli-8.13.1 → certora_cli-8.16.2}/LICENSE +0 -0
  17. {certora_cli-8.13.1 → certora_cli-8.16.2}/MANIFEST.in +0 -0
  18. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_bins/__init__.py +0 -0
  19. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/Compiler/CompilerCollector.py +0 -0
  20. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +0 -0
  21. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/Compiler/CompilerCollectorSol.py +0 -0
  22. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/Compiler/CompilerCollectorSolBased.py +0 -0
  23. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +0 -0
  24. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +0 -0
  25. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/Compiler/__init__.py +0 -0
  26. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/__init__.py +0 -0
  27. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/castingInstrumenter.py +0 -0
  28. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraApp.py +0 -0
  29. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraBuildCacheManager.py +0 -0
  30. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraBuildDataClasses.py +0 -0
  31. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraBuildRust.py +0 -0
  32. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraBuildSui.py +0 -0
  33. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +0 -0
  34. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraCollectRunMetadata.py +0 -0
  35. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraCompilerParameters.py +0 -0
  36. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraConfigIO.py +0 -0
  37. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraContext.py +0 -0
  38. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraContextAttributes.py +0 -0
  39. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraContextClass.py +0 -0
  40. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraContextValidator.py +0 -0
  41. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraContractFuncs.py +0 -0
  42. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraExtensionInfo.py +0 -0
  43. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraJobList.py +0 -0
  44. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraMiniSpecParser.py +0 -0
  45. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraNodeFilters.py +0 -0
  46. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraOffsetConverter.py +0 -0
  47. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraParseBuildScript.py +0 -0
  48. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraProjectScanner.py +0 -0
  49. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraType.py +0 -0
  50. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/certoraVerifyGenerator.py +0 -0
  51. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/erc7201.py +0 -0
  52. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/splitRules.py +0 -0
  53. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/storageExtension.py +0 -0
  54. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/CertoraProver/uncheckedOverflowInstrumenter.py +0 -0
  55. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/EquivalenceCheck/Eq_default.conf +0 -0
  56. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/EquivalenceCheck/Eq_mc_no_out_template.spec +0 -0
  57. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/EquivalenceCheck/Eq_mc_template.spec +0 -0
  58. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -0
  59. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/EquivalenceCheck/Eq_template.spec +0 -0
  60. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/EquivalenceCheck/__init__.py +0 -0
  61. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/EquivalenceCheck/equivCheck.py +0 -0
  62. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/EquivalenceCheck/sanity.spec +0 -0
  63. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Mutate/__init__.py +0 -0
  64. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Mutate/mutateAttributes.py +0 -0
  65. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Mutate/mutateConstants.py +0 -0
  66. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Mutate/mutateUtil.py +0 -0
  67. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Mutate/mutateValidate.py +0 -0
  68. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Shared/ExpectedComparator.py +0 -0
  69. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Shared/__init__.py +0 -0
  70. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Shared/certoraAttrUtil.py +0 -0
  71. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Shared/certoraLogging.py +0 -0
  72. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/Shared/certoraUtils.py +0 -0
  73. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/__init__.py +0 -0
  74. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/certoraCVLFormatter.py +0 -0
  75. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/certoraConcord.py +0 -0
  76. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/certoraEVMProver.py +0 -0
  77. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/certoraEqCheck.py +0 -0
  78. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/certoraMutate.py +0 -0
  79. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/certoraRanger.py +0 -0
  80. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/certoraRun.py +0 -0
  81. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/certoraSolanaProver.py +0 -0
  82. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/certoraSorobanProver.py +0 -0
  83. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/certoraSuiProver.py +0 -0
  84. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli/rustMutator.py +0 -0
  85. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli.egg-info/SOURCES.txt +0 -0
  86. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli.egg-info/dependency_links.txt +0 -0
  87. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli.egg-info/entry_points.txt +0 -0
  88. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli.egg-info/requires.txt +0 -0
  89. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_cli.egg-info/top_level.txt +0 -0
  90. {certora_cli-8.13.1 → certora_cli-8.16.2}/certora_jars/__init__.py +0 -0
  91. {certora_cli-8.13.1 → certora_cli-8.16.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: certora-cli
3
- Version: 8.13.1
3
+ Version: 8.16.2
4
4
  Summary: Runner for the Certora Prover
5
5
  Home-page: https://pypi.org/project/certora-cli
6
6
  Author: Certora
@@ -38,4 +38,4 @@ Dynamic: requires-dist
38
38
  Dynamic: requires-python
39
39
  Dynamic: summary
40
40
 
41
- Commit deb4423. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
41
+ Commit 94b86f7. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -0,0 +1 @@
1
+ Commit 94b86f7. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -1448,7 +1448,7 @@ class CertoraBuildGenerator:
1448
1448
  if not self.context.strict_solc_optimizer and self.get_solc_via_ir_value(contract_file_path):
1449
1449
  # The default optimizer steps (taken from libsolidity/interface/OptimiserSettings.h) but with the
1450
1450
  # full inliner step removed
1451
- solc0_8_34_to_0_8_34 = ("dfDvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLvifMCTUca[r]LSsTFOtfDnca[r]"
1451
+ solc0_8_34_to_0_8_35 = ("dfDvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLvifMCTUca[r]LSsTFOtfDnca[r]"
1452
1452
  "IulcscCTUtvifMx[scCTUt]TOntnfDIulvifMjmul[jul]VcTOculjmul")
1453
1453
  solc0_8_26_to_0_8_33 = ("dhfoDgvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLCTUca[r]LSsTFOtfDnca[r]" +
1454
1454
  "IulcscCTUtx[scCTUt]TOntnfDIuljmul[jul]VcTOculjmul")
@@ -1501,8 +1501,8 @@ class CertoraBuildGenerator:
1501
1501
  yul_optimizer_steps = solc0_8_13_to_0_8_25
1502
1502
  elif minor == 8 and 26 <= patch <= 33:
1503
1503
  yul_optimizer_steps = solc0_8_26_to_0_8_33
1504
- elif minor == 8 and 34 <= patch <= 34:
1505
- yul_optimizer_steps = solc0_8_34_to_0_8_34
1504
+ elif minor == 8 and 34 <= patch <= 35:
1505
+ yul_optimizer_steps = solc0_8_34_to_0_8_35
1506
1506
  assert yul_optimizer_steps is not None, \
1507
1507
  'Yul Optimizer steps missing for requested Solidity version. Please contact Certora team.'
1508
1508
 
@@ -1839,6 +1839,42 @@ class CertoraBuildGenerator:
1839
1839
  else:
1840
1840
  raise Util.CertoraUserInputError(err_msg)
1841
1841
 
1842
+ def _resolve_data_contracts_key(self,
1843
+ contract_file: str,
1844
+ compile_wd: Path,
1845
+ data: Dict[str, Any],
1846
+ resolved_to_orig: Optional[Dict[str, str]] = None,
1847
+ ) -> str:
1848
+ """Resolve which key in data[CONTRACTS] corresponds to ``contract_file``.
1849
+
1850
+ solc's standard-json output keys files in whatever form solc saw them — relative to
1851
+ compile_wd, absolute, the raw input string, or (for symlinks) a previously seen original.
1852
+ We try each in order and return the matching key. Raises if no form matches.
1853
+
1854
+ ``resolved_to_orig`` is optional: the worklist populates it from base contracts and link
1855
+ references; the pre-worklist seed for the primary file passes ``None``.
1856
+ """
1857
+ contract_file_abs = str(Util.abs_norm_path(contract_file))
1858
+ # using os.path.relpath because Path.relative_to cannot go up the directory tree (no ..)
1859
+ contract_file_rel = os.path.relpath(Path(contract_file_abs), compile_wd)
1860
+
1861
+ build_logger.debug(f"available keys: {data[CONTRACTS].keys()}")
1862
+ if contract_file_rel in data[CONTRACTS]:
1863
+ return contract_file_rel
1864
+ if contract_file_abs in data[CONTRACTS]:
1865
+ return contract_file_abs
1866
+ if contract_file in data[CONTRACTS]:
1867
+ # when does this happen? Saw this in TrustToken on a package source file
1868
+ return contract_file
1869
+ orig = resolved_to_orig.get(contract_file) if resolved_to_orig is not None else None
1870
+ if orig is not None and orig in data[CONTRACTS]:
1871
+ return orig
1872
+ # our file may be a symlink!
1873
+ raise Exception(
1874
+ f"Worklist contains {contract_file} (relative {contract_file_rel}, "
1875
+ f"absolute {contract_file_abs}), resolved from {orig} "
1876
+ f"that does not exist in contract set {orig in data[CONTRACTS] if orig else False}")
1877
+
1842
1878
  def collect_for_file(self,
1843
1879
  build_arg_contract_file: str,
1844
1880
  file_index: int,
@@ -1986,6 +2022,12 @@ class CertoraBuildGenerator:
1986
2022
  contracts_with_libraries = {}
1987
2023
  file_compiler_path = compiler_collector.normalize_file_compiler_path_name(file_abs_path)
1988
2024
 
2025
+ # Resolve the data[CONTRACTS] key for the primary file once, before the worklist runs.
2026
+ # This is the (file, *) half of the primary contract-handle set used to scope the bytecode
2027
+ # check inside get_bytecode — see the primary_contract_handles construction just below.
2028
+ primary_file_key = self._resolve_data_contracts_key(str(Path(file_compiler_path)), compile_wd, data)
2029
+ primary_contract_handles: Set[Tuple[str, str]] = {(primary_file_key, n) for n in contracts_in_file}
2030
+
1989
2031
  # But apparently this heavily depends on the Solidity AST format anyway
1990
2032
 
1991
2033
  # Need to add all library dependencies that are in a different file:
@@ -1997,31 +2039,8 @@ class CertoraBuildGenerator:
1997
2039
  contract_file_obj = contracts_to_add_dependencies_queue.pop()
1998
2040
  contract_file = str(contract_file_obj)
1999
2041
  build_logger.debug(f"Processing dependencies from file {contract_file}")
2000
- # make sure path name is in posix format.
2001
- contract_file_abs = str(Util.abs_norm_path(contract_file))
2002
-
2003
- # using os.path.relpath because Path.relative_to cannot go up the directory tree (no ..)
2004
- contract_file_rel = os.path.relpath(Path(contract_file_abs), compile_wd)
2005
-
2006
- build_logger.debug(f"available keys: {data['contracts'].keys()}")
2007
- if contract_file_rel in data[CONTRACTS]:
2008
- contract_file = contract_file_rel
2009
- unsorted_contract_list = data[CONTRACTS][contract_file]
2010
- elif contract_file_abs in data[CONTRACTS]:
2011
- contract_file = contract_file_abs
2012
- unsorted_contract_list = data[CONTRACTS][contract_file_abs]
2013
- elif contract_file in data[CONTRACTS]:
2014
- # when does this happen? Saw this in TrustToken on a package source file
2015
- unsorted_contract_list = data[CONTRACTS][contract_file]
2016
- elif resolved_to_orig.get(contract_file) in data[CONTRACTS]:
2017
- unsorted_contract_list = data[CONTRACTS][resolved_to_orig[contract_file]]
2018
- contract_file = resolved_to_orig[contract_file]
2019
- else:
2020
- # our file may be a symlink!
2021
- raise Exception(
2022
- f"Worklist contains {contract_file} (relative {contract_file_rel}, "
2023
- f"absolute {contract_file_abs}), resolved from {resolved_to_orig.get(contract_file)} "
2024
- f"that does not exist in contract set {resolved_to_orig.get(contract_file) in data['contracts']}")
2042
+ contract_file = self._resolve_data_contracts_key(contract_file, compile_wd, data, resolved_to_orig)
2043
+ unsorted_contract_list = data[CONTRACTS][contract_file]
2025
2044
 
2026
2045
  contract_list = sorted([c for c in unsorted_contract_list])
2027
2046
  # every contract file may contain numerous primary contracts, but the dependent contracts
@@ -2125,7 +2144,7 @@ class CertoraBuildGenerator:
2125
2144
  contracts_with_chosen_addresses,
2126
2145
  data,
2127
2146
  report_source_file,
2128
- contracts_in_file,
2147
+ primary_contract_handles,
2129
2148
  build_arg_contract_file,
2130
2149
  compiler_collector,
2131
2150
  compile_wd,
@@ -2208,7 +2227,7 @@ class CertoraBuildGenerator:
2208
2227
  def get_bytecode(self,
2209
2228
  bytecode_object: Dict[str, Any],
2210
2229
  contract_name: str,
2211
- primary_contracts: List[str],
2230
+ is_primary: bool,
2212
2231
  contracts_with_chosen_addresses: List[Tuple[int, Any]],
2213
2232
  is_deployed_bytecode: bool
2214
2233
  ) -> str:
@@ -2218,7 +2237,11 @@ class CertoraBuildGenerator:
2218
2237
 
2219
2238
  @param bytecode_object - the output from the Solidity compiler
2220
2239
  @param contract_name - the contract that we are working on
2221
- @param primary_contracts - the names of the primary contracts we check to have a bytecode
2240
+ @param is_primary - whether (source_file, contract_name) is a primary handle in the current
2241
+ compilation; only primaries are required to have non-empty bytecode. The (file, name)
2242
+ scoping avoids false positives when a transitively-imported abstract contract shares a
2243
+ name with a primary (e.g. local concrete `Proxy` vs OpenZeppelin's abstract `Proxy`).
2244
+ Caller computes membership against the primary_contract_handles set.
2222
2245
  @param contracts_with_chosen_addresses - a list of tuples of addresses and the
2223
2246
  associated contract identifier
2224
2247
  @param is_deployed_bytecode - true if we deal with deployed (runtime) bytecode,
@@ -2241,7 +2264,7 @@ class CertoraBuildGenerator:
2241
2264
  f"If you need the contract, import the missing library directly and add a dummy usage:\n"
2242
2265
  orig_msg = str(e)
2243
2266
  raise Util.CertoraUserInputError(prefix_msg + orig_msg)
2244
- if contract_name in primary_contracts and len(bytecode) == 0:
2267
+ if is_primary and len(bytecode) == 0:
2245
2268
  msg = f"Contract {contract_name} has no bytecode. " \
2246
2269
  f"It may be caused because the contract is abstract, " \
2247
2270
  f"or is missing constructor code. Please check the output of the Solidity compiler."
@@ -2350,7 +2373,7 @@ class CertoraBuildGenerator:
2350
2373
  contracts_with_chosen_addresses: List[Tuple[int, Any]],
2351
2374
  data: Dict[str, Any],
2352
2375
  report_source_file: str,
2353
- primary_contracts: List[str],
2376
+ primary_contract_handles: Set[Tuple[str, str]],
2354
2377
  build_arg_contract_file: str,
2355
2378
  compiler_collector_for_contract_file: CompilerCollector,
2356
2379
  compile_wd: Path,
@@ -2422,12 +2445,14 @@ class CertoraBuildGenerator:
2422
2445
  (srcmap, constructor_srcmap) = self.collect_srcmap(contract_data)
2423
2446
 
2424
2447
  varmap = ""
2448
+ contract_is_primary = (source_code_file, contract_name) in primary_contract_handles
2425
2449
  deployed_bytecode = self.get_bytecode(contract_data["evm"]["deployedBytecode"], contract_name,
2426
- primary_contracts,
2450
+ contract_is_primary,
2427
2451
  contracts_with_chosen_addresses, True)
2428
2452
  deployed_bytecode = compiler_lang.normalize_deployed_bytecode(
2429
2453
  deployed_bytecode)
2430
- constructor_bytecode = self.get_bytecode(contract_data["evm"]["bytecode"], contract_name, primary_contracts,
2454
+ constructor_bytecode = self.get_bytecode(contract_data["evm"]["bytecode"], contract_name,
2455
+ contract_is_primary,
2431
2456
  contracts_with_chosen_addresses, False)
2432
2457
  constructor_bytecode = compiler_lang.normalize_deployed_bytecode(
2433
2458
  constructor_bytecode)
@@ -2509,7 +2534,24 @@ class CertoraBuildGenerator:
2509
2534
 
2510
2535
  @staticmethod
2511
2536
  def get_primary_contract_from_sdc(contracts: List[ContractInSDC], primary: str, primary_file: str) -> List[ContractInSDC]:
2512
- return [x for x in contracts if x.name == primary and Util.abs_norm_path(x.original_file) == Util.abs_norm_path(primary_file)]
2537
+ name_matched = [x for x in contracts if x.name == primary]
2538
+ if not name_matched:
2539
+ return []
2540
+ primary_parts = Path(Util.abs_norm_path(primary_file)).parts
2541
+ best = None
2542
+ best_suffix_len = -1
2543
+ for x in name_matched:
2544
+ x_parts = Path(Util.abs_norm_path(x.original_file)).parts
2545
+ suffix_len = 0
2546
+ for a, b in zip(reversed(primary_parts), reversed(x_parts)):
2547
+ if a == b:
2548
+ suffix_len += 1
2549
+ else:
2550
+ break
2551
+ if suffix_len > best_suffix_len:
2552
+ best_suffix_len = suffix_len
2553
+ best = x
2554
+ return [best] if best is not None else []
2513
2555
 
2514
2556
  @staticmethod
2515
2557
  def generate_library_import(file_absolute_path: str, library_name: str) -> str:
@@ -18,6 +18,8 @@ import json
18
18
  import os
19
19
  import re
20
20
  import uuid
21
+ from collections import UserDict
22
+ from collections.abc import MutableMapping
21
23
 
22
24
  import requests
23
25
  import sys
@@ -823,24 +825,22 @@ class CloudVerification:
823
825
 
824
826
  return True
825
827
 
826
- def cli_verify_and_report(self, cl_args: str, wait_for_results: str) -> bool:
828
+ def cli_verify(self, cl_args: str) -> Optional[JobList]:
827
829
  """
828
830
  Sends a verification request to HTTP Handler, uploads a zip file, and outputs the results.
829
- :param wait_for_results: If "all", we will wait for the results of the verification. If "none",
830
- the run will be terminated after sending the job to the cloud.
831
831
  :param cl_args: A string that can be copied to and run by the shell to recreate this run.
832
- @returns If compareToExpected is True, returns True when the expected output equals the actual results.
833
- Otherwise, returns False if there was at least one violated rule.
832
+ @returns job_list which defines the jobs that have been submitted successfully.
833
+ Otherwise, returns None.
834
834
  """
835
835
 
836
836
  post_result = self.__send_verification_request(cl_args)
837
837
  if not post_result:
838
- return False
838
+ return None
839
839
 
840
840
  file_upload_success = self.__compress_and_upload_zip_files()
841
841
 
842
842
  if not file_upload_success:
843
- return False
843
+ return None
844
844
 
845
845
  # set results urls. They are all functions of the form: self.set_output_url(self.userId, self.anonymousKey)
846
846
  for func_name in ["set_output_url", "set_report_url", "set_zip_output_url", "set_json_output_url",
@@ -860,6 +860,18 @@ class CloudVerification:
860
860
  self.vscode_extension_info_writer.add_field("verification_report_url", self.url_print_format())
861
861
  self.vscode_extension_info_writer.write_file()
862
862
 
863
+ return job_list
864
+
865
+ def cli_report(self, job_list: JobList, wait_for_results: str) -> bool:
866
+ """
867
+ Outputs and report the results from verification.
868
+ :param wait_for_results: If "all", we will wait for the results of the verification. If "none",
869
+ the run will be terminated after sending the job to the cloud.
870
+ :param job_list: A JobList which contains data relevant to the job which was sent for verification.
871
+ @returns If compareToExpected is True, returns True when the expected output equals the actual results.
872
+ Otherwise, returns False if there was at least one violated rule.
873
+ """
874
+
863
875
  if not wait_for_results or wait_for_results == str(Vf.WaitForResultOptions.NONE):
864
876
  job_list.save_data()
865
877
  job_list.save_recent_jobs_to_path()
@@ -927,10 +939,10 @@ class CloudVerification:
927
939
  response = None
928
940
  print("\nConnecting to server...")
929
941
  cloud_logger.debug(f"requesting verification from {verify_url}")
930
- headers = {
942
+ headers: MutableMapping[str, str | bytes] = UserDict({
931
943
  'Authorization': f'Bearer {self.context.key}',
932
944
  "Content-Type": "application/json"
933
- }
945
+ })
934
946
 
935
947
  # retry on request timeout or 502 (must take no more than 3 minutes)
936
948
  # print error message on the 3rd exception and return
@@ -953,6 +965,9 @@ class CloudVerification:
953
965
  elif status == 401:
954
966
  print("Invalid key or key is missing. Please, make sure you entered a valid key.")
955
967
  return None
968
+ elif status == 426:
969
+ output_error_response(response)
970
+ return None
956
971
  elif status >= 500:
957
972
  cloud_logger.debug(f'{status} received. Retry...')
958
973
  if i < self.verification_request_retries:
@@ -961,7 +976,7 @@ class CloudVerification:
961
976
  else:
962
977
  print("Oops, an error occurred when sending your request. Please try again later")
963
978
  return None
964
- else: # status != 200, 403, 5XX
979
+ else: # status != 200, 403, 401, 426, 5XX
965
980
  output_error_response(response)
966
981
  return None
967
982
  except requests.exceptions.Timeout:
@@ -220,6 +220,33 @@ def handle_unsupported_decl_value(source_snippet: str,
220
220
  False)
221
221
 
222
222
 
223
+ def is_idempotent_index_expr(expr: Dict[str, Any]) -> bool:
224
+ """Check if an index expression is idempotent (safe to duplicate without side effects).
225
+ Only allows Identifier, Literal, and arithmetic BinaryOperations (recursively)."""
226
+ node_type = expr.get("nodeType")
227
+ if node_type in ("Identifier", "Literal"):
228
+ return True
229
+ if node_type == "BinaryOperation" and expr.get("operator") in ("+", "-", "*", "/", "%"):
230
+ left = expr.get("leftExpression", {})
231
+ right = expr.get("rightExpression", {})
232
+ return is_idempotent_index_expr(left) and is_idempotent_index_expr(right)
233
+ return False
234
+
235
+
236
+ def has_non_idempotent_index(left_hand_side: Dict[str, Any]) -> bool:
237
+ """Walk the LHS chain and check that all IndexAccess indexExpressions are idempotent."""
238
+ node = left_hand_side
239
+ while node["nodeType"] in ("MemberAccess", "IndexAccess"):
240
+ if node["nodeType"] == "IndexAccess":
241
+ index_expr = node.get("indexExpression", {})
242
+ if not is_idempotent_index_expr(index_expr):
243
+ return True
244
+ node = node["baseExpression"]
245
+ else: # MemberAccess
246
+ node = node["expression"]
247
+ return False
248
+
249
+
223
250
  def handle_multi_def(source_snippet: str, assignment: Dict[str, Any]) -> LocalAssignmentFinderData:
224
251
  return handle_unsupported_def_value(source_snippet, assignment)
225
252
 
@@ -260,6 +287,12 @@ def handle_single_def(source_snippet: str, finder_running_id: int, assignment: D
260
287
  f"The assignment {source_snippet} assigns to a storage variable, skipping")
261
288
  return handle_to_skip()
262
289
 
290
+ # check that all index expressions in the LHS chain are idempotent (no side effects like i++)
291
+ if has_non_idempotent_index(left_hand_side):
292
+ instrumentation_logger.debug(
293
+ f"The assignment {source_snippet} has a non-idempotent index expression, treating as unsupported")
294
+ return handle_unsupported_def_value(source_snippet, assignment)
295
+
263
296
  type_string = left_hand_side["typeDescriptions"]["typeString"]
264
297
 
265
298
  if is_unsupported_type(type_string):
@@ -22,6 +22,7 @@ import re
22
22
  import shutil
23
23
  import subprocess
24
24
  import sys
25
+ from collections import UserDict
25
26
  from dataclasses import dataclass
26
27
  from pathlib import Path
27
28
  from typing import Optional, Any, List, Dict, Tuple, Set, Union
@@ -503,7 +504,7 @@ class WebUtils:
503
504
  self.mutation_test_final_result_url = f"https://{mutation_test_domain}"
504
505
  mutation_logger.debug(f"Using server {args.server} with mutation_test_id_url {self.mutation_test_id_url}")
505
506
 
506
- def put_response_with_timeout(self, url: str, data: Any, headers: Dict[str, str]) -> Optional[requests.Response]:
507
+ def put_response_with_timeout(self, url: str, data: Any, headers: UserDict) -> Optional[requests.Response]:
507
508
  """
508
509
  Executes a put request and returns the response, uses a timeout mechanism
509
510
 
@@ -526,7 +527,7 @@ class WebUtils:
526
527
  return None
527
528
 
528
529
  def put_json_request_with_timeout(self, url: str, body: Dict[str, Any],
529
- headers: Dict[str, str]) -> Optional[requests.Response]:
530
+ headers: UserDict) -> Optional[requests.Response]:
530
531
  """
531
532
  Executes a put request and returns the response, uses a timeout mechanism
532
533
 
@@ -1725,7 +1726,7 @@ class MutateApp:
1725
1726
  @staticmethod
1726
1727
  def upload_file_to_cloud_storage(web_utils: WebUtils, presigned_url: str, data: Any) -> None:
1727
1728
  mutation_logger.debug("Uploading file")
1728
- headers = {"Content-Type": "application/json"}
1729
+ headers = UserDict({"Content-Type": "application/json"})
1729
1730
  put_resp = web_utils.put_response_with_timeout(presigned_url, json.dumps(data), headers)
1730
1731
  if not put_resp:
1731
1732
  raise ConnectionError(f"Failed to submit to presigned URL. url - {presigned_url}")
@@ -1973,7 +1974,7 @@ def get_top_level_rules(progress_json: Dict[str, Any]) -> Optional[List[Dict[str
1973
1974
 
1974
1975
 
1975
1976
  certora_key = os.getenv(KEY_ENV_VAR, '')
1976
- auth_headers = {"Authorization": f"Bearer {certora_key}", "Content-Type": "application/json"}
1977
+ auth_headers = UserDict({"Authorization": f"Bearer {certora_key}", "Content-Type": "application/json"})
1977
1978
  default_cookies = {str(MConstants.CERTORA_KEY): certora_key}
1978
1979
 
1979
1980
 
@@ -93,6 +93,8 @@ class RunSources(Util.NoValEnum):
93
93
  LIGHT_TEST = auto()
94
94
  REPORT = auto()
95
95
  TIMEOUTER = auto()
96
+ STATIC_ANALYZER = auto()
97
+ AUTO_PROVER = auto()
96
98
 
97
99
 
98
100
  class WaitForResultOptions(Util.NoValEnum):
@@ -50,6 +50,7 @@ log = logging.getLogger(__name__)
50
50
 
51
51
 
52
52
  VIOLATIONS_EXIT_CODE = 100
53
+ CLOUD_ERROR_EXIT_CODE = 426
53
54
 
54
55
  @dataclass
55
56
  class CertoraRunResult:
@@ -220,9 +221,13 @@ def run_remote(
220
221
 
221
222
  pretty_args = [f"'{a}'" if " " in a else a for a in args]
222
223
 
223
- ok = cloud.cli_verify_and_report(" ".join(pretty_args), context.wait_for_results)
224
+ job_list = cloud.cli_verify(" ".join(pretty_args))
225
+ if job_list:
226
+ ok = cloud.cli_report(job_list, context.wait_for_results)
227
+ exit_code = 0 if ok else VIOLATIONS_EXIT_CODE
228
+ else:
229
+ exit_code = CLOUD_ERROR_EXIT_CODE
224
230
 
225
- exit_code = 0 if ok else VIOLATIONS_EXIT_CODE
226
231
  result: Optional[CertoraRunResult] = None
227
232
  if cloud.statusUrl:
228
233
  result = CertoraRunResult(
@@ -248,8 +253,10 @@ def handle_exit(exit_code: int, return_value: Optional[CertoraRunResult]) -> Opt
248
253
  """
249
254
  if exit_code == VIOLATIONS_EXIT_CODE:
250
255
  raise CertoraFoundViolations("violations were found", return_value)
256
+ if exit_code == CLOUD_ERROR_EXIT_CODE:
257
+ raise Util.CertoraUserInputError("Certora Prover run failed due to a job submission error")
251
258
  if exit_code != 0:
252
- raise Util.CertoraUserInputError(f"run_certora failed (code {exit_code})")
259
+ raise Util.CertoraUserInputError(f"Certora Prover run failed (code {exit_code})")
253
260
  return return_value
254
261
 
255
262
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: certora-cli
3
- Version: 8.13.1
3
+ Version: 8.16.2
4
4
  Summary: Runner for the Certora Prover
5
5
  Home-page: https://pypi.org/project/certora-cli
6
6
  Author: Certora
@@ -38,4 +38,4 @@ Dynamic: requires-dist
38
38
  Dynamic: requires-python
39
39
  Dynamic: summary
40
40
 
41
- Commit deb4423. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
41
+ Commit 94b86f7. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -0,0 +1 @@
1
+ {"name": "certora-cli", "tag": "8.16.2", "branch": "", "commit": "94b86f7", "timestamp": "20260625.11.18.041053", "version": "8.16.2"}
@@ -3,11 +3,11 @@ import setuptools
3
3
 
4
4
  setuptools.setup(
5
5
  name="certora-cli",
6
- version="8.13.1",
6
+ version="8.16.2",
7
7
  author="Certora",
8
8
  author_email="support@certora.com",
9
9
  description="Runner for the Certora Prover",
10
- long_description="Commit deb4423. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.",
10
+ long_description="Commit 94b86f7. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.",
11
11
  long_description_content_type="text/markdown",
12
12
  url="https://pypi.org/project/certora-cli",
13
13
  packages=setuptools.find_packages(),
@@ -1 +0,0 @@
1
- Commit deb4423. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -1 +0,0 @@
1
- {"name": "certora-cli", "tag": "8.13.1", "branch": "", "commit": "deb4423", "timestamp": "20260519.10.54.736103", "version": "8.13.1"}
File without changes
File without changes
File without changes