Skip to content
Quantix Security Labs

Risk summary

State at remediation commit 5343f231c925

Findings
6
Critical and high
2
Open or partial
0
Remediation
100%

Findings by severity

  • Critical1
  • High1
  • Medium2
  • Low2
  • Informational0

Remediation statusAnalysis

  • Resolved5
  • Partially resolved0
  • Acknowledged1
  • Open0

Corvid AMM

DEX

Document
QX-TSA-2026-003 · v1.1
Type
Smart Contract Audit
Review
11 May 2026 – 20 May 2026
Published
2 Jun 2026
Ecosystems
Solana
Languages
Rust (Anchor)
Risk Index
BBB76.1
Assessment contents · 12 sections

01

Assessment overview

Corvid AMM is a constant-product market maker on Solana built with Anchor. On Solana every account an instruction touches is supplied by the caller, so the review concentrated on whether each account is constrained to the one the program expects.

Six findings were reported. The critical issue allowed an attacker to burn tokens from a mint they control and withdraw the pool’s reserves, because the withdrawal instruction did not check that the LP mint belonged to the pool. A high-severity issue let anyone withdraw protocol fees because the fee authority was compared but never required to sign.

All critical, high and medium findings were fixed and verified, and negative tests were added for every account constraint. One low finding about pool initialisation was acknowledged with a front-end mitigation.

02

Technical analysis: protocol architecture

Each pool is a program-derived account (PDA) seeded by the two token mints. The pool PDA is the authority over two SPL token vaults that hold the reserves, and over the LP mint.

Swaps transfer the input token into one vault and the computed output from the other, preserving reserve_a × reserve_b after fees. Deposits mint LP tokens in proportion to the contribution; withdrawals burn LP tokens and return a proportional share of both vaults.

A fee authority stored in the pool account can withdraw accumulated protocol fees, tracked separately from LP reserves.

Trusted· 5

Assumed to behave as specified; compromise is out of the threat model.

  • corvid_amm programInstruction handlers and invariant checks
  • Pool PDAPool state; authority over vaults and LP mint
  • Token vaultsSPL token accounts holding reserves
  • LP mintMint for liquidity provider tokens
  • Fee authorityWithdraws protocol fees
trust boundary

Untrusted· 1

May act adversarially; every input is validated.

  • Caller-supplied accountsEvery account passed to an instruction
Components grouped by trust level. Calls that cross a trust boundary are where the review concentrated input validation and authorisation checks.

Trust assumptions

  • The program upgrade authority is held by the protocol team and not compromised.
  • Pooled tokens use the standard SPL Token program; Token-2022 extensions such as transfer fees and hooks are not supported.
  • The fee authority key is held securely.

03

Scope

Pool initialisation, swaps, liquidity deposits and withdrawals, and fee collection. 2,310 lines of Rust.

Repository
github.com/example-org/corvid-amm
Review effort
Two reviewers, eight working days
Reviewed commit
b1a8ddef6dd1966c179277f2f929dbf74114494e
Remediation commit
5343f231c925b19fbf5a2f800d728f95337ab06e
Files and components in scope
PathDescriptionnSLOC
programs/corvid-amm/src/instructions/initialize_pool.rsPool creation, PDA derivation and vault setup286
programs/corvid-amm/src/instructions/swap.rsConstant-product swap with fees and slippage checks412
programs/corvid-amm/src/instructions/deposit.rsLiquidity deposits and LP token minting344
programs/corvid-amm/src/instructions/withdraw.rsLP token burning and proportional withdrawal318
programs/corvid-amm/src/instructions/collect_fees.rsProtocol fee withdrawal142
programs/corvid-amm/src/state.rsPool account layout and invariant helpers230
Total6 items1,732

Out of scope

  • TypeScript SDK and front end
  • Upgrade-authority management of the deployed program
  • The SPL Token program and Anchor framework (assumed to behave as documented)

04

Threat model

Each threat lists the actor, the vector and the mitigation, and links to the findings that realised it in the reviewed code.

Assets at risk

  • Pool reserves in both vaults
  • Accrued protocol fees
  • LP token supply integrity

