Skip to content

feat: add integration test for zap-protocol validation (do not merge)#197

Draft
bangyro wants to merge 1 commit intomainfrom
feat/test-protocol-zap-validate-first-swap-account
Draft

feat: add integration test for zap-protocol validation (do not merge)#197
bangyro wants to merge 1 commit intomainfrom
feat/test-protocol-zap-validate-first-swap-account

Conversation

@bangyro
Copy link
Collaborator

@bangyro bangyro commented Mar 25, 2026

  • should reject when first swap input token account does not match claiming account

- should reject when first swap input token account does not match claiming account
@bangyro
Copy link
Collaborator Author

bangyro commented Mar 25, 2026

/bot review

@metoeraag-bot
Copy link

metoeraag-bot bot commented Mar 25, 2026

Bot Agent progress: /bot review

Progress:

  • Repo checks (5 ok, 1 failed)
  • Context build
  • Codex review (1 pass + first look)
  • Buddy review (agreed after 2/10)
  • Post review

Copy link

@metoeraag-bot metoeraag-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary

⚠️ Limited-confidence review: check failed: cargo clippy --workspace --all-targets -- -D warnings.

The test has a real correctness issue: it reads state from the outer-scope pool and passes pool into zapProtocolFee, so the negative case is not actually exercising the freshly created dammV2PoolAddress. Even after correcting that, the test should ensure the selected pool has accrued non-zero protocol fees so the claim path is not trivially zero. Separately, the test is also brittle because it mutates zapOutIx.keys[14] based on a hard-coded Jupiter/protocol-zap account index, which can silently break if account ordering changes.

Inline comments

See inline comments on the diff.

Claude (API) review

Rounds: 2 (min=1, max=10).
First look: Codex + Claude (API).
Agreement: yes (no new findings/questions).

Debate transcript

First look comparison

Codex (first look):

Summary:
The dependency pin update and negative integration test mostly match the PR intent. The main issue I found is a maintainability risk in the new test: it hard-codes a Jupiter/protocol-zap account index without verifying it still points to the first swap source account.

Inline comment highlights:

  • tests/zapProtocolFees.test.ts:462: This assertion path is brittle because it assumes zapOutIx.keys[14] will always be the first DAMM v2 swap source account. That index is derived from Jupiter/protocol-zap account ordering, so a future rev bump can silently make this mutate

Claude (API) (first look):

Summary:
Integration test for zap-protocol validation. The test has a bug where it reads pool state from the wrong pool variable, and the dependency bump introduces clippy regressions (pre-existing). The test logic for account substitution is otherwise sound.

Inline comment highlights:

  • tests/zapProtocolFees.test.ts:432: Bug: getPool(svm, pool) reads the state of the outer-scope pool, not the freshly created dammV2PoolAddress. This means claimAmount, tokenVault, tokenMint, and receiverToken all refer to a different pool than the one used to bu
  • tests/zapProtocolFees.test.ts:435: If pool (the outer-scope pool) hasn't accumulated any protocol fees by the time this test runs, claimAmount (totalProtocolAFee) could be zero, which may make the claim trivially succeed or fail for an unrelated reason, masking the int
  • tests/zapProtocolFees.test.ts:462: The hard-coded index 14 is fragile and relies on a specific account layout of the Jupiter route + DAMM v2 swap instruction. A comment explains the derivation, which is good, but if buildZapOutJupV6UsingDammV2RouteInstruction ever change

Comparison notes:

Review synthesis

Top differences in emphasis

  1. Correctness vs. maintainability: Buddy flags a likely test bug (getPool(svm, pool) uses the wrong pool) and a possible zero-fee false positive; Codex does not mention these.
  2. Shared issue, different framing: Both call out the hard-coded zapOutIx.keys[14], but Codex emphasizes future silent breakage/maintainability, while Buddy says the substitution logic is otherwise sound and mainly fragile.
  3. Dependency update impact: Buddy notes clippy regressions from the dependency bump (pre-existing); Codex says the pin update mostly matches intent and focuses only on the test.

Most important follow-up

  • Fix the new integration test’s pool-state bug first: use dammV2PoolAddress consistently when reading pool state, so the test validates the intended pool before addressing the brittle hard-coded account index.

Round 1

Codex (primary):

Summary:
The dependency pin update and negative integration test mostly match the PR intent. The main issue I found is a maintainability risk in the new test: it hard-codes a Jupiter/protocol-zap account index without verifying it still points to the first swap source account.

