Nirvana Finance
Summary
ExpandThe Nirvana Finance protocol contains a critical access control vulnerability in the `set_debug_mode` instruction which has no signer or admin check, allowing any user to enable debug mode. Many privileged operations (minting, bond configuration, etc.) are gated behind a `is_debug` check, making this the master key to significant protocol damage.
This vulnerability is compounded by the `mint_pre_ana` instruction which lacks an admin access control check—only gated by `is_debug`. Together, these two issues allow any attacker to mint unlimited prANA tokens for free. Additionally, the LBP reward claim logic contains an inverted comparison that allows users who did not meet their commitment target to claim rewards, while blocking users who exceeded it.
The program makes good use of Anchor's constraint system for PDA validation and signer checks across most instructions. Checked arithmetic is consistently used. However, the critical access control gaps identified above undermine the overall security posture.
Findings
6 issues identified
set_debug_mode has no access control — anyone can enable debug mode
nirvana/src/instructions/set_debug_mode.rs:1
View Details
Description
The set_debug_mode instruction has no signer validation, admin check, or any access control. Any user can call this to enable debug_mode on NirvCenter, unlocking all is_debug-gated privileged functions including minting, bond configuration, and more.
Impact
Any user can enable debug mode on a production deployment, unlocking privileged operations across the entire protocol. Combined with the missing admin check on mint_pre_ana, this enables arbitrary token minting and treasury draining.
Recommendation
Add a Signer account and admin access control check to set_debug_mode, consistent with all other admin functions in the protocol.
Fix
Adds a Signer account and admin access control check so only the policy_owner can toggle debug mode, preventing unauthorized access to debug-gated functions.
nirvana/src/instructions/set_debug_mode.rs
mint_pre_ana lacks admin access control
nirvana/src/instructions/mint_pre_ana.rs:30
View Details
Description
The mint_pre_ana handler is only gated by is_debug and does not include an admin access control check. The authority Signer is never validated against nirv_center.policy_owner. When debug mode is enabled (either legitimately or via C-1), any signer can mint unlimited prANA tokens. This contrasts with mint_nirv and mint_alms which both correctly require admin + is_debug.
Impact
An attacker mints arbitrary prANA, converts it to ANA via realize_pre_ana (paying floor price), then sells ANA at market price via the swap AMM. The difference between market and floor price is extracted from the treasury. With sufficient volume, the entire treasury can be drained.
Recommendation
Add admin access control check to mint_pre_ana handler.
Fix
Adds the admin access_control check that validates ctx.accounts.authority matches nirv_center.policy_owner, consistent with mint_nirv and mint_alms. The admin import is already available via crate::utils.
nirvana/src/instructions/mint_pre_ana.rs
Inverted commitment check allows unqualified users to claim LBP rewards
nirvana/src/instructions/claim_lbp_rewards.rs:120
View Details
Description
The claim_lbp_rewards instruction checks commitment.target_spend_usd < history.net_spent_usd which rejects users who spent MORE than their target and allows users who spent LESS. The logic is inverted — users who didn’t meet their commitment target can claim rewards, while users who exceeded it are blocked.
Impact
Users can commit to a large spending target (earning a high reward rate), pay only the 1% escrow, spend nothing during bootstrap, and still claim the full prANA reward and escrow refund. This allows free extraction of prANA rewards and drains the USDC escrow.
Recommendation
Swap the comparison to reject users whose net spending is below their commitment target.
Fix
Corrects the comparison so that users who spent less than their committed target are rejected, and users who met or exceeded their target can claim rewards.
nirvana/src/instructions/claim_lbp_rewards.rs
reward_by_time config account not mutable — reward state never persists
nirvana/src/instructions/reward_by_time.rs:13
View Details
Description
The config account in RewardByTime is missing the mut constraint. The handler modifies config.reward_index (via drop_prana_reward) and config.time_of_last_prana_reward, but since the account is not marked mutable, Anchor does not serialize these changes back. All modifications are silently discarded at the end of the instruction.
Impact
The public reward distribution mechanism is completely non-functional. ANA stakers never accumulate prANA rewards through the permissionless reward_by_time path. Only admin-called reward() works (which has mut). This breaks the core staking incentive mechanism, affecting all protocol participants.
Recommendation
Add mut constraint to config account in RewardByTime.
Fix
Adding mut to the config account constraint ensures Anchor serializes the modified state (reward_index and time_of_last_prana_reward) back to the account at the end of the instruction, making reward distribution functional.
nirvana/src/instructions/reward_by_time.rs
Unvalidated mint in buyback allows draining arbitrary treasury token accounts
nirvana/src/instructions/buyback_ana.rs:18-56
View Details
Description
The BuybackAna instruction accepts an arbitrary mint_u with no constraint linking it to an expected money market or configuration. The treasury_u account is only validated as owner == nirv_center.key() and mint == mint_u.key(). A user can choose ANY mint for which nirv_center holds a treasury account, burn ANA, and receive ana_amount * floor_price worth of that token. The floor price is a protocol constant in USD terms, but there is no oracle price conversion for the chosen output token. If the protocol treasury holds high-value tokens (e.g., wBTC, SOL), a user could drain those at an incorrect 1:1-with-floor-price rate instead of the intended stablecoin payout.
Impact
An attacker can burn ANA and receive disproportionate value from treasury accounts holding non-stablecoin tokens, effectively draining the treasury of high-value assets at deeply discounted rates.
Recommendation
Validate that mint_u corresponds to an accepted money market mint by requiring a MoneyMarket account with PDA seeds verification, or constrain mint_u to a specific expected stablecoin mint stored in the config.
Fix
Missing PDA seeds validation on money_market in realize_pre_ana allows account substitution
nirvana/src/instructions/realize_pre_ana.rs:43-47
View Details
Description
The money_market account in RealizePreAna is validated only with constraint = money_market.nirv_center == nirv_center.key() but lacks the seeds and bump constraint that would verify it is the correct PDA. The developer acknowledged this in a comment: ‘if there is more than one money market this might be spoofed’. In contrast, the same money_market account in swap.rs properly uses seeds = [b"mm1".as_ref(), treasury_token_account.mint.as_ref(), nirv_center.key().as_ref()], bump = money_market.bump. An attacker could substitute a different legitimate MoneyMarket account (one with for_prana = true but backed by a different, cheaper underlying token) to realize prANA at a lower effective cost, since the payment amount is calculated with hardcoded 6 decimals rather than using the money_market’s actual decimal configuration.
Impact
If multiple MoneyMarket accounts exist with for_prana = true, an attacker could pick the one with the most favorable terms to realize prANA, potentially paying less than intended by the protocol. The treasury_u constraint ties to money_market.token_account, so the substituted market’s treasury would receive funds in a potentially less valuable token while the user receives the same amount of ANA.
Recommendation
Add seeds and bump validation to the money_market account to ensure only the intended PDA can be used, matching the pattern used in swap.rs.
Fix
Conclusion
ExpandThe codebase demonstrates functional understanding of the Solana programming model and the Anchor framework, but it lacks the defensive rigor expected of a DeFi protocol managing user deposits and token minting authority. Access control is applied inconsistently — some instructions correctly verify admin signers while others with equivalent privilege levels do not. Account validation is similarly uneven, with certain PDA derivations properly constrained by seeds while structurally identical accounts in other instructions omit these checks entirely. The mutable account annotation omission on reward_by_time (H-2) points to insufficient integration testing, as any test exercising the reward persistence path across multiple transactions would have surfaced the bug. Taken together, these findings indicate that the codebase has not undergone systematic security review prior to this engagement.
The two critical findings (C-1, C-2) and the treasury drain vector (H-3) should be remediated and deployed immediately, as they are trivially exploitable by any external actor and represent direct risk of fund loss or protocol manipulation. The inverted commitment check (H-1) and reward persistence failure (H-2) should follow in the same patch cycle, as they undermine the economic integrity of the LBP and staking subsystems. The PDA validation gap (M-1) should be addressed concurrently given its low implementation cost. Following remediation, the Nirvana team should conduct a comprehensive access control audit across all remaining instructions, add Anchor constraint annotations for every account in every instruction context, and establish an integration test suite that exercises privileged operations with unauthorized signers to prevent regressions of this class.
Legal Disclaimer: This report covers the code submitted for analysis. It does not account for infrastructure, deployment configuration, third-party dependencies, or changes made after the audit date. Automated analysis may produce false positives or miss context-dependent vulnerabilities. audited.xyz provides this report “as is” without warranty of any kind.