-
Notifications
You must be signed in to change notification settings - Fork 70
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
base: one-click-lp-fees
Are you sure you want to change the base?
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
async (vaultId: number, lowerTickInput: number, upperTickInput: number, onTxConfirmed?: () => void) => { | ||
const vaultBefore = await getVault(vaultId) | ||
const uniTokenId = vaultBefore?.NFTCollateralId | ||
const position = await getPosition(uniTokenId) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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) |
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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) | ||
|
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add in limit price
d73e710
to
b9e7ad0
Compare
c968f14
to
ad0a32f
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
There was a problem hiding this 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) |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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], |
There was a problem hiding this comment.
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
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.