Inline comment highlights:

  • tests/zapProtocolFees.test.ts:462: This assertion path is brittle because it assumes zapOutIx.keys[14] will always be the first DAMM v2 swap source account. That index is derived from Jupiter/protocol-zap account ordering, so a future rev bump can silently make this mutate

Claude (API):

The primary review correctly identified the hard-coded zapOutIx.keys[14] brittleness, which is a real maintainability concern. However, it missed the higher-severity correctness bug: the test uses the wrong pool variable in two places (line 432 and line 470), meaning the test does not actually validate what it claims to. This is the most important finding in this diff — the test will either spuriously pass or fail for unrelated reasons, providing zero signal about the zap-protocol-fee validation logic.

The zero-fee risk (line 435) is a secondary consequence: the freshly created dammV2PoolAddress has had no swaps, so totalProtocolAFee on it would be zero even if the pool reference were corrected. Both bugs together mean this test is essentially dead code.

I agree with the primary review's hard-coded-index finding but would elevate the pool-variable bug as the blocking issue.

Codex (response):

  • New findings applied: 2/2
  • Open questions kept for author: 1/1

Round 2

Codex (primary):

Summary:
The hard-coded Jupiter/protocol-zap account index is still a maintainability risk, but the more serious issue in this test is correctness: it reads state from the outer-scope pool and also passes pool into zapProtocolFee, so the negative case is not actually exercising the freshly created dammV2PoolAddress. After fixing that, the test should also ensure the selected pool has accrued non-zero protocol fees so the claim path cannot pass or fail trivially with a zero amount.

Inline comment highlights:

  • tests/zapProtocolFees.test.ts:432: Bug: this test is pulling pool state from the outer-scope pool instead of the freshly created dammV2PoolAddress. That means claimAmount, tokenVault, tokenMint, and receiverToken all come from a different pool, and `zapProtocolFe
  • tests/zapProtocolFees.test.ts:435: Even after fixing the pool reference, this test can still be non-deterministic if the target pool has not accrued any protocol fees yet: claimAmount may be zero, and claiming zero can succeed or fail for reasons unrelated to the mismatche
  • tests/zapProtocolFees.test.ts:462: This assertion path is brittle because it assumes zapOutIx.keys[14] will always be the first DAMM v2 swap source account. That index is derived from Jupiter/protocol-zap account ordering, so a future rev bump can silently make this mutate

Claude (API):

The primary review now covers all three key issues: the wrong-pool-variable bug (line 432 and 470), the zero-fee risk (line 435), and the brittle hard-coded index (line 462). The prior critique's findings were accepted (2/2) and the open question was kept for the author. I have no additional high-signal findings to add and no false positives to retract.

Codex (response):

Agreement reached. No further changes.

Synthesis

Review synthesis

Top differences in emphasis

  1. Correctness vs. maintainability: Buddy flags a likely test bug (getPool(svm, pool) uses the wrong pool) and a possible zero-fee false positive; Codex does not mention these.
  2. Shared issue, different framing: Both call out the hard-coded zapOutIx.keys[14], but Codex emphasizes future silent breakage/maintainability, while Buddy says the substitution logic is otherwise sound and mainly fragile.
  3. Dependency update impact: Buddy notes clippy regressions from the dependency bump (pre-existing); Codex says the pin update mostly matches intent and focuses only on the test.

Most important follow-up

  • Fix the new integration test’s pool-state bug first: use dammV2PoolAddress consistently when reading pool state, so the test validates the intended pool before addressing the brittle hard-coded account index.
Repo checks

Repo Checks

  • LLM checks planner: added package install step before running JS commands.

  • rustup component add --toolchain 1.85.0-x86_64-unknown-linux-gnu rustfmt: ok

info: downloading component rustfmt
  • rustup component add --toolchain 1.85.0-x86_64-unknown-linux-gnu clippy: ok
info: component clippy is up to date
  • cargo fetch --locked: ok
Updating git repository `https://github.com/MeteoraAg/zap-program`
From https://github.com/MeteoraAg/zap-program
 * [new ref]         e44c59b5b6cf0324bcc4d3bc182811a8762beeb3 -> refs/commit/e44c59b5b6cf0324bcc4d3bc182811a8762beeb3
  • pnpm install --frozen-lockfile: ok
Lockfile is up to date, resolution step is skipped
Progress: resolved 1, reused 0, downloaded 0, added 0
Packages: +192
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 192, reused 192, downloaded 0, added 192, done

dependencies:
+ @coral-xyz/anchor 0.31.1
+ @coral-xyz/borsh 0.31.1
+ @solana/spl-token 0.4.14
+ @solana/web3.js 1.98.4
+ borsh 2.0.0
+ decimal.js 10.6.0
+ litesvm 0.1.0

