Skip to content

Commit b54e187

Browse files
committed
wip: add a test case for partial fill recovery
1 parent efc193c commit b54e187

File tree

1 file changed

+228
-1
lines changed

1 file changed

+228
-1
lines changed

mm2src/mm2_main/tests/docker_tests/eth_docker_tests.rs

Lines changed: 228 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ use mm2_test_helpers::structs::MyOrdersRpcResult;
4242
use mm2_test_helpers::structs::{Bip44Chain, EnableCoinBalanceMap, EthWithTokensActivationResult, HDAccountAddressId,
4343
TokenInfo};
4444
use serde_json::Value as Json;
45-
#[cfg(any(feature = "sepolia-maker-swap-v2-tests", feature = "sepolia-taker-swap-v2-tests"))]
4645
use std::str::FromStr;
4746
use std::thread;
4847
use 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+
30093236
fn 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

Comments
 (0)