Skip to content

Commit 2aaec12

Browse files
authoredSep 16, 2022
Directly construct KCFGs in foundry-kompile (#1377)
* kevm-pyk/: allow passing --depth through to kprove from kevm foundry-prove * kevm-pyk/solc_to_k: abstract pre/post ACCOUNTS cell map rest * kevm-pyk/__main__: output failing proof results for foundry-prove * kevm-pyk/__main__: generate KCFGs directly * kevm-pyk/__main__: avoiding building spec modules at all * kevm-pyk/__main__: remove unused spec_module * kevm-pyk/solc_to_k: use symbol attribute to make sure klabels are unqualified * kevm-pyk/__main__: allowing rekompiling/reiniting individually * kevm-pyk/: minimize prover output by default, allow turning off * kevm-pyk/__main__: format failures from prover better * tests/foundry: update expected output * kevm-pyk/: add support for --lemma argument to foundry-prove
1 parent 0f57d7e commit 2aaec12

File tree

4 files changed

+389
-28319
lines changed

4 files changed

+389
-28319
lines changed
 

Diff for: ‎kevm-pyk/src/kevm_pyk/__main__.py

+64-64
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@
44
import sys
55
from argparse import ArgumentParser, Namespace
66
from pathlib import Path
7-
from typing import Any, Dict, Final, Iterable, List, Optional, TextIO, Tuple
7+
from typing import Any, Dict, Final, Iterable, List, Optional, Tuple
88

99
from pathos.pools import ProcessPool # type: ignore
1010
from pyk.cli_utils import dir_path, file_path
11-
from pyk.kast import KApply, KClaim, KDefinition, KFlatModule, KImport, KInner, KRequire, KSort
11+
from pyk.kast import KApply, KAtt, KClaim, KDefinition, KFlatModule, KImport, KRequire, KRule, KSort, KToken
12+
from pyk.kastManip import minimize_term
1213
from pyk.kcfg import KCFG
1314
from pyk.ktool.krun import _krun
1415
from pyk.prelude import mlTop
1516

1617
from .gst_to_kore import gst_to_kore
1718
from .kevm import KEVM
1819
from .solc_to_k import Contract, contract_to_k, solc_compile
19-
from .utils import KCFG_from_claim, KPrint_make_unparsing, KProve_prove_claim, add_include_arg, read_kast_flatmodulelist
20+
from .utils import KCFG_from_claim, KPrint_make_unparsing, KProve_prove_claim, add_include_arg
2021

2122
_LOGGER: Final = logging.getLogger(__name__)
2223
_LOG_FORMAT: Final = '%(levelname)s %(asctime)s %(name)s - %(message)s'
@@ -118,17 +119,15 @@ def exec_foundry_to_k(
118119
profile: bool,
119120
foundry_out: Path,
120121
main_module: Optional[str],
121-
spec_module: Optional[str],
122122
requires: List[str],
123123
imports: List[str],
124-
output: Optional[TextIO],
125124
**kwargs,
126-
) -> None:
125+
) -> Tuple[KDefinition, List[Tuple[str, KClaim]]]:
127126
kevm = KEVM(definition_dir, profile=profile)
128127
empty_config = kevm.definition.empty_config(KSort('KevmCell'))
129128
path_glob = str(foundry_out) + '/*.t.sol/*.json'
130-
modules: List[KFlatModule] = []
131-
claims_modules: List[KFlatModule] = []
129+
modules = []
130+
claims: List[Tuple[str, KClaim]] = []
132131
# Must sort to get consistent output order on different platforms.
133132
for json_file in sorted(glob.glob(path_glob)):
134133
if json_file.endswith('.metadata.json'):
@@ -150,26 +149,17 @@ def exec_foundry_to_k(
150149
modules.append(module)
151150
if claims_module:
152151
_LOGGER.info(f'Produced claim module: {claims_module.name}')
153-
claims_modules.append(claims_module)
152+
claims.extend((claims_module.name, claim) for claim in claims_module.claims)
154153
_main_module = KFlatModule(
155154
main_module if main_module else 'MAIN', [], [KImport(mname) for mname in [_m.name for _m in modules] + imports]
156155
)
157-
_spec_module = KFlatModule(
158-
spec_module if spec_module else 'SPEC', [], [KImport(mname) for mname in [_m.name for _m in claims_modules]]
159-
)
160156
modules.append(_main_module)
161-
modules.append(_spec_module)
162157
bin_runtime_definition = KDefinition(
163158
_main_module.name,
164-
modules + claims_modules,
159+
modules,
165160
requires=[KRequire(req) for req in ['edsl.md'] + requires],
166161
)
167-
_kprint = KPrint_make_unparsing(kevm, extra_modules=modules)
168-
KEVM._patch_symbol_table(_kprint.symbol_table)
169-
if not output:
170-
output = sys.stdout
171-
output.write(_kprint.pretty_print(bin_runtime_definition) + '\n')
172-
output.flush()
162+
return bin_runtime_definition, claims
173163

174164

175165
def exec_foundry_kompile(
@@ -180,7 +170,6 @@ def exec_foundry_kompile(
180170
md_selector: Optional[str],
181171
regen: bool = False,
182172
rekompile: bool = False,
183-
reparse: bool = False,
184173
reinit: bool = False,
185174
requires: Iterable[str] = (),
186175
imports: Iterable[str] = (),
@@ -191,29 +180,33 @@ def exec_foundry_kompile(
191180
_ignore_arg(kwargs, 'spec_module', f'--spec-module {kwargs["spec_module"]}')
192181
main_module = 'FOUNDRY-MAIN'
193182
syntax_module = 'FOUNDRY-MAIN'
194-
spec_module = 'FOUNDRY-SPEC'
195183
foundry_definition_dir = foundry_out / 'kompiled'
196184
foundry_main_file = foundry_definition_dir / 'foundry.k'
197185
kompiled_timestamp = foundry_definition_dir / 'timestamp'
198-
parsed_spec = foundry_definition_dir / 'spec.json'
199186
kcfgs_file = foundry_definition_dir / 'kcfgs.json'
200187
requires = ['lemmas/lemmas.k', 'lemmas/int-simplification.k'] + list(requires)
201188
imports = ['LEMMAS', 'INT-SIMPLIFICATION'] + list(imports)
189+
202190
if not foundry_definition_dir.exists():
203191
foundry_definition_dir.mkdir()
192+
193+
bin_runtime_definition, claims = exec_foundry_to_k(
194+
definition_dir=definition_dir,
195+
profile=profile,
196+
foundry_out=foundry_out,
197+
main_module=main_module,
198+
requires=list(requires),
199+
imports=list(imports),
200+
)
201+
204202
if regen or not foundry_main_file.exists():
205-
_LOGGER.info(f'Generating K: {foundry_main_file}')
206203
with open(foundry_main_file, 'w') as fmf:
207-
exec_foundry_to_k(
208-
definition_dir=definition_dir,
209-
profile=profile,
210-
foundry_out=foundry_out,
211-
main_module=main_module,
212-
spec_module=spec_module,
213-
requires=list(requires),
214-
imports=list(imports),
215-
output=fmf,
216-
)
204+
_LOGGER.info(f'Writing file: {foundry_main_file}')
205+
_kevm = KEVM(definition_dir=definition_dir)
206+
_kprint = KPrint_make_unparsing(_kevm, extra_modules=bin_runtime_definition.modules)
207+
KEVM._patch_symbol_table(_kprint.symbol_table)
208+
fmf.write(_kprint.pretty_print(bin_runtime_definition) + '\n')
209+
217210
if regen or rekompile or not kompiled_timestamp.exists():
218211
_LOGGER.info(f'Kompiling definition: {foundry_main_file}')
219212
KEVM.kompile(
@@ -226,24 +219,15 @@ def exec_foundry_kompile(
226219
md_selector=md_selector,
227220
profile=profile,
228221
)
222+
229223
kevm = KEVM(foundry_definition_dir, main_file=foundry_main_file, profile=profile)
230-
if regen or rekompile or reparse or not parsed_spec.exists():
231-
_LOGGER.info(f'Parsing specs: {foundry_main_file}')
232-
prove_args = add_include_arg(includes)
233-
kevm.prove(
234-
foundry_main_file,
235-
spec_module_name=spec_module,
236-
dry_run=True,
237-
args=(['--emit-json-spec', str(parsed_spec)] + prove_args),
238-
)
239-
if regen or rekompile or reparse or reinit or not kcfgs_file.exists():
224+
if reinit or not kcfgs_file.exists():
240225
_LOGGER.info(f'Initializing KCFGs: {kcfgs_file}')
241226
cfgs: Dict[str, Dict] = {}
242-
for module in read_kast_flatmodulelist(parsed_spec).modules:
243-
for claim in module.claims:
244-
cfg_label = claim.att["label"]
245-
_LOGGER.info(f'Producing KCFG: {cfg_label}')
246-
cfgs[cfg_label] = KCFG_from_claim(kevm.definition, claim).to_dict()
227+
for module_name, claim in claims:
228+
cfg_label = f'{module_name}.{claim.att["label"]}'
229+
_LOGGER.info(f'Producing KCFG: {cfg_label}')
230+
cfgs[cfg_label] = KCFG_from_claim(kevm.definition, claim).to_dict()
247231
with open(kcfgs_file, 'w') as kf:
248232
kf.write(json.dumps(cfgs))
249233
kf.close()
@@ -261,8 +245,10 @@ def exec_prove(
261245
depth: Optional[int],
262246
claims: Iterable[str] = (),
263247
exclude_claims: Iterable[str] = (),
248+
minimize: bool = True,
264249
**kwargs,
265250
) -> None:
251+
_ignore_arg(kwargs, 'lemmas', '--lemma')
266252
kevm = KEVM(definition_dir, profile=profile)
267253
prove_args = add_include_arg(includes)
268254
haskell_args = []
@@ -277,6 +263,8 @@ def exec_prove(
277263
if exclude_claims:
278264
prove_args += ['--exclude', ','.join(exclude_claims)]
279265
final_state = kevm.prove(spec_file, spec_module_name=spec_module, args=prove_args, haskell_args=haskell_args)
266+
if minimize:
267+
final_state = minimize_term(final_state)
280268
print(kevm.pretty_print(final_state) + '\n')
281269
if not (type(final_state) is KApply and final_state.label.name == '#Top'):
282270
_LOGGER.error('Proof failed!')
@@ -293,6 +281,8 @@ def exec_foundry_prove(
293281
tests: Iterable[str] = (),
294282
exclude_tests: Iterable[str] = (),
295283
workers: int = 1,
284+
minimize: bool = True,
285+
lemmas: Iterable[str] = (),
296286
**kwargs,
297287
) -> None:
298288
_ignore_arg(kwargs, 'main_module', f'--main-module: {kwargs["main_module"]}')
@@ -336,21 +326,25 @@ def _kcfg_unproven_to_claim(_kcfg: KCFG) -> KClaim:
336326

337327
kevm = KEVM(definition_dir, profile=profile, use_directory=use_directory)
338328

339-
def prove_it(_id_and_claim: Tuple[str, KClaim]) -> Tuple[bool, KInner]:
329+
lemma_rules = [KRule(KToken(lr, 'K'), att=KAtt({'simplification': ''})) for lr in lemmas]
330+
331+
def prove_it(_id_and_claim: Tuple[str, KClaim]) -> bool:
340332
_claim_id, _claim = _id_and_claim
341-
return KProve_prove_claim(kevm, _claim, _claim_id, _LOGGER)
333+
ret, result = KProve_prove_claim(kevm, _claim, _claim_id, _LOGGER, depth=depth, lemmas=lemma_rules)
334+
if minimize:
335+
result = minimize_term(result)
336+
print(f'Result for {_claim_id}:\n{kevm.pretty_print(result)}\n')
337+
return ret
342338

343339
with ProcessPool(ncpus=workers) as process_pool:
344340
results = process_pool.map(prove_it, claims)
345341
process_pool.close()
346342

347-
failed_claims = [(cid, result) for ((cid, _), (failed, result)) in zip(claims, results) if failed]
348-
_failed_claim_ids = [cid for cid, _ in failed_claims]
349-
350-
if _failed_claim_ids:
351-
print(f'Failed to prove KCFGs: {_failed_claim_ids}\n')
343+
failed_claims = [cid for ((cid, _), failed) in zip(claims, results) if failed]
344+
if failed_claims:
345+
print(f'Failed to prove KCFGs: {failed_claims}\n')
352346

353-
sys.exit(len(_failed_claim_ids))
347+
sys.exit(len(failed_claims))
354348

355349

356350
def exec_run(
@@ -415,6 +409,19 @@ def parse(s):
415409
action='store_true',
416410
help='Generate a haskell-backend bug report for the execution.',
417411
)
412+
kprove_args.add_argument(
413+
'--lemma',
414+
dest='lemmas',
415+
default=[],
416+
action='append',
417+
help='Additional lemmas to include as simplification rules during execution.',
418+
)
419+
kprove_args.add_argument(
420+
'--minimize', dest='minimize', default=True, action='store_true', help='Minimize prover output.'
421+
)
422+
kprove_args.add_argument(
423+
'--no-minimize', dest='minimize', action='store_false', help='Do not minimize prover output.'
424+
)
418425

419426
k_kompile_args = ArgumentParser(add_help=False)
420427
k_kompile_args.add_argument(
@@ -550,13 +557,6 @@ def parse(s):
550557
action='store_true',
551558
help='Rekompile foundry.k even if kompiled definition already exists.',
552559
)
553-
foundry_kompile.add_argument(
554-
'--reparse',
555-
dest='reparse',
556-
default=False,
557-
action='store_true',
558-
help='Reparse K specifications even if the parsed spec already exists.',
559-
)
560560
foundry_kompile.add_argument(
561561
'--reinit',
562562
dest='reinit',

Diff for: ‎kevm-pyk/src/kevm_pyk/solc_to_k.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ def production(self) -> KProduction:
7373
self.sort,
7474
items_before + items_args + items_after,
7575
klabel=KLabel(f'method_{self.contract_name}_{self.name}'),
76+
att=KAtt({'symbol': ''}),
7677
)
7778

7879
def rule(self, contract: KInner, application_label: KLabel, contract_name: str) -> Optional[KRule]:
@@ -195,7 +196,7 @@ def subsort_field(self) -> KProduction:
195196

196197
@property
197198
def production(self) -> KProduction:
198-
return KProduction(self.sort, [KTerminal(self.name)], klabel=self.klabel)
199+
return KProduction(self.sort, [KTerminal(self.name)], klabel=self.klabel, att=KAtt({'symbol': ''}))
199200

200201
@property
201202
def macro_bin_runtime(self) -> KRule:
@@ -207,7 +208,7 @@ def method_sentences(self) -> List[KSentence]:
207208
KSort('ByteArray'),
208209
[KNonTerminal(self.sort), KTerminal('.'), KNonTerminal(self.sort_method)],
209210
klabel=self.klabel_method,
210-
att=KAtt({'function': ''}),
211+
att=KAtt({'function': '', 'symbol': ''}),
211212
)
212213
res: List[KSentence] = [method_application_production]
213214
res.extend(method.production for method in self.methods)
@@ -405,7 +406,7 @@ def _init_term(empty_config: KInner, contract_name: str) -> KInner:
405406
Foundry.account_CALLER(),
406407
Foundry.account_CHEATCODE_ADDRESS(KVariable('CHEATCODE_STORAGE')),
407408
Foundry.account_HARDHAT_CONSOLE_ADDRESS(),
408-
KToken('.Bag', 'K'),
409+
KVariable('ACCOUNTS_INIT'),
409410
]
410411
),
411412
}
@@ -436,7 +437,9 @@ def _final_term(empty_config: KInner, contract_name: str) -> KInner:
436437
]
437438
),
438439
}
439-
return abstract_cell_vars(substitute(empty_config, final_subst), [KVariable('STATUSCODE_FINAL')])
440+
return abstract_cell_vars(
441+
substitute(empty_config, final_subst), [KVariable('STATUSCODE_FINAL'), KVariable('ACCOUNTS_FINAL')]
442+
)
440443

