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

Add struct support #2051

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
32c292e
add struct support
iFrostizz Aug 28, 2023
80399f1
Set Version: 1.0.283
Aug 28, 2023
6a7954c
add #tuple rule
iFrostizz Aug 29, 2023
36d37c8
write tuple abi encoding
iFrostizz Aug 30, 2023
decfcf6
Set Version: 1.0.284
Aug 30, 2023
1d92e45
add tuple predicate
iFrostizz Aug 30, 2023
43c684d
correct recursion in tuple input
iFrostizz Aug 30, 2023
fe185b1
add TODO
iFrostizz Aug 30, 2023
acac6c9
tuple returns TypedArg
iFrostizz Aug 31, 2023
0102b82
Set Version: 1.0.285
Aug 31, 2023
81294fc
fix typo on klabel
iFrostizz Aug 31, 2023
a751020
apply review suggestions
iFrostizz Aug 31, 2023
4d0cd30
Merge branch 'master' into struct-support2
iFrostizz Aug 31, 2023
cac9c74
Set Version: 1.0.285
Aug 31, 2023
2086e9c
fmt
iFrostizz Aug 31, 2023
ea0bc0e
pyupgrade
iFrostizz Aug 31, 2023
7753e56
format
iFrostizz Aug 31, 2023
acb7aae
number var name
iFrostizz Aug 31, 2023
6ce697d
forge install: forge-std
iFrostizz Aug 31, 2023
a22a1f7
fix foundry test, return TRUE on string
iFrostizz Aug 31, 2023
6958863
Revert "forge install: forge-std"
iFrostizz Aug 31, 2023
095d2f9
remove comment
iFrostizz Aug 31, 2023
6dd8bbe
adjust to tuple size
iFrostizz Aug 31, 2023
574b21d
create #tuple on rhs
iFrostizz Sep 3, 2023
5dd95ae
don't forget to return res
iFrostizz Sep 4, 2023
5024509
Merge branch 'master' into struct-support2
iFrostizz Sep 4, 2023
b4e08e4
Set Version: 1.0.287
Sep 4, 2023
79e228b
update encode semantics
iFrostizz Sep 4, 2023
3f1c341
fix unit test
iFrostizz Sep 4, 2023
759db87
Update expected output for `test_foundry_kompile`
palinatolmach Sep 4, 2023
1ba1942
formatting
iFrostizz Sep 5, 2023
cb15345
Merge branch 'master' into struct-support2
palinatolmach Sep 22, 2023
3d59660
Set Version: 1.0.302
Sep 22, 2023
8e5af88
Fix merge conflict
palinatolmach Sep 22, 2023
e3aec60
Merge branch 'master' into struct-support2
palinatolmach Sep 25, 2023
a497775
Set Version: 1.0.305
Sep 25, 2023
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
12 changes: 8 additions & 4 deletions include/kframework/abi.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ where `F1 : F2 : F3 : F4` is the (two's complement) byte-array representation of
| #bytes ( Bytes ) [klabel(abi_type_bytes), symbol]
| #string ( String ) [klabel(abi_type_string), symbol]
| #array ( TypedArg , Int , TypedArgs ) [klabel(abi_type_array), symbol]
| #tuple ( TypedArgs ) [klabel(abi_type_tuple), symbol]
// ----------------------------------------------------------------------------------------------

syntax TypedArgs ::= List{TypedArg, ","} [klabel(typedArgs)]
Expand All @@ -154,7 +155,7 @@ where `F1 : F2 : F3 : F4` is the (two's complement) byte-array representation of
rule #generateSignatureArgs(TARGA:TypedArg, .TypedArgs) => #typeName(TARGA)
rule #generateSignatureArgs(TARGA:TypedArg, TARGB:TypedArg, TARGS) => #typeName(TARGA) +String "," +String #generateSignatureArgs(TARGB, TARGS)

syntax String ::= #typeName ( TypedArg ) [function, total]
syntax String ::= #typeName ( TypedArg ) [function, total]
iFrostizz marked this conversation as resolved.
Show resolved Hide resolved
// ----------------------------------------------------------
rule #typeName( #address( _ )) => "address"

Expand Down Expand Up @@ -265,10 +266,13 @@ where `F1 : F2 : F3 : F4` is the (two's complement) byte-array representation of

rule #typeName( #array(T, _, _)) => #typeName(T) +String "[]"

rule #typeName(#tuple(TARGS)) => "(" +String #generateSignatureArgs(TARGS) +String ")"

syntax Bytes ::= #encodeArgs ( TypedArgs ) [function]
syntax Bytes ::= #encodeArgsAux ( TypedArgs , Int , Bytes , Bytes ) [function]
// ------------------------------------------------------------------------------
rule #encodeArgs(ARGS) => #encodeArgsAux(ARGS, #lenOfHeads(ARGS), .Bytes, .Bytes)
rule #encodeArgs(#tuple(ARGS)) => #encodeArgs(ARGS)
rule #encodeArgs(ARGS) => #encodeArgsAux(ARGS, #lenOfHeads(ARGS), .Bytes, .Bytes)

rule #encodeArgsAux(.TypedArgs, _:Int, HEADS, TAILS) => HEADS +Bytes TAILS

Expand All @@ -282,8 +286,8 @@ where `F1 : F2 : F3 : F4` is the (two's complement) byte-array representation of

syntax Int ::= #lenOfHeads ( TypedArgs ) [function, total]
// ----------------------------------------------------------
rule #lenOfHeads(.TypedArgs) => 0
rule #lenOfHeads(ARG, ARGS) => #lenOfHead(ARG) +Int #lenOfHeads(ARGS)
rule #lenOfHeads(.TypedArgs) => 0
rule #lenOfHeads(ARG, ARGS) => #lenOfHead(ARG) +Int #lenOfHeads(ARGS)
iFrostizz marked this conversation as resolved.
Show resolved Hide resolved

syntax Int ::= #lenOfHead ( TypedArg ) [function, total]
// --------------------------------------------------------
Expand Down
12 changes: 6 additions & 6 deletions kevm-pyk/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion kevm-pyk/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 = "kevm-pyk"
version = "1.0.286"
version = "1.0.287"
description = ""
authors = [
"Runtime Verification, Inc. <[email protected]>",
Expand Down
25 changes: 19 additions & 6 deletions kevm-pyk/src/kevm_pyk/kevm.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from pyk.proof.show import APRBMCProofNodePrinter, APRProofNodePrinter

if TYPE_CHECKING:
from collections.abc import Iterable
from collections.abc import Iterable, Sequence
from pathlib import Path
from typing import Final

Expand Down Expand Up @@ -326,7 +326,7 @@ def lookup(map: KInner, key: KInner) -> KApply:
return KApply('#lookup(_,_)_EVM-TYPES_Int_Map_Int', [map, key])

@staticmethod
def abi_calldata(name: str, args: list[KInner]) -> KApply:
def abi_calldata(name: str, args: list[KApply]) -> KApply:
return KApply('#abiCallData(_,_)_EVM-ABI_Bytes_String_TypedArgs', [stringToken(name), KEVM.typed_args(args)])

@staticmethod
Expand All @@ -345,6 +345,10 @@ def abi_bool(b: KInner) -> KApply:
def abi_type(type: str, value: KInner) -> KApply:
return KApply('abi_type_' + type, [value])

@staticmethod
def abi_tuple(values: Sequence[KInner]) -> KApply:
return KApply('abi_type_tuple', values)

@staticmethod
def empty_typedargs() -> KApply:
return KApply('.List{"_,__EVM-ABI_TypedArgs_TypedArg_TypedArgs"}_TypedArgs')
Expand Down Expand Up @@ -389,12 +393,21 @@ def intlist(ints: list[KInner]) -> KApply:
return res

@staticmethod
def typed_args(args: list[KInner]) -> KApply:
res = KApply('.List{"_,__EVM-ABI_TypedArgs_TypedArg_TypedArgs"}_TypedArgs')
for i in reversed(args):
res = KApply('_,__EVM-ABI_TypedArgs_TypedArg_TypedArgs', [i, res])
def typed_args(args: list[KApply], res: KApply | None = None) -> KApply:
res = KEVM.empty_typedargs() if res is None else res
for arg in reversed(args):
res = KEVM.to_typed_arg(arg, res)
return res

@staticmethod
def to_typed_arg(arg: KApply, res: KApply) -> KApply:
if arg.label.name == 'abi_type_tuple':
args = [arg for arg in arg.args if type(arg) is KApply]
ret_arg = KApply('abi_type_tuple', [KEVM.typed_args(args), KEVM.empty_typedargs()])
else:
ret_arg = arg
return KApply('_,__EVM-ABI_TypedArgs_TypedArg_TypedArgs', [ret_arg, res])

@staticmethod
def accounts(accts: list[KInner]) -> KInner:
wrapped_accounts: list[KInner] = []
Expand Down
145 changes: 122 additions & 23 deletions kevm-pyk/src/kontrol/solc_to_k.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import json
import logging
import re
from dataclasses import dataclass
from dataclasses import dataclass, field
from functools import cached_property
from subprocess import CalledProcessError
from typing import TYPE_CHECKING
Expand Down Expand Up @@ -65,15 +65,68 @@ def solc_to_k(
return _kprint.pretty_print(bin_runtime_definition, unalias=False) + '\n'


@dataclass(frozen=True)
class Input:
name: str
type: str
components: list[Input] = field(default_factory=list)
idx: int = 0

@staticmethod
def from_dict(_input: dict, i: int = 0) -> Input:
name = _input['name']
type = _input['type']
if _input.get('components') is not None and _input['type'] != 'tuple[]':
return Input(name, type, Input.recurse_comp(_input['components'], i), i)
else:
return Input(name, type, idx=i)

@staticmethod
def recurse_comp(components: dict, i: int = 0) -> list[Input]:
comps = []
for comp in components:
_name = comp['name']
_type = comp['type']
if comp.get('components') is not None and type != 'tuple[]':
new_comps = Input.recurse_comp(comp['components'], i)
else:
new_comps = []
comps.append(Input(_name, _type, new_comps, i))
i += 1
return comps

def to_abi(self) -> KApply:
if self.type == 'tuple':
return Contract.recurse_components(self.components)
else:
return Contract.make_single_type(self)

def flattened(self) -> list[Input]:
if len(self.components) > 0:
nest = [comp.flattened() for comp in self.components]
return [fcomp for fncomp in nest for fcomp in fncomp]
else:
return [self]


def inputs_from_abi(abi_inputs: list[dict]) -> list[Input]:
inputs = []
i = 0
for input in abi_inputs:
cur_input = Input.from_dict(input, i)
inputs.append(cur_input)
i += len(cur_input.flattened())
return inputs


@dataclass
class Contract:
@dataclass
class Method:
name: str
id: int
sort: KSort
arg_names: tuple[str, ...]
arg_types: tuple[str, ...]
inputs: list[Input]
contract_name: str
contract_digest: str
contract_storage_digest: str
Expand All @@ -95,8 +148,10 @@ def __init__(
self.signature = msig
self.name = abi['name']
self.id = id
self.arg_names = tuple([f'V{i}_{input["name"].replace("-", "_")}' for i, input in enumerate(abi['inputs'])])
self.arg_types = tuple([input['type'] for input in abi['inputs']])
self.inputs = inputs_from_abi(abi['inputs'])
flat_inputs = [input for sub_inputs in self.inputs for input in sub_inputs.flattened()]
self.arg_names = tuple([Contract.arg_name(input) for input in flat_inputs])
self.arg_types = tuple([input.type for input in flat_inputs])
self.contract_name = contract_name
self.contract_digest = contract_digest
self.contract_storage_digest = contract_storage_digest
Expand Down Expand Up @@ -189,20 +244,21 @@ def production(self) -> KProduction:
)

def rule(self, contract: KInner, application_label: KLabel, contract_name: str) -> KRule | None:
arg_vars = [KVariable(aname) for aname in self.arg_names]
prod_klabel = self.unique_klabel
assert prod_klabel is not None
args: list[KInner] = []
arg_vars = [KVariable(aname) for aname in self.arg_names]
args: list[KApply] = []
conjuncts: list[KInner] = []
for input_name, input_type in zip(self.arg_names, self.arg_types, strict=True):
args.append(KEVM.abi_type(input_type, KVariable(input_name)))
rp = _range_predicate(KVariable(input_name), input_type)
if rp is None:
_LOGGER.info(
f'Unsupported ABI type for method {contract_name}.{prod_klabel.name}, will not generate calldata sugar: {input_type}'
)
return None
conjuncts.append(rp)
for input in self.inputs:
abi_type = input.to_abi()
args.append(abi_type)
rps = _range_predicates(abi_type)
for rp in rps:
if rp is None:
_LOGGER.info(
f'Unsupported ABI type for method {contract_name}.{prod_klabel.name}, will not generate calldata sugar:'
)
return None
conjuncts.append(rp)
lhs = KApply(application_label, [contract, KApply(prod_klabel, arg_vars)])
rhs = KEVM.abi_calldata(self.name, args)
ensures = andBool(conjuncts)
Expand Down Expand Up @@ -479,13 +535,39 @@ def method_sentences(self) -> list[KSentence]:
res.extend(method.selector_alias_rule for method in self.methods)
return res if len(res) > 1 else []

@staticmethod
def arg_name(input: Input) -> str:
return f'V{input.idx}_{input.name.replace("-", "_")}'

@staticmethod
def make_single_type(input: Input) -> KApply:
input_name = Contract.arg_name(input)
input_type = input.type
return KEVM.abi_type(input_type, KVariable(input_name))

@staticmethod
def recurse_components(components: list[Input]) -> KApply:
"""
do a recursive build of inner types
"""
abi_types = []
for comp in components:
# nested tuple, go deeper
if comp.type == 'tuple':
tup = Contract.recurse_components(comp.components)
abi_type = tup
else:
abi_type = Contract.make_single_type(comp)
abi_types.append(abi_type)
return KEVM.abi_tuple(abi_types)

@property
def field_sentences(self) -> list[KSentence]:
prods: list[KSentence] = [self.subsort_field]
rules: list[KSentence] = []
for field, slot in self.fields.items():
klabel = KLabel(self.klabel_field.name + f'_{field}')
prods.append(KProduction(self.sort_field, [KTerminal(field)], klabel=klabel, att=KAtt({'symbol': ''})))
for _field, slot in self.fields.items():
klabel = KLabel(self.klabel_field.name + f'_{_field}')
prods.append(KProduction(self.sort_field, [KTerminal(_field)], klabel=klabel, att=KAtt({'symbol': ''})))
rule_lhs = KEVM.loc(KApply(KLabel('contract_access_field'), [KApply(self.klabel), KApply(klabel)]))
rule_rhs = intToken(slot)
rules.append(KRule(KRewrite(rule_lhs, rule_rhs)))
Expand Down Expand Up @@ -620,6 +702,23 @@ def _evm_base_sort_int(type_label: str) -> bool:
return success


def _range_predicates(abi: KApply) -> list[KInner | None]:
rp: list[KInner | None] = []
if abi.label.name == 'abi_type_tuple':
for arg in abi.args:
if type(arg) is KApply:
rp += _range_predicates(arg)
else:
if abi.label.name == 'abi_type_tuple':
for arg in abi.args:
if type(arg) is KApply:
rp += _range_predicates(arg)
else:
type_label = abi.label.name.removeprefix('abi_type_')
rp.append(_range_predicate(single(abi.args), type_label))
return rp


def _range_predicate(term: KInner, type_label: str) -> KInner | None:
match type_label:
case 'address':
Expand All @@ -646,7 +745,7 @@ def _range_predicate(term: KInner, type_label: str) -> KInner | None:
return None


def _range_predicate_uint(term: KInner, type_label: str) -> tuple[bool, KInner | None]:
def _range_predicate_uint(term: KInner, type_label: str) -> tuple[bool, KApply | None]:
if type_label.startswith('uint') and not type_label.endswith(']'):
if type_label == 'uint':
width = 256
Expand All @@ -659,7 +758,7 @@ def _range_predicate_uint(term: KInner, type_label: str) -> tuple[bool, KInner |
return (False, None)


def _range_predicate_int(term: KInner, type_label: str) -> tuple[bool, KInner | None]:
def _range_predicate_int(term: KInner, type_label: str) -> tuple[bool, KApply | None]:
if type_label.startswith('int') and not type_label.endswith(']'):
if type_label == 'int':
width = 256
Expand All @@ -672,7 +771,7 @@ def _range_predicate_int(term: KInner, type_label: str) -> tuple[bool, KInner |
return (False, None)


def _range_predicate_bytes(term: KInner, type_label: str) -> tuple[bool, KInner | None]:
def _range_predicate_bytes(term: KInner, type_label: str) -> tuple[bool, KApply | None]:
if type_label.startswith('bytes') and not type_label.endswith(']'):
str_width = type_label[5:]
if str_width != '':
Expand Down
24 changes: 24 additions & 0 deletions kevm-pyk/src/tests/integration/test-data/contracts.k.expected
Original file line number Diff line number Diff line change
Expand Up @@ -5883,6 +5883,30 @@ module IntTypeTest-CONTRACT
rule ( selector ( "test_int64_fail(int64)" ) => 2744099616 )


endmodule

module StructTypeTest-CONTRACT
imports public FOUNDRY

syntax Contract ::= S2KStructTypeTestContract

syntax S2KStructTypeTestContract ::= "S2KStructTypeTest" [symbol(), klabel(contract_StructTypeTest)]



syntax Bytes ::= S2KStructTypeTestContract "." S2KStructTypeTestMethod [function(), symbol(), klabel(method_StructTypeTest)]

syntax S2KStructTypeTestMethod ::= "S2KtestZUndvars" "(" Int ":" "uint8" "," Int ":" "uint32" ")" [symbol(), klabel(method_StructTypeTest_S2KtestZUndvars_uint8_uint32)]

rule ( S2KStructTypeTest . S2KtestZUndvars ( V0_a : uint8 , V1_timestamp : uint32 ) => #abiCallData ( "test_vars" , #tuple ( #uint8 ( V0_a ) , #uint32 ( V1_timestamp ) , .TypedArgs ) , .TypedArgs ) )
ensures ( #rangeUInt ( 8 , V0_a )
andBool ( #rangeUInt ( 32 , V1_timestamp )
))


rule ( selector ( "test_vars((uint8,uint32))" ) => 1202804621 )


endmodule

module UintTypeTest-CONTRACT
Expand Down
1 change: 1 addition & 0 deletions kevm-pyk/src/tests/integration/test-data/foundry-prove-all
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,4 @@ IntTypeTest.test_uint256(uint256)
IntTypeTest.test_uint256_fail(uint256)
IntTypeTest.test_uint64(uint64)
IntTypeTest.test_uint64_fail(uint64)
StructTypeTest.test_vars((uint8,uint32))
Loading