since the withdraw function reverts anyway due to the CPI issue, the test is done with a user account attempting to call the updateRewards function on a 0 liquidity position using whirlpool SDK.
the modified contents are from 240
-
-
Save 10xhash/a687ef66de8210444a41360b86ed4bca to your computer and use it in GitHub Desktop.
2023-12-autonolas-withdraw_rewards_when_0_liquidity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as anchor from "@coral-xyz/anchor"; | |
import { Program } from "@coral-xyz/anchor"; | |
import { LiquidityLockbox } from "../target/types/liquidity_lockbox"; | |
import { TestPosition } from "../target/types/test_position"; | |
import { createMint, mintTo, transfer, getOrCreateAssociatedTokenAccount, unpackAccount, TOKEN_PROGRAM_ID } from "@solana/spl-token"; | |
import { | |
WhirlpoolContext, buildWhirlpoolClient, ORCA_WHIRLPOOL_PROGRAM_ID, | |
PDAUtil, PoolUtil, PriceMath, increaseLiquidityQuoteByInputTokenWithParams, | |
decreaseLiquidityQuoteByLiquidityWithParams, toTx, WhirlpoolIx, collectFeesQuote | |
} from "@orca-so/whirlpools-sdk"; | |
import { DecimalUtil, Percentage, SimpleAccountFetcher } from "@orca-so/common-sdk"; | |
import Decimal from "decimal.js"; | |
import expect from "expect"; | |
describe("Liquidity Lockbox", () => { | |
// Configure the client to use the local cluster. | |
const provider = anchor.AnchorProvider.env(); | |
anchor.setProvider(provider); | |
// @ts-ignore | |
console.log("Provider wallet:", provider.wallet.payer.publicKey.toBase58()); | |
const program = anchor.workspace.LiquidityLockbox as Program<LiquidityLockbox>; | |
const positionProgram = anchor.workspace.TestPosition as Program<TestPosition>; | |
const orca = new anchor.web3.PublicKey("whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"); | |
const whirlpool = new anchor.web3.PublicKey("7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm"); | |
const sol = new anchor.web3.PublicKey("So11111111111111111111111111111111111111112"); | |
const usdc = new anchor.web3.PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); | |
const tokenVaultA = new anchor.web3.PublicKey("9RfZwn2Prux6QesG1Noo4HzMEBv3rPndJ2bN2Wwd6a7p"); | |
const tokenVaultB = new anchor.web3.PublicKey("BVNo8ftg2LkkssnWT4ZWdtoFaevnfD6ExYeramwM27pe"); | |
const tickArrayLower = new anchor.web3.PublicKey("DJBLVHo3uTQBYpSHbVdDq8LoRsSiYV9EVhDUguXszvCi"); | |
const tickArrayUpper = new anchor.web3.PublicKey("ZPyVkTuj9TBr1ER4Fnubyz1w7bm5LsXctLiZb8Fs2Do"); | |
it("Adding liquidity to the Lockbox in exchange of tokens and decreasing it when getting tokens back", async () => { | |
// User wallet is the provider payer | |
// @ts-ignore | |
const userWallet = provider.wallet.payer; | |
console.log("User wallet:", userWallet.publicKey.toBase58()); | |
const ctx = WhirlpoolContext.withProvider(provider, orca); | |
const client = buildWhirlpoolClient(ctx); | |
const whirlpoolClient = await client.getPool(whirlpool); | |
// Get the current price of the pool | |
const sqrt_price_x64 = whirlpoolClient.getData().sqrtPrice; | |
const price = PriceMath.sqrtPriceX64ToPrice(sqrt_price_x64, 9, 6); | |
console.log("price:", price.toFixed(6)); | |
// Set price range, amount of tokens to deposit, and acceptable slippage | |
const usdc_amount = DecimalUtil.toBN(new Decimal("10" /* usdc */), 6); | |
const slippage = Percentage.fromFraction(10, 1000); // 1% | |
// Full range price | |
const lower_tick_index = -443632; | |
const upper_tick_index = 443632; | |
// Adjust price range (not all prices can be set, only a limited number of prices are available for range specification) | |
// (prices corresponding to InitializableTickIndex are available) | |
const whirlpool_data = whirlpoolClient.getData(); | |
const token_a = whirlpoolClient.getTokenAInfo(); | |
const token_b = whirlpoolClient.getTokenBInfo(); | |
console.log("lower & upper tick_index:", lower_tick_index, upper_tick_index); | |
console.log("lower & upper price:", | |
PriceMath.tickIndexToPrice(lower_tick_index, token_a.decimals, token_b.decimals).toFixed(token_b.decimals), | |
PriceMath.tickIndexToPrice(upper_tick_index, token_a.decimals, token_b.decimals).toFixed(token_b.decimals) | |
); | |
// Obtain deposit estimation | |
let quote = increaseLiquidityQuoteByInputTokenWithParams({ | |
// Pass the pool definition and state | |
tokenMintA: token_a.mint, | |
tokenMintB: token_b.mint, | |
sqrtPrice: whirlpool_data.sqrtPrice, | |
tickCurrentIndex: whirlpool_data.tickCurrentIndex, | |
// Price range | |
tickLowerIndex: lower_tick_index, | |
tickUpperIndex: upper_tick_index, | |
// Input token and amount | |
inputTokenMint: usdc, | |
inputTokenAmount: usdc_amount, | |
// Acceptable slippage | |
slippageTolerance: slippage, | |
}); | |
// Output the estimation | |
console.log("SOL max input:", DecimalUtil.fromBN(quote.tokenMaxA, token_a.decimals).toFixed(token_a.decimals)); | |
console.log("USDC max input:", DecimalUtil.fromBN(quote.tokenMaxB, token_b.decimals).toFixed(token_b.decimals)); | |
// Create a transaction | |
// Use openPosition method instead of openPositionWithMetadata method | |
const open_position_tx = await whirlpoolClient.openPosition( | |
lower_tick_index, | |
upper_tick_index, | |
quote | |
); | |
// Send the transaction to open a position | |
let signature = await open_position_tx.tx.buildAndExecute(); | |
console.log("signature:", signature); | |
console.log("position NFT:", open_position_tx.positionMint.toBase58()); | |
const positionMint = open_position_tx.positionMint; | |
// Wait for the transaction to complete | |
let latest_blockhash = await ctx.connection.getLatestBlockhash(); | |
await ctx.connection.confirmTransaction({ signature, ...latest_blockhash }, "confirmed"); | |
// Find a PDA account for the program | |
const [pdaProgram, bump] = await anchor.web3.PublicKey.findProgramAddress([Buffer.from("pdaProgram", "utf-8")], program.programId); | |
const bumpBytes = Buffer.from(new Uint8Array([bump])); | |
console.log("Program PDA:", pdaProgram.toBase58()); | |
// Create new bridged token mint with the pda mint authority | |
const bridgedTokenMint = await createMint(provider.connection, userWallet, pdaProgram, null, 9); | |
console.log("Bridged token mint:", bridgedTokenMint.toBase58()); | |
// Get the ATA of the userWallet address, and if it does not exist, create it | |
// This account will have bridged tokens | |
const pdaBridgedTokenAccount = await getOrCreateAssociatedTokenAccount( | |
provider.connection, | |
userWallet, | |
bridgedTokenMint, | |
pdaProgram, | |
true // allowOwnerOfCurve - allow pda accounts to be have associated token account | |
); | |
console.log("PDA ATA for bridged token:", pdaBridgedTokenAccount.address.toBase58()); | |
// Deploy the LiquidityLockbox program | |
// @ts-ignore | |
signature = await program.methods.new(whirlpool, bridgedTokenMint, pdaBridgedTokenAccount.address, bumpBytes) | |
.accounts({ dataAccount: pdaProgram }) | |
.rpc(); | |
//console.log("Your transaction signature", signature); | |
// Wait for program creation confirmation | |
await provider.connection.confirmTransaction({ | |
signature: signature, | |
...(await provider.connection.getLatestBlockhash()), | |
}); | |
// Get all token accounts | |
const token_accounts = (await ctx.connection.getTokenAccountsByOwner(ctx.wallet.publicKey, { programId: TOKEN_PROGRAM_ID })).value; | |
let parsed; | |
let position; | |
for (let i = 0; i < token_accounts.length; i++) { | |
const ta = token_accounts[i]; | |
parsed = unpackAccount(ta.pubkey, ta.account); | |
if (parsed.amount.toString() === "1") { | |
position = PDAUtil.getPosition(ctx.program.programId, parsed.mint); | |
break; | |
} | |
} | |
// NFT position mint | |
let accountInfo = await provider.connection.getAccountInfo(positionMint); | |
//console.log(accountInfo); | |
// Get the ATA of the userWallet address, and if it does not exist, create it | |
// This account has an NFT token | |
const userPositionAccount = parsed.address; | |
console.log("User ATA for NFT:", userPositionAccount.toBase58()); | |
// Get the ATA of the userWallet address, and if it does not exist, create it | |
// This account will have bridged tokens | |
const userBridgedTokenAccount = await getOrCreateAssociatedTokenAccount( | |
provider.connection, | |
userWallet, | |
bridgedTokenMint, | |
userWallet.publicKey | |
); | |
console.log("User ATA for bridged:", userBridgedTokenAccount.address.toBase58()); | |
// accountInfo = await provider.connection.getAccountInfo(userPositionAccount); | |
// console.log(accountInfo); | |
let balance = await program.methods.getBalance() | |
.accounts({ account: userPositionAccount }) | |
.view(); | |
console.log("User ATA must have one NFT, balance:", balance.toNumber()); | |
// ATA for the PDA to store the position NFT | |
const pdaPositionAccount = await getOrCreateAssociatedTokenAccount( | |
provider.connection, | |
userWallet, | |
positionMint, | |
pdaProgram, | |
true // allowOwnerOfCurve - allow pda accounts to be have associated token account | |
); | |
console.log("PDA ATA", pdaPositionAccount.address.toBase58()); | |
// Get the tokenA ATA of the userWallet address, and if it does not exist, create it | |
// This account will have bridged tokens | |
const userTokenAccountA = await getOrCreateAssociatedTokenAccount( | |
provider.connection, | |
userWallet, | |
token_a.mint, | |
userWallet.publicKey | |
); | |
console.log("User ATA for tokenA:", userTokenAccountA.address.toBase58()); | |
// Get the tokenA ATA of the userWallet address, and if it does not exist, create it | |
// This account will have bridged tokens | |
const userTokenAccountB = await getOrCreateAssociatedTokenAccount( | |
provider.connection, | |
userWallet, | |
token_b.mint, | |
userWallet.publicKey | |
); | |
console.log("User ATA for tokenB:", userTokenAccountB.address.toBase58()); | |
// Get the status of the position | |
const positionSDK = await client.getPosition(position.publicKey); | |
const data = positionSDK.getData(); | |
// Get the price range of the position | |
const lower_price = PriceMath.tickIndexToPrice(data.tickLowerIndex, token_a.decimals, token_b.decimals); | |
const upper_price = PriceMath.tickIndexToPrice(data.tickUpperIndex, token_a.decimals, token_b.decimals); | |
// Calculate the amount of tokens that can be withdrawn from the position | |
const amounts = PoolUtil.getTokenAmountsFromLiquidity( | |
data.liquidity, | |
whirlpoolClient.getData().sqrtPrice, | |
PriceMath.tickIndexToSqrtPriceX64(data.tickLowerIndex), | |
PriceMath.tickIndexToSqrtPriceX64(data.tickUpperIndex), | |
true | |
); | |
// Output the status of the position | |
console.log("position:", position.publicKey.toBase58()); | |
console.log("\twhirlpool address:", data.whirlpool.toBase58()); | |
console.log("\ttokenA:", token_a.mint.toBase58()); | |
console.log("\ttokenB:", token_b.mint.toBase58()); | |
console.log("\tliquidity:", data.liquidity.toNumber()); | |
console.log("\tlower:", data.tickLowerIndex, lower_price.toFixed(token_b.decimals)); | |
console.log("\tupper:", data.tickUpperIndex, upper_price.toFixed(token_b.decimals)); | |
console.log("\tamountA:", DecimalUtil.fromBN(amounts.tokenA, token_a.decimals).toString()); | |
console.log("\tamountB:", DecimalUtil.fromBN(amounts.tokenB, token_b.decimals).toString()); | |
// Test updateFeesAndRewards with the SDK when totalLiquidity is 0 | |
const decQuote = decreaseLiquidityQuoteByLiquidityWithParams({ | |
// Pass the pool state as is | |
sqrtPrice: whirlpool_data.sqrtPrice, | |
tickCurrentIndex: whirlpool_data.tickCurrentIndex, | |
// Pass the price range of the position as is | |
tickLowerIndex: data.tickLowerIndex, | |
tickUpperIndex: data.tickUpperIndex, | |
// Liquidity to be withdrawn. Remove the entire liquidity | |
liquidity: data.liquidity, | |
// Acceptable slippage | |
slippageTolerance: slippage, | |
}); | |
console.log("decQuote", decQuote); | |
// Create a transaction | |
const decrease_liquidity_tx = await positionSDK.decreaseLiquidity(decQuote); | |
// Overwrite the tokenA ATA as it is the only difference | |
decrease_liquidity_tx.instructions[2].instructions[0].keys[5].pubkey = userTokenAccountA.address; | |
console.log(decrease_liquidity_tx.instructions[2].instructions); | |
console.log(decrease_liquidity_tx.instructions[2].instructions[0].keys); | |
// Send the transaction | |
signature = await decrease_liquidity_tx.buildAndExecute(); | |
console.log("signature:", signature); | |
// Wait for the transaction to complete | |
latest_blockhash = await ctx.connection.getLatestBlockhash(); | |
await ctx.connection.confirmTransaction({ signature, ...latest_blockhash }, "confirmed"); | |
// liquidity is 0 | |
const liquidityAfter = (await positionSDK.refreshData()).liquidity.toNumber(); | |
expect(liquidityAfter).toBe(0); | |
await toTx(ctx, WhirlpoolIx.updateFeesAndRewardsIx(ctx.program, { | |
whirlpool: whirlpool, | |
position: positionSDK.getAddress(), | |
tickArrayLower, | |
tickArrayUpper, | |
})).buildAndExecute(); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment