From 103ab1f77b8554669d85ac354571fc1d32df78bf Mon Sep 17 00:00:00 2001 From: 1xstj <106580853+1xstj@users.noreply.github.com> Date: Thu, 7 Aug 2025 22:52:32 +0100 Subject: [PATCH 1/2] fix: update process_schedule_delegator_unstake --- .../src/functions/delegate.rs | 2 +- .../src/tests/delegate.rs | 95 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/pallets/multi-asset-delegation/src/functions/delegate.rs b/pallets/multi-asset-delegation/src/functions/delegate.rs index be86e0a8d..36050d8a2 100644 --- a/pallets/multi-asset-delegation/src/functions/delegate.rs +++ b/pallets/multi-asset-delegation/src/functions/delegate.rs @@ -239,7 +239,7 @@ impl Pallet { let pending_unstake_amount: BalanceOf = metadata .delegator_unstake_requests .iter() - .filter(|r| r.operator == operator && r.asset == asset) + .filter(|r| r.operator == operator && r.asset == asset && !r.is_nomination) .fold(Zero::zero(), |acc, r| acc.saturating_add(r.amount)); let available_amount = delegation.amount.saturating_sub(pending_unstake_amount); diff --git a/pallets/multi-asset-delegation/src/tests/delegate.rs b/pallets/multi-asset-delegation/src/tests/delegate.rs index e5f4a4fba..6e17161f4 100644 --- a/pallets/multi-asset-delegation/src/tests/delegate.rs +++ b/pallets/multi-asset-delegation/src/tests/delegate.rs @@ -936,3 +936,98 @@ fn debug_tnt_delegation_verify_nomination_issue() { assert_eq!(delegation.amount, delegate_amount); }); } + +#[test] +fn delegation_unstake_bug_with_nomination_pending() { + // Test case that reproduces the bug where delegation unstake calculation + // incorrectly includes nomination unstake requests + new_test_ext().execute_with(|| { + let delegator: AccountId = Bob.into(); + let operator: AccountId = Alice.into(); + // Use the same asset that nominations use: Asset::Custom(Zero::zero()) which is Asset::Custom(0) + let asset = Asset::Custom(0); + let delegation_amount = 500; + let nomination_amount = 300; + let _delegation_unstake_amount = 200; + let nomination_unstake_amount = 100; + + // Setup operator + assert_ok!(MultiAssetDelegation::join_operators( + RuntimeOrigin::signed(operator.clone()), + 10_000 + )); + + // Create tokens and deposits for delegator + create_and_mint_tokens(0, delegator.clone(), delegation_amount); + assert_ok!(MultiAssetDelegation::deposit( + RuntimeOrigin::signed(delegator.clone()), + asset.clone(), + delegation_amount, + None, + None, + )); + + // Create a regular delegation + assert_ok!(MultiAssetDelegation::delegate( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + asset.clone(), + delegation_amount, + Default::default(), + )); + + // Setup nomination for native restaking + assert_ok!(Staking::bond( + RuntimeOrigin::signed(delegator.clone()), + nomination_amount, + pallet_staking::RewardDestination::Staked + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(delegator.clone()), vec![operator.clone()])); + + // Create nomination delegation (simulate native restaking) + assert_ok!(MultiAssetDelegation::delegate_nomination( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + nomination_amount, + Default::default(), + )); + + // Schedule nomination unstake first + assert_ok!(MultiAssetDelegation::schedule_nomination_unstake( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + nomination_unstake_amount, + Default::default(), + )); + + // Verify nomination unstake request exists + let metadata = MultiAssetDelegation::delegators(delegator.clone()).unwrap(); + assert_eq!(metadata.delegator_unstake_requests.len(), 1); + let nomination_request = &metadata.delegator_unstake_requests[0]; + assert!(nomination_request.is_nomination); + assert_eq!(nomination_request.amount, nomination_unstake_amount); + + // Now try to schedule regular delegation unstake for the full amount + // This should succeed as we have 500 - 0 = 500 available for delegation + assert_ok!(MultiAssetDelegation::schedule_delegator_unstake( + RuntimeOrigin::signed(delegator.clone()), + operator.clone(), + asset.clone(), + delegation_amount, + )); + + // Verify both unstake requests exist + let metadata = MultiAssetDelegation::delegators(delegator.clone()).unwrap(); + assert_eq!(metadata.delegator_unstake_requests.len(), 2); + + // Check first request is nomination + let first_request = &metadata.delegator_unstake_requests[0]; + assert!(first_request.is_nomination); + assert_eq!(first_request.amount, nomination_unstake_amount); + + // Check second request is delegation + let second_request = &metadata.delegator_unstake_requests[1]; + assert!(!second_request.is_nomination); + assert_eq!(second_request.amount, delegation_amount); + }); +} From 4a3c2dceaad13d27064a1e6688d85962ae919ea1 Mon Sep 17 00:00:00 2001 From: 1xstj <106580853+1xstj@users.noreply.github.com> Date: Thu, 7 Aug 2025 23:22:31 +0100 Subject: [PATCH 2/2] chore: format --- .../src/tests/delegate.rs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pallets/multi-asset-delegation/src/tests/delegate.rs b/pallets/multi-asset-delegation/src/tests/delegate.rs index 6e17161f4..490b0d192 100644 --- a/pallets/multi-asset-delegation/src/tests/delegate.rs +++ b/pallets/multi-asset-delegation/src/tests/delegate.rs @@ -939,13 +939,14 @@ fn debug_tnt_delegation_verify_nomination_issue() { #[test] fn delegation_unstake_bug_with_nomination_pending() { - // Test case that reproduces the bug where delegation unstake calculation + // Test case that reproduces the bug where delegation unstake calculation // incorrectly includes nomination unstake requests new_test_ext().execute_with(|| { let delegator: AccountId = Bob.into(); let operator: AccountId = Alice.into(); - // Use the same asset that nominations use: Asset::Custom(Zero::zero()) which is Asset::Custom(0) - let asset = Asset::Custom(0); + // Use the same asset that nominations use: Asset::Custom(Zero::zero()) which is + // Asset::Custom(0) + let asset = Asset::Custom(0); let delegation_amount = 500; let nomination_amount = 300; let _delegation_unstake_amount = 200; @@ -982,7 +983,9 @@ fn delegation_unstake_bug_with_nomination_pending() { nomination_amount, pallet_staking::RewardDestination::Staked )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(delegator.clone()), vec![operator.clone()])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(delegator.clone()), vec![ + operator.clone() + ])); // Create nomination delegation (simulate native restaking) assert_ok!(MultiAssetDelegation::delegate_nomination( @@ -1019,13 +1022,13 @@ fn delegation_unstake_bug_with_nomination_pending() { // Verify both unstake requests exist let metadata = MultiAssetDelegation::delegators(delegator.clone()).unwrap(); assert_eq!(metadata.delegator_unstake_requests.len(), 2); - + // Check first request is nomination let first_request = &metadata.delegator_unstake_requests[0]; assert!(first_request.is_nomination); assert_eq!(first_request.amount, nomination_unstake_amount); - - // Check second request is delegation + + // Check second request is delegation let second_request = &metadata.delegator_unstake_requests[1]; assert!(!second_request.is_nomination); assert_eq!(second_request.amount, delegation_amount);