certora-cli-beta 8.19.2__tar.gz → 8.20.0__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 (95) hide show
  1. {certora_cli_beta-8.19.2/certora_cli_beta.egg-info → certora_cli_beta-8.20.0}/PKG-INFO +2 -2
  2. certora_cli_beta-8.20.0/README.md +1 -0
  3. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraBuild.py +70 -37
  4. certora_cli_beta-8.20.0/certora_cli/CertoraProver/certoraBuildRust.py +351 -0
  5. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraCloudIO.py +119 -55
  6. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraContextValidator.py +22 -7
  7. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraJobList.py +10 -5
  8. certora_cli_beta-8.20.0/certora_cli/CertoraProver/test_certoraCloudIO.py +157 -0
  9. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/mutateApp.py +85 -92
  10. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/mutateConstants.py +3 -11
  11. certora_cli_beta-8.20.0/certora_cli/Mutate/test_mutateApp.py +60 -0
  12. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/certoraUtils.py +44 -1
  13. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/proverCommon.py +7 -4
  14. certora_cli_beta-8.20.0/certora_cli/Shared/test_proverCommon.py +76 -0
  15. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0/certora_cli_beta.egg-info}/PKG-INFO +2 -2
  16. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli_beta.egg-info/SOURCES.txt +3 -0
  17. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_jars/ASTExtraction.jar +0 -0
  18. certora_cli_beta-8.20.0/certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -0
  19. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_jars/Typechecker.jar +0 -0
  20. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/setup.py +2 -2
  21. certora_cli_beta-8.19.2/README.md +0 -1
  22. certora_cli_beta-8.19.2/certora_cli/CertoraProver/certoraBuildRust.py +0 -154
  23. certora_cli_beta-8.19.2/certora_jars/CERTORA-CLI-VERSION-METADATA.json +0 -1
  24. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/LICENSE +0 -0
  25. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/MANIFEST.in +0 -0
  26. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_bins/__init__.py +0 -0
  27. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollector.py +0 -0
  28. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +0 -0
  29. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorSol.py +0 -0
  30. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorSolBased.py +0 -0
  31. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +0 -0
  32. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +0 -0
  33. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/__init__.py +0 -0
  34. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/__init__.py +0 -0
  35. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/castingInstrumenter.py +0 -0
  36. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraApp.py +0 -0
  37. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraBuildCacheManager.py +0 -0
  38. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraBuildDataClasses.py +0 -0
  39. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraBuildSui.py +0 -0
  40. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +0 -0
  41. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraCollectRunMetadata.py +0 -0
  42. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraCompilerParameters.py +0 -0
  43. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraConfigIO.py +0 -0
  44. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraContext.py +0 -0
  45. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraContextAttributes.py +0 -0
  46. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraContextClass.py +0 -0
  47. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraContractFuncs.py +0 -0
  48. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraExtensionInfo.py +0 -0
  49. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraMiniSpecParser.py +0 -0
  50. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraNodeFilters.py +0 -0
  51. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraOffsetConverter.py +0 -0
  52. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraParseBuildScript.py +0 -0
  53. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraProjectScanner.py +0 -0
  54. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraSourceFinders.py +0 -0
  55. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraType.py +0 -0
  56. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraVerifyGenerator.py +0 -0
  57. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/erc7201.py +0 -0
  58. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/splitRules.py +0 -0
  59. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/storageExtension.py +0 -0
  60. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/uncheckedOverflowInstrumenter.py +0 -0
  61. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/Eq_default.conf +0 -0
  62. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/Eq_mc_no_out_template.spec +0 -0
  63. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/Eq_mc_template.spec +0 -0
  64. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -0
  65. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/Eq_template.spec +0 -0
  66. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/__init__.py +0 -0
  67. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/equivCheck.py +0 -0
  68. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/sanity.spec +0 -0
  69. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/__init__.py +0 -0
  70. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/mutateAttributes.py +0 -0
  71. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/mutateUtil.py +0 -0
  72. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/mutateValidate.py +0 -0
  73. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/ExpectedComparator.py +0 -0
  74. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/__init__.py +0 -0
  75. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/certoraAttrUtil.py +0 -0
  76. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/certoraLogging.py +0 -0
  77. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/certoraValidateFuncs.py +0 -0
  78. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/__init__.py +0 -0
  79. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraCVLFormatter.py +0 -0
  80. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraConcord.py +0 -0
  81. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraEVMProver.py +0 -0
  82. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraEqCheck.py +0 -0
  83. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraMutate.py +0 -0
  84. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraRanger.py +0 -0
  85. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraRun.py +0 -0
  86. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraSolanaProver.py +0 -0
  87. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraSorobanProver.py +0 -0
  88. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraSuiProver.py +0 -0
  89. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/rustMutator.py +0 -0
  90. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli_beta.egg-info/dependency_links.txt +0 -0
  91. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli_beta.egg-info/entry_points.txt +0 -0
  92. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli_beta.egg-info/requires.txt +0 -0
  93. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli_beta.egg-info/top_level.txt +0 -0
  94. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_jars/__init__.py +0 -0
  95. {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: certora-cli-beta
3
- Version: 8.19.2
3
+ Version: 8.20.0
4
4
  Summary: Runner for the Certora Prover
5
5
  Home-page: https://pypi.org/project/certora-cli-beta
6
6
  Author: Certora
@@ -38,4 +38,4 @@ Dynamic: requires-dist
38
38
  Dynamic: requires-python
39
39
  Dynamic: summary
40
40
 
41
- Commit 63fdea8. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
41
+ Commit f567630. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -0,0 +1 @@
1
+ Commit f567630. Build and Run scripts for executing the Certora Prover on Solidity smart contracts.
@@ -1438,6 +1438,19 @@ class CertoraBuildGenerator:
1438
1438
  if match:
1439
1439
  settings_dict["evmVersion"] = match
1440
1440
 
1441
+ @staticmethod
1442
+ def _solc_takes_inliner_detail(compiler_version: Tuple[int, int, int]) -> bool:
1443
+ """Whether this compiler accepts `settings.optimizer.details.inliner`.
1444
+
1445
+ solc learned the key in 0.8.5; older compilers reject the whole standard-json input with
1446
+ `Unknown key "inliner"`, so the file fails to compile rather than compiling un-inlined.
1447
+ See https://github.com/ethereum/solidity/blob/v0.8.4/libsolidity/interface/StandardCompiler.cpp
1448
+ """
1449
+ major, minor, patch = compiler_version
1450
+ if major != 0:
1451
+ raise Util.CertoraUserInputError(f"Unrecognized version {major}.{minor}.{patch}")
1452
+ return (minor == 8 and patch >= 5) or (minor > 8)
1453
+
1441
1454
  def _handle_optimize(self, contract_file_path: Path, settings_dict: Dict[str, Any],
1442
1455
  compiler_collector: CompilerCollector) -> None:
1443
1456
  """
@@ -1456,29 +1469,47 @@ class CertoraBuildGenerator:
1456
1469
  if optimizer and isinstance(optimizer, dict) and optimizer.get('enabled'):
1457
1470
  # if we are not disabling finder friendly optimizer specifically, enable it whenever viaIR is also enabled
1458
1471
  if not self.context.strict_solc_optimizer and self.get_solc_via_ir_value(contract_file_path):
1459
- # The default optimizer steps (taken from libsolidity/interface/OptimiserSettings.h) but with the
1460
- # full inliner step removed
1461
- solc0_8_34_to_0_8_36 = ("dfDvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLvifMCTUca[r]LSsTFOtfDnca[r]"
1462
- "IulcscCTUtvifMx[scCTUt]TOntnfDIulvifMjmul[jul]VcTOculjmul")
1463
- solc0_8_26_to_0_8_33 = ("dhfoDgvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLCTUca[r]LSsTFOtfDnca[r]" +
1464
- "IulcscCTUtx[scCTUt]TOntnfDIuljmul[jul]VcTOculjmul")
1465
- solc0_8_13_to_0_8_25 = "dhfoDgvulfnTUtnIf[xa[r]EscLMcCTUtTOntnfDIulLculVcul[j]T" + \
1466
- "peulxa[rul]xa[r]cLgvifCTUca[r]LSsTFOtfDnca[r]Iulc]jmul[jul]VcTOculjmul"
1467
- solc0_8_12 = "dhfoDgvulfnTUtnIf[xa[r]EscLMcCTUtTOntnfDIulLculVcul[j]T" + \
1468
- "peulxa[rul]xa[r]cLgvifCTUca[r]LsTFOtfDnca[r]Iulc]jmul[jul]VcTOculjmul"
1469
- solc0_8_10_to_0_8_11 = "dhfoDgvulfnTUtnIf[xa[r]scLMcCTUtTOntnfDIulLculVcul[j]T" + \
1470
- "peulxa[rul]xa[r]cLgvifCTUca[r]LsTFOtfDnca[r]Iulc]jmul[jul]VcTOculjmul"
1471
- solc0_8_1_to_0_8_9 = "dhfoDgvulfnTUtnIf[xarrscLMcCTUtTOntnfDIulLculVculjjT" + \
1472
- "peulxarulrulxarrcLgvifCTUcarrLsTFOtfDncarrIulc]jmuljuljulVcTOculjmul"
1473
- solc0_7_6_to_0_8_0 = "dhfoDgvulfnTUtnIf[xarrscLMcCTUtTOntnfDIulLculVculjjT" + \
1474
- "peulxarulrulxarrcLgvifCTUcarrLsTFOtfDncarrIulc]jmuljuljulVcTOculjmul"
1475
- solc0_7_2_to_0_7_5 = "NdhfoDgvulfnTUtnIf[xarrscLMcCTUtTOntnfDIulLculVculjjT" + \
1476
- "peulxarulrulxarrcLgvifCTUcarrLsTFOtfDncarrIulc]jmuljuljulVcTOculjmulN"
1477
- solc0_6_7_to_0_7_1 = "dhfoDgvulfnTUtnIf[xarrscLMcCTUtTOntnfDIulLculVculjj" + \
1478
- "eulxarulrulxarrcLgvifCTUcarrLsTFOtfDncarrIulc]jmuljuljulVcTOculjmul"
1479
-
1480
- def standard_replacement(optimizer_steps: str) -> str:
1481
- return optimizer_steps.replace("gvif", "gvf") # replace gvif with gvf
1472
+ # The default Yul optimizer steps, copied verbatim from
1473
+ # libsolidity/interface/OptimiserSettings.h at each release that changed them. Keeping them
1474
+ # verbatim is what makes the full inliner the only difference between what we send and what
1475
+ # solc would have run by itself.
1476
+ solc0_8_34_to_0_8_36 = ("dfDvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLvifMCTUca[r]LSsTFOtfDnca[r]Iulcsc"
1477
+ "CTUtvifMx[scCTUt]TOntnfDIulvifMjmul[jul]VcTOculjmul")
1478
+ solc0_8_26_to_0_8_33 = ("dhfoDgvulfnTUtnIfxa[r]EscLMVcul[j]Trpeulxa[r]cLgvifMCTUca[r]LSsTFOtfDnca[r]Iu"
1479
+ "lcscCTUtgvifMx[scCTUt]TOntnfDIulgvifMjmul[jul]VcTOculjmul")
1480
+ solc0_8_13_to_0_8_25 = ("dhfoDgvulfnTUtnIf[xa[r]EscLMcCTUtTOntnfDIulLculVcul[j]Tpeulxa[rul]xa[r]cLgvif"
1481
+ "CTUca[r]LSsTFOtfDnca[r]Iulc]jmul[jul]VcTOculjmul")
1482
+ solc0_8_12 = ("dhfoDgvulfnTUtnIf[xa[r]EscLMcCTUtTOntnfDIulLculVcul[j]Tpeulxa[rul]xa[r]cLgvifCTUca[r]Ls"
1483
+ "TFOtfDnca[r]Iulc]jmul[jul]VcTOculjmul")
1484
+ solc0_8_10_to_0_8_11 = ("dhfoDgvulfnTUtnIf[xa[r]scLMcCTUtTOntnfDIulLculVcul[j]Tpeulxa[rul]xa[r]cLgvifC"
1485
+ "TUca[r]LsTFOtfDnca[r]Iulc]jmul[jul]VcTOculjmul")
1486
+ solc0_8_4_to_0_8_9 = ("dhfoDgvulfnTUtnIf[xarrscLMcCTUtTOntnfDIulLculVculjjTpeulxarulrulxarrcLgvifCTUca"
1487
+ "rrLsTFOtfDncarrIulc]jmuljuljulVcTOculjmul")
1488
+ solc0_7_6_to_0_8_3 = ("dhfoDgvulfnTUtnIf[xarrscLMcCTUtTOntnfDIulLculVculjjTpeulxarulrulxarrcLgvifCTUca"
1489
+ "rrLsTOtfDncarrIulc]jmuljuljulVcTOculjmul")
1490
+ solc0_7_2_to_0_7_5 = ("NdhfoDgvulfnTUtnIf[xarrscLMcCTUtTOntnfDIulLculVculjjTpeulxarulrulxarrcLgvifCTUc"
1491
+ "arrLsTOtfDncarrIulc]jmuljuljulVcTOculjmulN")
1492
+ solc0_6_7_to_0_7_1 = ("dhfoDgvulfnTUtnIf[xarrscLMcCTUtTOntnfDIulLculVculjjeulxarulrulxarrcLgvifCTUcarr"
1493
+ "LsTOtfDncarrIulc]jmuljuljulVcTOculjmul")
1494
+
1495
+ def without_full_inliner(optimizer_steps: str) -> str:
1496
+ """Take the full inliner out of a Yul optimizer step sequence.
1497
+
1498
+ The full inliner pulls internal functions into their callers, which erases the autofinders
1499
+ that internal-function summaries are matched against. It is the letter `i`, and in every
1500
+ sequence solc ships it appears only inside the trigram `vif`: EquivalentFunctionCombiner,
1501
+ FullInliner, BlockFlattener, the flattener being there to tidy up after the inliner. Dropping
1502
+ the middle letter of that trigram removes the inliner and leaves every other pass in place
1503
+ and in order.
1504
+
1505
+ Upstream writes the trigram plus its surrounding steps as one line in
1506
+ OptimiserSettings.h and comments the whole line "Run full inliner", so it is tempting to
1507
+ drop all of it. On every rung here, taking out `i` alone keeps just as many internal
1508
+ functions as taking out the whole group, so this is the smaller deviation from what solc
1509
+ would have run for the same result. Public/CITests/testYulOptimizerStepsInlining.py is
1510
+ what checks the rewrite still does its job on each compiler.
1511
+ """
1512
+ return optimizer_steps.replace("vif", "vf")
1482
1513
 
1483
1514
  compiler_version = compiler_collector.compiler_version
1484
1515
  major, minor, patch = compiler_version
@@ -1499,10 +1530,10 @@ class CertoraBuildGenerator:
1499
1530
  yul_optimizer_steps = solc0_6_7_to_0_7_1
1500
1531
  elif minor == 7 and 2 <= patch <= 5:
1501
1532
  yul_optimizer_steps = solc0_7_2_to_0_7_5
1502
- elif (minor == 7 and 6 <= patch) or (minor == 8 and patch == 0):
1503
- yul_optimizer_steps = solc0_7_6_to_0_8_0
1504
- elif minor == 8 and 1 <= patch <= 9:
1505
- yul_optimizer_steps = solc0_8_1_to_0_8_9
1533
+ elif (minor == 7 and 6 <= patch) or (minor == 8 and patch <= 3):
1534
+ yul_optimizer_steps = solc0_7_6_to_0_8_3
1535
+ elif minor == 8 and 4 <= patch <= 9:
1536
+ yul_optimizer_steps = solc0_8_4_to_0_8_9
1506
1537
  elif minor == 8 and 10 <= patch <= 11:
1507
1538
  yul_optimizer_steps = solc0_8_10_to_0_8_11
1508
1539
  elif minor == 8 and patch == 12:
@@ -1516,17 +1547,19 @@ class CertoraBuildGenerator:
1516
1547
  assert yul_optimizer_steps is not None, \
1517
1548
  'Yul Optimizer steps missing for requested Solidity version. Please contact Certora team.'
1518
1549
 
1519
- yul_optimizer_steps = standard_replacement(yul_optimizer_steps)
1520
-
1521
- settings_dict["optimizer"]["details"] = {"inliner": False, "yulDetails": {
1522
- "optimizerSteps": yul_optimizer_steps
1523
- }}
1550
+ yul_optimizer_steps = without_full_inliner(yul_optimizer_steps)
1551
+ assert self.context.yul_optimizer_steps or "i" not in yul_optimizer_steps, \
1552
+ 'Full inliner still present in the Yul optimizer steps. Please contact Certora team.'
1553
+
1554
+ # The Yul steps carry the inlining decision for the via-IR pipeline on every
1555
+ # supported version; `inliner` speaks to the legacy-codegen inliner and exists only
1556
+ # from 0.8.5, so it is added where it is understood and left out where it is not.
1557
+ details: Dict[str, Any] = {"yulDetails": {"optimizerSteps": yul_optimizer_steps}}
1558
+ if self._solc_takes_inliner_detail(compiler_version):
1559
+ details["inliner"] = False
1560
+ settings_dict["optimizer"]["details"] = details
1524
1561
  elif not self.context.strict_solc_optimizer:
1525
- compiler_version = compiler_collector.compiler_version
1526
- major, minor, patch = compiler_version
1527
- if major != 0:
1528
- raise Util.CertoraUserInputError(f"Unrecognized version {major}.{minor}.{patch}")
1529
- if (minor == 8 and patch >= 5) or (minor > 8):
1562
+ if self._solc_takes_inliner_detail(compiler_collector.compiler_version):
1530
1563
  settings_dict["optimizer"]["details"] = {"inliner": False}
1531
1564
  if self.context.disable_solc_optimizers is not None:
1532
1565
  if "details" not in settings_dict["optimizer"]:
@@ -0,0 +1,351 @@
1
+ # The Certora Prover
2
+ # Copyright (C) 2025 Certora Ltd.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, version 3 of the License.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ #
13
+ # You should have received a copy of the GNU General Public License
14
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
15
+
16
+ from __future__ import annotations
17
+
18
+ import sys
19
+ from pathlib import Path
20
+
21
+ scripts_dir_path = Path(__file__).parent.parent.resolve() # containing directory
22
+ sys.path.insert(0, str(scripts_dir_path))
23
+
24
+ import glob
25
+ import json
26
+ import os
27
+ import shutil
28
+ import subprocess
29
+ import time
30
+ import logging
31
+ from collections.abc import Iterable
32
+ from pathlib import Path
33
+ from typing import Any, Set, Dict, cast
34
+
35
+ from CertoraProver.certoraBuild import build_source_tree
36
+ from CertoraProver.certoraContextClass import CertoraContext
37
+ from CertoraProver.certoraParseBuildScript import run_rust_build
38
+ import CertoraProver.certoraContextAttributes as Attrs
39
+ from Shared import certoraUtils as Util
40
+
41
+
42
+ log = logging.getLogger(__name__)
43
+
44
+
45
+ def build_rust_project(context: CertoraContext, timings: Dict) -> None:
46
+ """
47
+ Compile the Rust artefact and record elapsed time in *timings*.
48
+
49
+ Args:
50
+ context: The CertoraContext object containing the configuration.
51
+ timings: A dictionary to store timing information.
52
+ """
53
+ log.debug("Build Rust target")
54
+ start = time.perf_counter()
55
+ set_rust_build_directory(context)
56
+ timings["buildTime"] = round(time.perf_counter() - start, 4)
57
+ if context.test == str(Util.TestValue.AFTER_BUILD):
58
+ raise Util.TestResultsReady(context)
59
+
60
+
61
+ def set_rust_build_directory(context: CertoraContext) -> None:
62
+ if not context.files:
63
+ build_rust_app(context)
64
+
65
+ copy_files_to_build_dir(context)
66
+
67
+ sources: Set[Path] = set()
68
+ collect_files_from_rust_sources(context, sources)
69
+
70
+ try:
71
+ # Create generators
72
+ build_source_tree(sources, context)
73
+
74
+ except Exception as e:
75
+ raise Util.CertoraUserInputError(f"Collecting build files failed with the exception: {e}")
76
+
77
+
78
+ def build_rust_app(context: CertoraContext) -> None:
79
+ assert not context.files, "build_rust_app: expecting files to be empty"
80
+ if context.build_script:
81
+ build_command = [context.build_script, '--json', '-l']
82
+ feature_flag = '--cargo_features'
83
+ else: # cargo
84
+ build_command = ["cargo", "certora-sbf", '--json']
85
+ feature_flag = '--features'
86
+ if context.cargo_tools_version:
87
+ build_command.extend(["--tools-version", context.cargo_tools_version])
88
+ context.rust_project_directory = Util.find_nearest_cargo_toml()
89
+
90
+ if getattr(context, 'solana_sbf_arch', None) is not None:
91
+ build_command.extend(["--arch", context.solana_sbf_arch])
92
+
93
+ if context.cargo_features is not None:
94
+ build_command.append(feature_flag)
95
+ build_command.append(' '.join(context.cargo_features))
96
+
97
+ if context.test == str(Util.TestValue.SOLANA_BUILD_CMD):
98
+ raise Util.TestResultsReady(build_command)
99
+
100
+ run_rust_build(context, build_command)
101
+
102
+
103
+ def add_solana_files_to_sources(context: CertoraContext, sources: Set[Path]) -> None:
104
+ for attr in [Attrs.SolanaProverAttributes.SOLANA_INLINING,
105
+ Attrs.SolanaProverAttributes.SOLANA_SUMMARIES,
106
+ Attrs.SolanaProverAttributes.BUILD_SCRIPT,
107
+ Attrs.SolanaProverAttributes.FILES]:
108
+ attr_name = attr.get_conf_key()
109
+ attr_value = getattr(context, attr_name, None)
110
+ if not attr_value:
111
+ continue
112
+ if isinstance(attr_value, str):
113
+ attr_value = [attr_value]
114
+ if not isinstance(attr_value, list):
115
+ raise Util.CertoraUserInputError(f"{attr_value} is not a valid value for {attr_name} {attr_value}. Value "
116
+ f"must be a string or a llist ")
117
+ file_paths = [Path(s) for s in attr_value]
118
+ for file_path in file_paths:
119
+ if not file_path.exists():
120
+ raise Util.CertoraUserInputError(f"in {attr_name} file {file_path} does not exist")
121
+ sources.add(file_path.absolute().resolve())
122
+
123
+
124
+ def collect_files_from_rust_sources(context: CertoraContext, sources: Set[Path]) -> None:
125
+ if hasattr(context, 'rust_project_directory'):
126
+ project_directory = Path(context.rust_project_directory)
127
+
128
+ if not project_directory.is_dir():
129
+ raise ValueError(f"The given directory '{project_directory}' is not valid.")
130
+
131
+ collect_declared_sources(context, project_directory, sources)
132
+ sources.update(collect_build_inputs(context, project_directory.resolve()))
133
+
134
+ sources.add(project_directory.absolute())
135
+ if context.build_script:
136
+ sources.add(Path(context.build_script).resolve())
137
+ if getattr(context, 'conf_file', None) and Path(context.conf_file).exists():
138
+ sources.add(Path(context.conf_file).absolute())
139
+
140
+ add_solana_files_to_sources(context, sources)
141
+
142
+
143
+ def collect_declared_sources(context: CertoraContext, project_directory: Path, sources: Set[Path]) -> None:
144
+ """
145
+ The files matched by the `sources` globs the build reported (from `[package.metadata.certora]` or the build
146
+ script's JSON), restricted to the kinds of files the report knows how to show.
147
+ """
148
+ patterns = ["*.rs", "*.so", "*.wasm", Util.CARGO_TOML_FILE, "Cargo.lock", "justfile"]
149
+ exclude_dirs = [".certora_internal"]
150
+
151
+ for source in context.rust_sources:
152
+ for file in glob.glob(f'{project_directory.joinpath(source)}', recursive=True):
153
+ file_path = Path(file)
154
+ if any(excluded in file_path.parts for excluded in exclude_dirs):
155
+ continue
156
+ if file_path.is_file() and any(file_path.match(pattern) for pattern in patterns):
157
+ sources.add(file_path)
158
+
159
+
160
+ # cargo and rustup look these up in the working directory and each of its ancestors
161
+ CARGO_CONFIG_FILES = [".cargo/config.toml", ".cargo/config", "rust-toolchain.toml", "rust-toolchain"]
162
+
163
+
164
+ def collect_build_inputs(context: CertoraContext, project_directory: Path) -> set[Path]:
165
+ """
166
+ Every local file the cargo build actually consumed, so that the uploaded sources suffice to rebuild the artifact:
167
+ the files listed in cargo's dep-info for the artifact, the manifests of all path packages in the dependency graph,
168
+ the workspace manifest and lockfile, and the cargo/rustup configuration files visible from the working
169
+ directories. Each part degrades independently; a missing dep-info or a failing `cargo metadata` only leaves that
170
+ part out, since the artifact may have been prebuilt or the build script may not go through cargo at all.
171
+ """
172
+ metadata = cargo_metadata(project_directory)
173
+ workspace_root = Path(metadata["workspace_root"]).resolve() if metadata else project_directory
174
+ target_directory = Path(metadata["target_directory"]).resolve() if metadata else workspace_root / "target"
175
+ local_manifests: set[Path] = set()
176
+ if metadata:
177
+ local_manifests = {Path(p["manifest_path"]).resolve() for p in metadata["packages"] if p["source"] is None}
178
+
179
+ inputs: set[Path] = {workspace_root / Util.CARGO_TOML_FILE, workspace_root / "Cargo.lock"}
180
+ inputs.update(local_manifests)
181
+
182
+ dep_info = find_dep_info(Path(context.files[0]).resolve(), target_directory)
183
+ if dep_info is None:
184
+ log.debug(f"No cargo dep-info found for {context.files[0]}; uploading only the declared sources")
185
+ else:
186
+ dep_files = parse_dep_info(dep_info, workspace_root)
187
+ log.debug(f"Read {len(dep_files)} build inputs from {dep_info}")
188
+ inputs.update(dep_files)
189
+ # the manifests of the crates these files belong to, for when `cargo metadata` was unavailable
190
+ for directory in {f.parent for f in dep_files}:
191
+ manifest = enclosing_manifest(directory, workspace_root)
192
+ if manifest is not None:
193
+ inputs.add(manifest)
194
+
195
+ start_dirs = {Path.cwd().resolve(), workspace_root, project_directory} | {m.parent for m in local_manifests}
196
+ inputs.update(find_toolchain_config_files(start_dirs))
197
+
198
+ excluded_roots = [target_directory, *external_tool_roots()]
199
+
200
+ def is_project_file(file: Path) -> bool:
201
+ return file.is_file() and ".certora_internal" not in file.parts and \
202
+ not any(file.is_relative_to(root) for root in excluded_roots)
203
+
204
+ return {f for f in inputs if is_project_file(f)}
205
+
206
+
207
+ def cargo_metadata(project_directory: Path) -> dict[str, Any] | None:
208
+ """
209
+ `cargo metadata` for the workspace containing project_directory, or None if it cannot be obtained. `--frozen`
210
+ keeps cargo off the network and away from the lockfile; `--all-features` lists optional path dependencies too,
211
+ since cargo reads their manifests whether or not they end up compiled.
212
+ """
213
+ manifest = project_directory / Util.CARGO_TOML_FILE
214
+ if not manifest.is_file():
215
+ return None
216
+ cmd = ["cargo", "metadata", "--format-version", "1", "--frozen", "--all-features",
217
+ "--manifest-path", str(manifest)]
218
+ try:
219
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
220
+ except (OSError, subprocess.TimeoutExpired) as e:
221
+ log.debug(f"Could not run `{' '.join(cmd)}`: {e}")
222
+ return None
223
+ if result.returncode != 0:
224
+ log.debug(f"`{' '.join(cmd)}` failed:\n{result.stderr}")
225
+ return None
226
+ try:
227
+ metadata = json.loads(result.stdout)
228
+ except json.JSONDecodeError as e:
229
+ log.debug(f"Could not parse the output of `{' '.join(cmd)}`: {e}")
230
+ return None
231
+ return metadata if isinstance(metadata, dict) else None
232
+
233
+
234
+ def find_dep_info(artifact: Path, target_directory: Path) -> Path | None:
235
+ """
236
+ Cargo writes the dep-info of an uplifted artifact next to it, with the artifact's stem and a `.d` suffix. A build
237
+ script may report a copy of the artifact instead, so fall back to looking for that stem in the profile directories
238
+ under the cargo target directory, taking the most recently written one.
239
+ """
240
+ beside = artifact.with_suffix(".d")
241
+ if beside.is_file():
242
+ return beside
243
+ if not target_directory.is_dir():
244
+ return None
245
+ candidates = [p for pattern in [f"*/{artifact.stem}.d", f"*/*/{artifact.stem}.d"]
246
+ for p in target_directory.glob(pattern) if p.is_file()]
247
+ return max(candidates, key=lambda p: cast(Path, p).stat().st_mtime, default=None)
248
+
249
+
250
+ def parse_dep_info(dep_info: Path, base_dir: Path) -> set[Path]:
251
+ """
252
+ Parse a cargo dep-info file. It is in Makefile syntax: `target: prerequisite prerequisite ...`, with spaces inside
253
+ paths escaped as `\\ ` and long lines continued with a trailing backslash. Cargo emits absolute paths unless
254
+ `build.dep-info-basedir` is configured, so relative paths are resolved against base_dir.
255
+ """
256
+ text = dep_info.read_text().replace("\\\n", " ")
257
+ files: set[Path] = set()
258
+ for line in text.splitlines():
259
+ tokens = split_make_tokens(line)
260
+ if tokens and tokens[0].endswith(":"):
261
+ prerequisites = tokens[1:]
262
+ elif len(tokens) > 1 and tokens[1] == ":":
263
+ prerequisites = tokens[2:]
264
+ else:
265
+ continue
266
+ for token in prerequisites:
267
+ path = Path(token)
268
+ files.add((path if path.is_absolute() else base_dir / path).resolve())
269
+ return files
270
+
271
+
272
+ def split_make_tokens(line: str) -> list[str]:
273
+ """
274
+ Split a Makefile rule line on unescaped whitespace, undoing the `\\ `, `\\#`, `\\\\` and `$$` escapes.
275
+ """
276
+ tokens: list[str] = []
277
+ current: list[str] = []
278
+ i = 0
279
+ while i < len(line):
280
+ char = line[i]
281
+ if char == "\\" and i + 1 < len(line) and line[i + 1] in " #\\":
282
+ current.append(line[i + 1])
283
+ i += 2
284
+ elif line.startswith("$$", i):
285
+ current.append("$")
286
+ i += 2
287
+ elif char.isspace():
288
+ if current:
289
+ tokens.append("".join(current))
290
+ current = []
291
+ i += 1
292
+ else:
293
+ current.append(char)
294
+ i += 1
295
+ if current:
296
+ tokens.append("".join(current))
297
+ return tokens
298
+
299
+
300
+ def enclosing_manifest(directory: Path, workspace_root: Path) -> Path | None:
301
+ """
302
+ The manifest of the crate a source directory belongs to: the nearest Cargo.toml at or above it, not looking
303
+ further up than the workspace root.
304
+ """
305
+ for candidate_dir in [directory, *directory.parents]:
306
+ manifest = candidate_dir / Util.CARGO_TOML_FILE
307
+ if manifest.is_file():
308
+ return manifest
309
+ if candidate_dir == workspace_root:
310
+ return None
311
+ return None
312
+
313
+
314
+ def find_toolchain_config_files(start_dirs: Iterable[Path]) -> set[Path]:
315
+ """
316
+ cargo and rustup read their configuration from the working directory and each of its ancestors. The walk stops
317
+ before the home directory and anything above it: configuration there is the user's rather than the project's, and
318
+ including it would hoist the root of .certora_sources up to the home directory.
319
+ """
320
+ home = Path.home().resolve()
321
+ user_level_dirs = {home, *home.parents}
322
+ found: set[Path] = set()
323
+ for start in start_dirs:
324
+ for directory in [start, *start.parents]:
325
+ if directory in user_level_dirs and directory != start:
326
+ break
327
+ for name in CARGO_CONFIG_FILES:
328
+ candidate = directory / name
329
+ if candidate.is_file():
330
+ found.add(candidate)
331
+ return found
332
+
333
+
334
+ def external_tool_roots() -> list[Path]:
335
+ """
336
+ Where toolchains and registry/git checkouts live. Files under these are never part of the user's project.
337
+ """
338
+ home = Path.home()
339
+ return [Path(os.environ.get("CARGO_HOME", home / ".cargo")).resolve(),
340
+ Path(os.environ.get("RUSTUP_HOME", home / ".rustup")).resolve(),
341
+ Path(os.environ.get("CERTORA_PLATFORM_TOOLS_ROOT", home / ".cache" / "solana")).resolve()]
342
+
343
+
344
+ def copy_files_to_build_dir(context: CertoraContext) -> None:
345
+ assert context.files, "copy_files_to_build_dir: expecting files to be non-empty"
346
+ shutil.copyfile(context.files[0], Util.get_build_dir() / Path(context.files[0]).name)
347
+
348
+ if rust_logs := getattr(context, 'rust_logs_stdout', None):
349
+ shutil.copy(Path(rust_logs), Util.get_build_dir() / Path(rust_logs).name)
350
+ if rust_logs := getattr(context, 'rust_logs_stderr', None):
351
+ shutil.copy(Path(rust_logs), Util.get_build_dir() / Path(rust_logs).name)