Skip to content

Rust reCLAMM pricing#153

Merged
johngrantuk merged 6 commits intomainfrom
reclamm-pricing
Nov 14, 2025
Merged

Rust reCLAMM pricing#153
johngrantuk merged 6 commits intomainfrom
reclamm-pricing

Conversation

@johngrantuk
Copy link
Member

Add reCLAMM pricing helpers based off maths pasted below.

Two Test Types:

A. Price Accuracy Test
Validates that the swap actually achieves the target price:

  • Call swap_reclamm_to_price() with target price
  • Apply the calculated swap to pool balances
  • Calculate new price from updated balances
  • Assert: new price equals target price (within 0.01% tolerance)

B. Swap Math Validation Test
Validates that amounts match existing ReCLAMM swap math:

  • Call swap_reclamm_to_price() to get amount_in, amount_out and token indices
  • Call existing vault.swap with same pool state and amount_in
  • Assert: amount_out from existing function matches swap_reclamm_to_price result

Original Maths

function swapReclammToPrice(props: {
  balancesRaw: number[],
  balancesScaled18: number[],
  virtualBalances: number[],
  swapFeePercentage: number,
  protocolFeePercentage: number,
  poolCreatorFeePercentage: number,
  decimalsA: number,
  decimalsB: number,
  targetPrice: number
}): {tokenIn: number, tokenOut: number, amountIn: number, amountOut: number} {
  // Assuming we receive the raw values from the vault, we need to convert all scaled numbers to float.
  const balanceA = props.balancesScaled18[0] / 1e18;
  const balanceB = props.balancesScaled18[1] / 1e18;
  const virtualA = props.virtualBalances[0] / 1e18;
  const virtualB = props.virtualBalances[1] / 1e18;
  const swapFee = props.swapFeePercentage / 1e18;
  const protocolFee = props.protocolFeePercentage / 1e18;
  const poolCreatorFee = props.poolCreatorFeePercentage / 1e18;

  // rates are computed as float.
  const rateA = props.balancesScaled18[0]/(Math.pow(10, 18-props.decimalsA)*props.balancesRaw[0]);
  const rateB = props.balancesScaled18[1]/(Math.pow(10, 18-props.decimalsB)*props.balancesRaw[1]);

  // invariant and currentPrice are float.
  const invariant = (balanceA + virtualA)*(balanceB + virtualB);
  const currentPrice = (balanceB + virtualB)/(balanceA + virtualA);

  // Assuming that the received target price is a float, and is in terms of the raw balances (e.g. if the pool is 
  // wstETH/USDC, and wstETH has a rate provider to ETH, we assume that the price given is wstETH/USDC, 
  // and the targetPriceScaled will be ETH/USDC).
  const targetPriceScaled = props.targetPrice * rateA/rateB;

  // To compute the balances of the pool in the target price, we need to consider which fees will be paid to the pool,
  // which affect the final price.
  const amountInFees = (swapFee - protocolFee)*(1-poolCreatorFees)
   
  if (priceTargetScaled > currentPrice) {
    // tokenB in, tokenA out
     
	// invariant = (Ra + Va)(Rb + Vb), so (Rb + Vb) = invariant/(Ra + Va).
    // Since poolPrice = (Rb + Vb)/(Ra + Va), replacing (Rb + Vb) we have poolPrice = invariant/((Ra + Va)^2)
    // If amount out (Ao) is in terms of A, we have that:
    // (Ra - Ao + Va) = sqrt(invariant/poolPrice)
    // Isolating Ao, we have the formula below
    const amountOutScaled = balanceA + virtualA - Math.sqrt(invariant/targetPriceScaled);

    // In the invariant formula, replacing Ra by `Ra - Ao`, and Rb by `Rb + Ai*(1 + aiFees)`, we have the formula below
    const amountInScaled = ((invariant / (balanceA - amountOutScaled + virtualA)) - balanceB - virtualB)/(1 + amountInFees);
   
    return {
      tokenIn: 1,
      tokenOut: 0,
      amountIn: Math.ceil(amountInScaled*Math.pow(10, props.decimalsA)/rateA),
      amountOut: Math.floor(amountOutScaled*Math.pow(10, props.decimalsB)/rateB)
    }

  } else {
    // tokenA in, tokenB out
     
    const amountOutScaled = balanceB + virtualB - Math.sqrt(invariant * targetPriceScaled);
    const amountInScaled = ((invariant / (balanceB - amountOutScaled + virtualB)) - balanceA - virtualA)/(1 + amountInFees);
   
    return {
      tokenIn: 0,
      tokenOut: 1,
      amountIn: Math.ceil(amountInScaled*Math.pow(10, props.decimalsB)/rateB),
      amountOut: Math.floor(amountOutScaled*Math.pow(10, props.decimalsA)/rateA)
    }
  }
}

@johngrantuk

This comment was marked as resolved.

@johngrantuk johngrantuk marked this pull request as ready for review November 13, 2025 11:44
@johngrantuk johngrantuk changed the title Initial Rust reCLAMM pricing. WIP as has failing tests. Rust reCLAMM pricing Nov 13, 2025
Copy link
Member

@brunoguerios brunoguerios left a comment

Choose a reason for hiding this comment

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

LGTM ✅

@johngrantuk johngrantuk merged commit fa9520b into main Nov 14, 2025
3 checks passed
@johngrantuk johngrantuk deleted the reclamm-pricing branch November 14, 2025 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants