@@ -42,7 +42,6 @@ use mm2_test_helpers::structs::MyOrdersRpcResult;
4242use mm2_test_helpers:: structs:: { Bip44Chain , EnableCoinBalanceMap , EthWithTokensActivationResult , HDAccountAddressId ,
4343 TokenInfo } ;
4444use serde_json:: Value as Json ;
45- #[ cfg( any( feature = "sepolia-maker-swap-v2-tests" , feature = "sepolia-taker-swap-v2-tests" ) ) ]
4645use std:: str:: FromStr ;
4746use std:: thread;
4847use std:: time:: Duration ;
@@ -3006,6 +3005,234 @@ fn test_maker_order_recovery_on_tpu() {
30063005 ) ;
30073006}
30083007
3008+ #[ test]
3009+ fn test_maker_order_partial_fill_recovery_on_tpu ( ) {
3010+ // Initialize swap addresses and configurations
3011+ let swap_addresses = SwapAddresses :: init ( ) ;
3012+ let contracts = SwapV2TestContracts {
3013+ maker_swap_v2_contract : swap_addresses. swap_v2_contracts . maker_swap_v2_contract . addr_to_string ( ) ,
3014+ taker_swap_v2_contract : swap_addresses. swap_v2_contracts . taker_swap_v2_contract . addr_to_string ( ) ,
3015+ nft_maker_swap_v2_contract : swap_addresses
3016+ . swap_v2_contracts
3017+ . nft_maker_swap_v2_contract
3018+ . addr_to_string ( ) ,
3019+ } ;
3020+ let swap_contract_address = swap_addresses. swap_contract_address . addr_to_string ( ) ;
3021+ let node = TestNode {
3022+ url : GETH_RPC_URL . to_string ( ) ,
3023+ } ;
3024+
3025+ // start Bob, Alice and Onur
3026+ let ( _, bob_priv_key) =
3027+ eth_coin_v2_activation_with_random_privkey ( & MM_CTX , ETH , & eth_dev_conf ( ) , swap_addresses, false ) ;
3028+ let ( _, alice_priv_key) =
3029+ eth_coin_v2_activation_with_random_privkey ( & MM_CTX1 , ETH1 , & eth1_dev_conf ( ) , swap_addresses, false ) ;
3030+ let ( _, onur_priv_key) =
3031+ eth_coin_v2_activation_with_random_privkey ( & MM_CTX1 , ETH1 , & eth1_dev_conf ( ) , swap_addresses, false ) ;
3032+
3033+ let coins = json ! ( [ eth_dev_conf( ) , eth1_dev_conf( ) ] ) ;
3034+
3035+ let bob_conf = Mm2TestConf :: seednode_trade_v2 ( & format ! ( "0x{}" , hex:: encode( bob_priv_key) ) , & coins) ;
3036+ let mut mm_bob = block_on ( MarketMakerIt :: start_with_envs (
3037+ bob_conf. conf . clone ( ) ,
3038+ bob_conf. rpc_password ,
3039+ None ,
3040+ & [ ( "ABORT_SWAP_FOR_TEST" , "1" ) ] ,
3041+ ) )
3042+ . unwrap ( ) ;
3043+
3044+ let alice_conf =
3045+ Mm2TestConf :: light_node_trade_v2 ( & format ! ( "0x{}" , hex:: encode( alice_priv_key) ) , & coins, & [ & mm_bob
3046+ . ip
3047+ . to_string ( ) ] ) ;
3048+ let mm_alice = MarketMakerIt :: start ( alice_conf. conf . clone ( ) , alice_conf. rpc_password , None ) . unwrap ( ) ;
3049+
3050+ let onur_conf = Mm2TestConf :: light_node_trade_v2 ( & format ! ( "0x{}" , hex:: encode( onur_priv_key) ) , & coins, & [ & mm_bob
3051+ . ip
3052+ . to_string ( ) ] ) ;
3053+ let mm_onur = MarketMakerIt :: start ( onur_conf. conf . clone ( ) , onur_conf. rpc_password , None ) . unwrap ( ) ;
3054+
3055+ // 1. Enable coins for Bob and fund his addresses.
3056+ let bob_eth_activation_json = block_on ( enable_eth_coin_v2 (
3057+ & mm_bob,
3058+ ETH ,
3059+ & swap_contract_address,
3060+ contracts. clone ( ) ,
3061+ None ,
3062+ & [ node. clone ( ) ] ,
3063+ ) ) ;
3064+
3065+ let bob_address_str = bob_eth_activation_json[ "result" ] [ "eth_addresses_infos" ]
3066+ . as_object ( )
3067+ . unwrap ( )
3068+ . keys ( )
3069+ . next ( )
3070+ . unwrap ( ) ;
3071+ let bob_address = Address :: from_str ( bob_address_str) . unwrap ( ) ;
3072+ fill_eth ( bob_address, U256 :: from ( 10 ) . pow ( U256 :: from ( 19 ) ) ) ; // Fund Bob with 10 ETH
3073+
3074+ let bob_eth1_activation_json = block_on ( enable_eth_coin_v2 (
3075+ & mm_bob,
3076+ ETH1 ,
3077+ & swap_contract_address,
3078+ contracts. clone ( ) ,
3079+ None ,
3080+ & [ node. clone ( ) ] ,
3081+ ) ) ;
3082+
3083+ let bob_eth1_address_str = bob_eth1_activation_json[ "result" ] [ "eth_addresses_infos" ]
3084+ . as_object ( )
3085+ . unwrap ( )
3086+ . keys ( )
3087+ . next ( )
3088+ . unwrap ( ) ;
3089+ let bob_eth1_address = Address :: from_str ( bob_eth1_address_str) . unwrap ( ) ;
3090+ fill_eth ( bob_eth1_address, U256 :: from ( 10 ) . pow ( U256 :: from ( 19 ) ) ) ; // Fund Bob with 10 ETH1
3091+
3092+ // 2. Enable coins for Alice and fund her addresses.
3093+ let alice_eth_activation_json = block_on ( enable_eth_coin_v2 (
3094+ & mm_alice,
3095+ ETH ,
3096+ & swap_contract_address,
3097+ contracts. clone ( ) ,
3098+ None ,
3099+ & [ node. clone ( ) ] ,
3100+ ) ) ;
3101+
3102+ let alice_address_str = alice_eth_activation_json[ "result" ] [ "eth_addresses_infos" ]
3103+ . as_object ( )
3104+ . unwrap ( )
3105+ . keys ( )
3106+ . next ( )
3107+ . unwrap ( ) ;
3108+ let alice_address = Address :: from_str ( alice_address_str) . unwrap ( ) ;
3109+ fill_eth ( alice_address, U256 :: from ( 10 ) . pow ( U256 :: from ( 19 ) ) ) ; // Fund Alice with 10 ETH
3110+
3111+ let alice_eth1_activation_json = block_on ( enable_eth_coin_v2 (
3112+ & mm_alice,
3113+ ETH1 ,
3114+ & swap_contract_address,
3115+ contracts. clone ( ) ,
3116+ None ,
3117+ & [ node. clone ( ) ] ,
3118+ ) ) ;
3119+
3120+ let alice_address_str = alice_eth1_activation_json[ "result" ] [ "eth_addresses_infos" ]
3121+ . as_object ( )
3122+ . unwrap ( )
3123+ . keys ( )
3124+ . next ( )
3125+ . unwrap ( ) ;
3126+ let alice_address = Address :: from_str ( alice_address_str) . unwrap ( ) ;
3127+ fill_eth ( alice_address, U256 :: from ( 10 ) . pow ( U256 :: from ( 19 ) ) ) ; // Fund Alice with 10 ETH1
3128+
3129+ // 3. Enable coins for Onur (observer, no funding needed).
3130+ block_on ( enable_eth_coin_v2 (
3131+ & mm_onur,
3132+ ETH ,
3133+ & swap_contract_address,
3134+ contracts. clone ( ) ,
3135+ None ,
3136+ & [ node. clone ( ) ] ,
3137+ ) ) ;
3138+ block_on ( enable_eth_coin_v2 (
3139+ & mm_onur,
3140+ ETH1 ,
3141+ & swap_contract_address,
3142+ contracts. clone ( ) ,
3143+ None ,
3144+ & [ node. clone ( ) ] ,
3145+ ) ) ;
3146+
3147+ let send_my_orders_rpc = |mm : & MarketMakerIt | -> MyOrdersRpcResult {
3148+ let rc = block_on ( mm. rpc ( & json ! ( {
3149+ "userpass" : mm. userpass,
3150+ "method" : "my_orders" ,
3151+ } ) ) )
3152+ . unwrap ( ) ;
3153+ assert ! ( rc. 0 . is_success( ) , "!my_orders: {}" , rc. 1 ) ;
3154+ serde_json:: from_str ( & rc. 1 ) . unwrap ( )
3155+ } ;
3156+
3157+ let send_orderbook_rpc = |mm : & MarketMakerIt | -> Json {
3158+ let rc = block_on ( mm. rpc ( & json ! ( {
3159+ "userpass" : mm. userpass,
3160+ "method" : "orderbook" ,
3161+ "mmrpc" : "2.0" ,
3162+ "params" : {
3163+ "base" : "ETH" ,
3164+ "rel" : "ETH1" ,
3165+ } ,
3166+ } ) ) )
3167+ . unwrap ( ) ;
3168+ assert ! ( rc. 0 . is_success( ) , "!orderbook: {}" , rc. 1 ) ;
3169+ serde_json:: from_str ( & rc. 1 ) . unwrap ( )
3170+ } ;
3171+
3172+ let bob_set_price = block_on ( mm_bob. rpc ( & json ! ( {
3173+ "userpass" : mm_bob. userpass,
3174+ "method" : "set_price" ,
3175+ "base" : "ETH" ,
3176+ "rel" : "ETH1" ,
3177+ "price" : "1.0" ,
3178+ "volume" : "2.0" ,
3179+ } ) ) )
3180+ . unwrap ( ) ;
3181+ assert ! ( bob_set_price. 0 . is_success( ) , "set_price failed: {}" , bob_set_price. 1 ) ;
3182+
3183+ let orderbook_result = send_orderbook_rpc ( & mm_onur) ;
3184+ let asks = orderbook_result[ "result" ] [ "asks" ] . as_array ( ) . unwrap ( ) ;
3185+ assert_eq ! ( asks. len( ) , 1 ) ;
3186+ assert_eq ! ( asks[ 0 ] [ "price" ] , "1" ) ;
3187+ assert_eq ! ( asks[ 0 ] [ "base_max_volume" ] [ "decimal" ] , "2" ) ;
3188+
3189+ // Alice takes a partial amount
3190+ let alice_buy = block_on ( mm_alice. rpc ( & json ! ( {
3191+ "userpass" : mm_alice. userpass,
3192+ "method" : "buy" ,
3193+ "base" : "ETH" ,
3194+ "rel" : "ETH1" ,
3195+ "price" : "1.0" ,
3196+ "volume" : "1.0" ,
3197+ } ) ) )
3198+ . unwrap ( ) ;
3199+ assert ! ( alice_buy. 0 . is_success( ) , "buy failed: {}" , alice_buy. 1 ) ;
3200+ let buy_result: Json = serde_json:: from_str ( & alice_buy. 1 ) . unwrap ( ) ;
3201+ let swap_uuid = buy_result[ "result" ] [ "uuid" ] . as_str ( ) . unwrap ( ) ;
3202+
3203+ // Verify intermediate state (order partially filled)
3204+ block_on ( mm_bob. wait_for_log ( 30. , |log| {
3205+ log. contains ( & format ! ( "Maker swap {} has successfully started" , swap_uuid) )
3206+ } ) )
3207+ . unwrap ( ) ;
3208+
3209+ let my_orders_rpc = send_my_orders_rpc ( & mm_bob) ;
3210+ let maker_order = my_orders_rpc. result . maker_orders . values ( ) . next ( ) . unwrap ( ) ;
3211+ assert_eq ! ( maker_order. available_amount, BigDecimal :: from( 1 ) ) ;
3212+
3213+ let orderbook_result = send_orderbook_rpc ( & mm_onur) ;
3214+ let asks = orderbook_result[ "result" ] [ "asks" ] . as_array ( ) . unwrap ( ) ;
3215+ assert_eq ! ( asks. len( ) , 1 ) ;
3216+ assert_eq ! ( asks[ 0 ] [ "base_max_volume" ] [ "decimal" ] , "1" ) ;
3217+
3218+ // Wait for abort and recovery
3219+ block_on ( mm_bob. wait_for_log ( 30. , |log| log. contains ( "Aborting it intentionally." ) ) ) . unwrap ( ) ;
3220+ block_on ( mm_bob. wait_for_log ( 30. , |log| log. contains ( "Successfully recovered volume for order" ) ) ) . unwrap ( ) ;
3221+
3222+ // Stop Alice to prevent her from re-taking the order
3223+ block_on ( mm_alice. stop ( ) ) . unwrap ( ) ;
3224+
3225+ // Verify final state (volume recovered)
3226+ let my_orders_rpc = send_my_orders_rpc ( & mm_bob) ;
3227+ let maker_order = my_orders_rpc. result . maker_orders . values ( ) . next ( ) . unwrap ( ) ;
3228+ assert_eq ! ( maker_order. available_amount, BigDecimal :: from( 2 ) ) ;
3229+
3230+ let orderbook_result = send_orderbook_rpc ( & mm_onur) ;
3231+ let asks = orderbook_result[ "result" ] [ "asks" ] . as_array ( ) . unwrap ( ) ;
3232+ assert_eq ! ( asks. len( ) , 1 ) ;
3233+ assert_eq ! ( asks[ 0 ] [ "base_max_volume" ] [ "decimal" ] , "2" ) ;
3234+ }
3235+
30093236fn log_swap_status_before_stop ( mm : & MarketMakerIt , uuid : & str , role : & str ) {
30103237 let status = block_on ( my_swap_status ( mm, uuid) ) ;
30113238 log ! ( "{} swap {} status before stop: {:?}" , role, uuid, status) ;
0 commit comments