Assessment reports>Aqua Pool>Medium findings>Invariant may sometimes be calculated incorrectly
Category: Coding Mistakes

Invariant may sometimes be calculated incorrectly

Medium Severity
Medium Impact
Medium Likelihood

Description

The _tweakPrice function adjusts the priceOracle, lastPrices and priceScale variables, used in the calculation of the prices within the pool. This function is an essential component of the pool, and is called whenever there is an inbalance during any of the liquidity operations, such as swap, mint or burn.

In _tweakPrice, the Vyper code calculates D_unadjusted:

# ------------------ If new_D is set to 0, calculate it ------------------

D_unadjusted: uint256 = new_D
if new_D == 0:  #  <--------------------------- _exchange sets new_D to 0.
    D_unadjusted = MATH.newton_D(A_gamma[0], A_gamma[1], _xp, K0_prev)

The corresponding Solidity code-names this value newInvariant:

//  ---------------- If `newInvariant` is 0, calculate it -----------------

if (newInvariant == 0) {
    newInvariant = MATH.computeD(a, gamma, xp0, xp1, _k0_prev);
}

The invariant that this pool uses is essentially somewhere between the constant-product invariant(like in Uniswap, Balancer, etc.) and the StableSwap(which is essentially Curve v1).

Further down the function, Vyper calculates a value and stores to a new variable D this time (not D_unadjusted):

# ------------------------------------------ Update D with new xp.
D: uint256 = MATH.newton_D(A_gamma[0], A_gamma[1], xp, 0)

However, Solidity overwrites newInvariant (which was D_unadjusted) with this value instead of defining a new variable too:

// ------------------------------------------ Update D with new xp.

// Calculate "extended constant product" invariant xCP and virtual price.
newInvariant = MATH.computeD(a, gamma, _xp0, _xp1, 0);

Finally, at the end of the function, Vyper stores D_unadjusted to self.D. Solidity stores newInvariant (which is not D_unadjusted but D in Vyper) to invariantLast (i.e., the equivalent of self.D in Vyper).

Impact

For any cases in the Vyper code where D != D_unadjusted at the end of the function, the Solidity code will store the wrong value. Per SyncSwap Labs,

This can cause wrong value to be updated to invariantLast/D if the pool didn't got enough profit to rebalance.

Recommendations

Use an ephemeral variable when determining whether or not to rebalance profits.

Remediation

This issue has been acknowledged by SyncSwap Labs, and a fix was implemented in commit ff28d01a.

Zellic © 2024Back to top ↑