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.
- {certora_cli_beta-8.19.2/certora_cli_beta.egg-info → certora_cli_beta-8.20.0}/PKG-INFO +2 -2
- certora_cli_beta-8.20.0/README.md +1 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraBuild.py +70 -37
- certora_cli_beta-8.20.0/certora_cli/CertoraProver/certoraBuildRust.py +351 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraCloudIO.py +119 -55
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraContextValidator.py +22 -7
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraJobList.py +10 -5
- certora_cli_beta-8.20.0/certora_cli/CertoraProver/test_certoraCloudIO.py +157 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/mutateApp.py +85 -92
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/mutateConstants.py +3 -11
- certora_cli_beta-8.20.0/certora_cli/Mutate/test_mutateApp.py +60 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/certoraUtils.py +44 -1
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/proverCommon.py +7 -4
- certora_cli_beta-8.20.0/certora_cli/Shared/test_proverCommon.py +76 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0/certora_cli_beta.egg-info}/PKG-INFO +2 -2
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli_beta.egg-info/SOURCES.txt +3 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_jars/ASTExtraction.jar +0 -0
- certora_cli_beta-8.20.0/certora_jars/CERTORA-CLI-VERSION-METADATA.json +1 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_jars/Typechecker.jar +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/setup.py +2 -2
- certora_cli_beta-8.19.2/README.md +0 -1
- certora_cli_beta-8.19.2/certora_cli/CertoraProver/certoraBuildRust.py +0 -154
- certora_cli_beta-8.19.2/certora_jars/CERTORA-CLI-VERSION-METADATA.json +0 -1
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/LICENSE +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/MANIFEST.in +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_bins/__init__.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollector.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorFactory.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorSol.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorSolBased.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorVy.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/CompilerCollectorYul.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/Compiler/__init__.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/__init__.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/castingInstrumenter.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraApp.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraBuildCacheManager.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraBuildDataClasses.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraBuildSui.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraCollectConfigurationLayout.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraCollectRunMetadata.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraCompilerParameters.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraConfigIO.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraContext.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraContextAttributes.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraContextClass.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraContractFuncs.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraExtensionInfo.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraMiniSpecParser.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraNodeFilters.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraOffsetConverter.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraParseBuildScript.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraProjectScanner.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraSourceFinders.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraType.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraVerifyGenerator.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/erc7201.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/splitRules.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/storageExtension.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/uncheckedOverflowInstrumenter.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/Eq_default.conf +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/Eq_mc_no_out_template.spec +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/Eq_mc_template.spec +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/Eq_sanity.conf +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/Eq_template.spec +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/__init__.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/equivCheck.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/EquivalenceCheck/sanity.spec +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/__init__.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/mutateAttributes.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/mutateUtil.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Mutate/mutateValidate.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/ExpectedComparator.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/__init__.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/certoraAttrUtil.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/certoraLogging.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/Shared/certoraValidateFuncs.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/__init__.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraCVLFormatter.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraConcord.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraEVMProver.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraEqCheck.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraMutate.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraRanger.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraRun.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraSolanaProver.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraSorobanProver.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/certoraSuiProver.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/rustMutator.py +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli_beta.egg-info/dependency_links.txt +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli_beta.egg-info/entry_points.txt +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli_beta.egg-info/requires.txt +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli_beta.egg-info/top_level.txt +0 -0
- {certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_jars/__init__.py +0 -0
- {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.
|
|
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
|
|
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.
|
{certora_cli_beta-8.19.2 → certora_cli_beta-8.20.0}/certora_cli/CertoraProver/certoraBuild.py
RENAMED
|
@@ -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
|
|
1460
|
-
#
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
"
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
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
|
|
1503
|
-
yul_optimizer_steps =
|
|
1504
|
-
elif minor == 8 and
|
|
1505
|
-
yul_optimizer_steps =
|
|
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 =
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
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
|
-
|
|
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)
|