441444

442445
# Helpers

Diff for: ‎kevm-pyk/src/kevm_pyk/utils.py

+23-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,21 @@
11
from logging import Logger
22
from pathlib import Path
3-
from typing import Collection, Iterable, List, Tuple
3+
from typing import Collection, Iterable, List, Optional, Tuple
44

55
from pyk.cfg_manager import instantiate_cell_vars, rename_generated_vars
66
from pyk.cterm import CTerm
7-
from pyk.kast import KApply, KClaim, KDefinition, KFlatModule, KFlatModuleList, KImport, KInner, KVariable, read_kast
7+
from pyk.kast import (
8+
KApply,
9+
KClaim,
10+
KDefinition,
11+
KFlatModule,
12+
KFlatModuleList,
13+
KImport,
14+
KInner,
15+
KRule,
16+
KVariable,
17+
read_kast,
18+
)
819
from pyk.kastManip import (
920
abstract_term_safely,
1021
bool_to_ml_pred,
@@ -27,10 +38,18 @@
2738

2839

2940
def KProve_prove_claim( # noqa: N802
30-
kprove: KProve, claim: KClaim, claim_id: str, logger: Logger
41+
kprove: KProve,
42+
claim: KClaim,
43+
claim_id: str,
44+
logger: Logger,
45+
depth: Optional[int] = None,
46+
lemmas: Iterable[KRule] = (),
3147
) -> Tuple[bool, KInner]:
3248
logger.info(f'Proving KCFG: {claim_id}')
33-
result = kprove.prove_claim(claim, claim_id)
49+
prove_args = []
50+
if depth is not None:
51+
prove_args += ['--depth', str(depth)]
52+
result = kprove.prove_claim(claim, claim_id, args=prove_args, lemmas=lemmas)
3453
failed = False
3554
if type(result) is KApply and result.label.name == '#Top':
3655
logger.info(f'Proved KCFG: {claim_id}')

Diff for: ‎tests/foundry/foundry.k.check.expected

+295-28,247
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.