diff --git a/CHANGELOG.md b/CHANGELOG.md index 7bb0b27..6ca4eb8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fix zap in damm-v2 with new base fee mode +- Add missing instructions sysvar in remaining accounts for `zap_in_damm_v2` which is used in rate limited DAMM v2 Pool for single swap validation ## zap [0.2.0] [PR #15](https://github.com/MeteoraAg/zap-program/pull/15) diff --git a/idls/damm_v2.json b/idls/damm_v2.json index fee333e..6532516 100644 --- a/idls/damm_v2.json +++ b/idls/damm_v2.json @@ -2,7 +2,7 @@ "address": "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG", "metadata": { "name": "cp_amm", - "version": "0.1.6", + "version": "0.1.7", "spec": "0.1.0", "description": "Created with Anchor" }, @@ -305,79 +305,20 @@ }, { "name": "token_a_account", - "docs": ["The treasury token a account"], - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 48, 9, 89, 123, 106, 114, 131, 251, 50, 173, 254, 250, 10, 80, - 160, 84, 143, 100, 81, 249, 134, 112, 30, 213, 50, 166, 239, - 78, 53, 175, 188, 85 - ] - }, - { - "kind": "account", - "path": "token_a_program" - }, - { - "kind": "account", - "path": "token_a_mint" - } - ], - "program": { - "kind": "const", - "value": [ - 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, - 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, - 219, 233, 248, 89 - ] - } - } + "writable": true }, { "name": "token_b_account", - "docs": ["The treasury token b account"], - "writable": true, - "pda": { - "seeds": [ - { - "kind": "const", - "value": [ - 48, 9, 89, 123, 106, 114, 131, 251, 50, 173, 254, 250, 10, 80, - 160, 84, 143, 100, 81, 249, 134, 112, 30, 213, 50, 166, 239, - 78, 53, 175, 188, 85 - ] - }, - { - "kind": "account", - "path": "token_b_program" - }, - { - "kind": "account", - "path": "token_b_mint" - } - ], - "program": { - "kind": "const", - "value": [ - 140, 151, 37, 143, 78, 36, 137, 241, 187, 61, 16, 41, 20, 142, - 13, 131, 11, 90, 19, 153, 218, 255, 16, 132, 4, 142, 123, 216, - 219, 233, 248, 89 - ] - } - } + "writable": true }, { "name": "operator", "docs": ["Claim fee operator"] }, { - "name": "whitelisted_address", + "name": "signer", "docs": ["Operator"], - "signer": true, - "relations": ["operator"] + "signer": true }, { "name": "token_a_program", @@ -498,9 +439,8 @@ "name": "operator" }, { - "name": "whitelisted_address", - "signer": true, - "relations": ["operator"] + "name": "signer", + "signer": true }, { "name": "rent_receiver", @@ -535,7 +475,7 @@ "writable": true }, { - "name": "admin", + "name": "signer", "signer": true }, { @@ -637,9 +577,8 @@ "name": "operator" }, { - "name": "whitelisted_address", - "signer": true, - "relations": ["operator"] + "name": "signer", + "signer": true }, { "name": "rent_receiver", @@ -690,9 +629,8 @@ "name": "operator" }, { - "name": "whitelisted_address", - "signer": true, - "relations": ["operator"] + "name": "signer", + "signer": true }, { "name": "payer", @@ -760,9 +698,8 @@ "name": "operator" }, { - "name": "whitelisted_address", - "signer": true, - "relations": ["operator"] + "name": "signer", + "signer": true }, { "name": "payer", @@ -831,7 +768,7 @@ "name": "whitelisted_address" }, { - "name": "admin", + "name": "signer", "signer": true }, { @@ -989,9 +926,8 @@ "name": "operator" }, { - "name": "whitelisted_address", - "signer": true, - "relations": ["operator"] + "name": "signer", + "signer": true }, { "name": "payer", @@ -2057,9 +1993,8 @@ "name": "operator" }, { - "name": "whitelisted_address", - "signer": true, - "relations": ["operator"] + "name": "signer", + "signer": true }, { "name": "event_authority", @@ -2408,9 +2343,8 @@ "name": "operator" }, { - "name": "whitelisted_address", - "signer": true, - "relations": ["operator"] + "name": "signer", + "signer": true }, { "name": "event_authority", @@ -2577,6 +2511,54 @@ "type": "u8" } ] + }, + { + "name": "zap_protocol_fee", + "discriminator": [213, 155, 187, 34, 56, 182, 91, 240], + "accounts": [ + { + "name": "pool_authority", + "address": "HLnpSz9h2S4hiLQ43rnSD9XkcUThA7B8hQMKmDaiTLcC" + }, + { + "name": "pool", + "writable": true + }, + { + "name": "token_vault", + "writable": true + }, + { + "name": "token_mint" + }, + { + "name": "receiver_token", + "writable": true + }, + { + "name": "operator", + "docs": ["zap claim fee operator"] + }, + { + "name": "signer", + "docs": ["Operator"], + "signer": true + }, + { + "name": "token_program", + "docs": ["Token program"] + }, + { + "name": "sysvar_instructions", + "address": "Sysvar1nstructions1111111111111111111111111" + } + ], + "args": [ + { + "name": "max_amount", + "type": "u64" + } + ] } ], "accounts": [ @@ -3011,6 +2993,41 @@ "code": 6059, "name": "MissingOperatorAccount", "msg": "Missing operator account" + }, + { + "code": 6060, + "name": "IncorrectATA", + "msg": "Incorrect ATA" + }, + { + "code": 6061, + "name": "InvalidZapOutParameters", + "msg": "Invalid zap out parameters" + }, + { + "code": 6062, + "name": "InvalidWithdrawProtocolFeeZapAccounts", + "msg": "Invalid withdraw protocol fee zap accounts" + }, + { + "code": 6063, + "name": "MintRestrictedFromZap", + "msg": "SOL,USDC protocol fee cannot be withdrawn via zap" + }, + { + "code": 6064, + "name": "CpiDisabled", + "msg": "CPI disabled" + }, + { + "code": 6065, + "name": "MissingZapOutInstruction", + "msg": "Missing zap out instruction" + }, + { + "code": 6066, + "name": "InvalidZapAccounts", + "msg": "Invalid zap accounts" } ], "types": [ diff --git a/programs/zap/src/instructions/ix_zap_in_damm_v2.rs b/programs/zap/src/instructions/ix_zap_in_damm_v2.rs index 06544f8..362970a 100644 --- a/programs/zap/src/instructions/ix_zap_in_damm_v2.rs +++ b/programs/zap/src/instructions/ix_zap_in_damm_v2.rs @@ -67,7 +67,12 @@ pub struct ZapInDammv2Ctx<'info> { } impl<'info> ZapInDammv2Ctx<'info> { - fn swap(&self, amount: u64, trade_direction: TradeDirection) -> Result<()> { + fn swap( + &self, + amount: u64, + trade_direction: TradeDirection, + remaining_accounts: &[AccountInfo<'info>], + ) -> Result<()> { let (input_token_account, output_token_account) = if trade_direction == TradeDirection::AtoB { ( @@ -99,7 +104,8 @@ impl<'info> ZapInDammv2Ctx<'info> { payer: self.owner.to_account_info(), referral_token_account: None, // TODO check whether it should be some(damm_program) }, - ), + ) + .with_remaining_accounts(remaining_accounts.to_vec()), SwapParameters2 { amount_0: amount, amount_1: 0, @@ -140,8 +146,8 @@ impl<'info> ZapInDammv2Ctx<'info> { } } -pub fn handle_zap_in_damm_v2( - ctx: Context, +pub fn handle_zap_in_damm_v2<'c: 'info, 'info>( + ctx: Context<'_, '_, 'c, 'info, ZapInDammv2Ctx<'info>>, pre_sqrt_price: u128, // sqrt price user observe in local max_sqrt_price_change_bps: u32, // max sqrt price change after swap ) -> Result<()> { @@ -216,7 +222,8 @@ pub fn handle_zap_in_damm_v2( return Ok(()); // no need to swap, just return } drop(pool); - ctx.accounts.swap(swap_in_amount, trade_direction)?; + ctx.accounts + .swap(swap_in_amount, trade_direction, &ctx.remaining_accounts)?; } Err(err) => { // if calculation fail, we just skip swap and add liquidity with remaining amount diff --git a/programs/zap/src/lib.rs b/programs/zap/src/lib.rs index e998a23..74e8104 100644 --- a/programs/zap/src/lib.rs +++ b/programs/zap/src/lib.rs @@ -57,8 +57,8 @@ pub mod zap { ) } - pub fn zap_in_damm_v2( - ctx: Context, + pub fn zap_in_damm_v2<'c: 'info, 'info>( + ctx: Context<'_, '_, 'c, 'info, ZapInDammv2Ctx<'info>>, pre_sqrt_price: u128, max_sqrt_price_change_bps: u32, ) -> Result<()> { diff --git a/tests/common/endpoints/zapIn.ts b/tests/common/endpoints/zapIn.ts index 8f6c7f1..869b273 100644 --- a/tests/common/endpoints/zapIn.ts +++ b/tests/common/endpoints/zapIn.ts @@ -4,6 +4,7 @@ import { AccountMeta, PublicKey, SystemProgram, + SYSVAR_INSTRUCTIONS_PUBKEY, Transaction, } from "@solana/web3.js"; import { DAMM_V2_PROGRAM_ID } from "../damm_v2"; @@ -14,15 +15,12 @@ import { deriveLedgerAccount, getDammV2Pool, } from "../pda"; -import { createZapProgram, ZAP_PROGRAM_ID } from "./zapOut"; +import { createZapProgram } from "./zapOut"; import { getAssociatedTokenAddressSync } from "@solana/spl-token"; import { DLMM_PROGRAM_ID_LOCAL, - getBinArrayAccountMetaByBinRange, getLbPairState, MEMO_PROGRAM_ID, - SET_COMPUTE_UNIT_LIMIT_IX, - StrategyType, } from "../dlmm"; export async function zapInDammv2(params: { @@ -87,6 +85,13 @@ export async function zapInDammv2(params: { dammProgram: DAMM_V2_PROGRAM_ID, dammEventAuthority: deriveDammV2EventAuthority(), }) + .remainingAccounts([ + { + isSigner: false, + isWritable: false, + pubkey: SYSVAR_INSTRUCTIONS_PUBKEY, + }, + ]) .transaction(); } diff --git a/tests/common/idl/damm_v2.ts b/tests/common/idl/damm_v2.ts index 6b90f42..df26138 100644 --- a/tests/common/idl/damm_v2.ts +++ b/tests/common/idl/damm_v2.ts @@ -8,7 +8,7 @@ export type CpAmm = { address: "cpamdpZCGKUy5JxQXB4dcpGPiikHawvSWAd6mEn1sGG"; metadata: { name: "cpAmm"; - version: "0.1.6"; + version: "0.1.7"; spec: "0.1.0"; description: "Created with Anchor"; }; @@ -356,195 +356,20 @@ export type CpAmm = { }, { name: "tokenAAccount"; - docs: ["The treasury token a account"]; writable: true; - pda: { - seeds: [ - { - kind: "const"; - value: [ - 48, - 9, - 89, - 123, - 106, - 114, - 131, - 251, - 50, - 173, - 254, - 250, - 10, - 80, - 160, - 84, - 143, - 100, - 81, - 249, - 134, - 112, - 30, - 213, - 50, - 166, - 239, - 78, - 53, - 175, - 188, - 85 - ]; - }, - { - kind: "account"; - path: "tokenAProgram"; - }, - { - kind: "account"; - path: "tokenAMint"; - } - ]; - program: { - kind: "const"; - value: [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 - ]; - }; - }; }, { name: "tokenBAccount"; - docs: ["The treasury token b account"]; writable: true; - pda: { - seeds: [ - { - kind: "const"; - value: [ - 48, - 9, - 89, - 123, - 106, - 114, - 131, - 251, - 50, - 173, - 254, - 250, - 10, - 80, - 160, - 84, - 143, - 100, - 81, - 249, - 134, - 112, - 30, - 213, - 50, - 166, - 239, - 78, - 53, - 175, - 188, - 85 - ]; - }, - { - kind: "account"; - path: "tokenBProgram"; - }, - { - kind: "account"; - path: "tokenBMint"; - } - ]; - program: { - kind: "const"; - value: [ - 140, - 151, - 37, - 143, - 78, - 36, - 137, - 241, - 187, - 61, - 16, - 41, - 20, - 142, - 13, - 131, - 11, - 90, - 19, - 153, - 218, - 255, - 16, - 132, - 4, - 142, - 123, - 216, - 219, - 233, - 248, - 89 - ]; - }; - }; }, { name: "operator"; docs: ["Claim fee operator"]; }, { - name: "whitelistedAddress"; + name: "signer"; docs: ["operator"]; signer: true; - relations: ["operator"]; }, { name: "tokenAProgram"; @@ -695,9 +520,8 @@ export type CpAmm = { name: "operator"; }, { - name: "whitelistedAddress"; + name: "signer"; signer: true; - relations: ["operator"]; }, { name: "rentReceiver"; @@ -747,7 +571,7 @@ export type CpAmm = { writable: true; }, { - name: "admin"; + name: "signer"; signer: true; }, { @@ -879,9 +703,8 @@ export type CpAmm = { name: "operator"; }, { - name: "whitelistedAddress"; + name: "signer"; signer: true; - relations: ["operator"]; }, { name: "rentReceiver"; @@ -947,9 +770,8 @@ export type CpAmm = { name: "operator"; }, { - name: "whitelistedAddress"; + name: "signer"; signer: true; - relations: ["operator"]; }, { name: "payer"; @@ -1032,9 +854,8 @@ export type CpAmm = { name: "operator"; }, { - name: "whitelistedAddress"; + name: "signer"; signer: true; - relations: ["operator"]; }, { name: "payer"; @@ -1118,7 +939,7 @@ export type CpAmm = { name: "whitelistedAddress"; }, { - name: "admin"; + name: "signer"; signer: true; }, { @@ -1324,9 +1145,8 @@ export type CpAmm = { name: "operator"; }, { - name: "whitelistedAddress"; + name: "signer"; signer: true; - relations: ["operator"]; }, { name: "payer"; @@ -2607,9 +2427,8 @@ export type CpAmm = { name: "operator"; }, { - name: "whitelistedAddress"; + name: "signer"; signer: true; - relations: ["operator"]; }, { name: "eventAuthority"; @@ -3033,9 +2852,8 @@ export type CpAmm = { name: "operator"; }, { - name: "whitelistedAddress"; + name: "signer"; signer: true; - relations: ["operator"]; }, { name: "eventAuthority"; @@ -3262,6 +3080,54 @@ export type CpAmm = { type: "u8"; } ]; + }, + { + name: "zapProtocolFee"; + discriminator: [213, 155, 187, 34, 56, 182, 91, 240]; + accounts: [ + { + name: "poolAuthority"; + address: "HLnpSz9h2S4hiLQ43rnSD9XkcUThA7B8hQMKmDaiTLcC"; + }, + { + name: "pool"; + writable: true; + }, + { + name: "tokenVault"; + writable: true; + }, + { + name: "tokenMint"; + }, + { + name: "receiverToken"; + writable: true; + }, + { + name: "operator"; + docs: ["zap claim fee operator"]; + }, + { + name: "signer"; + docs: ["operator"]; + signer: true; + }, + { + name: "tokenProgram"; + docs: ["Token program"]; + }, + { + name: "sysvarInstructions"; + address: "Sysvar1nstructions1111111111111111111111111"; + } + ]; + args: [ + { + name: "maxAmount"; + type: "u64"; + } + ]; } ]; accounts: [ @@ -3696,6 +3562,41 @@ export type CpAmm = { code: 6059; name: "missingOperatorAccount"; msg: "Missing operator account"; + }, + { + code: 6060; + name: "incorrectAta"; + msg: "Incorrect ATA"; + }, + { + code: 6061; + name: "invalidZapOutParameters"; + msg: "Invalid zap out parameters"; + }, + { + code: 6062; + name: "invalidWithdrawProtocolFeeZapAccounts"; + msg: "Invalid withdraw protocol fee zap accounts"; + }, + { + code: 6063; + name: "mintRestrictedFromZap"; + msg: "SOL,USDC protocol fee cannot be withdrawn via zap"; + }, + { + code: 6064; + name: "cpiDisabled"; + msg: "CPI disabled"; + }, + { + code: 6065; + name: "missingZapOutInstruction"; + msg: "Missing zap out instruction"; + }, + { + code: 6066; + name: "invalidZapAccounts"; + msg: "Invalid zap accounts"; } ]; types: [ diff --git a/tests/common/utils.ts b/tests/common/utils.ts index 8cdc070..c1d6dca 100644 --- a/tests/common/utils.ts +++ b/tests/common/utils.ts @@ -36,6 +36,7 @@ export const MAX_SQRT_PRICE = new BN("79226673521066979257578248091"); export const LIQUIDITY_DELTA = new BN("1844674407800459963300003758876517305"); export const INIT_PRICE = new BN("18446744073709551616"); export const LIQUIDITY_DELTA_2 = new BN("18446744078004599633000037588765"); +export const U32_MAX = new BN("4294967295"); export const U64_MAX = new BN("18446744073709551615"); export function createToken( diff --git a/tests/fixtures/damm_v2.so b/tests/fixtures/damm_v2.so index 27ca88a..fe1ed6b 100755 Binary files a/tests/fixtures/damm_v2.so and b/tests/fixtures/damm_v2.so differ diff --git a/tests/test_zapin/zapin_dammv2.test.ts b/tests/test_zapin/zapin_dammv2.test.ts index 2d3daba..5862ec0 100644 --- a/tests/test_zapin/zapin_dammv2.test.ts +++ b/tests/test_zapin/zapin_dammv2.test.ts @@ -23,6 +23,7 @@ import { warpSlotBy, TOKEN_DECIMALS, U64_MAX, + U32_MAX, } from "../common"; import ZapIDL from "../../target/idl/zap.json"; @@ -378,7 +379,6 @@ describe("Zap In damm V2", () => { ); // zapin - const zapInTx = await zapInDammv2({ svm, user: user.publicKey, @@ -389,7 +389,88 @@ describe("Zap In damm V2", () => { maxSqrtPriceChangeBps: 5000, }); - // close ledge + // close ledger + const closeLedgerTx = await closeLedgerAccount(user.publicKey); + + const finalTx = new Transaction() + .add(initializeLedgerTx) + .add(setLedgerBalanceTx) + .add(updateLedgerBalanceAfterSwapTx) + .add(zapInTx) + .add(closeLedgerTx); + + finalTx.recentBlockhash = svm.latestBlockhash(); + finalTx.sign(user); + + const result = svm.sendTransaction(finalTx); + if (result instanceof FailedTransactionMetadata) { + console.log(result.meta().logs()); + } + expect(result).instanceOf(TransactionMetadata); + }); + + it("zap in without external swap with rate limiter and remaining accounts", async () => { + const baseFee = encodeFeeRateLimiterParams( + new BN(10_000_00), // 1% cliff fee + 1, // 10 bps fee increment + 10, // max limiter duration + 5000, // 50% max fee + new BN(LAMPORTS_PER_SOL) // reference amount: 1 SOL + ); + + const pool = await createDammV2Pool( + svm, + admin, + tokenAMint, + tokenBMint, + new BN(LAMPORTS_PER_SOL), + new BN(LAMPORTS_PER_SOL), + baseFee + ); + + const { position, positionNftAccount } = await createDammV2Position( + svm, + user, + pool + ); + + let poolState = getDammV2Pool(svm, pool); + + const totalAmountB = new BN(LAMPORTS_PER_SOL / 2); // 0.5 SOL + const initializeLedgerTx = await initializeLedgerAccount(user.publicKey); + + // swap BtoA to trigger remaining account validation in dammv2 + const setLedgerBalanceTx = await setLedgerBalance( + user.publicKey, + totalAmountB, + false + ); + + const tokenAAccount = getAssociatedTokenAddressSync( + tokenAMint, + user.publicKey + ); + + const updateLedgerBalanceAfterSwapTx = await updateLedgerBalanceAfterSwap( + user.publicKey, + tokenAAccount, + new BN(0), // no token A + U64_MAX, + false + ); + + // zapin + const zapInTx = await zapInDammv2({ + svm, + user: user.publicKey, + pool, + position, + positionNftAccount, + preSqrtPrice: poolState.sqrtPrice, + maxSqrtPriceChangeBps: U32_MAX.toNumber(), + }); + + // close ledger const closeLedgerTx = await closeLedgerAccount(user.publicKey); const finalTx = new Transaction()