Skip to content

Commit

Permalink
Reduced close position transaction builder to a single transaction (#128
Browse files Browse the repository at this point in the history
)

* New approach

* Small tweaks
  • Loading branch information
wjthieme authored Jan 8, 2024
1 parent 3206c9c commit fc7dac3
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 19 deletions.
4 changes: 2 additions & 2 deletions sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
{
"name": "@orca-so/whirlpools-sdk",
"version": "0.11.8",
"version": "0.11.9",
"description": "Typescript SDK to interact with Orca's Whirlpool program.",
"license": "Apache-2.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"dependencies": {
"@coral-xyz/anchor": "~0.27.0",
"@orca-so/common-sdk": "^0.3.3",
"@orca-so/common-sdk": "^0.3.6",
"@solana/spl-token": "^0.3.8",
"@solana/web3.js": "^1.75.0",
"decimal.js": "^10.3.1",
Expand Down
23 changes: 17 additions & 6 deletions sdk/src/impl/whirlpool-impl.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Address, BN, translateAddress } from "@coral-xyz/anchor";
import {
AddressUtil,
MEASUREMENT_BLOCKHASH,
Percentage,
TokenUtil,
TransactionBuilder,
Expand Down Expand Up @@ -40,6 +41,7 @@ import {
import { Whirlpool } from "../whirlpool-client";
import { PositionImpl } from "./position-impl";
import { getRewardInfos, getTokenVaultAccountInfos } from "./util";
import { checkMergedTransactionSizeIsValid } from "../utils/txn-utils";

export class WhirlpoolImpl implements Whirlpool {
private data: WhirlpoolData;
Expand Down Expand Up @@ -584,15 +586,24 @@ export class WhirlpoolImpl implements Whirlpool {

txBuilder.addInstruction(positionIx);

const txBuilders: TransactionBuilder[] = [];

if (!tokenAccountsTxBuilder.isEmpty()) {
txBuilders.push(tokenAccountsTxBuilder);
if (tokenAccountsTxBuilder.isEmpty()) {
return [txBuilder]
}

txBuilders.push(txBuilder);
// This handles an edge case where the instructions are too
// large to fit in a single transaction and we need to split the
// instructions into two transactions.
const canFitInOneTransaction = await checkMergedTransactionSizeIsValid(
this.ctx,
[tokenAccountsTxBuilder, txBuilder],
MEASUREMENT_BLOCKHASH
)
if (!canFitInOneTransaction) {
return [tokenAccountsTxBuilder, txBuilder]
}

return txBuilders;
tokenAccountsTxBuilder.addInstruction(txBuilder.compressIx(false));
return [tokenAccountsTxBuilder]
}

private async refresh() {
Expand Down
128 changes: 121 additions & 7 deletions sdk/tests/sdk/whirlpools/whirlpool-impl#closePosition.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,12 @@ describe("WhirlpoolImpl#closePosition()", () => {
// TODO: Our createWSOLAccountInstructions ignores payer and requires destinationWallet to sign
// We can remove this once we move to syncNative and wSOL becomes another ATA to handle.
if (isWSOLTest) {
txs[1].addSigner(otherWallet)
txs[txs.length - 1].addSigner(otherWallet)
}

await txs[0].buildAndExecute();
await txs[1].buildAndExecute();
for (const tx of txs) {
await tx.buildAndExecute();
}

// Verify liquidity and fees collected
const liquidityCollectedQuote = decreaseLiquidityQuoteByLiquidity(
Expand Down Expand Up @@ -441,8 +442,11 @@ describe("WhirlpoolImpl#closePosition()", () => {
ctx.wallet.publicKey
);

await txs[0].buildAndExecute();
await txs[1].addSigner(otherWallet).buildAndExecute();
txs[txs.length - 1].addSigner(otherWallet)

for (const tx of txs) {
await tx.buildAndExecute();
}

const positionDataAfter = await testCtx.whirlpoolCtx.fetcher.getPosition(
position.getAddress(),
Expand Down Expand Up @@ -544,8 +548,11 @@ describe("WhirlpoolImpl#closePosition()", () => {
ctx.wallet.publicKey
);

await txs[0].buildAndExecute();
await txs[1].addSigner(otherWallet).buildAndExecute();
txs[txs.length - 1].addSigner(otherWallet)

for (const tx of txs) {
await tx.buildAndExecute();
}

const positionDataAfter = await testCtx.whirlpoolCtx.fetcher.getPosition(
position.getAddress(),
Expand All @@ -555,4 +562,111 @@ describe("WhirlpoolImpl#closePosition()", () => {
assert.equal(positionDataAfter, null);
});
});

it("should only create 2 transactions if absolutely necessary", async () => {
const ctx = testCtx.whirlpoolCtx;
const fixture = await new WhirlpoolTestFixture(testCtx.whirlpoolCtx).init({
tickSpacing,
positions: [
{ tickLowerIndex, tickUpperIndex, liquidityAmount }, // In range position
],
rewards: [
{
emissionsPerSecondX64: MathUtil.toX64(new Decimal(10)),
vaultAmount: new BN(vaultStartBalance),
},
{
emissionsPerSecondX64: MathUtil.toX64(new Decimal(10)),
vaultAmount: new BN(vaultStartBalance),
},
{
emissionsPerSecondX64: MathUtil.toX64(new Decimal(10)),
vaultAmount: new BN(vaultStartBalance),
},
],
tokenAIsNative: true,
});

const otherWallet = anchor.web3.Keypair.generate();
const positionData = fixture.getInfos().positions[0];

const position = await testCtx.whirlpoolClient.getPosition(positionData.publicKey, IGNORE_CACHE);

const walletPositionTokenAccount = getAssociatedTokenAddressSync(
positionData.mintKeypair.publicKey,
testCtx.whirlpoolCtx.wallet.publicKey,
);

const newOwnerPositionTokenAccount = await createAssociatedTokenAccount(
ctx.provider,
positionData.mintKeypair.publicKey,
otherWallet.publicKey,
ctx.wallet.publicKey
);

await accrueFeesAndRewards(fixture);
await transferToken(testCtx.provider, walletPositionTokenAccount, newOwnerPositionTokenAccount, 1);

const { poolInitInfo } = fixture.getInfos();

const pool = await testCtx.whirlpoolClient.getPool(poolInitInfo.whirlpoolPda.publicKey);

const txsWith4Ata = await pool.closePosition(
position.getAddress(),
Percentage.fromFraction(10, 100),
otherWallet.publicKey,
otherWallet.publicKey,
ctx.wallet.publicKey
);
assert.equal(txsWith4Ata.length, 2);

await createAssociatedTokenAccount(
ctx.provider,
position.getWhirlpoolData().rewardInfos[0].mint,
otherWallet.publicKey,
ctx.wallet.publicKey
);

const txsWith3Ata = await pool.closePosition(
position.getAddress(),
Percentage.fromFraction(10, 100),
otherWallet.publicKey,
otherWallet.publicKey,
ctx.wallet.publicKey
);
assert.equal(txsWith3Ata.length, 2);

await createAssociatedTokenAccount(
ctx.provider,
position.getWhirlpoolData().rewardInfos[1].mint,
otherWallet.publicKey,
ctx.wallet.publicKey
);

const txsWith2Ata = await pool.closePosition(
position.getAddress(),
Percentage.fromFraction(10, 100),
otherWallet.publicKey,
otherWallet.publicKey,
ctx.wallet.publicKey
);
assert.equal(txsWith2Ata.length, 2);

await createAssociatedTokenAccount(
ctx.provider,
position.getWhirlpoolData().rewardInfos[2].mint,
otherWallet.publicKey,
ctx.wallet.publicKey
);

const txsWith1Ata = await pool.closePosition(
position.getAddress(),
Percentage.fromFraction(10, 100),
otherWallet.publicKey,
otherWallet.publicKey,
ctx.wallet.publicKey
);
assert.equal(txsWith1Ata.length, 1);
await txsWith1Ata[0].addSigner(otherWallet).buildAndExecute();
});
});
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,10 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"

"@orca-so/common-sdk@^0.3.3":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@orca-so/common-sdk/-/common-sdk-0.3.3.tgz#d208c9a3a77c91f057519dc590c4725b60de5b39"
integrity sha512-g4LSOkSjq9IwXgeVB5pV5bbB9DBX8MBJyE2dpv7KE/B2jyFe8yEqjCOk9aTwbM2ftVDLJRH0jBP4z0rKz6oFCQ==
"@orca-so/common-sdk@^0.3.6":
version "0.3.6"
resolved "https://registry.yarnpkg.com/@orca-so/common-sdk/-/common-sdk-0.3.6.tgz#18ce8176118bdf3dba276e0d5fb8cf9d79aacffa"
integrity sha512-Yf4AZqnJI881SmDn2tf/KL0KOcXVuwHsP1pF4O28UyoSgQG1hBHfhNExd6Bn0K3ocv/pIbi215U4zlydtPTJDw==
dependencies:
"@solana/spl-token" "^0.3.8"
"@solana/web3.js" "^1.75.0"
Expand Down

0 comments on commit fc7dac3

Please sign in to comment.