devDependencies:
+ @solana/spl-token-metadata 0.1.6
+ @types/bn.js 5.2.0
+ @types/chai 4.3.20
+ @types/mocha 9.1.1
+ chai 4.5.0
+ mocha 9.2.2
+ prettier 2.8.8
+ ts-mocha 10.1.0
+ typescript 5.9.3

╭ Warning ─────────────────────────────────────────────────────────────────────╮
│                                                                              │
│   Ignored build scripts: bigint-buffer@1.1.5, bufferutil@4.0.9,              │
│   utf-8-validate@5.0.10.                                                     │
│   Run "pnpm approve-builds" to pick which dependencies should be allowed     │
│   to run scripts.                                                            │
│                                                                              │
╰──────────────────────────────────────────────────────────────────────────────╯
Done in 1s using pnpm v10.33.0

! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-10.33.0.tgz
  • cargo fmt --check: ok
  • cargo clippy --workspace --all-targets -- -D warnings: failed
ee_mode.fees_on_input);
   |

error: used `assert_eq!` with a literal bool
  --> programs/cp-amm/src/tests/test_collect_fee_mode.rs:60:5
   |
60 |     assert_eq!(fee_mode.fees_on_token_a, false);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
help: replace it with `assert!(..)`
   |
60 -     assert_eq!(fee_mode.fees_on_token_a, false);
60 +     assert!(!fee_mode.fees_on_token_a);
   |

error: importing legacy numeric constants
  --> programs/cp-amm/src/tests/test_liquidity_compounding.rs:18:5
   |
18 | use std::u64;
   |     ^^^^^^^^
   |
   = help: remove this import
   = note: then `u64::<CONST>` will resolve to the respective associated constant
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants

error: manual `RangeInclusive::contains` implementation
  --> programs/cp-amm/src/tests/test_liquidity_compounding.rs:34:9
   |
