11
11
from kevm_pyk .utils import KDefinition__expand_macros , abstract_cell_vars , run_prover
12
12
from pathos .pools import ProcessPool # type: ignore
13
13
from pyk .cterm import CTerm , CTermSymbolic
14
- from pyk .kast .inner import KApply , KSequence , KSort , KToken , KVariable , Subst
14
+ from pyk .kast .inner import KApply , KSequence , KSort , KVariable , Subst
15
15
from pyk .kast .manip import flatten_label , set_cell
16
16
from pyk .kcfg import KCFG , KCFGExplore
17
17
from pyk .kore .rpc import KoreClient , TransportType , kore_server
42
42
43
43
from .deployment import DeploymentStateEntry
44
44
from .options import ProveOptions
45
+ from .solc_to_k import StorageField
45
46
46
47
_LOGGER : Final = logging .getLogger (__name__ )
47
48
@@ -520,6 +521,7 @@ def _method_to_initialized_cfg(
520
521
521
522
empty_config = foundry .kevm .definition .empty_config (GENERATED_TOP_CELL )
522
523
kcfg , new_node_ids , init_node_id , target_node_id = _method_to_cfg (
524
+ foundry ,
523
525
empty_config ,
524
526
test .contract ,
525
527
test .method ,
@@ -555,6 +557,7 @@ def _method_to_initialized_cfg(
555
557
556
558
557
559
def _method_to_cfg (
560
+ foundry : Foundry ,
558
561
empty_config : KInner ,
559
562
contract : Contract ,
560
563
method : Contract .Method | Contract .Constructor ,
@@ -581,9 +584,11 @@ def _method_to_cfg(
581
584
program = contract_code
582
585
583
586
init_cterm = _init_cterm (
587
+ foundry ,
584
588
empty_config ,
585
589
program = program ,
586
590
contract_code = bytesToken (contract_code ),
591
+ storage_fields = contract .fields ,
587
592
method = method ,
588
593
use_gas = use_gas ,
589
594
deployment_state_entries = deployment_state_entries ,
@@ -652,66 +657,76 @@ def _method_to_cfg(
652
657
653
658
654
659
def _update_cterm_from_node (cterm : CTerm , node : KCFG .Node , config_type : ConfigType ) -> CTerm :
655
- new_accounts_cell = node .cterm .cell ('ACCOUNTS_CELL' )
656
- number_cell = node .cterm .cell ('NUMBER_CELL' )
657
- timestamp_cell = node .cterm .cell ('TIMESTAMP_CELL' )
658
- basefee_cell = node .cterm .cell ('BASEFEE_CELL' )
659
- chainid_cell = node .cterm .cell ('CHAINID_CELL' )
660
- coinbase_cell = node .cterm .cell ('COINBASE_CELL' )
661
- prevcaller_cell = node .cterm .cell ('PREVCALLER_CELL' )
662
- prevorigin_cell = node .cterm .cell ('PREVORIGIN_CELL' )
663
- newcaller_cell = node .cterm .cell ('NEWCALLER_CELL' )
664
- neworigin_cell = node .cterm .cell ('NEWORIGIN_CELL' )
665
- active_cell = node .cterm .cell ('ACTIVE_CELL' )
666
- depth_cell = node .cterm .cell ('DEPTH_CELL' )
667
- singlecall_cell = node .cterm .cell ('SINGLECALL_CELL' )
668
- gas_cell = node .cterm .cell ('GAS_CELL' )
669
- callgas_cell = node .cterm .cell ('CALLGAS_CELL' )
670
-
671
- all_accounts = flatten_label ('_AccountCellMap_' , new_accounts_cell )
672
-
673
- new_accounts = [CTerm (account , []) for account in all_accounts if (type (account ) is KApply and account .is_cell )]
674
- non_cell_accounts = [account for account in all_accounts if not (type (account ) is KApply and account .is_cell )]
675
-
676
- new_accounts_map = {account .cell ('ACCTID_CELL' ): account for account in new_accounts }
677
-
678
- if config_type == ConfigType .SUMMARY_CONFIG :
679
- for account_id , account in new_accounts_map .items ():
680
- acct_id_cell = account .cell ('ACCTID_CELL' )
681
- if type (acct_id_cell ) is KVariable :
682
- acct_id = acct_id_cell .name
683
- elif type (acct_id_cell ) is KToken :
684
- acct_id = acct_id_cell .token
685
- else :
686
- raise RuntimeError (
687
- f'Failed to abstract storage. acctId cell is neither variable nor token: { acct_id_cell } '
688
- )
689
- new_accounts_map [account_id ] = CTerm (
690
- set_cell (
691
- account .config ,
692
- 'STORAGE_CELL' ,
693
- KVariable (f'STORAGE_{ acct_id } ' , sort = KSort ('Map' )),
694
- ),
695
- [],
696
- )
660
+ if config_type == ConfigType .TEST_CONFIG :
661
+ cell_names = [
662
+ 'ACCOUNTS_CELL' ,
663
+ 'NUMBER_CELL' ,
664
+ 'TIMESTAMP_CELL' ,
665
+ 'BASEFEE_CELL' ,
666
+ 'CHAINID_CELL' ,
667
+ 'COINBASE_CELL' ,
668
+ 'PREVCALLER_CELL' ,
669
+ 'PREVORIGIN_CELL' ,
670
+ 'NEWCALLER_CELL' ,
671
+ 'NEWORIGIN_CELL' ,
672
+ 'ACTIVE_CELL' ,
673
+ 'DEPTH_CELL' ,
674
+ 'SINGLECALL_CELL' ,
675
+ 'GAS_CELL' ,
676
+ 'CALLGAS_CELL' ,
677
+ ]
678
+
679
+ cells = {name : node .cterm .cell (name ) for name in cell_names }
680
+ all_accounts = flatten_label ('_AccountCellMap_' , cells ['ACCOUNTS_CELL' ])
681
+
682
+ new_accounts = [CTerm (account , []) for account in all_accounts if (type (account ) is KApply and account .is_cell )]
683
+ non_cell_accounts = [account for account in all_accounts if not (type (account ) is KApply and account .is_cell )]
684
+
685
+ new_accounts_map = {account .cell ('ACCTID_CELL' ): account for account in new_accounts }
686
+
687
+ cells ['ACCOUNTS_CELL' ] = KEVM .accounts (
688
+ [account .config for account in new_accounts_map .values ()] + non_cell_accounts
689
+ )
690
+
691
+ new_init_cterm = CTerm (cterm .config , [])
692
+
693
+ for cell_name , cell_value in cells .items ():
694
+ new_init_cterm = CTerm (set_cell (new_init_cterm .config , cell_name , cell_value ), [])
695
+
696
+ # config_type == ConfigType.SUMMARY_CONFIG
697
+ # This means that a function is being run in isolation, that is, that the `node` we are
698
+ # taking information from has come from a constructor and not a setUp function.
699
+ # In this case, the <output> cell of the constructor execution becomes the program cell
700
+ # of the function execution and the constraints from the constructor are propagated.
701
+ else :
702
+ new_program_cell = node .cterm .cell ('OUTPUT_CELL' )
703
+ accounts_cell = cterm .cell ('ACCOUNTS_CELL' )
704
+ contract_id = cterm .cell ('ID_CELL' )
705
+
706
+ all_accounts = flatten_label ('_AccountCellMap_' , accounts_cell )
707
+ # TODO: Understand why there are two possible KInner representations or accounts,
708
+ # one directly with `<acccount>` and the other with `AccountCellMapItem`.
709
+ cell_accounts = [
710
+ CTerm (account , []) for account in all_accounts if (type (account ) is KApply and account .is_cell )
711
+ ] + [
712
+ CTerm (account .args [1 ], [])
713
+ for account in all_accounts
714
+ if (type (account ) is KApply and account .label .name == 'AccountCellMapItem' )
715
+ ]
716
+ other_accounts = [
717
+ account
718
+ for account in all_accounts
719
+ if not (type (account ) is KApply and (account .is_cell or account .label .name == 'AccountCellMapItem' ))
720
+ ]
721
+ cell_accounts_map = {account .cell ('ACCTID_CELL' ): account for account in cell_accounts }
722
+ # Set the code of the active contract to the one produced by the constructor
723
+ contract_account = cell_accounts_map [contract_id ]
724
+ contract_account = CTerm (set_cell (contract_account .config , 'CODE_CELL' , new_program_cell ), [])
725
+ cell_accounts_map [contract_id ] = contract_account
726
+ new_accounts_cell = KEVM .accounts ([account .config for account in cell_accounts_map .values ()] + other_accounts )
697
727
698
- new_accounts_cell = KEVM .accounts ([account .config for account in new_accounts_map .values ()] + non_cell_accounts )
699
-
700
- new_init_cterm = CTerm (set_cell (cterm .config , 'ACCOUNTS_CELL' , new_accounts_cell ), [])
701
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'NUMBER_CELL' , number_cell ), [])
702
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'TIMESTAMP_CELL' , timestamp_cell ), [])
703
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'BASEFEE_CELL' , basefee_cell ), [])
704
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'CHAINID_CELL' , chainid_cell ), [])
705
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'COINBASE_CELL' , coinbase_cell ), [])
706
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'PREVCALLER_CELL' , prevcaller_cell ), [])
707
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'PREVORIGIN_CELL' , prevorigin_cell ), [])
708
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'NEWCALLER_CELL' , newcaller_cell ), [])
709
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'NEWORIGIN_CELL' , neworigin_cell ), [])
710
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'ACTIVE_CELL' , active_cell ), [])
711
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'DEPTH_CELL' , depth_cell ), [])
712
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'SINGLECALL_CELL' , singlecall_cell ), [])
713
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'GAS_CELL' , gas_cell ), [])
714
- new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'CALLGAS_CELL' , callgas_cell ), [])
728
+ new_init_cterm = CTerm (set_cell (cterm .config , 'PROGRAM_CELL' , new_program_cell ), [])
729
+ new_init_cterm = CTerm (set_cell (new_init_cterm .config , 'ACCOUNTS_CELL' , new_accounts_cell ), [])
715
730
716
731
# Adding constraints from the initial cterm and initial node
717
732
for constraint in cterm .constraints + node .cterm .constraints :
@@ -773,9 +788,11 @@ def _init_account(address: int) -> None:
773
788
774
789
775
790
def _init_cterm (
791
+ foundry : Foundry ,
776
792
empty_config : KInner ,
777
793
program : bytes ,
778
794
contract_code : KInner ,
795
+ storage_fields : tuple [StorageField , ...],
779
796
method : Contract .Method | Contract .Constructor ,
780
797
use_gas : bool ,
781
798
config_type : ConfigType ,
@@ -827,6 +844,8 @@ def _init_cterm(
827
844
'TRACEDATA_CELL' : KApply ('.List' ),
828
845
}
829
846
847
+ storage_constraints : list [KApply ] = []
848
+
830
849
if config_type == ConfigType .TEST_CONFIG or active_symbolik :
831
850
init_account_list = _create_initial_account_list (contract_code , deployment_state_entries )
832
851
init_subst_test = {
@@ -844,13 +863,18 @@ def _init_cterm(
844
863
}
845
864
init_subst .update (init_subst_test )
846
865
else :
847
- # Symbolic accounts of all relevant contracts
848
- # Status: Currently, only the executing contract
849
- # TODO: Add all other accounts belonging to relevant contracts
850
- accounts : list [KInner ] = [
851
- Foundry .symbolic_account (Foundry .symbolic_contract_prefix (), contract_code ),
852
- KVariable ('ACCOUNTS_REST' , sort = KSort ('AccountCellMap' )),
853
- ]
866
+ accounts : list [KInner ] = []
867
+
868
+ if isinstance (method , Contract .Constructor ):
869
+ # Symbolic account for the contract being executed
870
+ accounts .append (Foundry .symbolic_account (Foundry .symbolic_contract_prefix (), contract_code ))
871
+ else :
872
+ # Symbolic accounts of all relevant contracts
873
+ accounts , storage_constraints = _create_cse_accounts (
874
+ foundry , storage_fields , Foundry .symbolic_contract_prefix (), contract_code
875
+ )
876
+
877
+ accounts .append (KVariable ('ACCOUNTS_REST' , sort = KSort ('AccountCellMap' )))
854
878
855
879
init_subst_accounts = {'ACCOUNTS_CELL' : KEVM .accounts (accounts )}
856
880
init_subst .update (init_subst_accounts )
@@ -881,6 +905,9 @@ def _init_cterm(
881
905
mlEqualsFalse (KApply ('_==Int_' , [KVariable (contract_id , sort = KSort ('Int' )), Foundry .address_CHEATCODE ()]))
882
906
)
883
907
908
+ for constraint in storage_constraints :
909
+ init_cterm = init_cterm .add_constraint (constraint )
910
+
884
911
# The calling contract is assumed to be in the present accounts for non-tests
885
912
if not (config_type == ConfigType .TEST_CONFIG or active_symbolik ):
886
913
init_cterm .add_constraint (
@@ -893,8 +920,8 @@ def _init_cterm(
893
920
)
894
921
895
922
if isinstance (method , Contract .Constructor ) and len (arg_constraints ) > 0 :
896
- for constraint in arg_constraints :
897
- init_cterm = init_cterm .add_constraint (mlEqualsTrue (constraint ))
923
+ for arg_constraint in arg_constraints :
924
+ init_cterm = init_cterm .add_constraint (mlEqualsTrue (arg_constraint ))
898
925
899
926
init_cterm = KEVM .add_invariant (init_cterm )
900
927
@@ -922,6 +949,75 @@ def _create_initial_account_list(
922
949
return init_account_list
923
950
924
951
952
+ def _create_cse_accounts (
953
+ foundry : Foundry , storage_fields : tuple [StorageField , ...], contract_name : str , contract_code : KInner
954
+ ) -> tuple [list [KInner ], list [KApply ]]:
955
+ """
956
+ Recursively generates a list of new accounts corresponding to `contract` fields, each having <code> and <storage> cell (partially) set up.
957
+ Args:
958
+ foundry (Foundry): The Foundry object containing the information about contracts in the project.
959
+ storage_fields (tuple[StorageField, ...]): A tuple of StorageField objects representing the contract's storage fields.
960
+ contract_name (str): The name of the contract being executed to be used in the account-related symbolic variables.
961
+ contract_code (KInner): The KInner object representing the contract's runtime bytecode.
962
+ Returns:
963
+ tuple[list[KInner], list[KApply]]:
964
+ - A list of accounts to be included in the initial configuration.
965
+ - A list of constraints on symbolic account IDs.
966
+ """
967
+
968
+ new_accounts : list [KInner ] = []
969
+ new_account_constraints : list [KApply ] = []
970
+
971
+ storage_map = KVariable (contract_name + '_STORAGE' , sort = KSort ('Map' ))
972
+ new_accounts .append (Foundry .symbolic_account (contract_name , contract_code , storage_map ))
973
+
974
+ for field in storage_fields :
975
+ if field .data_type .startswith ('contract ' ):
976
+ contract_type = field .data_type .split (' ' )[1 ]
977
+ for full_contract_name , contract_obj in foundry .contracts .items ():
978
+ # TODO: this is not enough, it is possible that the same contract comes with
979
+ # src% and test%, in which case we don't know automatically which one to choose
980
+ if full_contract_name .split ('%' )[- 1 ] == contract_type :
981
+ contract_account_code = bytesToken (bytes .fromhex (contract_obj .deployed_bytecode ))
982
+ contract_account_name = 'CONTRACT-' + field .label .upper ()
983
+
984
+ # TODO: handle the case where the field has a non-zero offset
985
+ # maxUInt160 &Int #lookup ( CONTRACT_STORAGE:Map , 0 )
986
+ contract_storage_lookup = KApply (
987
+ '_&Int_' ,
988
+ [
989
+ intToken (1461501637330902918203684832716283019655932542975 ),
990
+ KEVM .lookup (storage_map , intToken (field .slot )),
991
+ ],
992
+ )
993
+ new_account_constraints .append (
994
+ mlEqualsTrue (
995
+ KApply ('_==Int_' , [contract_storage_lookup , KVariable (contract_account_name + '_ID' )])
996
+ )
997
+ )
998
+
999
+ # New contract account is not the cheatcode contract
1000
+ new_account_constraints .append (
1001
+ mlEqualsFalse (
1002
+ KApply (
1003
+ '_==Int_' ,
1004
+ [
1005
+ KVariable (contract_account_name + '_ID' , sort = KSort ('Int' )),
1006
+ Foundry .address_CHEATCODE (),
1007
+ ],
1008
+ )
1009
+ )
1010
+ )
1011
+
1012
+ contract_accounts , contract_constraints = _create_cse_accounts (
1013
+ foundry , contract_obj .fields , contract_account_name , contract_account_code
1014
+ )
1015
+ new_accounts .extend (contract_accounts )
1016
+ new_account_constraints .extend (contract_constraints )
1017
+
1018
+ return new_accounts , new_account_constraints
1019
+
1020
+
925
1021
def _final_cterm (
926
1022
empty_config : KInner ,
927
1023
program : KInner ,
0 commit comments