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
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions packages/frontend/src/state/lp/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,164 @@ export const useCollectFees = () => {
return collectFees
}

// Rebalance via general swap
export const useRebalanceGeneralSwap = () => {
const address = useAtomValue(addressAtom)
const controllerHelperContract = useAtomValue(controllerHelperHelperContractAtom)
const { controllerHelper, weth, oSqueeth, squeethPool } = useAtomValue(addressesAtom)
const controllerContract = useAtomValue(controllerContractAtom)
const handleTransaction = useHandleTransaction()
const isWethToken0 = useAtomValue(isWethToken0Atom)
const getDebtAmount = useGetDebtAmount()
const getVault = useGetVault()
const getDecreaseLiquidity = useGetDecreaseLiquidity()
const getPosition = useGetPosition()
const squeethPoolContract = useAtomValue(squeethPoolContractAtom)
const getExactIn = useGetExactIn()
const rebalanceGeneralSwap = useAppCallback(
async (vaultId: number, lowerTickInput: number, upperTickInput: number, slippage: number, onTxConfirmed?: () => void) => {
const vaultBefore = await getVault(vaultId)
const uniTokenId = vaultBefore?.NFTCollateralId
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_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


// 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 amount0MinOld = new BigNumber(amount0).times(new BigNumber(1).minus(slippage)).toFixed(0)
const amount1MinOld = new BigNumber(amount1).times(new BigNumber(1).minus(slippage)).toFixed(0)
const wPowerPerpAmountInLPBefore = isWethToken0 ? amount1 : amount0
const wethAmountInLPBefore = isWethToken0 ? amount0 : amount1

// Calculate prices from ticks
const { tick, tickSpacing } = await getPoolState(squeethPoolContract)
const lowerTick = nearestUsableTick(lowerTickInput, Number(tickSpacing))
const upperTick = nearestUsableTick(upperTickInput, Number(tickSpacing))
const sqrtLowerPrice = new BigNumber(TickMath.getSqrtRatioAtTick(lowerTick).toString()).div(x96)
const sqrtUpperPrice = new BigNumber(TickMath.getSqrtRatioAtTick(upperTick).toString()).div(x96)
const squeethPrice = isWethToken0 ? new BigNumber(1).div(new BigNumber(TickMath.getSqrtRatioAtTick(Number(tick)).toString()).div(x96).pow(2))
: new BigNumber(TickMath.getSqrtRatioAtTick(Number(tick)).toString()).div(x96).pow(2)
const sqrtSqueethPrice = squeethPrice.sqrt()

// Get previous liquidity amount in ETH
const wPowerPerpAmountInLPBeforeInEth = new BigNumber(wPowerPerpAmountInLPBefore).times(squeethPrice)
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)
}

// All weth position
// newLiquidity = positionEthValue/(sqrt(upperPrice) - sqrt(lowerPrice))
const liquidity = positionEthValue.div(sqrtUpperPrice.minus(sqrtLowerPrice))
// wethAmount = L(sqrt(upperPrice) - sqrt(lowerPrice))
wethAmountInLPAfter = liquidity.times(sqrtUpperPrice.minus(sqrtLowerPrice))
wPowerPerpAmountInLPAfter = new BigNumber(0)
amountIn = wPowerPerpAmountInLPBefore
tokenIn = oSqueeth
tokenOut = weth
} else if (sqrtSqueethPrice.lt(sqrtLowerPrice)) {
// All squeeth position
// newLiquidity = positionEthValue/(squeethPrice/sqrt(lowerPrice) - squeethPrice/sqrt(upperPrice))
const liquidity = positionEthValue.div((squeethPrice.div(sqrtLowerPrice)).minus((squeethPrice.div(sqrtUpperPrice))))
wethAmountInLPAfter = new BigNumber(0)
// wPowerPerpAmount = L/sqrt(lowerPrice) - L/sqrt(upperPrice)
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

} else {
// newLiquidity = positionEthValue/(squeethPrice/sqrt(squeethPrice) - squeethPrice/sqrt(upperPrice) + sqrt(squeethPrice) - sqrt(lowerPrice))
const liquidity = positionEthValue.div((new BigNumber(squeethPrice).div(sqrtSqueethPrice))
.minus((new BigNumber(squeethPrice).div(sqrtUpperPrice)))
.plus(sqrtSqueethPrice)
.minus(sqrtLowerPrice))
// Calculate amounts of each asset to LP
// x = L(sqrt(upperPrice) - sqrt(squeethPrice))) / sqrt(squeethPrice) * sqrt(upperPrice)
// y = L(sqrt(squeethPrice) - sqrt(lowerPrice))
wPowerPerpAmountInLPAfter = liquidity.times(sqrtUpperPrice.minus(sqrtSqueethPrice)).div((sqrtSqueethPrice.times(sqrtUpperPrice)))
wethAmountInLPAfter = liquidity.times(sqrtSqueethPrice.minus(sqrtLowerPrice))
const needMoreWeth = new BigNumber(wethAmountInLPBefore).lt(new BigNumber(wethAmountInLPAfter))
const needMoreSqueeth = new BigNumber(wPowerPerpAmountInLPBefore).lt(new BigNumber(wPowerPerpAmountInLPAfter))
tokenIn = needMoreWeth ? oSqueeth : weth
tokenOut = needMoreWeth ? weth : oSqueeth
amountIn = needMoreWeth && !needMoreSqueeth ? new BigNumber(wPowerPerpAmountInLPBefore).minus(new BigNumber(wPowerPerpAmountInLPAfter)).toFixed(0) :
needMoreSqueeth && !needMoreWeth ? new BigNumber(wethAmountInLPBefore).minus(new BigNumber(wethAmountInLPAfter)).toFixed(0)
: 0
}

