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.
programs/anchor/Cargo.toml
Outdated
| 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.
| ctx.accounts.authority.to_account_info(), | ||
| ctx.accounts.payer.to_account_info(), | ||
| ctx.accounts.cpi_authority.to_account_info(), | ||
| ctx.accounts.mint.clone().unwrap(), |
There was a problem hiding this comment.
🔴 Unwrap of Option<AccountInfo> panics on Light-to-Light transfers when mint is None
The transfer function calls ctx.accounts.mint.clone().unwrap() at line 26, but mint is declared as Option<AccountInfo<'info>> at programs/anchor/basic-instructions/transfer-interface/src/lib.rs:71. When performing Light-to-Light transfers (no SPL interface), the test passes mint: None (see programs/anchor/basic-instructions/transfer-interface/tests/test.rs:45), which will cause a runtime panic.
Root Cause
The TransferInterfaceCpi::new() API was updated to require a mint parameter. The code was changed to pass ctx.accounts.mint.clone().unwrap(), but mint is an Option<AccountInfo> that is None for Light-to-Light transfers. The existing test at line 45 of the test file explicitly passes mint: None for this case.
The .unwrap() on None will panic with called Option::unwrap() on a None value, crashing the on-chain program.
Impact: Any Light-to-Light transfer through this program will fail with a panic. Only SPL-to-Light transfers (where mint is Some(...)) will work.
Prompt for agents
In programs/anchor/basic-instructions/transfer-interface/src/lib.rs at line 26, the code calls ctx.accounts.mint.clone().unwrap() but mint is Option<AccountInfo> and can be None for Light-to-Light transfers. The TransferInterfaceCpi::new() now requires a mint AccountInfo parameter. You need to either: (1) make mint a required (non-optional) account since TransferInterfaceCpi::new always needs it, or (2) handle the None case gracefully by returning an error instead of panicking. If mint is always required by the new API, change the account declaration at line 71 from Option<AccountInfo> to AccountInfo and update the test at tests/test.rs line 45 to always provide a mint.
Was this helpful? React with 👍 or 👎 to provide feedback.
- Add mint param to TransferInterfaceCpi::new() (all programs) - Make fee_payer mandatory, remove Option wrapper (rust-client, pinocchio) - Add fee_payer to Approve/Revoke structs (rust-client) - Add mint field to TransferInterface structs (rust-client, shared-test-utils) - Make escrow spl_interface_pda accounts optional (Option<AccountInfo>) with conditional with_spl_interface for Light-to-Light transfers - Use checked arithmetic in fundraiser contribute (matches refund) - Replace local path deps with git deps pinned to fix/authority-owner-mutability
645e1a7 to
944aea8
Compare
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