Skip to content

Commit e6af8a9

Browse files
authored
Merge pull request #2123 from opentensor/fix/protocol-lp-fees-on-injection
Add protocol fees to protocol LP proportionally to the price
2 parents 2c51598 + 3b31892 commit e6af8a9

File tree

3 files changed

+234
-7
lines changed

3 files changed

+234
-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 scrap 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: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2744,3 +2744,172 @@ 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 = 85u64.into();
2790+
let amount_alpha: AlphaCurrency = 20u64.into();
2791+
2792+
// tao_equivalent = alpha * price = 20 * 4 = 80 < 85 => alpha limits tao
2793+
// remainders: tao 5, alpha 0
2794+
let out =
2795+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2796+
assert_eq!(as_tuple(out), (80, 20, 5, 0));
2797+
}
2798+
2799+
#[test]
2800+
fn proportional_with_higher_price_and_tao_limiting() {
2801+
// Choose sqrt_price = 2.0 => price = 4.0 (since implementation squares it)
2802+
let sqrt = U64F64::from_num(2u64);
2803+
let amount_tao: TaoCurrency = 50u64.into();
2804+
let amount_alpha: AlphaCurrency = 20u64.into();
2805+
2806+
// tao_equivalent = alpha * price = 20 * 4 = 80 > 50 => tao limits alpha
2807+
// alpha_equivalent = floor(50 / 4) = 12
2808+
// remainders: tao 0, alpha 20 - 12 = 8
2809+
let out =
2810+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2811+
assert_eq!(as_tuple(out), (50, 12, 0, 8));
2812+
}
2813+
2814+
#[test]
2815+
fn zero_price_uses_no_tao_and_all_alpha() {
2816+
// sqrt_price = 0 => price = 0
2817+
let sqrt = U64F64::from_num(0u64);
2818+
let amount_tao: TaoCurrency = 42u64.into();
2819+
let amount_alpha: AlphaCurrency = 17u64.into();
2820+
2821+
// tao_equivalent = 17 * 0 = 0 <= 42
2822+
let out =
2823+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2824+
assert_eq!(as_tuple(out), (0, 17, 42, 0));
2825+
}
2826+
2827+
#[test]
2828+
fn rounding_down_behavior_when_dividing_by_price() {
2829+
// sqrt_price = 2.0 => price = 4.0
2830+
let sqrt = U64F64::from_num(2u64);
2831+
let amount_tao: TaoCurrency = 13u64.into();
2832+
let amount_alpha: AlphaCurrency = 100u64.into();
2833+
2834+
// tao is limiting; alpha_equiv = floor(13 / 4) = 3
2835+
// remainders: tao 0, alpha 100 - 3 = 97
2836+
let out =
2837+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2838+
assert_eq!(as_tuple(out), (13, 3, 0, 97));
2839+
}
2840+
2841+
#[test]
2842+
fn exact_fit_when_tao_matches_alpha_times_price() {
2843+
// sqrt_price = 1.0 => price = 1.0
2844+
let sqrt = U64F64::from_num(1u64);
2845+
let amount_tao: TaoCurrency = 9u64.into();
2846+
let amount_alpha: AlphaCurrency = 9u64.into();
2847+
2848+
let out =
2849+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, amount_tao, amount_alpha);
2850+
assert_eq!(as_tuple(out), (9, 9, 0, 0));
2851+
}
2852+
2853+
#[test]
2854+
fn handles_zero_balances() {
2855+
let sqrt = U64F64::from_num(1u64);
2856+
2857+
// Zero TAO, some alpha
2858+
let out =
2859+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, 0u64.into(), 7u64.into());
2860+
// tao limits; alpha_equiv = floor(0 / 1) = 0
2861+
assert_eq!(as_tuple(out), (0, 0, 0, 7));
2862+
2863+
// Some TAO, zero alpha
2864+
let out =
2865+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, 7u64.into(), 0u64.into());
2866+
// tao_equiv = 0 * 1 = 0 <= 7
2867+
assert_eq!(as_tuple(out), (0, 0, 7, 0));
2868+
2869+
// Both zero
2870+
let out =
2871+
Pallet::<Test>::get_proportional_alpha_tao_and_remainders(sqrt, 0u64.into(), 0u64.into());
2872+
assert_eq!(as_tuple(out), (0, 0, 0, 0));
2873+
}
2874+
2875+
#[test]
2876+
fn adjust_protocol_liquidity_uses_and_sets_scrap_reservoirs() {
2877+
new_test_ext().execute_with(|| {
2878+
// --- Arrange
2879+
let netuid: NetUid = 1u16.into();
2880+
// Price = 1.0 (since sqrt_price^2 = 1), so proportional match is 1:1
2881+
AlphaSqrtPrice::<Test>::insert(netuid, U64F64::saturating_from_num(1u64));
2882+
2883+
// Start with some non-zero scrap reservoirs
2884+
ScrapReservoirTao::<Test>::insert(netuid, TaoCurrency::from(7u64));
2885+
ScrapReservoirAlpha::<Test>::insert(netuid, AlphaCurrency::from(5u64));
2886+
2887+
// Create a minimal protocol position so the function’s body executes.
2888+
let protocol = Pallet::<Test>::protocol_account_id();
2889+
let position = Position::new(
2890+
PositionId::from(0),
2891+
netuid,
2892+
TickIndex::MIN,
2893+
TickIndex::MAX,
2894+
0,
2895+
);
2896+
// Ensure collect_fees() returns (0,0) via zeroed fees in `position` (default).
2897+
Positions::<Test>::insert((netuid, protocol, position.id), position.clone());
2898+
2899+
// --- Act
2900+
// No external deltas or fees; only reservoirs should be considered.
2901+
// With price=1, the exact proportional pair uses 5 alpha and 5 tao,
2902+
// leaving tao scrap = 7 - 5 = 2, alpha scrap = 5 - 5 = 0.
2903+
Pallet::<Test>::adjust_protocol_liquidity(netuid, 0u64.into(), 0u64.into());
2904+
2905+
// --- Assert: reservoirs were READ (used in proportional calc) and then SET (updated)
2906+
assert_eq!(
2907+
ScrapReservoirTao::<Test>::get(netuid),
2908+
TaoCurrency::from(2u64)
2909+
);
2910+
assert_eq!(
2911+
ScrapReservoirAlpha::<Test>::get(netuid),
2912+
AlphaCurrency::from(0u64)
2913+
);
2914+
});
2915+
}

0 commit comments

Comments
 (0)