Conversation
4c9e894 to
61dfdf8
Compare
programs/anchor/basic-macros/create-associated-token-account/Cargo.toml
Outdated
Show resolved
Hide resolved
5526128 to
bd39ca7
Compare
Peer-to-peer escrow, crowdfunding, AMM swap, and mint helper programs demonstrating rent-free Light Token vaults with full test coverage across SPL, Token-2022, and Light token standards.
bd39ca7 to
6a7061c
Compare
programs/anchor/token-swap/src/instructions/deposit_liquidity.rs
Outdated
Show resolved
Hide resolved
programs/anchor/token-swap/src/instructions/deposit_liquidity.rs
Outdated
Show resolved
Hide resolved
programs/anchor/token-swap/src/instructions/swap_exact_tokens_for_tokens.rs
Show resolved
Hide resolved
programs/anchor/token-swap/src/instructions/swap_exact_tokens_for_tokens.rs
Outdated
Show resolved
Hide resolved
programs/anchor/token-swap/src/instructions/swap_exact_tokens_for_tokens.rs
Outdated
Show resolved
Hide resolved
programs/anchor/token-swap/src/instructions/swap_exact_tokens_for_tokens.rs
Outdated
Show resolved
Hide resolved
programs/anchor/token-swap/src/instructions/withdraw_liquidity.rs
Outdated
Show resolved
Hide resolved
programs/anchor/token-swap/src/instructions/withdraw_liquidity.rs
Outdated
Show resolved
Hide resolved
programs/anchor/token-swap/src/instructions/swap_exact_tokens_for_tokens.rs
Show resolved
Hide resolved
programs/anchor/token-swap/src/instructions/swap_exact_tokens_for_tokens.rs
Outdated
Show resolved
Hide resolved
Co-authored-by: ananas-block <58553958+ananas-block@users.noreply.github.com>
Co-authored-by: ananas-block <58553958+ananas-block@users.noreply.github.com>
Co-authored-by: ananas-block <58553958+ananas-block@users.noreply.github.com>
…for_tokens.rs Co-authored-by: ananas-block <58553958+ananas-block@users.noreply.github.com>
Co-authored-by: ananas-block <58553958+ananas-block@users.noreply.github.com>
| let mut liquidity = I64F64::from_num(amount_a) | ||
| .checked_mul(I64F64::from_num(amount_b)) | ||
| .ok_or(SwapError::Overflow)? | ||
| .sqrt() | ||
| .to_num::<u64>(); |
There was a problem hiding this comment.
🟡 AMM deposit_liquidity uses sqrt for subsequent deposits instead of proportional minting, allowing value extraction from existing LPs
For non-initial deposits, deposit_liquidity computes LP tokens as sqrt(amount_a * amount_b) (line 66-70). The correct formula for subsequent deposits is min(amount_a * total_supply / reserve_a, amount_b * total_supply / reserve_b), which ensures the new depositor's share is proportional to their contribution.
Root Cause and Impact
After swaps with fees, the pool invariant R_A * R_B increases beyond the initial a1 * b1. Since sqrt(R_A * R_B) > sqrt(a1 * b1), a proportional deposit of k% of each reserve yields k * sqrt(R_A * R_B) LP tokens — more than the proportional k * total_supply.
Concrete example:
- Initial deposit: 1000 A + 1000 B →
lp_supply = 900(sqrt(1M) - 100) - After swaps with fees, pool is 1100 A, 920 B (product = 1,012,000 > 1,000,000)
- Second depositor adds 10% of each: 110 A, 92 B
- Correct LP:
min(110*1000/1100, 92*1000/920) = 100 - Actual LP:
sqrt(110*92) ≈ 100.6
The 0.6 extra LP tokens dilute existing LP holders' share. The discrepancy scales with the accumulated swap fees. This is inherited from the original Solana Program Examples SPL AMM that this code ports, but it is a real correctness issue in the newly added program.
Prompt for agents
In programs/anchor/token-swap/src/instructions/deposit_liquidity.rs, replace the sqrt-based LP minting at lines 66-77 with a proportional formula for non-initial deposits. For initial deposits (pool_creation == true), keep sqrt(amount_a * amount_b) - MINIMUM_LIQUIDITY. For subsequent deposits, compute: liquidity = min(amount_a * (lp_supply + MINIMUM_LIQUIDITY) / pool_a_balance, amount_b * (lp_supply + MINIMUM_LIQUIDITY) / pool_b_balance) using the existing I64F64 fixed-point math to avoid overflow. The lp_supply value should be read from ctx.accounts.pool.lp_supply, and MINIMUM_LIQUIDITY is already imported from constants.
Was this helpful? React with 👍 or 👎 to provide feedback.
Now that the SDK returns ProgramError directly, the manual conversion at every call site is unnecessary.
…-compressed-account light-token was a path dep while light-client and light-program-test were registry deps, causing two copies of light-compressed-account@0.11.0 (path vs registry). This made CompressedProof types incompatible across the two dependency trees. Changed all 12 Light Protocol workspace deps to path deps pointing to the local monorepo.
Replace UncheckedAccount + Pubkey::default() sentinel with Option<AccountInfo> + .is_some() for optional SPL interface PDAs in fundraiser (contribute, checker, refund) and deposit_liquidity. Remove redundant .map_err() in transfer-interface and create-and-transfer.
| light-sdk = { path = "/home/tilo/Workspace/light-protocol/sdk-libs/sdk", features = ["anchor", "v2", "cpi-context"] } | ||
| light-sdk-macros = { path = "/home/tilo/Workspace/light-protocol/sdk-libs/macros" } | ||
| light-sdk-types = { path = "/home/tilo/Workspace/light-protocol/sdk-libs/sdk-types", features = ["v2", "cpi-context"] } | ||
| light-account = { path = "/home/tilo/Workspace/light-protocol/sdk-libs/account", features = ["anchor", "token"] } | ||
| light-compressible = { path = "/home/tilo/Workspace/light-protocol/program-libs/compressible", features = ["anchor"] } | ||
| light-hasher = { path = "/home/tilo/Workspace/light-protocol/program-libs/hasher", features = ["solana"] } | ||
| light-macros = { path = "/home/tilo/Workspace/light-protocol/program-libs/macros" } | ||
| light-compressed-account = { path = "/home/tilo/Workspace/light-protocol/program-libs/compressed-account" } | ||
| light-token = { path = "/home/tilo/Workspace/light-protocol/sdk-libs/token-sdk", features = ["anchor"] } | ||
| light-token-types = { path = "/home/tilo/Workspace/light-protocol/sdk-libs/token-types", features = ["anchor"] } | ||
| light-token-interface = { path = "/home/tilo/Workspace/light-protocol/program-libs/token-interface" } | ||
| light-program-test = { path = "/home/tilo/Workspace/light-protocol/sdk-libs/program-test" } | ||
| light-client = { path = "/home/tilo/Workspace/light-protocol/sdk-libs/client", features = ["v2", "anchor"] } |
There was a problem hiding this comment.
🔴 All Light Protocol workspace dependencies use hardcoded local filesystem paths that break CI and other developers
All light-* workspace dependencies in Cargo.toml were changed from published crate versions to local filesystem paths pointing to /home/tilo/Workspace/light-protocol/.... This path only exists on one developer's machine.
Root Cause and Impact
The diff shows the change from versioned dependencies (e.g., light-sdk = { version = "0.22.0", ... }) to local path dependencies (e.g., light-sdk = { path = "/home/tilo/Workspace/light-protocol/sdk-libs/sdk", ... }) across lines 65-77 of programs/anchor/Cargo.toml.
While the commit message (fix(deps): unify Light crates to path deps to resolve duplicate light-compressed-account) suggests this was done intentionally to resolve a dependency conflict, the hardcoded absolute paths will cause cargo build-sbf and cargo test-sbf to fail on:
- The CI runner (
.github/workflows/rust-tests.ymlrunscargo test-sbffor each program) - Any other developer's machine
Impact: The project is unbuildable for anyone except the original developer. All CI jobs for the four new example programs (escrow, fundraiser, light-token-minter, swap_example) will fail.
Prompt for agents
In programs/anchor/Cargo.toml, replace all local path dependencies (lines 65-77) with published crate versions. The original versions were:
light-sdk = { version = "0.22.0", features = ["anchor", "v2", "cpi-context"] }
light-sdk-macros = "0.22.0"
light-sdk-types = { version = "0.22.0", features = ["v2", "cpi-context"] }
light-account = { version = "0.22.0", features = ["anchor", "token"] }
light-compressible = { version = "0.6.0", features = ["anchor"] }
light-hasher = { version = "5.0.0", features = ["solana"] }
light-macros = "2.2.0"
light-compressed-account = "0.11.0"
light-token = { version = "0.22.1", features = ["anchor"] }
light-token-types = { version = "0.22.0", features = ["anchor"] }
light-token-interface = "0.5.0"
light-program-test = "0.22.0"
light-client = { version = "0.22.0", features = ["v2", "anchor"] }
If these versions cause dependency conflicts, use compatible version ranges or a [patch] section in the workspace Cargo.toml to override specific crates, rather than absolute filesystem paths.
Was this helpful? React with 👍 or 👎 to provide feedback.
Replace TransferFromSpl with TransferInterface in shared-test-utils for consistency with program-side TransferInterfaceCpi. Update escrow, fundraiser, and token-swap programs and tests.
| require!(amount <= max_contribution, FundraiserError::ContributionTooBig); | ||
|
|
||
| let current_time = Clock::get()?.unix_timestamp; | ||
| let elapsed_days = ((current_time - self.fundraiser.time_started) / SECONDS_TO_DAYS) as u16; |
There was a problem hiding this comment.
🟡 Unchecked i64 subtraction in contribute can panic instead of returning an error
The contribute function uses unchecked subtraction current_time - self.fundraiser.time_started at line 82, which can panic (abort the program) if the result overflows. The refund function at programs/anchor/fundraiser/src/instructions/refund.rs:79 uses checked_sub for the identical operation, showing the intended safe pattern.
Detailed Explanation
The workspace Cargo.toml has overflow-checks = true in the release profile (programs/anchor/Cargo.toml:31). This means arithmetic overflow on i64 subtraction causes a runtime panic (program abort) rather than wrapping.
At contribute.rs:82:
let elapsed_days = ((current_time - self.fundraiser.time_started) / SECONDS_TO_DAYS) as u16;Compare with refund.rs:79:
current_time.checked_sub(self.fundraiser.time_started)
.ok_or(FundraiserError::CalculationOverflow)?While current_time < time_started is unlikely under normal conditions, Solana validators have some leeway in clock values across slots. A panic produces an unhelpful "Program failed" error, whereas checked_sub returns a descriptive CalculationOverflow error.
Impact: If the subtraction ever overflows, the program aborts with a generic error instead of returning FundraiserError::CalculationOverflow.
| let elapsed_days = ((current_time - self.fundraiser.time_started) / SECONDS_TO_DAYS) as u16; | |
| let elapsed_days = (current_time.checked_sub(self.fundraiser.time_started) | |
| .ok_or(FundraiserError::CalculationOverflow)? | |
| .checked_div(SECONDS_TO_DAYS) | |
| .ok_or(FundraiserError::CalculationOverflow)?) as u16; | |
Was this helpful? React with 👍 or 👎 to provide feedback.
| let cpi = TransferInterfaceCpi::new( | ||
| params.token_a_offered_amount, | ||
| decimals, | ||
| ctx.accounts.maker_token_account_a.to_account_info(), | ||
| ctx.accounts.vault.to_account_info(), | ||
| ctx.accounts.fee_payer.to_account_info(), | ||
| ctx.accounts.fee_payer.to_account_info(), | ||
| ctx.accounts.light_token_cpi_authority.to_account_info(), | ||
| ctx.accounts.system_program.to_account_info(), | ||
| ) | ||
| .with_spl_interface( | ||
| Some(ctx.accounts.token_mint_a.to_account_info()), | ||
| Some(ctx.accounts.token_program.to_account_info()), | ||
| Some(ctx.accounts.spl_interface_pda_a.to_account_info()), | ||
| Some(params.spl_interface_bump_a), | ||
| ) | ||
| ?; | ||
|
|
||
| cpi.invoke()?; |
There was a problem hiding this comment.
🔴 Escrow unconditionally configures SPL interface for all transfers, breaking Light-to-Light token config
The escrow program's make_offer and take_offer instructions always call with_spl_interface() on TransferInterfaceCpi, even for Light-to-Light transfers where no SPL interface PDA exists. For the Light token config, the test passes Pubkey::default() for the PDA and an unrelated bump, which will cause the Light Token CPI to fail at runtime.
Detailed Explanation
In escrow/src/instructions/make_offer.rs:106-122, the transfer always configures SPL interface:
let cpi = TransferInterfaceCpi::new(...)
.with_spl_interface(
Some(ctx.accounts.token_mint_a.to_account_info()),
Some(ctx.accounts.token_program.to_account_info()),
Some(ctx.accounts.spl_interface_pda_a.to_account_info()),
Some(params.spl_interface_bump_a),
)?;Similarly in escrow/src/instructions/take_offer.rs:99-115 and take_offer.rs:129-145.
Contrast with the fundraiser (fundraiser/src/instructions/contribute.rs:107-114) and token-swap (token-swap/src/instructions/swap_exact_tokens_for_tokens.rs:81,94-101) programs which correctly use Option<AccountInfo> for the SPL interface PDA and conditionally call with_spl_interface:
if self.spl_interface_pda.is_some() {
cpi = cpi.with_spl_interface(...)?
}
cpi.invoke()?;The escrow's spl_interface_pda_a and spl_interface_pda_b are declared as UncheckedAccount (required), not Option<AccountInfo>. For the Light config test, the test passes Pubkey::default() (escrow/tests/escrow.rs:289) because spl_interface_a is None. This bogus PDA will be sent to with_spl_interface, causing the Light Token program to attempt a cross-standard transfer path when a direct Light-to-Light transfer is needed.
Impact: The test_escrow_light, test_escrow_spl_light, and test_escrow_t22_light tests will fail at runtime (though this is masked by the local path deps issue preventing any CI execution).
Prompt for agents
In programs/anchor/escrow/src/instructions/make_offer.rs (the MakeOffer struct and send_offered_tokens_to_vault function) and programs/anchor/escrow/src/instructions/take_offer.rs (the TakeOffer struct and both send_wanted_tokens_to_maker and withdraw_from_vault functions):
1. Change `spl_interface_pda_a` and `spl_interface_pda_b` from `UncheckedAccount<'info>` to `Option<AccountInfo<'info>>` in both account structs (MakeOffer and TakeOffer).
2. Wrap the `with_spl_interface` calls in a conditional, matching the pattern used in fundraiser and token-swap:
let mut cpi = TransferInterfaceCpi::new(...);
if ctx.accounts.spl_interface_pda_a.is_some() {
cpi = cpi.with_spl_interface(
Some(ctx.accounts.token_mint_a.to_account_info()),
Some(ctx.accounts.token_program.to_account_info()),
ctx.accounts.spl_interface_pda_a.as_ref().map(|a| a.to_account_info()),
Some(params.spl_interface_bump_a),
)?;
}
cpi.invoke()?;
3. Update the test in programs/anchor/escrow/tests/escrow.rs to pass Option values (Some(pda) for SPL/T22, None for Light) instead of unwrap_or_default().
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
associated_token::bump(now derived on-chain)Programs
Test plan
cargo test-sbf -p escrowcargo test-sbf -p fundraisercargo test-sbf -p light-token-mintercargo test-sbf -p swap_example