// 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.

const limitPrice = new BigNumber(amountOut).div(new BigNumber(amountIn)).times(new BigNumber(1).minus(slippage))

const abiCoder = new ethers.utils.AbiCoder()
const liquidatePreviousLP = {
// Liquidate LP
rebalanceLpInVaultType: new BigNumber(1).toFixed(0), // DecreaseLpLiquidity:
// DecreaseLpLiquidityParams: [tokenId, liquidity, liquidityPercentage, amount0Min, amount1Min]
data: abiCoder.encode(
['uint256', 'uint256', 'uint256', 'uint128', 'uint128'],
[
uniTokenId,
position.liquidity,
fromTokenAmount(1, 18).toFixed(0),
amount0MinOld,
amount1MinOld,
],
),
}
const generalSwap = {
// Exchange necessary amount of oSQTH and ETH
rebalanceLpInVaultType: new BigNumber(5).toFixed(0), // GeneralSwap:
// 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

),
}
const mintNewLP = {
// Mint new LP
rebalanceLpInVaultType: new BigNumber(4).toFixed(0), // MintNewLP
// lpWPowerPerpPool: [recipient, wPowerPerpPool, vaultId, wPowerPerpAmount, collateralToDeposit, collateralToLP, amount0Min, amount1Min, lowerTick, upperTick ]
data: abiCoder.encode(
['address', 'address', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'uint256', 'int24', 'int24'],
[
controllerHelper,
squeethPool,
vaultId,
wPowerPerpAmountInLPAfter.toFixed(0),
0,
wethAmountInLPAfter.toFixed(0),
amount0MinNew,
amount1MinNew,
lowerTick,
upperTick,
],
),
}

const rebalanceLpInVaultParams = amountIn > 0 ? [liquidatePreviousLP, generalSwap, mintNewLP] : [liquidatePreviousLP, mintNewLP]

return handleTransaction(
await controllerHelperContract.methods
.rebalanceLpInVault(vaultId, collateralToFlashloan.toFixed(0), rebalanceLpInVaultParams)
.send({
from: address,
}),
onTxConfirmed,
)
},
[address, controllerHelperContract, controllerHelper, weth, oSqueeth, squeethPool, controllerContract, handleTransaction, isWethToken0, getDebtAmount, getVault, getDecreaseLiquidity, getPosition, squeethPoolContract, getExactIn],
)
return rebalanceGeneralSwap
}

/*** GETTERS ***/

export const useGetPosition = () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/frontend/src/state/positions/atoms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,4 +95,4 @@ export const vaultHistoryUpdatingAtom = atom(false)
export const isToHidePnLAtom = atom(false)
export const swapsAtom = atom<swaps | swapsRopsten>({ swaps: [] })
export const longPositionValueAtom = atom(BIG_ZERO)
export const shortPositionValueAtom = atom(BIG_ZERO)
export const shortPositionValueAtom = atom(BIG_ZERO)