Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

One-Click LP: Rebalance #599

Open
wants to merge 5 commits into
base: one-click-lp-fees
Choose a base branch
from

Conversation

daryakaviani
Copy link
Contributor

Task: One-Click LP Rebalance

Description

Rebalance an LP position via the ControllerHelper's rebalanceLpInVault function. Liquidate current position, calculate new LP amounts based on new ticks, swap oSQTH and ETH to meet necessary amounts, and mint the new LP position.

Learn more about the One-Click LP API here.

@vercel
Copy link

vercel bot commented Jul 13, 2022

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated
continuouscall ✅ Ready (Inspect) Visit Preview Aug 9, 2022 at 7:35AM (UTC)

@deepthinker250 deepthinker250 changed the base branch from main to one-click-lp-fees July 13, 2022 17:52
async (vaultId: number, lowerTickInput: number, upperTickInput: number, onTxConfirmed?: () => void) => {
const vaultBefore = await getVault(vaultId)
const uniTokenId = vaultBefore?.NFTCollateralId
const position = await getPosition(uniTokenId)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
const position = await getPosition(uniTokenId)
const position = uniTokenId ? await getPosition(uniTokenId) : null

if (!controllerContract || !controllerHelperContract || !address || !position || !vaultBefore || !squeethPoolContract) return
const shortAmount = fromTokenAmount(vaultBefore.shortAmount, OSQUEETH_DECIMALS)
const debtInEth = await getDebtAmount(shortAmount)
const collateralToFlashloan = debtInEth.multipliedBy(COLLAT_RATIO)
Copy link
Contributor

Choose a reason for hiding this comment

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

This will be more than we need for the flashloan because we also have vaultBefore.collateralAmount in eth, but I guess it doesn't hurt!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

is there a disadvantage to having more than we need for the flashloan?

Copy link
Contributor

Choose a reason for hiding this comment

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

only if we take more than euler has

const upperTick = nearestUsableTick(upperTickInput, TICK_SPACE)

// Get current LP positions
const { amount0, amount1 } = await getDecreaseLiquidity(uniTokenId, position.liquidity, 0, 0, Math.floor(Date.now() / 1000 + 86400))
Copy link
Contributor

@alpinechicken alpinechicken Jul 20, 2022

Choose a reason for hiding this comment

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

we can also calculate these directly like dis:
if p < pa:
amount0, amount1 = L*(1/np.sqrt(pa)- 1/np.sqrt(pb)), 0
elif p < pb:
amount0, amount1 = L*(1/np.sqrt(p)- 1/np.sqrt(pb)), L*(np.sqrt(p)- np.sqrt(pa))
else:
amount0, amount1 = 0, L*(np.sqrt(pb)- np.sqrt(pa))


const amount0Min = new BigNumber(0)
const amount1Min = new BigNumber(0)

Copy link
Contributor

@alpinechicken alpinechicken Aug 4, 2022

Choose a reason for hiding this comment

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

We might want to change these to something close to expected values to avoid getting sammiched. Probably should have done this in tests.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, planning to use:

amount0Min = amount0 * (1 - slippage)
amount1Min = amount1 * (1 - slippage)

Does that sound good?

Copy link
Contributor

Choose a reason for hiding this comment

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

sounds excellent - slippage as input param?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep

const squeethPrice = sqrtSqueethPrice.pow(2)

// Get previous liquidity amount in ETH
const wPowerPerpAmountInLPBeforeInEth = await getQuote(new BigNumber(wPowerPerpAmountInLPBefore), true)
Copy link
Contributor

@alpinechicken alpinechicken Aug 4, 2022

Choose a reason for hiding this comment

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

it might be better to just use to spot price: wPowerPerpAmountInLPBeforeInEth = wethAmountInLPBefore * squeethPrice since we don't need to consider uniswap price impact here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do you mean:
wPowerPerpAmountInLPBeforeInEth = wPowerPerpAmountInLPBefore * squeethPrice

Copy link
Contributor

Choose a reason for hiding this comment

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

yes!

const sqrtUpperPrice = new BigNumber(TickMath.getSqrtRatioAtTick(upperTick).toString()).div(x96)
const { sqrtPriceX96 } = await getPoolState(squeethPoolContract)
const sqrtSqueethPrice = new BigNumber(sqrtPriceX96.toString()).div(x96)
const squeethPrice = sqrtSqueethPrice.pow(2)
Copy link
Contributor

Choose a reason for hiding this comment

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

Need to switch prices if weth is token0 to get all these prices in eth per oSQTH. Will work on ropsten but not on mainnet where weth is token0.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 521

// x = L(sqrt(upperPrice) - sqrt(squeethPrice))) / sqrt(squeethPrice) * sqrt(upperPrice)
// y = L(sqrt(squeethPrice) - sqrt(lowerPrice))
newAmount0 = liquidity.times(sqrtUpperPrice.minus(sqrtSqueethPrice)).div((sqrtSqueethPrice.times(sqrtUpperPrice)))
newAmount1 = liquidity.times(sqrtSqueethPrice.minus(sqrtLowerPrice))
Copy link
Contributor

Choose a reason for hiding this comment

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

If we use the convention the squeeth price is eth per 1 oSQTH then newAmount0 is in oSQTH and newAmount1 is in weth so we don't need to switch on isWethToken0

wPowerPerpAmountInLPAfter = (liquidity.div(sqrtLowerPrice).minus(liquidity.div(sqrtUpperPrice)))
amountIn = wethAmountInLPBefore
tokenIn = weth
tokenOut = oSqueeth
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 have the amountIn/tokenIn/tokenOut logic after this

uniTokenId,
position.liquidity,
fromTokenAmount(1, 18).toFixed(0),
0,
Copy link
Contributor

Choose a reason for hiding this comment

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

add in amount0Min, amount1Min

// GeneralSwap: [tokenIn, tokenOut, amountIn, limitPrice]
data: abiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint24'],
[tokenIn, tokenOut, amountIn, 0, POOL_FEE],
Copy link
Contributor

Choose a reason for hiding this comment

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

add in limit price

Copy link
Contributor

@alpinechicken alpinechicken left a comment

Choose a reason for hiding this comment

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

lgtm

Copy link
Contributor

@aleone aleone left a comment

Choose a reason for hiding this comment

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

I think this one needs a decent amount of work. Happy to sync on it.

if (!controllerContract || !controllerHelperContract || !address || !position || !vaultBefore || !squeethPoolContract) return
const shortAmount = fromTokenAmount(vaultBefore.shortAmount, OSQUEETH_DECIMALS)
const debtInEth = await getDebtAmount(shortAmount)
const collateralToFlashloan = debtInEth.multipliedBy(COLLAT_RATIO_FLASHLOAN)
Copy link
Contributor

Choose a reason for hiding this comment

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

this will work, but its often going to be too much collateral - we do a more accurate calc in the closeLP calc taking into account the eth in the vault

const positionEthValue = new BigNumber(wethAmountInLPBefore).plus(wPowerPerpAmountInLPBeforeInEth)

let amountIn, wethAmountInLPAfter, wPowerPerpAmountInLPAfter, tokenIn, tokenOut
if (sqrtUpperPrice.lt(sqrtSqueethPrice)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this might not work (and may not work for testing on goerli), because all weth pool vs all oSQTH LP depends on if weth is token1 or token0. I thought we dealt with these comments already, but I can't find it implemented correctly anywhere.

bascially the calc is the opposite if weth is token 0 vs token 1 for all of these if else statements.

it all comes down to is the price ETH per oSQTH or oSQTH per ETH

Example lets say ticks are 0.05 and 0.1 ETH/oSQTH. The alternative tick is 10 and 20 oSQTH per ETH (where 20 = 0.05 and 10 = 0.1). If current spot price is 0.025 ETH per oSQTH or 40 oSQTH per ETH (same price), that means that in one situation it is above the high tick and in one case its below the lower tick. This position would be 100% oSQTH, but to calc that we need to know what token is token0 vs token1.

Copy link
Contributor

Choose a reason for hiding this comment

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

The other alternative to doing different logic is to switch our ticks around at the end and make the f/e only provide ticks in 1 unit (ie ETH per oSQTH) vs the way the pool is set up

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it might work if we convert all the prices like

if (isWethToken0) {
const squeethPrice = new BigNumber(1).div(new BigNumber(TickMath.getSqrtRatioAtTick(Number(tick)).toString()).div(x96).pow(2))
const sqrtLowerPrice = new BigNumber(1).div(new BigNumber(TickMath.getSqrtRatioAtTick(Number(lowerTick)).toString()).div(x96).pow(2))
const sqrtUpperPrice = new BigNumber(1).div(new BigNumber(TickMath.getSqrtRatioAtTick(Number(upperTick)).toString()).div(x96).pow(2))
} else {
const squeethPrice = new BigNumber(TickMath.getSqrtRatioAtTick(Number(tick)).toString()).div(x96).pow(2)
const sqrtLowerPrice = new BigNumber(TickMath.getSqrtRatioAtTick(Number(lowerTick)).toString()).div(x96).pow(2)
const sqrtUpperPrice = new BigNumber(TickMath.getSqrtRatioAtTick(Number(upperTick)).toString()).div(x96).pow(2)
}

// Get amount mins
const amount0New = isWethToken0 ? wethAmountInLPAfter : wPowerPerpAmountInLPAfter
const amount1New = isWethToken0 ? wPowerPerpAmountInLPAfter : wethAmountInLPAfter
const amount0MinNew = amount0New.times(new BigNumber(1).minus(slippage)).toFixed(0)
Copy link
Contributor

Choose a reason for hiding this comment

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

should have its own slippage param for LPing

const amount1MinNew = amount1New.times(new BigNumber(1).minus(slippage)).toFixed(0)

// Get limit price
const amountOut = await getExactIn(new BigNumber(amountIn), tokenIn == oSqueeth)
Copy link
Contributor

Choose a reason for hiding this comment

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

this isn't right. when we have too much oSQTH, we are doing exact in of oSQTH, when we have too much weth, we are doing an exact in of weth.

// GeneralSwap: [tokenIn, tokenOut, amountIn, limitPrice]
data: abiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint24'],
[tokenIn, tokenOut, amountIn, fromTokenAmount(limitPrice, 18).toFixed(0), POOL_FEE],
Copy link
Contributor

Choose a reason for hiding this comment

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

pool fee right now can only be 0.3% based on how we use the quoter to figure out expected amts of swap

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.

4 participants