Actors

  • TraderUntrustedCalls swap with any accounts
  • Liquidity providerUntrustedDeposits and withdraws with any accounts
  • Fee authorityTrustedWithdraws protocol fees
Threats, vectors, mitigations and related findings
ThreatActorVectorMitigationFindings
Reserve drain through a substituted LP mintLiquidity providerPassing an attacker-controlled mint to withdrawAddress and has_one constraintsCA-01
Unauthorised fee withdrawalTraderPassing the authority key without its signatureSigner constraintCA-02
Invariant bypass through overflowTraderLarge reserves with high-decimal tokensu128 checked arithmeticCA-03
Fee avoidanceTraderSplitting trades below the rounding thresholdMultiply before divide, round upCA-04

05

Findings

Severity

6 findings

CA-01CriticalResolvedAccount validation

Withdraw does not verify that the LP mint belongs to the pool

programs/corvid-amm/src/instructions/withdraw.rs:L18–L52

Likelihood
High
Impact
High
Weakness
CWE-345
Description

The Withdraw accounts struct declares lp_mint: Account<'info, Mint> without an address = pool.lp_mint or has_one = lp_mint constraint. Anchor therefore verifies only that the account is a valid mint owned by the SPL Token program, not that it is this pool’s mint.

The share of reserves returned is computed as lp_amount / lp_mint.supply applied to both vaults, and the burn is performed against the supplied mint. An attacker can create a new mint, mint a single token to themselves, and pass that mint and token account to withdraw.

The program burns the attacker’s one token from the fake mint, computes a share of 1/1 = 100%, and transfers the entire contents of both vaults.

Preconditions
  • The attacker can create an SPL mint (anyone can)
  • The pool holds non-zero reserves
Exploit scenario
  1. 1.Attacker creates mint M with themselves as mint authority and mints 1 token.
  2. 2.Attacker calls withdraw on the target pool, passing M as lp_mint and their M token account as the LP source.
  3. 3.The program burns 1 token of M and computes share = 1 / supply(M) = 100%.
  4. 4.Both vaults are transferred to the attacker’s token accounts in one transaction.
Impact

Complete loss of both reserves in every pool, in a single transaction per pool, with no capital required.

Recommendation