34 |         sqrt_price >= MIN_SQRT_PRICE && sqrt_price <= MAX_SQRT_PRICE,
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `(MIN_SQRT_PRICE..=MAX_SQRT_PRICE).contains(&sqrt_price)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains

error: could not compile `cp-amm` (lib) due to 117 previous errors
warning: build failed, waiting for other jobs to finish...
error: casting to the same type is unnecessary (`u16` -> `u16`)
   --> programs/cp-amm/src/params/fee_parameters.rs:167:38
    |
167 |             self.reduction_factor <= MAX_BASIS_POINT as u16,
    |                                      ^^^^^^^^^^^^^^^^^^^^^^ help: try: `MAX_BASIS_POINT`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast

error: could not compile `cp-amm` (lib test) due to 182 previous errors

const poolState = getPool(svm, pool);
const operatorAddress = whitelistedAccount.publicKey;

const claimAmount = poolState.metrics.totalProtocolAFee;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even after fixing the pool reference, this test can still be non-deterministic if the target pool has not accrued any protocol fees yet: claimAmount may be zero, and claiming zero can succeed or fail for reasons unrelated to the mismatched-account validation. Consider generating fees on dammV2PoolAddress before the assertion, or at minimum assert claimAmount.gtn(0) so the test cannot silently pass with a zero claim.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

// base offset is 12 (2 (ix_zap_out) + 9 (jup route accounts) + 1 (jup route swap_program)).
// the damm v2 input_token_account is the 3rd account (index 2). so (12 + 3 - 1 = 14)
// https://github.com/jup-ag/jupiter-aggregator-program/blob/e583ab6619f4646b4d7a0e2514aec62ae9fb62ec/dex_interfaces/src/lib.rs#L6150
zapOutIx.keys[14] = {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assertion path is brittle because it assumes zapOutIx.keys[14] will always be the first DAMM v2 swap source account. That index is derived from Jupiter/protocol-zap account ordering, so a future rev bump can silently make this mutate the wrong key and still produce a misleading pass/fail. Please add a guard before overwriting (for example, assert that keys[14].pubkey is the expected receiverToken) or derive the position from the generated route accounts so layout changes fail loudly.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

@bangyro
Copy link
Collaborator Author

bangyro commented Mar 25, 2026

/bot review

@metoeraag-bot
Copy link

metoeraag-bot bot commented Mar 25, 2026

Bot Agent progress: /bot review

Progress:

  • Repo checks (3 ok, 1 failed)
  • Context build
  • [~] Codex review (1 pass + first look)
  • Buddy review
  • Post review

@metoeraag-bot
Copy link

metoeraag-bot bot commented Mar 25, 2026

Bot Agent progress: /bot review

Progress:

  • [~] Repo checks
  • Context build
  • Codex review
  • Buddy review
  • Post review

@metoeraag-bot
Copy link

metoeraag-bot bot commented Mar 25, 2026

Bot Agent progress: /bot review

Progress:

  • Repo checks (4 ok, 1 failed)
  • Context build
  • Codex review (1 pass + first look)
  • Buddy review (agreed after 2/10)
  • Post review

Copy link

@metoeraag-bot metoeraag-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

Request changes

Summary

⚠️ Limited-confidence review: check failed: cargo clippy --workspace --all-targets -- -D warnings.

There's a merge-blocking correctness issue in the new negative test: it reads pool state from and submits zapProtocolFee against the outer-scope pool instead of the freshly created dammV2PoolAddress, so the assertion can pass for the wrong reason. I also still see a non-blocking brittleness issue from hardcoding the Jupiter account index (keys[14]), which makes the test fragile to upstream route/account-order changes.

Findings
  • [P0] tests/zapProtocolFees.test.ts:432 Bug: pool here refers to the outer-scope pool, not dammV2PoolAddress created above. Both getPool(svm, pool) and the later zapProtocolFee({ pool, ... }) call should use dammV2PoolAddress. As written, the test mixes pool state and instruction inputs from different pools, so the negative assertion may pass vacuously for a pool mismatch instead of the intended invalid source-account case.
  • [P3] tests/zapProtocolFees.test.ts:462 This test is tightly coupled to the current Jupiter/DAMM v2 account ordering by hardcoding keys[14]. Since this PR already bumps the upstream zap/Jupiter dependency, a future route-shape change can make the test mutate the wrong account and either fail for the wrong reason or stop exercising InvalidZapAccounts at all. If possible, have buildZapOutJupV6UsingDammV2RouteInstruction expose the source-account position (or locate it by pubkey/role) instead of relying on a magic absolute index.
Claude (API) review

Rounds: 2 (min=1, max=10).
First look: Codex + Claude (API).
Agreement: yes (no new findings/questions).

Synthesis

Review synthesis

Top differences in emphasis

  1. Severity: Codex sees the PR as largely correct with one P3 brittleness issue; Buddy treats it as request changes because of a P0 correctness bug.
  2. Primary risk: Codex focuses on the fragile hardcoded Jupiter account index (keys[14]); Buddy agrees on that, but says the bigger problem is the test may be reading the wrong pool state entirely.
  3. Additional scope issue: Buddy also flags tokenBMint coming from an outer scope as another source of false-positive behavior; Codex does not mention this.

Most important follow-up action

  • Fix the test to use the newly created pool/address consistently (dammV2PoolAddress / correct mint variables) before addressing the hardcoded account index. If the test is reading stale state, its assertions may be invalid regardless of the account-index brittleness.
Repo checks

Repo Checks

  • LLM checks planner: added package install step before running JS commands.

  • rustup component add --toolchain 1.85.0-x86_64-unknown-linux-gnu clippy: ok

info: downloading component clippy
  • cargo fetch --locked: ok
Updating git repository `https://github.com/MeteoraAg/zap-program`
From https://github.com/MeteoraAg/zap-program
 * [new ref]         e44c59b5b6cf0324bcc4d3bc182811a8762beeb3 -> refs/commit/e44c59b5b6cf0324bcc4d3bc182811a8762beeb3
  • pnpm install --frozen-lockfile: ok
Lockfile is up to date, resolution step is skipped
Progress: resolved 1, reused 0, downloaded 0, added 0
Packages: +192
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Progress: resolved 192, reused 91, downloaded 0, added 0
Progress: resolved 192, reused 192, downloaded 0, added 192, done

dependencies:
+ @coral-xyz/anchor 0.31.1
+ @coral-xyz/borsh 0.31.1
+ @solana/spl-token 0.4.14
+ @solana/web3.js 1.98.4
+ borsh 2.0.0
+ decimal.js 10.6.0
+ litesvm 0.1.0

devDependencies:
+ @solana/spl-token-metadata 0.1.6
+ @types/bn.js 5.2.0
+ @types/chai 4.3.20
+ @types/mocha 9.1.1
+ chai 4.5.0
+ mocha 9.2.2
+ prettier 2.8.8
+ ts-mocha 10.1.0
+ typescript 5.9.3

╭ Warning ─────────────────────────────────────────────────────────────────────╮
│                                                                              │
│   Ignored build scripts: bigint-buffer@1.1.5, bufferutil@4.0.9,              │
│   utf-8-validate@5.0.10.                                                     │
│   Run "pnpm approve-builds" to pick which dependencies should be allowed     │
│   to run scripts.                                                            │
│                                                                              │
╰──────────────────────────────────────────────────────────────────────────────╯
Done in 2.5s using pnpm v10.33.0

! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-10.33.0.tgz
  • cargo check --workspace: ok
   Checking solana-loader-v3-interface v3.0.0
    Checking solana-loader-v4-interface v2.2.1
    Checking solana-program-pack v2.2.1
    Checking solana-loader-v2-interface v2.2.1
    Checking solana-stable-layout v2.2.1
    Checking spl-discriminator v0.4.1
    Checking spl-memo v6.0.0
    Checking spl-associated-token-account-client v2.0.0
    Checking solana-sysvar v2.2.1
   Compiling anchor-attribute-error v0.31.1
   Compiling anchor-attribute-access-control v0.31.1
   Compiling anchor-attribute-event v0.31.1
   Compiling anchor-attribute-program v0.31.1
   Compiling anchor-derive-accounts v0.31.1
   Compiling anchor-attribute-constant v0.31.1
    Checking solana-program v2.2.1
    Checking spl-program-error v0.6.0
   Compiling anchor-derive-serde v0.31.1
   Compiling anchor-attribute-account v0.31.1
    Checking spl-token v7.0.0
    Checking spl-pod v0.5.1
    Checking spl-token-confidential-transfer-ciphertext-arithmetic v0.2.1
    Checking spl-token-confidential-transfer-proof-generation v0.2.0
    Checking spl-type-length-value v0.7.0
    Checking spl-token-confidential-transfer-proof-extraction v0.2.1
    Checking spl-token-group-interface v0.5.0
    Checking spl-tlv-account-resolution v0.9.0
    Checking spl-token-metadata-interface v0.6.0
    Checking spl-elgamal-registry v0.1.1
    Checking spl-transfer-hook-interface v0.9.0
    Checking spl-token-2022 v6.0.0
    Checking anchor-lang v0.31.1
    Checking jupiter v0.1.0 (https://github.com/MeteoraAg/zap-program?rev=e44c59b5b6cf0324bcc4d3bc182811a8762beeb3#e44c59b5)
    Checking spl-associated-token-account v6.0.0
    Checking protocol-zap v0.1.0 (https://github.com/MeteoraAg/zap-program?rev=e44c59b5b6cf0324bcc4d3bc182811a8762beeb3#e44c59b5)
    Checking anchor-spl v0.31.1
    Checking cp-amm v0.2.0 (/data/workdir/2962945/MeteoraAg/damm-v2/programs/cp-amm)
    Checking rust-sdk v0.2.0 (/data/workdir/2962945/MeteoraAg/damm-v2/rust-sdk)
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 50.94s
  • cargo clippy --workspace --all-targets -- -D warnings: failed
ee_mode.fees_on_input);
   |

error: used `assert_eq!` with a literal bool
  --> programs/cp-amm/src/tests/test_collect_fee_mode.rs:60:5
   |
60 |     assert_eq!(fee_mode.fees_on_token_a, false);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#bool_assert_comparison
help: replace it with `assert!(..)`
   |
60 -     assert_eq!(fee_mode.fees_on_token_a, false);
60 +     assert!(!fee_mode.fees_on_token_a);
   |

error: importing legacy numeric constants
  --> programs/cp-amm/src/tests/test_liquidity_compounding.rs:18:5
   |
18 | use std::u64;
   |     ^^^^^^^^
   |
   = help: remove this import
   = note: then `u64::<CONST>` will resolve to the respective associated constant
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#legacy_numeric_constants

error: manual `RangeInclusive::contains` implementation
  --> programs/cp-amm/src/tests/test_liquidity_compounding.rs:34:9
   |
34 |         sqrt_price >= MIN_SQRT_PRICE && sqrt_price <= MAX_SQRT_PRICE,
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use: `(MIN_SQRT_PRICE..=MAX_SQRT_PRICE).contains(&sqrt_price)`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#manual_range_contains

error: casting to the same type is unnecessary (`u16` -> `u16`)
   --> programs/cp-amm/src/params/fee_parameters.rs:167:38
    |
167 |             self.reduction_factor <= MAX_BASIS_POINT as u16,
    |                                      ^^^^^^^^^^^^^^^^^^^^^^ help: try: `MAX_BASIS_POINT`
    |
    = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_cast

error: could not compile `cp-amm` (lib) due to 117 previous errors
warning: build failed, waiting for other jobs to finish...
error: could not compile `cp-amm` (lib test) due to 182 previous errors

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant