Skip to content

Conversation

@ananas-block
Copy link

@ananas-block ananas-block commented Jan 20, 2026

- Remove non-existent example directories from matrix (counter, basic-operations, etc.)
- Keep only anchor/escrow and anchor/light-token-minter programs that exist
- Fix unused mut warning in minter_test.rs
- Update workspace dependencies to use crates.io versions instead of git
- light-sdk 0.18.0, light-token 0.3.0, light-hasher 5.0.0
- light-program-test 0.18.0, light-client 0.18.0
- Remove unnecessary dependencies that are re-exported:
  - light-sdk-macros, light-sdk-types (re-exported by light-sdk/light-token)
  - light-compressible (re-exported by light-sdk)
  - light-token-interface, light-token-types (re-exported by light-token)
- Update imports to use re-exported paths
…creation

- Switch light-protocol dependencies from crates.io to git rev 5a6ad4b
- Add vault_bump to MakeOfferParams for PDA signing
- Fix light_account authority seeds to use vault PDA seeds instead of auth seeds
- Remove unused CreateTokenAccountCpi import
The light-token MintTo instruction creates an AccountMeta with writable
flag for the authority account. This caused PrivilegeEscalation errors
because mint_authority was only marked as signer, not writable.
Crowdfunding program using:
- SPL mint for the token to raise
- SPL ATAs for contributor token accounts
- Light Protocol token account for the vault
- SPL interface for SPL<->Light transfers

Instructions: initialize, contribute, check_contributions, refund
Token swap AMM program using:
- SPL mints for token A and token B
- SPL ATAs for depositor/trader accounts
- Light Protocol token accounts for pool_account_a and pool_account_b
- SPL mint for liquidity tokens
- SPL interface for SPL<->Light transfers

Instructions: create_amm, create_pool, deposit_liquidity, swap, withdraw_liquidity
derive_mint_compressed_address(&mint_seed.pubkey(), &address_tree.tree);
let (mint, bump) = find_mint_address(&mint_seed.pubkey());

let rpc_result = rpc
Copy link
Contributor

Choose a reason for hiding this comment

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

use create proof inputs

Replace external path dependencies with their published crates.io versions:
- light-sdk: 0.19.0
- light-sdk-macros: 0.19.0
- light-hasher: 5.0.0
- light-token: 0.4.0
- light-program-test: 0.19.0
- light-client: 0.19.0
Copy link

@devin-ai-integration devin-ai-integration bot left a comment

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

View issues and 6 additional flags in Devin Review.

Open in Devin Review

pub fn contribute(&mut self, amount: u64) -> Result<()> {
// Check if the amount to contribute meets the minimum amount required
require!(
amount >= 1_u64.pow(self.mint_to_raise.decimals as u32),

Choose a reason for hiding this comment

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

🟡 Minimum contribution check is always 1 due to incorrect exponentiation base

The minimum contribution check uses 1_u64.pow(self.mint_to_raise.decimals as u32) which always evaluates to 1 regardless of the decimals value, since 1 raised to any power is 1.

Click to expand

Mechanism

The code at anchor/programs/fundraiser/src/instructions/contribute.rs:75 checks:

require!(
    amount >= 1_u64.pow(self.mint_to_raise.decimals as u32),
    FundraiserError::ContributionTooSmall
);

For a token with 9 decimals:

  • 1_u64.pow(9) = 1 (since 1^9 = 1)
  • The intended check was likely 10_u64.pow(9) = 1,000,000,000 (representing 1 whole token)

Actual vs Expected

  • Actual: Minimum contribution is 1 token unit (smallest possible amount)
  • Expected: Minimum contribution should be 1 whole token (10^decimals units)

Impact

This allows contributors to make dust contributions (e.g., 1 lamport worth of tokens), which could be used to spam the fundraiser with minimal cost or create many contributor accounts.

Recommendation: Change 1_u64.pow(self.mint_to_raise.decimals as u32) to 10_u64.pow(self.mint_to_raise.decimals as u32) to require a minimum of 1 whole token.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +78 to +82
require!(
self.fundraiser.duration
>= ((current_time - self.fundraiser.time_started) / SECONDS_TO_DAYS) as u16,
FundraiserError::FundraiserNotEnded
);

Choose a reason for hiding this comment

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

🔴 Refund time check is inverted - allows refunds during active fundraiser

The refund function's time check condition is inverted, allowing refunds while the fundraiser is still active instead of only after it has ended.

Click to expand

Mechanism

The code at anchor/programs/fundraiser/src/instructions/refund.rs:78-82 checks:

require!(
    self.fundraiser.duration
        >= ((current_time - self.fundraiser.time_started) / SECONDS_TO_DAYS) as u16,
    FundraiserError::FundraiserNotEnded
);

This condition duration >= elapsed_days is true when the fundraiser is still ongoing (elapsed time is less than or equal to duration).

Comparison with contribute.rs

In contribute.rs:89-92, the check is:

require!(
    elapsed_days < self.fundraiser.duration,
    FundraiserError::FundraiserEnded
);

This correctly allows contributions only while elapsed_days < duration.

Actual vs Expected

  • Actual: Refunds are allowed when elapsed_days <= duration (during the fundraiser)
  • Expected: Refunds should only be allowed when elapsed_days >= duration (after the fundraiser ends)

Impact

Contributors can request refunds while the fundraiser is still active, potentially draining the vault before the fundraiser has a chance to reach its goal. This defeats the purpose of the fundraiser mechanism.

Recommendation: Change the condition to ((current_time - self.fundraiser.time_started) / SECONDS_TO_DAYS) as u16 >= self.fundraiser.duration to only allow refunds after the fundraiser duration has passed.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +47 to +66
let ratio = I64F64::from_num(pool_a_balance)
.checked_mul(I64F64::from_num(pool_b_balance))
.unwrap();
if pool_a_balance > pool_b_balance {
(
I64F64::from_num(amount_b)
.checked_mul(ratio)
.unwrap()
.to_num::<u64>(),
amount_b,
)
} else {
(
amount_a,
I64F64::from_num(amount_a)
.checked_div(ratio)
.unwrap()
.to_num::<u64>(),
)
}

Choose a reason for hiding this comment

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

🔴 Incorrect ratio calculation in deposit_liquidity uses product instead of ratio

The liquidity deposit ratio calculation multiplies pool balances instead of dividing them, resulting in wildly incorrect deposit amounts that don't maintain the pool's price ratio.

Click to expand

Mechanism

The code at anchor/programs/token-swap/src/instructions/deposit_liquidity.rs:47-66 calculates:

let ratio = I64F64::from_num(pool_a_balance)
    .checked_mul(I64F64::from_num(pool_b_balance))
    .unwrap();
if pool_a_balance > pool_b_balance {
    (
        I64F64::from_num(amount_b)
            .checked_mul(ratio)  // amount_a = amount_b * (pool_a * pool_b)
            .unwrap()
            .to_num::<u64>(),
        amount_b,
    )
} else {
    (
        amount_a,
        I64F64::from_num(amount_a)
            .checked_div(ratio)  // amount_b = amount_a / (pool_a * pool_b)
            .unwrap()
            .to_num::<u64>(),
    )
}

Correct Formula

To maintain the pool ratio when adding liquidity:

  • amount_a / amount_b = pool_a_balance / pool_b_balance
  • So amount_a = amount_b * (pool_a_balance / pool_b_balance)

Example

With pool_a = 1000, pool_b = 500, and user wants to deposit amount_b = 100:

  • Actual: ratio = 1000 * 500 = 500000, amount_a = 100 * 500000 = 50,000,000
  • Expected: ratio = 1000 / 500 = 2, amount_a = 100 * 2 = 200

Impact

Depositors will have their deposits calculated with completely wrong amounts, either depositing far more than intended or receiving incorrect LP token amounts. This breaks the AMM's constant product invariant and can lead to significant financial losses.

Recommendation: Change the ratio calculation to use division: let ratio = I64F64::from_num(pool_a_balance).checked_div(I64F64::from_num(pool_b_balance)).unwrap(); and adjust the subsequent calculations accordingly.

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

3 participants