Constrain lp_mint to pool.lp_mint (for example #[account(mut, address = pool.lp_mint)] or has_one = lp_mint on the pool), and add a negative test that passes a foreign mint and expects failure.

Fix
programs/corvid-amm/src/instructions/withdraw.rsdiff
   #[derive(Accounts)]   pub struct Withdraw<'info> {-      #[account(mut)]+      #[account(mut, has_one = lp_mint, has_one = vault_a, has_one = vault_b)]       pub pool: Account<'info, Pool>,-      #[account(mut)]+      #[account(mut, address = pool.lp_mint)]       pub lp_mint: Account<'info, Mint>,
Resolution

Address constraint added, plus has_one constraints for the mint and both vaults. Negative tests pass a foreign mint and foreign vaults and expect ConstraintHasOne errors.

CA-02HighResolvedAccess control

Fee authority is compared but not required to sign

programs/corvid-amm/src/instructions/collect_fees.rs:L11–L29

Likelihood
High
Impact
Medium
Weakness
CWE-862
Description

collect_fees takes authority: UncheckedAccount<'info> and checks authority.key() == pool.fee_authority. The comparison proves only that the caller knows the authority’s public key, which is public. The account is never required to sign the transaction.

On Solana, authorisation comes from the signer flag on the account meta. Without Signer<'info> (or an explicit is_signer check), any caller can satisfy the comparison.

Preconditions
  • Accumulated protocol fees greater than zero
Exploit scenario
  1. 1.Attacker reads pool.fee_authority from the pool account.
  2. 2.Attacker calls collect_fees passing that public key as authority (unsigned) and their own token account as destination.
  3. 3.Fees are transferred to the attacker.
Impact

Loss of all accumulated protocol fees across all pools. LP reserves are not affected because fees are tracked separately.

Recommendation

Declare the account as Signer<'info> and keep the key comparison through has_one = fee_authority on the pool.

Fix
programs/corvid-amm/src/instructions/collect_fees.rsdiff
   #[derive(Accounts)]   pub struct CollectFees<'info> {-      pub pool: Account<'info, Pool>,-      /// CHECK: compared against pool.fee_authority-      pub authority: UncheckedAccount<'info>,+      #[account(mut, has_one = fee_authority)]+      pub pool: Account<'info, Pool>,+      pub fee_authority: Signer<'info>,
Resolution

Account changed to Signer with a has_one constraint; negative test with an unsigned authority added.

CA-03MediumResolvedPrecision and rounding

Constant-product calculation can overflow in release builds

programs/corvid-amm/src/state.rs:L88–L104

Likelihood
Low
Impact
High
Weakness
CWE-190
Description

The invariant reserve_a * reserve_b is computed in u64. Rust release builds wrap on integer overflow unless overflow-checks = true is set in the release profile, and the workspace did not set it.

Two vaults of a 9-decimal token holding about 4.3 million whole tokens each already exceed u64::MAX when multiplied. At that point the computed product wraps to a small number and the invariant check after a swap compares against a meaningless value.

Preconditions
  • Reserves large enough that the product exceeds 2^64
  • Release build without overflow checks
Exploit scenario
  1. 1.A pool grows past the overflow threshold.
  2. 2.A swap’s post-trade product wraps and passes the invariant check at an incorrect price.
  3. 3.The trader receives more output than the curve allows.
Impact

Swaps can succeed at incorrect prices once reserves are large, extracting value from liquidity providers.

Recommendation

Compute in u128 with checked_mul, return an explicit error on overflow, and enable overflow-checks = true in the release profile as defence in depth.

Fix
programs/corvid-amm/src/state.rsdiff
-  let k = self.reserve_a * self.reserve_b;+  let k = (self.reserve_a as u128)+      .checked_mul(self.reserve_b as u128)+      .ok_or(AmmError::MathOverflow)?;
Resolution

Moved to u128 checked arithmetic throughout; overflow-checks enabled in Cargo.toml.

CA-04MediumResolvedPrecision and rounding

Swap fee rounds down to zero for small trades

programs/corvid-amm/src/instructions/swap.rs:L71

Likelihood
High
Impact
Low
Weakness
CWE-682
Description

The fee is computed as amount_in / 10_000 * fee_bps. Division before multiplication truncates the intermediate result, so any trade below 10,000 base units pays no fee at all, and larger trades are under-charged by up to fee_bps − 1 units.

On Solana, transaction fees are low enough that splitting a large trade into many small ones can be cheaper than paying the swap fee.

Preconditions
  • A non-zero fee_bps configuration
Exploit scenario
  1. 1.Trader splits a large swap into many trades just below 10,000 base units in one transaction.
  2. 2.Each trade pays zero fee.
Impact

Liquidity providers lose fee income; value transfers to traders who split orders.

Recommendation

Multiply before dividing and round the fee up: (amount_in * fee_bps + 9_999) / 10_000 computed in u128.

Fix
programs/corvid-amm/src/instructions/swap.rsdiff
-  let fee = amount_in / 10_000 * fee_bps as u64;+  let fee = ((amount_in as u128) * (fee_bps as u128) + 9_999) / 10_000;+  let fee = u64::try_from(fee).map_err(|_| AmmError::MathOverflow)?;
Resolution

Formula corrected with upward rounding; PB-03 holds.

CA-05LowResolvedBusiness logic

Deposits have no minimum LP output

programs/corvid-amm/src/instructions/deposit.rs:L40

Likelihood
Medium
Impact
Low
Description

Swaps enforce minimum_amount_out, but deposits accept any LP amount. A deposit that lands after a large price move (or after a sandwiching trade) mints fewer LP tokens than the user was quoted.

Preconditions
  • Price moves between quote and execution
Exploit scenario
  1. 1.A searcher moves the pool price before a deposit lands.
  2. 2.The deposit mints fewer LP tokens; the searcher reverses the trade.
Impact

Depositors can receive materially fewer LP tokens than quoted.

Recommendation

Add a min_lp_out argument and revert when it is not met.

Fix
programs/corvid-amm/src/instructions/deposit.rsdiff
-  pub fn deposit(ctx: Context<Deposit>, amount_a: u64, amount_b: u64) -> Result<()> {+  pub fn deposit(ctx: Context<Deposit>, amount_a: u64, amount_b: u64, min_lp_out: u64) -> Result<()> {       let lp_out = ctx.accounts.pool.lp_for(amount_a, amount_b)?;+      require!(lp_out >= min_lp_out, AmmError::SlippageExceeded);
Resolution

Parameter added; SDK passes quoted values with a tolerance.

CA-06LowAcknowledgedBusiness logic

First depositor sets the initial price without bounds

programs/corvid-amm/src/instructions/initialize_pool.rs:L33

Likelihood
Medium
Impact
Low
Description

Pool creation and the first deposit are separate instructions. Between them, any third party can make the first deposit at an arbitrary ratio, setting the pool’s opening price.

Preconditions
  • A pool is created without an atomic first deposit
Exploit scenario
  1. 1.A pool is initialised.
  2. 2.A third party deposits at a skewed ratio before the creator.
  3. 3.The first traders trade at a misleading price until arbitrage corrects it.
Impact

The pool opens at a misleading price; the cost falls on whoever trades first.

Recommendation

Combine initialisation with the first deposit, or restrict the first deposit to the pool creator.

Resolution

Acknowledged. The front end creates and seeds pools atomically in one transaction; the program behaviour is documented for integrators.

06

Severity

6findings
  • Critical1 · 17%
  • High1 · 17%
  • Medium2 · 33%
  • Low2 · 33%
  • Informational0 · 0%

By vulnerability class

  • Business logic2
  • Precision and rounding2
  • Access control1
  • Account validation1

Likelihood and impact

Each finding placed by the likelihood of its preconditions and the impact of exploitation.

Findings by likelihood (columns) and impact (rows)
Impact ↓ / Likelihood →LowMediumHigh
High
0
Medium
0
0
Low
0

07

Remediation

5 of 6 findings are resolved, 1 acknowledged, 0 partially resolved and 0 open at remediation commit 5343f231c925.

Remediation progress100%

Resolved and acknowledged findings count fully; partially resolved findings count half.

100%remediated
  • Resolved5 · 83%
  • Partially resolved0 · 0%
  • Acknowledged1 · 17%
  • Open0 · 0%
Findings by severity and remediation status
SeverityResolvedPartially resolvedAcknowledgedOpenTotal
Critical10001
High10001
Medium20002
Low10102
Informational00000
All findings50106

Residual items

  • CA-06LowAcknowledgedAcknowledged. The front end creates and seeds pools atomically in one transaction; the program behaviour is documented for integrators.
  • ResolvedFixed and verified in the remediation review.
  • Partially resolvedMitigated; the residual risk is documented.
  • AcknowledgedAccepted by the protocol team with a stated rationale.
  • OpenNot yet addressed at the reviewed remediation commit.

08

Formal verification

No formal verification was performed; Solana program verification tooling was not in scope. The constant-product and LP-share properties below were checked with property-based tests over random instruction sequences.

Tool: Property-based testing (Rust)

  • PB-01Inconclusivereserve_a × reserve_b never decreases across a swap (after fees)Held across 1M random sequences after the CA-03 fix.
  • PB-02InconclusiveWithdrawn amounts never exceed the caller’s LP share of reservesViolated on the original commit via CA-01; held after the fix.
  • PB-03InconclusiveEvery swap pays a fee of at least one base unit when fee_bps > 0Violated on the original commit (CA-04); held after the fix.

A proved property holds for every execution of the model under its stated assumptions. Fuzzing and property-based results are evidence, not proofs, and are recorded as inconclusive.

09

Recommendations

  1. 01

    Constrain every account

    Adopt a rule that every account in every instruction has an explicit owner, address, seeds or has_one constraint, and keep a negative test for each.

  2. 02

    Support Token-2022 deliberately

    If Token-2022 mints are to be supported, review transfer fees, transfer hooks and confidential transfers before enabling them; until then reject them explicitly.

  3. 03

    Protect the upgrade authority

    The program is upgradeable. Hold the upgrade authority in a multisig and consider a timelocked upgrade process.

10

Methodology and limitations

  1. 01Manual review of every instruction and its account constraints
  2. 02Account-substitution analysis: for each account, what happens if the caller supplies a different one
  3. 03Property-based tests of the constant-product invariant across swap and liquidity sequences
  4. 04Arithmetic review of all integer operations under release-build overflow semantics
  5. 05Remediation review against the fix commit, including negative tests for each constraint

Severity classification

Critical
Direct loss of funds or permanent loss of control is achievable with little or no precondition.
High
Material loss or protocol disruption is achievable under realistic conditions.
Medium
Limited loss, degraded functionality, or impact that depends on specific conditions.
Low
Minor impact, defence-in-depth gaps, or issues with unlikely preconditions.
Informational
Code quality, documentation or best-practice observations without direct security impact.

Limitations

  • Only the program code at the listed commit was reviewed. The TypeScript SDK and front end were out of scope.
  • The SPL Token program and Anchor framework were treated as behaving as documented.
  • Upgrade-authority management for the deployed program was not assessed.

No assessment can show that a system is free of vulnerabilities. This research assessment reflects the code at the commits listed. Quantix methodology

11

Technical appendix

Tools

  • Anchor test frameworkIntegration and negative constraint tests
  • Property-based testingInvariant checks over random instruction sequences
  • Manual reviewAccount-substitution analysis for every instruction
Reviewed files (SHA-256) · 5 files
SHA-256 hashes of reviewed files
FileSHA-256
programs/corvid-amm/src/instructions/withdraw.rs7e3b94d8b5e77e27cdeb167637de152fcd6dfae823ea00e3a481aebbb1008aa4
programs/corvid-amm/src/instructions/swap.rs9d1afef9355e0b767d90b8304fa083f1105fed921d941ec14371b5cbd91e07cd
programs/corvid-amm/src/instructions/deposit.rs9ceb8b0c3ef7737279d2466be046aad07b3f629640e2a9597da13a7193d51130
programs/corvid-amm/src/instructions/collect_fees.rs338704eceed1f358ec2c173a274e993ad059ad650a5997b4f695a749e6997b8a
programs/corvid-amm/src/state.rs1e99070596a7d5325eb31e7682284ead71c6f296f472e3ac147925c11ba35926

Notes

  • Program built with Rust 1.79 and Anchor 0.30.

Technical references

  1. 1.Anchor account constraints (opens in a new tab)
  2. 2.Solana account model (opens in a new tab)
  3. 3.CWE-345: Insufficient Verification of Data Authenticity (opens in a new tab)
  4. 4.CWE-862: Missing Authorization (opens in a new tab)
  5. 5.Cargo profiles: overflow-checks (opens in a new tab)
  6. 6.CWE-190: Integer Overflow or Wraparound (opens in a new tab)
  7. 7.CWE-682: Incorrect Calculation (opens in a new tab)
  8. 8.Solana programs: instruction design (opens in a new tab)
  9. 9.Solana transactions: atomic instruction execution (opens in a new tab)

Quantix Technical Security Assessment QX-TSA-2026-003

Version 1.1, published 2 Jun 2026. A4 PDF generated from this assessment record.

Download Report

12

Report history

  1. 11 May 2026Review openedReviewed commit b1a8ddef6dd1 in github.com/example-org/corvid-amm
  2. 20 May 2026Review closedTwo reviewers, eight working days
  3. After reviewRemediation reviewFixes checked at commit 5343f231c925
  4. 2 Jun 2026Version 1.1 publishedQX-TSA-2026-003, A4 PDF generated from this record

Commission an assessment written to be verified.

Share the repository and the commit you plan to deploy. We reply with scoping questions and an estimate.