Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify returning of proof results #114

Merged
merged 20 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.36
0.1.37
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"

[tool.poetry]
name = "kontrol"
version = "0.1.36"
version = "0.1.37"
description = "Foundry integration for KEVM"
authors = [
"Runtime Verification, Inc. <contact@runtimeverification.com>",
Expand Down
2 changes: 1 addition & 1 deletion src/kontrol/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
if TYPE_CHECKING:
from typing import Final

VERSION: Final = '0.1.36'
VERSION: Final = '0.1.37'
17 changes: 10 additions & 7 deletions src/kontrol/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from kevm_pyk.cli import node_id_like
from kevm_pyk.utils import arg_pair_of
from pyk.cli.utils import file_path
from pyk.proof.reachability import APRProof
from pyk.proof.tui import APRProofViewer

from . import VERSION
Expand Down Expand Up @@ -203,16 +204,18 @@ def exec_prove(
tests=tests,
)
failed = 0
for pid, r in results.items():
passed, failure_log = r
if passed:
print(f'PROOF PASSED: {pid}')
for proof in results:
if proof.passed:
print(f'PROOF PASSED: {proof.id}')
else:
failed += 1
print(f'PROOF FAILED: {pid}')
print(f'PROOF FAILED: {proof.id}')
failure_log = None
if isinstance(proof, APRProof):
failure_log = proof.failure_info
if failure_info and failure_log is not None:
failure_log += Foundry.help_info()
for line in failure_log:
log = failure_log.print() + Foundry.help_info()
for line in log:
print(line)

sys.exit(failed)
Expand Down
38 changes: 13 additions & 25 deletions src/kontrol/prove.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,7 @@
from typing import TYPE_CHECKING, NamedTuple

from kevm_pyk.kevm import KEVM, KEVMSemantics
from kevm_pyk.utils import (
KDefinition__expand_macros,
abstract_cell_vars,
kevm_prove,
legacy_explore,
print_failure_info,
)
from kevm_pyk.utils import KDefinition__expand_macros, abstract_cell_vars, kevm_prove, legacy_explore
from pathos.pools import ProcessPool # type: ignore
from pyk.cterm import CTerm
from pyk.kast.inner import KApply, KSequence, KVariable, Subst
Expand Down Expand Up @@ -46,7 +40,7 @@ def foundry_prove(
foundry_root: Path,
options: ProveOptions,
tests: Iterable[tuple[str, int | None]] = (),
) -> dict[tuple[str, int], tuple[bool, list[str] | None]]:
) -> list[Proof]:
if options.workers <= 0:
raise ValueError(f'Must have at least one worker, found: --workers {options.workers}')
if options.max_iterations is not None and options.max_iterations < 0:
Expand Down Expand Up @@ -89,7 +83,7 @@ def foundry_prove(
for test in constructor_tests:
test.method.update_digest(foundry.digest_file)

def run_prover(test_suite: list[FoundryTest]) -> dict[tuple[str, int], tuple[bool, list[str] | None]]:
def run_prover(test_suite: list[FoundryTest]) -> list[Proof]:
return _run_cfg_group(
tests=test_suite,
foundry=foundry,
Expand All @@ -99,14 +93,14 @@ def run_prover(test_suite: list[FoundryTest]) -> dict[tuple[str, int], tuple[boo
if options.run_constructor:
_LOGGER.info(f'Running initialization code for contracts in parallel: {constructor_names}')
results = run_prover(constructor_tests)
failed = [init_cfg for init_cfg, passed in results.items() if not passed]
failed = [proof for proof in results if not proof.passed]
if failed:
raise ValueError(f'Running initialization code failed for {len(failed)} contracts: {failed}')

_LOGGER.info(f'Running setup functions in parallel: {setup_method_names}')
results = run_prover(setup_method_tests)

failed = [setup_cfg for setup_cfg, passed in results.items() if not passed]
failed = [proof for proof in results if not proof.passed]
if failed:
raise ValueError(f'Running setUp method failed for {len(failed)} contracts: {failed}')

Expand Down Expand Up @@ -182,10 +176,9 @@ def _run_cfg_group(
tests: list[FoundryTest],
foundry: Foundry,
options: ProveOptions,
) -> dict[tuple[str, int], tuple[bool, list[str] | None]]:
def init_and_run_proof(test: FoundryTest) -> tuple[bool, list[str] | None]:
) -> list[Proof]:
def init_and_run_proof(test: FoundryTest) -> Proof:
start_server = options.port is None

with legacy_explore(
foundry.kevm,
kcfg_semantics=KEVMSemantics(auto_abstract_gas=options.auto_abstract_gas),
Expand All @@ -207,7 +200,7 @@ def init_and_run_proof(test: FoundryTest) -> tuple[bool, list[str] | None]:
run_constructor=options.run_constructor,
)

passed = kevm_prove(
kevm_prove(
foundry.kevm,
proof,
kcfg_explore,
Expand All @@ -217,22 +210,17 @@ def init_and_run_proof(test: FoundryTest) -> tuple[bool, list[str] | None]:
break_on_jumpi=options.break_on_jumpi,
break_on_calls=options.break_on_calls,
)
failure_log = None
if not passed:
failure_log = print_failure_info(proof, kcfg_explore, options.counterexample_info)
return passed, failure_log
return proof

_apr_proofs: list[tuple[bool, list[str] | None]]
apr_proofs: list[Proof]
if options.workers > 1:
with ProcessPool(ncpus=options.workers) as process_pool:
_apr_proofs = process_pool.map(init_and_run_proof, tests)
apr_proofs = process_pool.map(init_and_run_proof, tests)
else:
_apr_proofs = []
apr_proofs = []
for test in tests:
_apr_proofs.append(init_and_run_proof(test))
apr_proofs.append(init_and_run_proof(test))

unparsed_tests = [test.unparsed for test in tests]
apr_proofs = dict(zip(unparsed_tests, _apr_proofs, strict=True))
return apr_proofs


Expand Down
53 changes: 25 additions & 28 deletions src/tests/integration/test_foundry_prove.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from typing import Final

from pyk.kore.rpc import KoreServer
from pyk.proof.proof import Proof
from pyk.utils import BugReport
from pytest import TempPathFactory

Expand Down Expand Up @@ -138,7 +139,7 @@ def test_foundry_prove(
)

# Then
assert_pass(test_id, prove_res)
assert_pass(test_id, single(prove_res))

if test_id not in SHOW_TESTS or use_booster:
return
Expand Down Expand Up @@ -185,7 +186,7 @@ def test_foundry_fail(
)

# Then
assert_fail(test_id, prove_res)
assert_fail(test_id, single(prove_res))

if test_id not in SHOW_TESTS or use_booster:
return
Expand Down Expand Up @@ -229,7 +230,7 @@ def test_foundry_bmc(test_id: str, foundry_root: Path, bug_report: BugReport | N
)

# Then
assert_pass(test_id, prove_res)
assert_pass(test_id, single(prove_res))


def test_foundry_merge_nodes(foundry_root: Path, bug_report: BugReport | None, server: KoreServer) -> None:
Expand Down Expand Up @@ -264,7 +265,7 @@ def test_foundry_merge_nodes(foundry_root: Path, bug_report: BugReport | None, s
bug_report=bug_report,
),
)
assert_pass(test, prove_res)
assert_pass(test, single(prove_res))


def check_pending(foundry_root: Path, test: str, pending: list[int]) -> None:
Expand Down Expand Up @@ -327,7 +328,7 @@ def test_foundry_remove_node(
bug_report=bug_report,
),
)
assert_pass(test, prove_res)
assert_pass(test, single(prove_res))

foundry_remove_node(
foundry_root=foundry_root,
Expand All @@ -347,26 +348,22 @@ def test_foundry_remove_node(
bug_report=bug_report,
),
)
assert_pass(test, prove_res)
assert_pass(test, single(prove_res))


def assert_pass(test: str, prove_res: dict[tuple[str, int], tuple[bool, list[str] | None]]) -> None:
id = id_for_test(test, prove_res)
passed, log = prove_res[(test, id)]
if not passed:
assert log
pytest.fail('\n'.join(log))
def assert_pass(test: str, proof: Proof) -> None:
if not proof.passed:
if isinstance(proof, APRProof):
assert proof.failure_info
pytest.fail('\n'.join(proof.failure_info.print()))
else:
pytest.fail()


def assert_fail(test: str, prove_res: dict[tuple[str, int], tuple[bool, list[str] | None]]) -> None:
id = id_for_test(test, prove_res)
passed, log = prove_res[test, id]
assert not passed
assert log


def id_for_test(test: str, prove_res: dict[tuple[str, int], tuple[bool, list[str] | None]]) -> int:
return single(_id for _test, _id in prove_res.keys() if _test == test and _id is not None)
def assert_fail(test: str, proof: Proof) -> None:
assert not proof.passed
if isinstance(proof, APRProof):
assert proof.failure_info


def assert_or_update_show_output(show_res: str, expected_file: Path, *, update: bool) -> None:
Expand Down Expand Up @@ -397,7 +394,6 @@ def assert_or_update_show_output(show_res: str, expected_file: Path, *, update:
def test_foundry_resume_proof(
foundry_root: Path, update_expected_output: bool, bug_report: BugReport | None, server: KoreServer
) -> None:
foundry = Foundry(foundry_root)
test = 'AssumeTest.test_assume_false(uint256,uint256)'

prove_res = foundry_prove(
Expand All @@ -411,23 +407,24 @@ def test_foundry_resume_proof(
bug_report=bug_report,
),
)
id = id_for_test(test, prove_res)

proof = foundry.get_apr_proof(f'{test}:{id}')
proof = single(prove_res)
assert isinstance(proof, APRProof)
assert proof.pending

prove_res = foundry_prove(
foundry_root,
tests=[(test, id)],
tests=[(test, None)],
options=ProveOptions(
auto_abstract_gas=True,
max_iterations=6,
max_iterations=10,
reinit=False,
port=server.port,
bug_report=bug_report,
),
)
assert_fail(test, prove_res)

assert_fail(test, single(prove_res))


ALL_INIT_CODE_TESTS: Final = ('InitCodeTest.test_init()', 'InitCodeTest.testFail_init()')
Expand All @@ -449,4 +446,4 @@ def test_foundry_init_code(test: str, foundry_root: Path, bug_report: BugReport
)

# Then
assert_pass(test, prove_res)
assert_pass(test, single(prove_res))