Skip to content

Conversation

@gztensor
Copy link
Contributor

Description

Implement PalSwap (similar to balancer swap) to replace Uniswap V3.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Other (please describe):

Breaking Change

This PR changes the following externally accessible entities:

  • add_liqudiity extrinsic: Remove tick_low and tick_high parameters.
  • Positions map is replaced with PositionsV2 map
  • Following state maps removed:
    • AlphaSqrtPrice
    • CurrentLiquidity
    • CurrentTick
    • FeeGlobalTao
    • FeeGlobalAlpha
    • ScrapReservoirTao (applied before removing)
    • ScrapReservoirAlpha (applied before removing)
    • Ticks
    • SwapV3Initialized
    • TickIndexBitmapWords

Checklist

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have run ./scripts/fix_rust.sh to ensure my code is formatted and linted correctly
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adjust_protocol_liquidity returns the actual adjustment that needs to be made to reserves, which includes previously collected tao and alpha fees.

When tao fees are charged, the tao is coming from user balance to FeesTao map, not accounted for in SubnetTAO, so it needs to be added here.

When alpha fees are charged, the alpha is coming from user's stake, and is also not accounted for in SubnetAlphaIn.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still an approximation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No formula for this exists, but we could do newtons method. We have an implementation if you want it.

@gztensor see discord.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continue removing storage for provided reserves here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yay.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Positions are unused atm, but I preserved the struct for later use, when we have user liquidity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is part of uniswap v3 logic. Not needed anymore.

@gztensor gztensor added the skip-cargo-audit This PR fails cargo audit but needs to be merged anyway label Jan 9, 2026
Copy link
Contributor

@0xcacti 0xcacti left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will provide one more review, looking at pallets/swap/src/pallet/swap_step.rs and also pallets/swap/src/pallet/impls.rs

it got late and we don't want to lose our progress.

Perquintill::from_rational(1u128, 2u128)
};

self.set_quote_weight(new_reserve_weight)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like you forgot to set the base weight here? we only set the quote weight.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how could we have possibly gotten through testing? are we missing something?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nevermind, base is always looked up as 1 - quote

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the only weight that is stored and needs to be set is quote. Base weight is always calculated from quote weight.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should just delete this whole function in which we have changes. The function isn't used anywhere other than in a test, and the logic for emissions is wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree. I did it in a separate PR because it is not related to balancer:
#2346

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love cleaning.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good point. correc.t

Self::add_liquidity_at_index(netuid, position.tick_low, liquidity_delta, false);
Self::add_liquidity_at_index(netuid, position.tick_high, liquidity_delta, true);
}
) -> (TaoCurrency, AlphaCurrency) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is high level correct.

of course, you know that I think it's really weird the way that the reserves are in another pallet so we update them in the calling context rather than here, but I suppose my view there doesn't really matter.

As is, it's correct.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't entirely understand this file

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hooks in substrate execute without user interaction on certain events. In this case, I am using on_runtime_upgrade to run the migration. It is executed in the beginning of the block following after the runtime upgrade.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is so much nicer.

/// Constraints limit balancer weights within certain range of values:
/// - Both weights are above minimum
/// - Sum of weights is equal to 1.0
fn check_constraints(quote: Perquintill) -> bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are working on inventing a more advanced theory here for when we reach the MIN_WEIGHT what we should do. All good for now, but these constraints may change in the future.

Comment on lines +206 to +212
pub fn update_weights_for_added_liquidity(
&mut self,
tao_reserve: u64,
alpha_reserve: u64,
tao_delta: u64,
alpha_delta: u64,
) -> Result<(), BalancerError> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to make sure that here we are passing in the actual_delta,
I.e. the quantity injected + fees

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked and it is because it's only called by adjust_protocol_liqudity which handles this.

/// y - tao reserve
/// w1 - base weight
/// w2 - quote weight
pub fn calculate_current_liquidity(&self, tao_reserve: u64, alpha_reserve: u64) -> u64 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we even need this function for anything? could probably get rid of it?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess if we want user liquidity at some point in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I am leaving a few unused things behind. In case we decide to have user liquidity, they will help us have much smoother transition and less or no migrations:

  • Positions
  • Current liquidity
  • Tests for user liquidity (all commented out)


let epsilon = U64F64::saturating_from_num(0.000000000001);
// Create balancer based on price
let balancer = Balancer::new(if let Some(price) = maybe_price {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice handles existing subnets just in case. excellent.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just to be clear, when we pass in maybe_price

is it the case that new subnets won't have a price at the time of initialization?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New subnets get their price right here before the first swap. This price is based on whatever reserves were assigned at creation and 0.5 weights.

let balancer = Balancer::new(if let Some(price) = maybe_price {
// Price is given, calculate weights:
// w_quote = y / (px + y)
let px_high = (price.saturating_to_num::<u64>() as u128)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have a test just to make sure this is correct. I mean it makes sense, mult the integer part and the reserve then the fractional part and the reserve and do some fun type casting to get to the end type, but just want to be sure because it's nontrivial.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a test for this that involves both integer and fractional part of price:

// Test that subnet price is still 1.23^2

Explanation: Converting U64F64 to u64 is the same as rounding to the floor integer. I am doing this to avoid both overflow and precision loss of u64 by U64F64 multiplication. This is very hypothetical, but if alpha_reserve is 21M and the price is 1000., it overflows U64F64.

);

Self::maybe_initialize_v3(netuid)?;
Self::maybe_initialize_palswap(netuid, None)?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Man should we just pass in a price of None here? what if it's a subnet with a price?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it's a subnet with a price, it would be handled in the migration right after the balancer upgrade and maybe_initialize_palswap will be a noop in this case. Here it is a non-noop only for new subnets.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

skip-cargo-audit This PR fails cargo audit but needs to be merged anyway

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants