Skip to content

CSE: Include symbolic contracts corresponding to contract fields into accounts #600

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

Merged
merged 45 commits into from
Jun 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
43878bb
Draft implementation and test for `contract` field accounts inclusion
palinatolmach Jun 5, 2024
8010577
Set Version: 0.1.301
rv-auditor Jun 5, 2024
1a929bc
Merge branch 'master' into cse-contract-fields
PetarMax Jun 5, 2024
e3794f8
Set Version: 0.1.301
rv-auditor Jun 5, 2024
0c6a57e
Merge branch 'master' into cse-contract-fields
palinatolmach Jun 6, 2024
a78dbb6
Set Version: 0.1.302
rv-auditor Jun 6, 2024
6092452
Use (simple) `#lookup` constraint for `contract` field in storage
palinatolmach Jun 6, 2024
fcd95fa
Minor formatting
palinatolmach Jun 6, 2024
6ac1afa
Merge branch 'master' into cse-contract-fields
palinatolmach Jun 7, 2024
a336d15
Set Version: 0.1.303
rv-auditor Jun 7, 2024
47f1263
Draft implementation refactoring
palinatolmach Jun 7, 2024
486d848
Merge branch 'master' into cse-contract-fields
PetarMax Jun 8, 2024
8bae8cf
Set Version: 0.1.306
rv-auditor Jun 8, 2024
b8aa43c
renaming contracts to avoid conflicts, removing unneeded constraints
Jun 8, 2024
1c5795d
minor streamlining
Jun 10, 2024
702d8c0
Merge branch 'master' into cse-contract-fields
palinatolmach Jun 11, 2024
90e9e63
Set Version: 0.1.307
rv-auditor Jun 11, 2024
ca23db9
Merge branch 'master' into cse-contract-fields
palinatolmach Jun 11, 2024
4941ea1
Set Version: 0.1.308
rv-auditor Jun 11, 2024
be0c2a3
removing unused variable
Jun 11, 2024
a736864
renaming re-assigned variable
Jun 11, 2024
7e71976
Make `_create_cse_accounts` apply recursively on `contract` field acc…
palinatolmach Jun 11, 2024
b1509f7
Code quality improvement
palinatolmach Jun 11, 2024
a085c81
Set Version: 0.1.309
rv-auditor Jun 11, 2024
c5c452c
Avoid adding new accs for constructors, add new CSE test for recursiv…
palinatolmach Jun 11, 2024
3c0b3ce
Enforce same symbolic storage variable name between constructor and f…
palinatolmach Jun 11, 2024
a46b3ec
tests with immutables
Jun 11, 2024
69132aa
stabilising tests
Jun 11, 2024
b2ae1fa
Merge branch 'master' into cse-contract-fields
PetarMax Jun 11, 2024
df4e23c
Set Version: 0.1.309
rv-auditor Jun 11, 2024
610d6e9
Update expected output, remove `Accounts` test
palinatolmach Jun 12, 2024
112a3fb
Update `contracts.k` expected output to reflect removal of `Accounts`
palinatolmach Jun 12, 2024
177c75e
Merge branch 'master' into cse-contract-fields
palinatolmach Jun 12, 2024
a630375
Set Version: 0.1.310
rv-auditor Jun 12, 2024
b3e9746
Use default symbolic storage var name for constructor storage with CSE
palinatolmach Jun 12, 2024
86d5bf2
Documented `_create_cse_accounts`
palinatolmach Jun 12, 2024
ef0acc3
One other output update
palinatolmach Jun 12, 2024
01c4d7a
Merge branch 'master' into cse-contract-fields
palinatolmach Jun 12, 2024
c8ad934
Set Version: 0.1.311
rv-auditor Jun 12, 2024
78f2894
Apply refactoring from review comments
palinatolmach Jun 12, 2024
16cd4e5
Apply refactoring from review comments, preserve `new_init_cterm`
palinatolmach Jun 12, 2024
78d538d
Add test for recursive storage generation in new accounts
palinatolmach Jun 12, 2024
e82ab0c
Update output to account for TGovernance
palinatolmach Jun 12, 2024
40e0f37
correction
Jun 12, 2024
4a89a45
Merge branch 'master' into cse-contract-fields
rv-jenkins Jun 12, 2024
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.310
0.1.311
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.310"
version = "0.1.311"
description = "Foundry integration for KEVM"
authors = [
"Runtime Verification, Inc. <[email protected]>",
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.310'
VERSION: Final = '0.1.311'
234 changes: 165 additions & 69 deletions src/kontrol/prove.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from kevm_pyk.utils import KDefinition__expand_macros, abstract_cell_vars, run_prover
from pathos.pools import ProcessPool # type: ignore
from pyk.cterm import CTerm, CTermSymbolic
from pyk.kast.inner import KApply, KSequence, KSort, KToken, KVariable, Subst
from pyk.kast.inner import KApply, KSequence, KSort, KVariable, Subst
from pyk.kast.manip import flatten_label, set_cell
from pyk.kcfg import KCFG, KCFGExplore
from pyk.kore.rpc import KoreClient, TransportType, kore_server
Expand Down Expand Up @@ -42,6 +42,7 @@

from .deployment import DeploymentStateEntry
from .options import ProveOptions
from .solc_to_k import StorageField

_LOGGER: Final = logging.getLogger(__name__)

Expand Down Expand Up @@ -520,6 +521,7 @@ def _method_to_initialized_cfg(

empty_config = foundry.kevm.definition.empty_config(GENERATED_TOP_CELL)
kcfg, new_node_ids, init_node_id, target_node_id = _method_to_cfg(
foundry,
empty_config,
test.contract,
test.method,
Expand Down Expand Up @@ -555,6 +557,7 @@ def _method_to_initialized_cfg(


def _method_to_cfg(
foundry: Foundry,
empty_config: KInner,
contract: Contract,
method: Contract.Method | Contract.Constructor,
Expand All @@ -581,9 +584,11 @@ def _method_to_cfg(
program = contract_code

init_cterm = _init_cterm(
foundry,
empty_config,
program=program,
contract_code=bytesToken(contract_code),
storage_fields=contract.fields,
method=method,
use_gas=use_gas,
deployment_state_entries=deployment_state_entries,
Expand Down Expand Up @@ -652,66 +657,76 @@ def _method_to_cfg(


def _update_cterm_from_node(cterm: CTerm, node: KCFG.Node, config_type: ConfigType) -> CTerm:
new_accounts_cell = node.cterm.cell('ACCOUNTS_CELL')
number_cell = node.cterm.cell('NUMBER_CELL')
timestamp_cell = node.cterm.cell('TIMESTAMP_CELL')
basefee_cell = node.cterm.cell('BASEFEE_CELL')
chainid_cell = node.cterm.cell('CHAINID_CELL')
coinbase_cell = node.cterm.cell('COINBASE_CELL')
prevcaller_cell = node.cterm.cell('PREVCALLER_CELL')
prevorigin_cell = node.cterm.cell('PREVORIGIN_CELL')
newcaller_cell = node.cterm.cell('NEWCALLER_CELL')
neworigin_cell = node.cterm.cell('NEWORIGIN_CELL')
active_cell = node.cterm.cell('ACTIVE_CELL')
depth_cell = node.cterm.cell('DEPTH_CELL')
singlecall_cell = node.cterm.cell('SINGLECALL_CELL')
gas_cell = node.cterm.cell('GAS_CELL')
callgas_cell = node.cterm.cell('CALLGAS_CELL')

all_accounts = flatten_label('_AccountCellMap_', new_accounts_cell)

new_accounts = [CTerm(account, []) for account in all_accounts if (type(account) is KApply and account.is_cell)]
non_cell_accounts = [account for account in all_accounts if not (type(account) is KApply and account.is_cell)]

new_accounts_map = {account.cell('ACCTID_CELL'): account for account in new_accounts}

if config_type == ConfigType.SUMMARY_CONFIG:
for account_id, account in new_accounts_map.items():
acct_id_cell = account.cell('ACCTID_CELL')
if type(acct_id_cell) is KVariable:
acct_id = acct_id_cell.name
elif type(acct_id_cell) is KToken:
acct_id = acct_id_cell.token
else:
raise RuntimeError(
f'Failed to abstract storage. acctId cell is neither variable nor token: {acct_id_cell}'
)
new_accounts_map[account_id] = CTerm(
set_cell(
account.config,
'STORAGE_CELL',
KVariable(f'STORAGE_{acct_id}', sort=KSort('Map')),
),
[],
)
if config_type == ConfigType.TEST_CONFIG:
cell_names = [
'ACCOUNTS_CELL',
'NUMBER_CELL',
'TIMESTAMP_CELL',
'BASEFEE_CELL',
'CHAINID_CELL',
'COINBASE_CELL',
'PREVCALLER_CELL',
'PREVORIGIN_CELL',
'NEWCALLER_CELL',
'NEWORIGIN_CELL',
'ACTIVE_CELL',
'DEPTH_CELL',
'SINGLECALL_CELL',
'GAS_CELL',
'CALLGAS_CELL',
]

cells = {name: node.cterm.cell(name) for name in cell_names}
all_accounts = flatten_label('_AccountCellMap_', cells['ACCOUNTS_CELL'])

new_accounts = [CTerm(account, []) for account in all_accounts if (type(account) is KApply and account.is_cell)]
non_cell_accounts = [account for account in all_accounts if not (type(account) is KApply and account.is_cell)]

new_accounts_map = {account.cell('ACCTID_CELL'): account for account in new_accounts}

cells['ACCOUNTS_CELL'] = KEVM.accounts(
[account.config for account in new_accounts_map.values()] + non_cell_accounts
)

new_init_cterm = CTerm(cterm.config, [])

for cell_name, cell_value in cells.items():
new_init_cterm = CTerm(set_cell(new_init_cterm.config, cell_name, cell_value), [])

# config_type == ConfigType.SUMMARY_CONFIG
# This means that a function is being run in isolation, that is, that the `node` we are
# taking information from has come from a constructor and not a setUp function.
# In this case, the <output> cell of the constructor execution becomes the program cell
# of the function execution and the constraints from the constructor are propagated.
else:
new_program_cell = node.cterm.cell('OUTPUT_CELL')
accounts_cell = cterm.cell('ACCOUNTS_CELL')
contract_id = cterm.cell('ID_CELL')

all_accounts = flatten_label('_AccountCellMap_', accounts_cell)
# TODO: Understand why there are two possible KInner representations or accounts,
# one directly with `<acccount>` and the other with `AccountCellMapItem`.
cell_accounts = [
CTerm(account, []) for account in all_accounts if (type(account) is KApply and account.is_cell)
] + [
CTerm(account.args[1], [])
for account in all_accounts
if (type(account) is KApply and account.label.name == 'AccountCellMapItem')
]
other_accounts = [
account
for account in all_accounts
if not (type(account) is KApply and (account.is_cell or account.label.name == 'AccountCellMapItem'))
]
cell_accounts_map = {account.cell('ACCTID_CELL'): account for account in cell_accounts}
# Set the code of the active contract to the one produced by the constructor
contract_account = cell_accounts_map[contract_id]
contract_account = CTerm(set_cell(contract_account.config, 'CODE_CELL', new_program_cell), [])
cell_accounts_map[contract_id] = contract_account
new_accounts_cell = KEVM.accounts([account.config for account in cell_accounts_map.values()] + other_accounts)

new_accounts_cell = KEVM.accounts([account.config for account in new_accounts_map.values()] + non_cell_accounts)

new_init_cterm = CTerm(set_cell(cterm.config, 'ACCOUNTS_CELL', new_accounts_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'NUMBER_CELL', number_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'TIMESTAMP_CELL', timestamp_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'BASEFEE_CELL', basefee_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'CHAINID_CELL', chainid_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'COINBASE_CELL', coinbase_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'PREVCALLER_CELL', prevcaller_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'PREVORIGIN_CELL', prevorigin_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'NEWCALLER_CELL', newcaller_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'NEWORIGIN_CELL', neworigin_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'ACTIVE_CELL', active_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'DEPTH_CELL', depth_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'SINGLECALL_CELL', singlecall_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'GAS_CELL', gas_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'CALLGAS_CELL', callgas_cell), [])
new_init_cterm = CTerm(set_cell(cterm.config, 'PROGRAM_CELL', new_program_cell), [])
new_init_cterm = CTerm(set_cell(new_init_cterm.config, 'ACCOUNTS_CELL', new_accounts_cell), [])

# Adding constraints from the initial cterm and initial node
for constraint in cterm.constraints + node.cterm.constraints:
Expand Down Expand Up @@ -773,9 +788,11 @@ def _init_account(address: int) -> None:


def _init_cterm(
foundry: Foundry,
empty_config: KInner,
program: bytes,
contract_code: KInner,
storage_fields: tuple[StorageField, ...],
method: Contract.Method | Contract.Constructor,
use_gas: bool,
config_type: ConfigType,
Expand Down Expand Up @@ -827,6 +844,8 @@ def _init_cterm(
'TRACEDATA_CELL': KApply('.List'),
}

storage_constraints: list[KApply] = []

if config_type == ConfigType.TEST_CONFIG or active_symbolik:
init_account_list = _create_initial_account_list(contract_code, deployment_state_entries)
init_subst_test = {
Expand All @@ -844,13 +863,18 @@ def _init_cterm(
}
init_subst.update(init_subst_test)
else:
# Symbolic accounts of all relevant contracts
# Status: Currently, only the executing contract
# TODO: Add all other accounts belonging to relevant contracts
accounts: list[KInner] = [
Foundry.symbolic_account(Foundry.symbolic_contract_prefix(), contract_code),
KVariable('ACCOUNTS_REST', sort=KSort('AccountCellMap')),
]
accounts: list[KInner] = []

if isinstance(method, Contract.Constructor):
# Symbolic account for the contract being executed
accounts.append(Foundry.symbolic_account(Foundry.symbolic_contract_prefix(), contract_code))
else:
# Symbolic accounts of all relevant contracts
accounts, storage_constraints = _create_cse_accounts(
foundry, storage_fields, Foundry.symbolic_contract_prefix(), contract_code
)

accounts.append(KVariable('ACCOUNTS_REST', sort=KSort('AccountCellMap')))

init_subst_accounts = {'ACCOUNTS_CELL': KEVM.accounts(accounts)}
init_subst.update(init_subst_accounts)
Expand Down Expand Up @@ -881,6 +905,9 @@ def _init_cterm(
mlEqualsFalse(KApply('_==Int_', [KVariable(contract_id, sort=KSort('Int')), Foundry.address_CHEATCODE()]))
)

for constraint in storage_constraints:
init_cterm = init_cterm.add_constraint(constraint)

# The calling contract is assumed to be in the present accounts for non-tests
if not (config_type == ConfigType.TEST_CONFIG or active_symbolik):
init_cterm.add_constraint(
Expand All @@ -893,8 +920,8 @@ def _init_cterm(
)

if isinstance(method, Contract.Constructor) and len(arg_constraints) > 0:
for constraint in arg_constraints:
init_cterm = init_cterm.add_constraint(mlEqualsTrue(constraint))
for arg_constraint in arg_constraints:
init_cterm = init_cterm.add_constraint(mlEqualsTrue(arg_constraint))

init_cterm = KEVM.add_invariant(init_cterm)

Expand Down Expand Up @@ -922,6 +949,75 @@ def _create_initial_account_list(
return init_account_list


def _create_cse_accounts(
foundry: Foundry, storage_fields: tuple[StorageField, ...], contract_name: str, contract_code: KInner
) -> tuple[list[KInner], list[KApply]]:
"""
Recursively generates a list of new accounts corresponding to `contract` fields, each having <code> and <storage> cell (partially) set up.
Args:
foundry (Foundry): The Foundry object containing the information about contracts in the project.
storage_fields (tuple[StorageField, ...]): A tuple of StorageField objects representing the contract's storage fields.
contract_name (str): The name of the contract being executed to be used in the account-related symbolic variables.
contract_code (KInner): The KInner object representing the contract's runtime bytecode.
Returns:
tuple[list[KInner], list[KApply]]:
- A list of accounts to be included in the initial configuration.
- A list of constraints on symbolic account IDs.
"""

new_accounts: list[KInner] = []
new_account_constraints: list[KApply] = []

storage_map = KVariable(contract_name + '_STORAGE', sort=KSort('Map'))
new_accounts.append(Foundry.symbolic_account(contract_name, contract_code, storage_map))

for field in storage_fields:
if field.data_type.startswith('contract '):
contract_type = field.data_type.split(' ')[1]
for full_contract_name, contract_obj in foundry.contracts.items():
# TODO: this is not enough, it is possible that the same contract comes with
# src% and test%, in which case we don't know automatically which one to choose
if full_contract_name.split('%')[-1] == contract_type:
contract_account_code = bytesToken(bytes.fromhex(contract_obj.deployed_bytecode))
contract_account_name = 'CONTRACT-' + field.label.upper()

# TODO: handle the case where the field has a non-zero offset
# maxUInt160 &Int #lookup ( CONTRACT_STORAGE:Map , 0 )
contract_storage_lookup = KApply(
'_&Int_',
[
intToken(1461501637330902918203684832716283019655932542975),
KEVM.lookup(storage_map, intToken(field.slot)),
],
)
new_account_constraints.append(
mlEqualsTrue(
KApply('_==Int_', [contract_storage_lookup, KVariable(contract_account_name + '_ID')])
)
)

# New contract account is not the cheatcode contract
new_account_constraints.append(
mlEqualsFalse(
KApply(
'_==Int_',
[
KVariable(contract_account_name + '_ID', sort=KSort('Int')),
Foundry.address_CHEATCODE(),
],
)
)
)

contract_accounts, contract_constraints = _create_cse_accounts(
foundry, contract_obj.fields, contract_account_name, contract_account_code
)
new_accounts.extend(contract_accounts)
new_account_constraints.extend(contract_constraints)

return new_accounts, new_account_constraints


def _final_cterm(
empty_config: KInner,
program: KInner,
Expand Down
16 changes: 15 additions & 1 deletion src/tests/integration/test-data/cse-lemmas.k
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ module CSE-LEMMAS
[simplification]

// Function selector does not match
rule { B:Bytes #Equals B1:Bytes +Bytes B2:Bytes } => #Bottom
rule { B:Bytes #Equals B1:Bytes +Bytes _:Bytes } => #Bottom
requires #range(B, 0, 4) =/=K #range (B1, 0, 4)
[simplification(60), concrete(B, B1)]

Expand All @@ -40,6 +40,20 @@ module CSE-LEMMAS
requires 4 <=Int lengthBytes(B) andBool #range(B, 0, 4) ==K B1
[simplification(60), concrete(B, B1)]

// Bitwise inequalities
rule [bitwise-and-maxUInt-lt-l]:
A <Int X &Int Y => false
requires 0 <=Int X andBool 0 <=Int Y
andBool X +Int 1 ==Int 2 ^Int log2Int(X +Int 1)
andBool X +Int 1 <=Int A
[concrete(X), simplification, preserves-definedness]

rule [bitwise-and-maxUInt-le-r]:
X &Int Y <=Int A => true
requires 0 <=Int X andBool 0 <=Int Y
andBool X +Int 1 ==Int 2 ^Int log2Int(X +Int 1)
andBool X +Int 1 <=Int A
[concrete(X), simplification, preserves-definedness]

endmodule

Expand Down
2 changes: 2 additions & 0 deletions src/tests/integration/test-data/foundry-dependency-all
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ ArithmeticContract.add_sub_external(uint256,uint256,uint256)
CSETest.test_add_const(uint256,uint256)
CSETest.test_identity(uint256,uint256)
ConstructorTest.test_contract_call()
ContractFieldTest.testEscrowToken()
TGovernance.getEscrowTokenTotalSupply()
Identity.applyOp(uint256)
Identity.identity(uint256)
ImportedContract.set(uint256)
Expand Down
Loading
Loading