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

InterestBearing support #294

Merged
merged 3 commits into from
Oct 8, 2024
Merged
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
1 change: 1 addition & 0 deletions legacy-sdk/whirlpool/src/utils/public/pool-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ export class PoolUtil {
switch (extension) {
// supported
case ExtensionType.TransferFeeConfig:
case ExtensionType.InterestBearingConfig:
case ExtensionType.TokenMetadata:
case ExtensionType.MetadataPointer:
case ExtensionType.ConfidentialTransferMint:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1347,6 +1347,17 @@ describe("initialize_pool_v2", () => {
});
});

it("Token-2022: with InterestBearingConfig", async () => {
await runTest({
supported: true,
createTokenBadge: false,
tokenTrait: {
isToken2022: true,
hasInterestBearingExtension: true,
},
});
});

it("Token-2022: with MetadataPointer & TokenMetadata", async () => {
await runTest({
supported: true,
Expand Down Expand Up @@ -1535,15 +1546,6 @@ describe("initialize_pool_v2", () => {
});
});

it("Token-2022: [FAIL] with/without TokenBadge with InterestBearingConfig", async () => {
const tokenTrait: TokenTrait = {
isToken2022: true,
hasInterestBearingExtension: true,
};
await runTest({ supported: false, createTokenBadge: true, tokenTrait });
await runTest({ supported: false, createTokenBadge: false, tokenTrait });
});

//[11 Mar, 2024] NOT IMPLEMENTED / I believe this extension is not stable yet
it.skip("Token-2022: [FAIL] with/without TokenBadge with Group", async () => {
const tokenTrait: TokenTrait = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,17 @@ describe("initialize_reward_v2", () => {
});
});

it("Token-2022: with InterestBearingConfig", async () => {
await runTest({
supported: true,
createTokenBadge: false,
tokenTrait: {
isToken2022: true,
hasInterestBearingExtension: true,
},
});
});

it("Token-2022: with MetadataPointer & TokenMetadata", async () => {
await runTest({
supported: true,
Expand Down Expand Up @@ -836,15 +847,6 @@ describe("initialize_reward_v2", () => {
});
});

it("Token-2022: [FAIL] with/without TokenBadge with InterestBearingConfig", async () => {
const tokenTrait: TokenTrait = {
isToken2022: true,
hasInterestBearingExtension: true,
};
await runTest({ supported: false, createTokenBadge: true, tokenTrait });
await runTest({ supported: false, createTokenBadge: false, tokenTrait });
});

// [11 Mar, 2024] NOT IMPLEMENTED / I believe this extension is not stable yet
it.skip("Token-2022: [FAIL] with/without TokenBadge with Group", async () => {
const tokenTrait: TokenTrait = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import * as anchor from "@coral-xyz/anchor";
import { BN } from "@coral-xyz/anchor";
import type NodeWallet from "@coral-xyz/anchor/dist/cjs/nodewallet";
import { Percentage } from "@orca-so/common-sdk";
import * as assert from "assert";
import type {
WhirlpoolData,
} from "../../../../src";
import {
PDAUtil,
swapQuoteWithParams,
SwapUtils,
toTx,
WhirlpoolContext,
WhirlpoolIx,
} from "../../../../src";
import { IGNORE_CACHE } from "../../../../src/network/public/fetcher";
import { getTokenBalance, sleep, TEST_TOKEN_2022_PROGRAM_ID, TickSpacing } from "../../../utils";
import { defaultConfirmOptions } from "../../../utils/const";
import {
fundPositionsV2,
initTestPoolWithTokensV2,
} from "../../../utils/v2/init-utils-v2";
import type { PublicKey } from "@solana/web3.js";
import { initTickArrayRange } from "../../../utils/init-utils";
import { TokenExtensionUtil } from "../../../../src/utils/public/token-extension-util";
import { amountToUiAmount, updateRateInterestBearingMint } from "@solana/spl-token";

describe("TokenExtension/InterestBearing", () => {
const provider = anchor.AnchorProvider.local(
undefined,
defaultConfirmOptions,
);
const program = anchor.workspace.Whirlpool;
const ctx = WhirlpoolContext.fromWorkspace(provider, program);
const fetcher = ctx.fetcher;

const payer = (ctx.wallet as NodeWallet).payer;

async function rawAmountToUIAmount(mint: PublicKey, rawAmount: BN): Promise<string> {
const result = await amountToUiAmount(ctx.connection, payer, mint, rawAmount.toNumber(), TEST_TOKEN_2022_PROGRAM_ID);
if (typeof result === "string") {
return result;
}
throw new Error("Failed to convert raw amount to UI amount");
}

// Since InterestBearing is no different from normal mint as far as handling raw amounts (u64 amounts),
// swap_v2 is executed to check the owner to vault and vault to owner logic.

// |----------|-----*S*T*|****------| (*: liquidity, S: start, T: end)
it("swap_v2 (covers both owner to vault and vault to owner transfer)", async () => {
const {
whirlpoolPda,
poolInitInfo,
tokenAccountA,
tokenAccountB,
} = await initTestPoolWithTokensV2(
ctx,
{ isToken2022: true, hasInterestBearingExtension: true, interestBearingRate: 0 }, // 0%
{ isToken2022: true, hasInterestBearingExtension: true, interestBearingRate: 0 }, // 0%
TickSpacing.Standard,
);

const initialRawBalanceA = new BN(await getTokenBalance(provider, tokenAccountA));
const initialRawBalanceB = new BN(await getTokenBalance(provider, tokenAccountB));
const initialUIBalanceA = await rawAmountToUIAmount(poolInitInfo.tokenMintA, initialRawBalanceA);
const initialUIBalanceB = await rawAmountToUIAmount(poolInitInfo.tokenMintB, initialRawBalanceB);

// rate is 0%, so these values should be equal
assert.ok(initialRawBalanceA.eq(new BN(Number.parseInt(initialUIBalanceA))));
assert.ok(initialRawBalanceB.eq(new BN(Number.parseInt(initialUIBalanceB))));

// set rate > 0%
const sigA = await updateRateInterestBearingMint(
ctx.connection,
payer,
poolInitInfo.tokenMintA,
payer,
30_000, // 300%
);
const sigB = await updateRateInterestBearingMint(
ctx.connection,
payer,
poolInitInfo.tokenMintB,
payer,
10_000, // 100%
);
await Promise.all([
ctx.connection.confirmTransaction(sigA),
ctx.connection.confirmTransaction(sigB),
]);

await sleep(10 * 1000);

const newUIBalanceA = await rawAmountToUIAmount(poolInitInfo.tokenMintA, initialRawBalanceA);
const newUIBalanceB = await rawAmountToUIAmount(poolInitInfo.tokenMintB, initialRawBalanceB);

// rate is >0%, so these values should NOT be equal
assert.ok(initialRawBalanceA.lt(new BN(Number.parseInt(newUIBalanceA))));
assert.ok(initialRawBalanceB.lt(new BN(Number.parseInt(newUIBalanceB))));

// now we can assure that InterestBearing works as expected on both tokens

const aToB = false;
await initTickArrayRange(
ctx,
whirlpoolPda.publicKey,
22528, // to 33792
3,
TickSpacing.Standard,
aToB,
);

await fundPositionsV2(
ctx,
poolInitInfo,
tokenAccountA,
tokenAccountB,
[
{
liquidityAmount: new anchor.BN(10_000_000),
tickLowerIndex: 29440,
tickUpperIndex: 33536,
},
]
);

const oraclePubkey = PDAUtil.getOracle(
ctx.program.programId,
whirlpoolPda.publicKey,
).publicKey;

const whirlpoolKey = poolInitInfo.whirlpoolPda.publicKey;
const whirlpoolData = (await fetcher.getPool(
whirlpoolKey,
IGNORE_CACHE,
)) as WhirlpoolData;

// tick: 32190 -> 32269
const quoteBToA = swapQuoteWithParams(
{
amountSpecifiedIsInput: true,
aToB: false,
tokenAmount: new BN(200000),
otherAmountThreshold: SwapUtils.getDefaultOtherAmountThreshold(true),
sqrtPriceLimit: SwapUtils.getDefaultSqrtPriceLimit(false),
whirlpoolData,
tickArrays: await SwapUtils.getTickArrays(
whirlpoolData.tickCurrentIndex,
whirlpoolData.tickSpacing,
false,
ctx.program.programId,
whirlpoolKey,
fetcher,
IGNORE_CACHE,
),
tokenExtensionCtx:
await TokenExtensionUtil.buildTokenExtensionContext(
fetcher,
whirlpoolData,
IGNORE_CACHE,
),
},
Percentage.fromFraction(0, 100),
);

assert.ok(quoteBToA.estimatedAmountIn.gtn(0));
assert.ok(quoteBToA.estimatedAmountOut.gtn(0));

const balanceA0 = new BN(await getTokenBalance(provider, tokenAccountA));
const balanceB0 = new BN(await getTokenBalance(provider, tokenAccountB));
await toTx(
ctx,
WhirlpoolIx.swapV2Ix(ctx.program, {
...quoteBToA,
whirlpool: whirlpoolPda.publicKey,
tokenAuthority: ctx.wallet.publicKey,
tokenMintA: poolInitInfo.tokenMintA,
tokenMintB: poolInitInfo.tokenMintB,
tokenProgramA: poolInitInfo.tokenProgramA,
tokenProgramB: poolInitInfo.tokenProgramB,
tokenOwnerAccountA: tokenAccountA,
tokenVaultA: poolInitInfo.tokenVaultAKeypair.publicKey,
tokenOwnerAccountB: tokenAccountB,
tokenVaultB: poolInitInfo.tokenVaultBKeypair.publicKey,
oracle: oraclePubkey,
}),
).buildAndExecute();
const balanceA1 = new BN(await getTokenBalance(provider, tokenAccountA));
const balanceB1 = new BN(await getTokenBalance(provider, tokenAccountB));

const diffA = balanceA1.sub(balanceA0);
const diffB = balanceB1.sub(balanceB0);
assert.ok(diffA.eq(quoteBToA.estimatedAmountOut));
assert.ok(diffB.eq(quoteBToA.estimatedAmountIn.neg()));
});

});
1 change: 1 addition & 0 deletions legacy-sdk/whirlpool/tests/utils/v2/init-utils-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export interface TokenTrait {
hasConfidentialTransferExtension?: boolean;

hasInterestBearingExtension?: boolean;
interestBearingRate?: number; // u16
hasMintCloseAuthorityExtension?: boolean;
hasDefaultAccountStateExtension?: boolean;
defaultAccountInitialState?: AccountState;
Expand Down
2 changes: 1 addition & 1 deletion legacy-sdk/whirlpool/tests/utils/v2/token-2022.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ async function createMintInstructions(
createInitializeInterestBearingMintInstruction(
mint,
authority,
1,
tokenTrait.interestBearingRate ?? 1, // default 1/10000
TEST_TOKEN_2022_PROGRAM_ID,
),
);
Expand Down
1 change: 1 addition & 0 deletions programs/whirlpool/src/util/v2/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ pub fn is_supported_token_mint(
match extension {
// supported
extension::ExtensionType::TransferFeeConfig => {}
extension::ExtensionType::InterestBearingConfig => {}
extension::ExtensionType::TokenMetadata => {}
extension::ExtensionType::MetadataPointer => {}
// partially supported
Expand Down