Skip to content

Commit 3a91f29

Browse files
committed
Add protocol fees to protocol LP proportionally to the price
1 parent 2c51598 commit 3a91f29

File tree

3 files changed

+220
-7
lines changed

3 files changed

+220
-7
lines changed

pallets/swap/src/pallet/impls.rs

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,41 @@ impl<T: Config> Pallet<T> {
113113
Ok(())
114114
}
115115

116+
pub(crate) fn get_proportional_alpha_tao_and_remainders(
117+
sqrt_alpha_price: U64F64,
118+
amount_tao: TaoCurrency,
119+
amount_alpha: AlphaCurrency,
120+
) -> (TaoCurrency, AlphaCurrency, TaoCurrency, AlphaCurrency) {
121+
let price = sqrt_alpha_price.saturating_mul(sqrt_alpha_price);
122+
let tao_equivalent: u64 = U64F64::saturating_from_num(u64::from(amount_alpha))
123+
.saturating_mul(price)
124+
.saturating_to_num();
125+
let amount_tao_u64 = u64::from(amount_tao);
126+
127+
if tao_equivalent <= amount_tao_u64 {
128+
// Too much or just enough TAO
129+
(
130+
tao_equivalent.into(),
131+
amount_alpha,
132+
amount_tao.saturating_sub(TaoCurrency::from(tao_equivalent)),
133+
0.into(),
134+
)
135+
} else {
136+
// Too much Alpha
137+
let alpha_equivalent: u64 = U64F64::saturating_from_num(u64::from(amount_tao))
138+
.safe_div(price)
139+
.saturating_to_num();
140+
(
141+
amount_tao,
142+
alpha_equivalent.into(),
143+
0.into(),
144+
u64::from(amount_alpha)
145+
.saturating_sub(alpha_equivalent)
146+
.into(),
147+
)
148+
}
149+
}
150+
116151
/// Adjusts protocol liquidity with new values of TAO and Alpha reserve
117152
pub(super) fn adjust_protocol_liquidity(
118153
netuid: NetUid,
@@ -129,17 +164,31 @@ impl<T: Config> Pallet<T> {
129164
// Claim protocol fees and add them to liquidity
130165
let (tao_fees, alpha_fees) = position.collect_fees();
131166

132-
// Adjust liquidity
167+
// Add fee reservoirs and get proportional amounts
133168
let current_sqrt_price = AlphaSqrtPrice::<T>::get(netuid);
169+
let tao_reservoir = ScrapReservoirTao::<T>::get(netuid);
170+
let alpha_reservoir = ScrapReservoirAlpha::<T>::get(netuid);
171+
let (corrected_tao_delta, corrected_alpha_delta, tao_scrap, alpha_scrap) =
172+
Self::get_proportional_alpha_tao_and_remainders(
173+
current_sqrt_price,
174+
tao_delta
175+
.saturating_add(TaoCurrency::from(tao_fees))
176+
.saturating_add(tao_reservoir),
177+
alpha_delta
178+
.saturating_add(AlphaCurrency::from(alpha_fees))
179+
.saturating_add(alpha_reservoir),
180+
);
181+
182+
// Update scarp reservoirs
183+
ScrapReservoirTao::<T>::insert(netuid, tao_scrap);
184+
ScrapReservoirAlpha::<T>::insert(netuid, alpha_scrap);
185+
186+
// Adjust liquidity
134187
let maybe_token_amounts = position.to_token_amounts(current_sqrt_price);
135188
if let Ok((tao, alpha)) = maybe_token_amounts {
136189
// Get updated reserves, calculate liquidity
137-
let new_tao_reserve = tao
138-
.saturating_add(tao_delta.to_u64())
139-
.saturating_add(tao_fees);
140-
let new_alpha_reserve = alpha
141-
.saturating_add(alpha_delta.to_u64())
142-
.saturating_add(alpha_fees);
190+
let new_tao_reserve = tao.saturating_add(corrected_tao_delta.to_u64());
191+
let new_alpha_reserve = alpha.saturating_add(corrected_alpha_delta.to_u64());
143192
let new_liquidity = helpers_128bit::sqrt(
144193
(new_tao_reserve as u128).saturating_mul(new_alpha_reserve as u128),
145194
) as u64;

pallets/swap/src/pallet/mod.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ mod pallet {
147147
ValueQuery,
148148
>;
149149

150+
/// TAO reservoir for scraps of protocol claimed fees.
151+
#[pallet::storage]
152+
pub type ScrapReservoirTao<T> = StorageMap<_, Twox64Concat, NetUid, TaoCurrency, ValueQuery>;
153+
154+
/// Alpha reservoir for scraps of protocol claimed fees.
155+
#[pallet::storage]
156+
pub type ScrapReservoirAlpha<T> =
157+
StorageMap<_, Twox64Concat, NetUid, AlphaCurrency, ValueQuery>;
158+
150159
#[pallet::event]
151160
#[pallet::generate_deposit(pub(super) fn deposit_event)]
152161
pub enum Event<T: Config> {

pallets/swap/src/pallet/tests.rs

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,3 +2744,158 @@ fn test_clear_protocol_liquidity_green_path() {
27442744
assert!(!SwapV3Initialized::<Test>::contains_key(netuid));
27452745
});
27462746
}
2747+
2748+
fn as_tuple(
2749+
(t_used, a_used, t_rem, a_rem): (TaoCurrency, AlphaCurrency, TaoCurrency, AlphaCurrency),
2750+
) -> (u64, u64, u64, u64) {
2751+
(
2752+
u64::from(t_used),
2753+
u64::from(a_used),
2754+
u64::from(t_rem),
2755+
u64::from(a_rem),
2756+
)
2757+
}
2758+
2759+
#[test]
2760+
fn proportional_when_price_is_one_and_tao_is_plenty() {
2761+
// sqrt_price = 1.0 => price = 1.0
2762+
let sqrt = U64F64::from_num(1u64);
2763+
let amount_tao: TaoCurrency = 10u64.into();
2764+
let amount_alpha: AlphaCurrency = 3u64.into();
2765+
2766+
// alpha * price = 3 * 1 = 3 <= amount_tao(10)
2767+
let out =
2768+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2769+
assert_eq!(as_tuple(out), (3, 3, 7, 0));
2770+
}
2771+
2772+
#[test]
2773+
fn proportional_when_price_is_one_and_alpha_is_excess() {
2774+
// sqrt_price = 1.0 => price = 1.0
2775+
let sqrt = U64F64::from_num(1u64);
2776+
let amount_tao: TaoCurrency = 5u64.into();
2777+
let amount_alpha: AlphaCurrency = 10u64.into();
2778+
2779+
// tao is limiting: alpha_equiv = floor(5 / 1) = 5
2780+
let out =
2781+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2782+
assert_eq!(as_tuple(out), (5, 5, 0, 5));
2783+
}
2784+
2785+
#[test]
2786+
fn proportional_with_higher_price_and_alpha_limiting() {
2787+
// Choose sqrt_price = 2.0 => price = 4.0 (since implementation squares it)
2788+
let sqrt = U64F64::from_num(2u64);
2789+
let amount_tao: TaoCurrency = 50u64.into();
2790+
let amount_alpha: AlphaCurrency = 20u64.into();
2791+
2792+
// tao_equivalent = alpha * price = 20 * 4 = 80 > 50 => tao limits alpha
2793+
// alpha_equivalent = floor(50 / 4) = 12
2794+
// remainders: tao 0, alpha 20 - 12 = 8
2795+
let out =
2796+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2797+
assert_eq!(as_tuple(out), (50, 12, 0, 8));
2798+
}
2799+
2800+
#[test]
2801+
fn zero_price_uses_no_tao_and_all_alpha() {
2802+
// sqrt_price = 0 => price = 0
2803+
let sqrt = U64F64::from_num(0u64);
2804+
let amount_tao: TaoCurrency = 42u64.into();
2805+
let amount_alpha: AlphaCurrency = 17u64.into();
2806+
2807+
// tao_equivalent = 17 * 0 = 0 <= 42
2808+
let out =
2809+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2810+
assert_eq!(as_tuple(out), (0, 17, 42, 0));
2811+
}
2812+
2813+
#[test]
2814+
fn rounding_down_behavior_when_dividing_by_price() {
2815+
// sqrt_price = 2.0 => price = 4.0
2816+
let sqrt = U64F64::from_num(2u64);
2817+
let amount_tao: TaoCurrency = 13u64.into();
2818+
let amount_alpha: AlphaCurrency = 100u64.into();
2819+
2820+
// tao is limiting; alpha_equiv = floor(13 / 4) = 3
2821+
// remainders: tao 0, alpha 100 - 3 = 97
2822+
let out =
2823+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2824+
assert_eq!(as_tuple(out), (13, 3, 0, 97));
2825+
}
2826+
2827+
#[test]
2828+
fn exact_fit_when_tao_matches_alpha_times_price() {
2829+
// sqrt_price = 1.0 => price = 1.0
2830+
let sqrt = U64F64::from_num(1u64);
2831+
let amount_tao: TaoCurrency = 9u64.into();
2832+
let amount_alpha: AlphaCurrency = 9u64.into();
2833+
2834+
let out =
2835+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2836+
assert_eq!(as_tuple(out), (9, 9, 0, 0));
2837+
}
2838+
2839+
#[test]
2840+
fn handles_zero_balances() {
2841+
let sqrt = U64F64::from_num(1u64);
2842+
2843+
// Zero TAO, some alpha
2844+
let out =
2845+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, 0u64.into(), 7u64.into());
2846+
// tao limits; alpha_equiv = floor(0 / 1) = 0
2847+
assert_eq!(as_tuple(out), (0, 0, 0, 7));
2848+
2849+
// Some TAO, zero alpha
2850+
let out =
2851+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, 7u64.into(), 0u64.into());
2852+
// tao_equiv = 0 * 1 = 0 <= 7
2853+
assert_eq!(as_tuple(out), (0, 0, 7, 0));
2854+
2855+
// Both zero
2856+
let out =
2857+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, 0u64.into(), 0u64.into());
2858+
assert_eq!(as_tuple(out), (0, 0, 0, 0));
2859+
}
2860+
2861+
#[test]
2862+
fn adjust_protocol_liquidity_uses_and_sets_scrap_reservoirs() {
2863+
new_test_ext().execute_with(|| {
2864+
// --- Arrange
2865+
let netuid: NetUid = 1u16.into();
2866+
// Price = 1.0 (since sqrt_price^2 = 1), so proportional match is 1:1
2867+
AlphaSqrtPrice::<Test>::insert(netuid, U64F64::saturating_from_num(1u64));
2868+
2869+
// Start with some non-zero scrap reservoirs
2870+
ScrapReservoirTao::<Test>::insert(netuid, TaoCurrency::from(7u64));
2871+
ScrapReservoirAlpha::<Test>::insert(netuid, AlphaCurrency::from(5u64));
2872+
2873+
// Create a minimal protocol position so the function’s body executes.
2874+
let protocol = Pallet::<Test>::protocol_account_id();
2875+
let position = Position::new(
2876+
PositionId::from(0),
2877+
netuid,
2878+
TickIndex::MIN,
2879+
TickIndex::MAX,
2880+
0,
2881+
);
2882+
// Ensure collect_fees() returns (0,0) via zeroed fees in `position` (default).
2883+
Positions::<Test>::insert((netuid, protocol, position.id), position.clone());
2884+
2885+
// --- Act
2886+
// No external deltas or fees; only reservoirs should be considered.
2887+
// With price=1, the exact proportional pair uses 5 alpha and 5 tao,
2888+
// leaving tao scrap = 7 - 5 = 2, alpha scrap = 5 - 5 = 0.
2889+
Pallet::<Test>::adjust_protocol_liquidity(netuid, 0u64.into(), 0u64.into());
2890+
2891+
// --- Assert: reservoirs were READ (used in proportional calc) and then SET (updated)
2892+
assert_eq!(
2893+
ScrapReservoirTao::<Test>::get(netuid),
2894+
TaoCurrency::from(2u64)
2895+
);
2896+
assert_eq!(
2897+
ScrapReservoirAlpha::<Test>::get(netuid),
2898+
AlphaCurrency::from(0u64)
2899+
);
2900+
});
2901+
}

0 commit comments

Comments
 (0)