@@ -894,6 +894,8 @@ mod tests {
894894 Ok ( ( ) )
895895 }
896896
897+ /// Tests that a balance increase allows the recipient to send a transaction using preconfirmed
898+ /// state.
897899 #[ tokio:: test]
898900 async fn test_balance_increase ( ) -> eyre:: Result < ( ) > {
899901 let _ = tracing_subscriber:: fmt:: try_init ( ) ;
@@ -954,6 +956,142 @@ mod tests {
954956 Ok ( ( ) )
955957 }
956958
959+ /// Tests that a balance increase is dropped if the preconfirmation is cancelled.
960+ #[ tokio:: test]
961+ async fn test_balance_increase_dropped ( ) -> eyre:: Result < ( ) > {
962+ let _ = tracing_subscriber:: fmt:: try_init ( ) ;
963+
964+ let anvil = launch_anvil ( ) ;
965+ let client = StateClient :: new ( anvil. endpoint_url ( ) ) ;
966+
967+ let mut state = ExecutionState :: new ( client. clone ( ) , LimitsOpts :: default ( ) ) . await ?;
968+
969+ let sender = anvil. addresses ( ) . first ( ) . unwrap ( ) ;
970+ let sender_pk = anvil. keys ( ) . first ( ) . unwrap ( ) ;
971+ let signer = LocalSigner :: random ( ) ;
972+
973+ // initialize the state by updating the head once
974+ let slot = client. get_head ( ) . await ?;
975+ state. update_head ( None , slot) . await ?;
976+ let target_slot = slot;
977+
978+ let recipient_sk = PrivateKeySigner :: random ( ) ;
979+ let recipient_pk = recipient_sk. address ( ) ;
980+
981+ // Create a transfer of 1 ETH to the recipient
982+ let tx = default_test_transaction ( * sender, Some ( 0 ) )
983+ . with_to ( recipient_pk)
984+ . with_value ( WEI_IN_ETHER ) ;
985+ let mut request =
986+ create_signed_inclusion_request ( & [ tx. clone ( ) ] , & sender_pk. to_bytes ( ) , 10 ) . await ?;
987+
988+ let validation = state. validate_request ( & mut request) . await ;
989+ assert ! ( validation. is_ok( ) , "Validation failed: {validation:?}" ) ;
990+
991+ add_constraint ( request. clone ( ) , & mut state, & signer, target_slot) ?;
992+
993+ // Send a cancel tx
994+ let tx = default_test_transaction ( * sender, Some ( 0 ) )
995+ . with_to ( * sender)
996+ . with_max_priority_fee_per_gas ( tx. max_priority_fee_per_gas . unwrap ( ) + 1 ) ;
997+
998+ let _ = client. inner ( ) . send_transaction ( tx) . await ?;
999+
1000+ // Wait 1s, update the head to include the cancel tx; the constraint should be dropped
1001+ tokio:: time:: sleep ( Duration :: from_secs ( 1 ) ) . await ;
1002+ state. update_head ( None , slot + 1 ) . await ?;
1003+
1004+ // Now the recipient should not have balance
1005+ let tx = default_test_transaction ( recipient_pk, Some ( 0 ) ) . with_value ( U256 :: from ( 1 ) ) ;
1006+ let mut request = create_signed_inclusion_request (
1007+ & [ tx] ,
1008+ recipient_sk. to_bytes ( ) . as_slice ( ) ,
1009+ target_slot + 1 ,
1010+ )
1011+ . await ?;
1012+
1013+ let validation_result = state. validate_request ( & mut request) . await ;
1014+
1015+ assert ! (
1016+ matches!( validation_result, Err ( ValidationError :: InsufficientBalance ) ) ,
1017+ "Expected InsufficientBalance error, got {:?}" ,
1018+ validation_result
1019+ ) ;
1020+
1021+ Ok ( ( ) )
1022+ }
1023+
1024+ /// Tests that a chained preconfirmation is dropped if the previous one is cancelled.
1025+ #[ tokio:: test]
1026+ async fn test_chained_preconfs_dropped ( ) -> eyre:: Result < ( ) > {
1027+ let _ = tracing_subscriber:: fmt:: try_init ( ) ;
1028+
1029+ let anvil = launch_anvil ( ) ;
1030+ let client = StateClient :: new ( anvil. endpoint_url ( ) ) ;
1031+
1032+ let mut state = ExecutionState :: new ( client. clone ( ) , LimitsOpts :: default ( ) ) . await ?;
1033+
1034+ let sender = anvil. addresses ( ) . first ( ) . unwrap ( ) ;
1035+ let sender_pk = anvil. keys ( ) . first ( ) . unwrap ( ) ;
1036+ let signer = LocalSigner :: random ( ) ;
1037+
1038+ // initialize the state by updating the head once
1039+ let slot = client. get_head ( ) . await ?;
1040+ state. update_head ( None , slot) . await ?;
1041+ let target_slot = slot + 2 ;
1042+
1043+ let recipient_sk = PrivateKeySigner :: random ( ) ;
1044+ let recipient_pk = recipient_sk. address ( ) ;
1045+
1046+ // Create a transfer of 1 ETH to the recipient
1047+ let tx_1 = default_test_transaction ( * sender, Some ( 0 ) )
1048+ . with_to ( recipient_pk)
1049+ . with_value ( WEI_IN_ETHER ) ;
1050+ let mut request =
1051+ create_signed_inclusion_request ( & [ tx_1. clone ( ) ] , & sender_pk. to_bytes ( ) , 10 ) . await ?;
1052+
1053+ let validation = state. validate_request ( & mut request) . await ;
1054+ assert ! ( validation. is_ok( ) , "Validation failed: {validation:?}" ) ;
1055+
1056+ add_constraint ( request. clone ( ) , & mut state, & signer, target_slot) ?;
1057+
1058+ // Now the sender should have enough balance to send a transaction
1059+ let tx_2 = default_test_transaction ( recipient_pk, Some ( 0 ) )
1060+ . with_value ( WEI_IN_ETHER . div_ceil ( U256 :: from ( 2 ) ) ) ;
1061+ let mut request =
1062+ create_signed_inclusion_request ( & [ tx_2] , recipient_sk. to_bytes ( ) . as_slice ( ) , 10 )
1063+ . await ?;
1064+
1065+ let validation_result = state. validate_request ( & mut request) . await ;
1066+
1067+ add_constraint ( request, & mut state, & signer, target_slot) ?;
1068+
1069+ assert ! ( validation_result. is_ok( ) , "validation failed: {validation_result:?}" ) ;
1070+
1071+ // Cancel the first preconfirmation request so that both preconfs are dropped.
1072+
1073+ // Send a cancel tx
1074+ let tx_3 = default_test_transaction ( * sender, Some ( 0 ) )
1075+ . with_to ( * sender)
1076+ . with_max_priority_fee_per_gas ( tx_1. max_priority_fee_per_gas . unwrap ( ) + 1 ) ;
1077+
1078+ let _ = client. inner ( ) . send_transaction ( tx_3) . await ?;
1079+
1080+ // Wait 1s, update the head to include the cancel tx; the constraint should be dropped
1081+ tokio:: time:: sleep ( Duration :: from_secs ( 1 ) ) . await ;
1082+ state. update_head ( None , slot + 1 ) . await ?;
1083+
1084+ // Check that both preconfs have been dropped
1085+
1086+ let template = state. block_templates . get ( & target_slot) . unwrap ( ) ;
1087+ assert ! (
1088+ template. signed_constraints_list. is_empty( ) ,
1089+ "block template should be empty, but got: {template:?}"
1090+ ) ;
1091+
1092+ Ok ( ( ) )
1093+ }
1094+
9571095 #[ tokio:: test]
9581096 async fn test_invalid_inclusion_request_basefee ( ) -> eyre:: Result < ( ) > {
9591097 let _ = tracing_subscriber:: fmt:: try_init ( ) ;
0 commit comments