Feat: create fund#3
Conversation
WalkthroughThis PR introduces the ChangesFund Program: CreateFund Instruction
Development Environment Setup
Developer Documentation and Guidance
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
AGENTS.md (1)
167-201:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAGENTS.md section on anchor-lang incompatibility needs clarification.
The "Known unresolved issue" section (lines 167–201) discusses problems with
anchor-lang = "1.0.2"and proposed solutions, butprograms/fund/Cargo.tomlactually usesanchor-lang = "0.31".Either update the section to explain that using 0.31 resolves the described 1.0.x incompatibility, or clarify that this section documents a future migration scenario. The current phrasing—presenting it as an unresolved issue—conflicts with the actual dependency choice and will confuse developers about whether the problem is real or hypothetical.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@AGENTS.md` around lines 167 - 201, The AGENTS.md "Known unresolved issue: anchor-lang 1.0.x vs platform-tools v1.51" section incorrectly presents a live problem for anchor-lang 1.0.2 while the repo's manifest (programs/fund/Cargo.toml) already pins anchor-lang = "0.31"; update AGENTS.md to state that the current project uses anchor-lang 0.31 which avoids the Cargo 1.84/edition2024 incompatibility, or explicitly mark the paragraph as a future-migration warning (i.e., describe the 1.0.x issue as relevant only if/when upgrading to anchor-lang 1.0.x), and include the recommended remediation steps (downgrade to 0.31 or upgrade SBF toolchain) and the verification commands (nix run .#regenerate-cargo-lock-sbf and nix run .#probe-cargo-build-sbf -- --clean --manifest-path programs/fund/Cargo.toml) so readers aren’t confused about the current repo state.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@Anchor.toml`:
- Line 9: The PR changed the on-chain Program ID to
8FUTArRdDgGDTYbnBMrzB7mBTwLrYpJDTzWY4d62GpCD (seen in Anchor.toml and
programs/fund/src/lib.rs) but AGENTS.md still documents the old ID
5nNVyzESLk4QNQh7HgxAAwFmHnN37WUz1aCttBLwFo2e; either update AGENTS.md to replace
the old ID with 8FUTArRdDgGDTYbnBMrzB7mBTwLrYpJDTzWY4d62GpCD and add a short
note about the breaking change, or revert the Program ID change in Anchor.toml
and programs/fund/src/lib.rs to the documented ID so deployments remain
compatible.
In `@programs/fund/SPEC.md`:
- Around line 52-97: Add an explicit "Error conditions" subsection to the
create_fund spec that enumerates expected failure modes for implementers and
tests: include duplicate Fund PDA (Fund PDA already exists / init fails),
invalid quote_mint (not an SPL mint or wrong mint account), invalid parameters
from FundParams (e.g., fee_bps > 10000 or other bounds), rent/exemption failures
or insufficient lamports for manager to fund account creation,
authority/ownership mismatches for Vault or SharesMint (wrong signer or wrong
program ownership), and token-program failures (initialize_mint /
initialize_account errors); reference the create_fund operation and the PDA
resources (Fund PDA, Vault, SharesMint), the quote_mint and FundParams so each
error maps to a clear check in implementation and tests.
- Line 77: The spec currently uses an undefined generic length `name: [u8; N]`;
update it to the concrete 32-byte length used by the implementation/tests by
replacing `name: [u8; N]` with `name: [u8; 32]` so the Fund PDA seed length is
explicit and matches tests (see the `name` field referenced in
tests/create-fund.ts).
In `@programs/fund/src/instructions/initialize.rs`:
- Around line 6-90: Move the CreateFund instruction (the #[derive(Accounts)] pub
struct CreateFund and its pub fn handler) out of initialize.rs into a new file
programs/fund/src/instructions/create_fund.rs and have the handler call the
existing apply_params(...) and use FundBumps as before; then in
programs/fund/src/instructions.rs add `pub mod create_fund; pub use
create_fund::*;` and add the thin wrapper entry in the #[program] mod fund in
lib.rs to call handler (so the public entrypoint remains create_fund); finally
remove or rename the stale CreateFund/handler definitions from initialize.rs so
the module name matches its contents.
- Around line 77-89: Before calling apply_params in handler, validate and
normalize CreateFundParams: check params.fee_bps <= 10_000 and return an error
if it exceeds; also canonicalize the fund name by trimming any trailing zero
bytes (or whitespace) and then re-padding to the params' fixed storage length
(the same fixed-size length used by CreateFundParams) so that on-chain bytes are
canonical (no distinct PDAs caused by different padding). Perform these
checks/normalization in handler just prior to calling apply_params (referencing
handler, CreateFundParams, params.fee_bps, params.name, and apply_params) and
return a Result::Err on invalid fee or if normalization cannot produce the
required fixed-length representation.
- Around line 63-89: apply_params is currently a no-op; implement it to populate
the Fund account from the provided inputs: set fund.manager = manager and
fund.quote_mint = quote_mint; copy all persistent fields from CreateFundParams
into the matching Fund fields (e.g., names, numeric params, fee/configuration
values) and set bump/public-key fields from FundBumps (fund.bump = bumps.fund,
fund.vault_bump = bumps.vault, fund.shares_mint_bump = bumps.shares_mint, and
any fund.shares_mint / fund.vault pubkey fields if present); also initialize any
counters/accumulators (e.g., total_shares, total_deposited) to zero and mark the
fund as initialized if there is a flag. Update the apply_params function body
(and not handler) so tests observe the persisted Fund state after create_fund.
In `@programs/fund/src/lib.rs`:
- Around line 18-19: The public instruction function create_fund currently
delegates to initialize::handler which mismatches the instruction name; update
the handler to match the instruction by renaming the module/file or the call:
either rename instructions/initialize.rs and its module declaration to
instructions/create_fund.rs / create_fund and update the call to
create_fund::handler, or keep the file name but change create_fund to call
create_fund_module::handler (and document why the divergence exists). Ensure the
function symbol create_fund calls the handler module whose name equals the
instruction name (e.g., create_fund::handler) and update module
declarations/imports accordingly.
In `@tests/create-fund.ts`:
- Around line 78-102: Add assertions that the bumps stored on the fetched fund
account equal the canonical PDA bumps used to derive the Vault and SharesMint;
derive the PDAs (using the same seeds and program id as the program) to get
their bump values and assert they match fundAccount.vaultBump and
fundAccount.sharesMintBump (or the actual field names used on fundAccount) so
the test verifies the cached bumps are correct.
---
Outside diff comments:
In `@AGENTS.md`:
- Around line 167-201: The AGENTS.md "Known unresolved issue: anchor-lang 1.0.x
vs platform-tools v1.51" section incorrectly presents a live problem for
anchor-lang 1.0.2 while the repo's manifest (programs/fund/Cargo.toml) already
pins anchor-lang = "0.31"; update AGENTS.md to state that the current project
uses anchor-lang 0.31 which avoids the Cargo 1.84/edition2024 incompatibility,
or explicitly mark the paragraph as a future-migration warning (i.e., describe
the 1.0.x issue as relevant only if/when upgrading to anchor-lang 1.0.x), and
include the recommended remediation steps (downgrade to 0.31 or upgrade SBF
toolchain) and the verification commands (nix run .#regenerate-cargo-lock-sbf
and nix run .#probe-cargo-build-sbf -- --clean --manifest-path
programs/fund/Cargo.toml) so readers aren’t confused about the current repo
state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: ed3e24a7-297a-42b3-a3dc-803dd1751153
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (14)
AGENTS.mdAnchor.tomldocs/anchor.mdflake.nixpackage.jsonprograms/fund/Cargo.tomlprograms/fund/SPEC.mdprograms/fund/src/instructions/initialize.rsprograms/fund/src/lib.rsprograms/fund/src/state.rsprograms/fund/tests/test_initialize.rsscripts/setup-rust-toolchain.nuscripts/setup-rust-toolchain.test.nutests/create-fund.ts
💤 Files with no reviewable changes (1)
- programs/fund/tests/test_initialize.rs
📜 Review details
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
JavaScript/TypeScript files must be linted and formatted via bun run lint and bun run lint:fix.
Files:
tests/create-fund.ts
scripts/*.nu
📄 CodeRabbit inference engine (AGENTS.md)
Non-trivial shell logic (anything beyond ~3 lines, commands with non-obvious quoting/escaping, or one-shot probes that might be useful again) must live in ./scripts/.nu with a paired ./scripts/.test.nu. The test suite must run as part of the script's nix derivation (checkPhase). Do not write inline bash inside flake.nix strings or as Bash tool commands except for read-only one-liners.
Files:
scripts/setup-rust-toolchain.test.nuscripts/setup-rust-toolchain.nu
Anchor.toml
📄 CodeRabbit inference engine (AGENTS.md)
The Program ID (5nNVyzESLk4QNQh7HgxAAwFmHnN37WUz1aCttBLwFo2e) must be declared in both programs/fund/src/lib.rs and Anchor.toml's [programs.localnet] section and kept in sync.
Files:
Anchor.toml
*
⚙️ CodeRabbit configuration file
Focus on providing constructive criticism. Whenever you see a suboptimal approach, suggest more idiomatic or robust alternative(s). Flag potential footguns. Suggest FP alternatives to mutable/imperative code. Point out architectural flaws like leaky abstractions, tight coupling, wrong level of abstraction, poor type modeling, over-abstraction, unclear domain boundaries. Code should generally be organized based on business concerns rather than technical aspects - suggest improvements if you find violations. Point out gaps in test coverage but suggest tests that are not too coupled to the implementation and actually test domain invariants and business logic.
Files:
Anchor.tomlpackage.jsonflake.nixAGENTS.md
programs/fund/src/**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
Rust code must use the Anchor framework with the four core macros: declare_id!, #[program], #[derive(Accounts)], and #[account]. Refer to
@docs/anchor.mdfor details on account validation, the 8-byte discriminator, and IDL/client codegen.
Files:
programs/fund/src/state.rsprograms/fund/src/lib.rsprograms/fund/src/instructions/initialize.rs
**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
Rust code must be formatted with cargo fmt as enforced by pre-commit hooks.
Files:
programs/fund/src/state.rsprograms/fund/src/lib.rsprograms/fund/src/instructions/initialize.rs
programs/fund/SPEC.md
📄 CodeRabbit inference engine (AGENTS.md)
Spec new features in programs/fund/SPEC.md with sections describing the model, instruction signature(s), accounts touched, error conditions, and a mermaid sequence diagram. Keep specs incremental and call out non-obvious design choices explicitly.
Files:
programs/fund/SPEC.md
**/*.md
⚙️ CodeRabbit configuration file
Focus on the contents of the docs and not on cosmetic things like markdown formatting. We use markdown files for various docs including but not limited to the north star system specification, SPEC.md, the plan for how to get there, ROADMAP.md, guidelines for AI contributors, AGENTS.md, project overview and instructions for human contributors, README.md. Think about the target audience of a document when deciding what comment to leave. For specifications and designs, suggest potential product, architectural, and UI/UX improvements. For plans, suggest changes that would make things more parallelizable and deliverable-focused. For instructions, suggest better rules and guidelines and point out missing instructions. In all cases, flag needless bloat, prefer clear concise writing, and consider the structure of the document and order of the sections
Files:
programs/fund/SPEC.mddocs/anchor.mdAGENTS.md
flake.nix
📄 CodeRabbit inference engine (AGENTS.md)
The dev shell is provided by Nix flake + devenv. flake.nix exposes a cargo-build-sbf shim that pre-fetches Solana platform-tools and symlinks them into .devenv/sbf-home/.cache/solana/v/platform-tools for offline builds.
Files:
flake.nix
programs/fund/src/instructions/**/*.rs
📄 CodeRabbit inference engine (AGENTS.md)
When adding a new instruction, create programs/fund/src/instructions/.rs with #[derive(Accounts)] pub struct and pub fn handler(...), add pub mod ; pub use ::*; to instructions.rs, and add a thin wrapper in #[program] mod fund in lib.rs.
Every new instruction must be reviewed against the Solana/Anchor attack catalogue in
@docs/sealevel-attacks.mdbefore merging. Walk each #[derive(Accounts)] struct through the security checklist explicitly.
Files:
programs/fund/src/instructions/initialize.rs
🧠 Learnings (1)
📓 Common learnings
Learnt from: CR
Repo: data-cartel/fund
Timestamp: 2026-05-13T20:39:59.830Z
Learning: Documentation (notes, learnings, conventions, rationale, and rules) must be in tracked markdown files (CLAUDE.md, programs/fund/README.md, programs/fund/SPEC.md, ADRs under adrs/, or dedicated docs). Agent memory, scratchpads, or .tmp/ files do not count as documentation.
Learnt from: CR
Repo: data-cartel/fund
Timestamp: 2026-05-13T20:39:59.830Z
Learning: Every new on-chain program feature must follow the feature workflow: (1) Spec the feature in programs/fund/SPEC.md, (2) Update the interface without implementing logic, (3) Write a failing test, (4) Implement the handler body, (5) Confirm tests pass, (6) Commit and push.
Learnt from: CR
Repo: data-cartel/fund
Timestamp: 2026-05-13T20:39:59.830Z
Learning: anchor build must run before cargo test because integration tests require the compiled target/deploy/fund.so artifact. CI/local workflows should chain build → test.
Learnt from: CR
Repo: data-cartel/fund
Timestamp: 2026-05-13T20:39:59.830Z
Learning: Solana tooling should not be fought. Match its version pins, directory layout, env-var conventions, and config defaults. Succumb to whatever Solana tooling wants.
Learnt from: CR
Repo: data-cartel/fund
Timestamp: 2026-05-13T20:39:59.830Z
Learning: Pre-commit hooks (via git-hooks.nix) run nixfmt, nil, cargo fmt, prettier, and taplo. Run nix flake check to invoke them outside a commit.
🔇 Additional comments (22)
scripts/setup-rust-toolchain.nu (5)
1-24: LGTM!
25-35: LGTM!
37-42: LGTM!
44-49: LGTM!
51-53: LGTM!flake.nix (2)
185-190: LGTM!
193-198: LGTM!scripts/setup-rust-toolchain.test.nu (6)
1-14: LGTM!
16-24: LGTM!
26-37: LGTM!
39-58: LGTM!
60-67: LGTM!
69-73: LGTM!programs/fund/Cargo.toml (1)
17-17: LGTM!Also applies to: 25-25
docs/anchor.md (2)
1-135: LGTM!
73-74: ⚡ Quick winThe referenced file
docs/sealevel-attacks.mdexists in the repository. The cross-reference is valid.AGENTS.md (2)
21-56: LGTM!
218-225: LGTM!programs/fund/SPEC.md (1)
1-111: LGTM!programs/fund/src/lib.rs (1)
10-10: LGTM!Also applies to: 12-12
tests/create-fund.ts (1)
1-121: LGTM!package.json (1)
9-10: ⚡ Quick winThe caret operator (^) in both dependencies ensures the latest compatible releases are automatically installed. The specified versions are appropriate:
@solana/spl-token^0.4.9 will pull in 0.4.14 and@solana/web3.js^1.95.4 will pull in 1.98.4 (both latest stable as of May 2026), which are compatible and align with Solana's documentation recommending web3.js ^1.x. No known security vulnerabilities reported for these versions.
|
|
||
| [programs.localnet] | ||
| fund = "5nNVyzESLk4QNQh7HgxAAwFmHnN37WUz1aCttBLwFo2e" | ||
| fund = "8FUTArRdDgGDTYbnBMrzB7mBTwLrYpJDTzWY4d62GpCD" |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the program ID in the deploy keypair matches the declared ID
if [ -f target/deploy/fund-keypair.json ]; then
solana-keygen pubkey target/deploy/fund-keypair.json
fiRepository: data-cartel/fund
Length of output: 42
🏁 Script executed:
cat -n Anchor.tomlRepository: data-cartel/fund
Length of output: 454
🏁 Script executed:
grep -n "declare_id" programs/fund/src/lib.rsRepository: data-cartel/fund
Length of output: 124
🏁 Script executed:
cat AGENTS.md | head -100Repository: data-cartel/fund
Length of output: 5559
🏁 Script executed:
fd -i agents.mdRepository: data-cartel/fund
Length of output: 70
🏁 Script executed:
cat AGENTS.md 2>/dev/null || echo "AGENTS.md not found"Repository: data-cartel/fund
Length of output: 12639
🏁 Script executed:
ls -la AGENTS.md CLAUDE.md 2>&1 | head -20Repository: data-cartel/fund
Length of output: 193
🏁 Script executed:
file AGENTS.md CLAUDE.mdRepository: data-cartel/fund
Length of output: 105
Program ID changed but documentation is out of sync.
The Program ID in Anchor.toml and programs/fund/src/lib.rs has been changed to 8FUTArRdDgGDTYbnBMrzB7mBTwLrYpJDTzWY4d62GpCD, but AGENTS.md documents the previous ID as 5nNVyzESLk4QNQh7HgxAAwFmHnN37WUz1aCttBLwFo2e. The new ID is correctly synchronized between both code locations, but this is a breaking change for all deployments and clients.
Update AGENTS.md to reflect the new Program ID, or revert the change if unintentional.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@Anchor.toml` at line 9, The PR changed the on-chain Program ID to
8FUTArRdDgGDTYbnBMrzB7mBTwLrYpJDTzWY4d62GpCD (seen in Anchor.toml and
programs/fund/src/lib.rs) but AGENTS.md still documents the old ID
5nNVyzESLk4QNQh7HgxAAwFmHnN37WUz1aCttBLwFo2e; either update AGENTS.md to replace
the old ID with 8FUTArRdDgGDTYbnBMrzB7mBTwLrYpJDTzWY4d62GpCD and add a short
note about the breaking change, or revert the Program ID change in Anchor.toml
and programs/fund/src/lib.rs to the documented ID so deployments remain
compatible.
|
|
||
| ### `create_fund` | ||
|
|
||
| Manager creates a fund with its parameters. Allocates the `Fund` PDA, a | ||
| `Vault` SPL token account, and a `SharesMint`. The shares mint's | ||
| decimals match the quote mint's, so on-chain share amounts read in the | ||
| same units as quote balances. | ||
|
|
||
| ```mermaid | ||
| sequenceDiagram | ||
| participant Manager | ||
| participant Program as fund program | ||
| participant System as System program | ||
| participant Token as Token program | ||
|
|
||
| Manager->>Program: create_fund(name, params) | ||
| Program->>System: create Fund PDA (rent-exempt) | ||
| Program->>System: create Vault token account | ||
| Program->>Token: initialize Vault (mint = quote_mint, authority = Fund PDA) | ||
| Program->>System: create SharesMint | ||
| Program->>Token: initialize SharesMint (decimals = quote_mint decimals, mint authority = Fund PDA) | ||
| Program-->>Manager: Fund PDA address | ||
| ``` | ||
|
|
||
| **Inputs** | ||
| - `name: [u8; N]` — small byte slice, part of the Fund PDA seeds. | ||
| - `params: FundParams` — the table above (excluding `manager`, which is | ||
| read from the signer). | ||
|
|
||
| **Accounts** | ||
| - `manager` — `Signer`, pays rent. | ||
| - `fund` — `init` PDA at the seeds above. | ||
| - `vault` — `init` SPL token account at the derived PDA. | ||
| - `shares_mint` — `init` SPL mint at the derived PDA. | ||
| - `quote_mint` — the SPL mint referenced by `params.quote_mint`, | ||
| read-only. | ||
| - system program, token program, rent sysvar. | ||
|
|
||
| **Effects** | ||
| - `Fund` account is initialized with the supplied parameters and the | ||
| PDA bumps needed to re-derive `Vault` / `SharesMint` cheaply. | ||
| - `Vault` is a fresh quote-mint token account with balance 0, authority | ||
| set to the Fund PDA. | ||
| - `SharesMint` is a fresh SPL mint with supply 0, mint authority set to | ||
| the Fund PDA, decimals matching `quote_mint`. | ||
|
|
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Add an explicit error conditions section.
The spec covers inputs, accounts, and effects thoroughly, but does not explicitly enumerate error conditions. As per coding guidelines, specs should describe error conditions. Consider adding a subsection listing cases like:
- Fund PDA already exists (duplicate creation)
- Invalid quote_mint (e.g., not a valid SPL mint)
- Invalid parameters (e.g., fee_bps > 10000)
- Insufficient rent exemption balance
- Authority/ownership mismatches
This helps implementation and testing by clarifying expected failure modes. As per coding guidelines, specs should call out error conditions explicitly.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@programs/fund/SPEC.md` around lines 52 - 97, Add an explicit "Error
conditions" subsection to the create_fund spec that enumerates expected failure
modes for implementers and tests: include duplicate Fund PDA (Fund PDA already
exists / init fails), invalid quote_mint (not an SPL mint or wrong mint
account), invalid parameters from FundParams (e.g., fee_bps > 10000 or other
bounds), rent/exemption failures or insufficient lamports for manager to fund
account creation, authority/ownership mismatches for Vault or SharesMint (wrong
signer or wrong program ownership), and token-program failures (initialize_mint
/ initialize_account errors); reference the create_fund operation and the PDA
resources (Fund PDA, Vault, SharesMint), the quote_mint and FundParams so each
error maps to a clear check in implementation and tests.
| ``` | ||
|
|
||
| **Inputs** | ||
| - `name: [u8; N]` — small byte slice, part of the Fund PDA seeds. |
There was a problem hiding this comment.
Specify the concrete length constraint for name.
The input is declared as name: [u8; N] but N is not defined. Based on the test file (tests/create-fund.ts line 11-14), it appears to be 32 bytes. The spec should state the exact length: name: [u8; 32].
📝 Proposed fix
-**Inputs**
-- `name: [u8; N]` — small byte slice, part of the Fund PDA seeds.
+**Inputs**
+- `name: [u8; 32]` — fixed 32-byte name, part of the Fund PDA seeds. UTF-8 strings should be padded with zeros.
- `params: FundParams` — the table above (excluding `manager`, which is
read from the signer).📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - `name: [u8; N]` — small byte slice, part of the Fund PDA seeds. | |
| **Inputs** | |
| - `name: [u8; 32]` — fixed 32-byte name, part of the Fund PDA seeds. UTF-8 strings should be padded with zeros. |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@programs/fund/SPEC.md` at line 77, The spec currently uses an undefined
generic length `name: [u8; N]`; update it to the concrete 32-byte length used by
the implementation/tests by replacing `name: [u8; N]` with `name: [u8; 32]` so
the Fund PDA seed length is explicit and matches tests (see the `name` field
referenced in tests/create-fund.ts).
| #[derive(Accounts)] | ||
| pub struct Initialize {} | ||
| #[instruction(params: CreateFundParams)] | ||
| pub struct CreateFund<'info> { | ||
| #[account(mut)] | ||
| pub manager: Signer<'info>, | ||
|
|
||
| #[account( | ||
| init, | ||
| payer = manager, | ||
| space = 8 + Fund::INIT_SPACE, | ||
| seeds = [b"fund", manager.key().as_ref(), ¶ms.name], | ||
| bump, | ||
| )] | ||
| pub fund: Account<'info, Fund>, | ||
|
|
||
| pub quote_mint: Account<'info, Mint>, | ||
|
|
||
| pub fn handler(ctx: Context<Initialize>) -> Result<()> { | ||
| msg!("Yo, is {:?} ready to make some shmoney?", ctx.program_id); | ||
| #[account( | ||
| init, | ||
| payer = manager, | ||
| seeds = [b"vault", fund.key().as_ref()], | ||
| bump, | ||
| token::mint = quote_mint, | ||
| token::authority = fund, | ||
| )] | ||
| pub vault: Account<'info, TokenAccount>, | ||
|
|
||
| #[account( | ||
| init, | ||
| payer = manager, | ||
| seeds = [b"shares", fund.key().as_ref()], | ||
| bump, | ||
| mint::decimals = quote_mint.decimals, | ||
| mint::authority = fund, | ||
| )] | ||
| pub shares_mint: Account<'info, Mint>, | ||
|
|
||
| pub system_program: Program<'info, System>, | ||
| pub token_program: Program<'info, Token>, | ||
| pub rent: Sysvar<'info, Rent>, | ||
| } | ||
|
|
||
| /// PDA bumps cached on the `Fund` account. | ||
| /// | ||
| /// Exists so the pure `apply_params` function can be unit-tested without | ||
| /// pulling in Anchor's auto-generated `CreateFundBumps` (which lives | ||
| /// inside the `#[derive(Accounts)]` macro expansion). | ||
| pub struct FundBumps { | ||
| pub fund: u8, | ||
| pub vault: u8, | ||
| pub shares_mint: u8, | ||
| } | ||
|
|
||
| /// Pure function that writes the manager-supplied parameters and PDA | ||
| /// bumps onto a freshly-allocated `Fund`. Lives outside `handler` so it | ||
| /// can be exercised from the unit tests below without constructing an | ||
| /// Anchor `Context`. | ||
| pub fn apply_params( | ||
| fund: &mut Fund, | ||
| manager: Pubkey, | ||
| quote_mint: Pubkey, | ||
| params: &CreateFundParams, | ||
| bumps: FundBumps, | ||
| ) { | ||
| // Stub. Implemented in a later step of the create_fund TTDD loop; | ||
| // the unit tests in this file and the `tests/create-fund.ts` | ||
| // integration test both fail until this function actually sets the | ||
| // Fund fields. | ||
| let _ = (fund, manager, quote_mint, params, bumps); | ||
| } | ||
|
|
||
| pub fn handler(ctx: Context<CreateFund>, params: CreateFundParams) -> Result<()> { | ||
| apply_params( | ||
| &mut ctx.accounts.fund, | ||
| ctx.accounts.manager.key(), | ||
| ctx.accounts.quote_mint.key(), | ||
| ¶ms, | ||
| FundBumps { | ||
| fund: ctx.bumps.fund, | ||
| vault: ctx.bumps.vault, | ||
| shares_mint: ctx.bumps.shares_mint, | ||
| }, | ||
| ); | ||
| Ok(()) | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Move this instruction into instructions/create_fund.rs.
This PR adds a new instruction, but the implementation still lives in initialize.rs. That diverges from the repo’s required instruction layout and leaves the module name misleading now that the public entrypoint is create_fund.
As per coding guidelines, "When adding a new instruction, create programs/fund/src/instructions/.rs with #[derive(Accounts)] pub struct and pub fn handler(...), add pub mod ; pub use ::*; to instructions.rs, and add a thin wrapper in #[program] mod fund in lib.rs."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@programs/fund/src/instructions/initialize.rs` around lines 6 - 90, Move the
CreateFund instruction (the #[derive(Accounts)] pub struct CreateFund and its
pub fn handler) out of initialize.rs into a new file
programs/fund/src/instructions/create_fund.rs and have the handler call the
existing apply_params(...) and use FundBumps as before; then in
programs/fund/src/instructions.rs add `pub mod create_fund; pub use
create_fund::*;` and add the thin wrapper entry in the #[program] mod fund in
lib.rs to call handler (so the public entrypoint remains create_fund); finally
remove or rename the stale CreateFund/handler definitions from initialize.rs so
the module name matches its contents.
| pub fn apply_params( | ||
| fund: &mut Fund, | ||
| manager: Pubkey, | ||
| quote_mint: Pubkey, | ||
| params: &CreateFundParams, | ||
| bumps: FundBumps, | ||
| ) { | ||
| // Stub. Implemented in a later step of the create_fund TTDD loop; | ||
| // the unit tests in this file and the `tests/create-fund.ts` | ||
| // integration test both fail until this function actually sets the | ||
| // Fund fields. | ||
| let _ = (fund, manager, quote_mint, params, bumps); | ||
| } | ||
|
|
||
| pub fn handler(ctx: Context<CreateFund>, params: CreateFundParams) -> Result<()> { | ||
| apply_params( | ||
| &mut ctx.accounts.fund, | ||
| ctx.accounts.manager.key(), | ||
| ctx.accounts.quote_mint.key(), | ||
| ¶ms, | ||
| FundBumps { | ||
| fund: ctx.bumps.fund, | ||
| vault: ctx.bumps.vault, | ||
| shares_mint: ctx.bumps.shares_mint, | ||
| }, | ||
| ); | ||
| Ok(()) |
There was a problem hiding this comment.
Implement apply_params before merge.
create_fund currently allocates the accounts and returns Ok(()), but apply_params is a no-op, so the persisted Fund stays zeroed and all three unit tests in this file remain red.
Suggested fix
pub fn apply_params(
fund: &mut Fund,
manager: Pubkey,
quote_mint: Pubkey,
params: &CreateFundParams,
bumps: FundBumps,
) {
- // Stub. Implemented in a later step of the create_fund TTDD loop;
- // the unit tests in this file and the `tests/create-fund.ts`
- // integration test both fail until this function actually sets the
- // Fund fields.
- let _ = (fund, manager, quote_mint, params, bumps);
+ fund.manager = manager;
+ fund.name = params.name;
+ fund.quote_mint = quote_mint;
+ fund.management_fee_bps = params.management_fee_bps;
+ fund.performance_fee_bps = params.performance_fee_bps;
+ fund.capacity = params.capacity;
+ fund.withdrawal_delay_days = params.withdrawal_delay_days;
+ fund.fund_bump = bumps.fund;
+ fund.vault_bump = bumps.vault;
+ fund.shares_mint_bump = bumps.shares_mint;
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pub fn apply_params( | |
| fund: &mut Fund, | |
| manager: Pubkey, | |
| quote_mint: Pubkey, | |
| params: &CreateFundParams, | |
| bumps: FundBumps, | |
| ) { | |
| // Stub. Implemented in a later step of the create_fund TTDD loop; | |
| // the unit tests in this file and the `tests/create-fund.ts` | |
| // integration test both fail until this function actually sets the | |
| // Fund fields. | |
| let _ = (fund, manager, quote_mint, params, bumps); | |
| } | |
| pub fn handler(ctx: Context<CreateFund>, params: CreateFundParams) -> Result<()> { | |
| apply_params( | |
| &mut ctx.accounts.fund, | |
| ctx.accounts.manager.key(), | |
| ctx.accounts.quote_mint.key(), | |
| ¶ms, | |
| FundBumps { | |
| fund: ctx.bumps.fund, | |
| vault: ctx.bumps.vault, | |
| shares_mint: ctx.bumps.shares_mint, | |
| }, | |
| ); | |
| Ok(()) | |
| pub fn apply_params( | |
| fund: &mut Fund, | |
| manager: Pubkey, | |
| quote_mint: Pubkey, | |
| params: &CreateFundParams, | |
| bumps: FundBumps, | |
| ) { | |
| fund.manager = manager; | |
| fund.name = params.name; | |
| fund.quote_mint = quote_mint; | |
| fund.management_fee_bps = params.management_fee_bps; | |
| fund.performance_fee_bps = params.performance_fee_bps; | |
| fund.capacity = params.capacity; | |
| fund.withdrawal_delay_days = params.withdrawal_delay_days; | |
| fund.fund_bump = bumps.fund; | |
| fund.vault_bump = bumps.vault; | |
| fund.shares_mint_bump = bumps.shares_mint; | |
| } | |
| pub fn handler(ctx: Context<CreateFund>, params: CreateFundParams) -> Result<()> { | |
| apply_params( | |
| &mut ctx.accounts.fund, | |
| ctx.accounts.manager.key(), | |
| ctx.accounts.quote_mint.key(), | |
| ¶ms, | |
| FundBumps { | |
| fund: ctx.bumps.fund, | |
| vault: ctx.bumps.vault, | |
| shares_mint: ctx.bumps.shares_mint, | |
| }, | |
| ); | |
| Ok(()) | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@programs/fund/src/instructions/initialize.rs` around lines 63 - 89,
apply_params is currently a no-op; implement it to populate the Fund account
from the provided inputs: set fund.manager = manager and fund.quote_mint =
quote_mint; copy all persistent fields from CreateFundParams into the matching
Fund fields (e.g., names, numeric params, fee/configuration values) and set
bump/public-key fields from FundBumps (fund.bump = bumps.fund, fund.vault_bump =
bumps.vault, fund.shares_mint_bump = bumps.shares_mint, and any fund.shares_mint
/ fund.vault pubkey fields if present); also initialize any
counters/accumulators (e.g., total_shares, total_deposited) to zero and mark the
fund as initialized if there is a flag. Update the apply_params function body
(and not handler) so tests observe the persisted Fund state after create_fund.
| pub fn handler(ctx: Context<CreateFund>, params: CreateFundParams) -> Result<()> { | ||
| apply_params( | ||
| &mut ctx.accounts.fund, | ||
| ctx.accounts.manager.key(), | ||
| ctx.accounts.quote_mint.key(), | ||
| ¶ms, | ||
| FundBumps { | ||
| fund: ctx.bumps.fund, | ||
| vault: ctx.bumps.vault, | ||
| shares_mint: ctx.bumps.shares_mint, | ||
| }, | ||
| ); | ||
| Ok(()) |
There was a problem hiding this comment.
Validate CreateFundParams before writing them on-chain.
Nothing here rejects fee bps above 10_000 or enforces canonical zero-padding for name. That allows impossible fee schedules and distinct PDAs that can render as the same human-readable name once clients trim trailing zeros.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@programs/fund/src/instructions/initialize.rs` around lines 77 - 89, Before
calling apply_params in handler, validate and normalize CreateFundParams: check
params.fee_bps <= 10_000 and return an error if it exceeds; also canonicalize
the fund name by trimming any trailing zero bytes (or whitespace) and then
re-padding to the params' fixed storage length (the same fixed-size length used
by CreateFundParams) so that on-chain bytes are canonical (no distinct PDAs
caused by different padding). Perform these checks/normalization in handler just
prior to calling apply_params (referencing handler, CreateFundParams,
params.fee_bps, params.name, and apply_params) and return a Result::Err on
invalid fee or if normalization cannot produce the required fixed-length
representation.
| pub fn create_fund(ctx: Context<CreateFund>, params: CreateFundParams) -> Result<()> { | ||
| initialize::handler(ctx, params) |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Instruction name doesn't match the handler module name.
The instruction is named create_fund but delegates to initialize::handler. According to the coding guidelines and docs/anchor.md, "Each instruction lives at programs/fund/src/instructions/<name>.rs" and "Anchor does not read .rs file names" but "renaming the instruction frees us to rename the file."
For clarity and consistency with repo conventions, the handler module should match the instruction name. Consider either:
- Renaming
instructions/initialize.rs→instructions/create_fund.rsand updating the module declaration, or - Documenting why
initializeis used as the module name for thecreate_fundinstruction
The current setup where the instruction name (create_fund) differs from the handler module (initialize) may confuse developers navigating the codebase.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@programs/fund/src/lib.rs` around lines 18 - 19, The public instruction
function create_fund currently delegates to initialize::handler which mismatches
the instruction name; update the handler to match the instruction by renaming
the module/file or the call: either rename instructions/initialize.rs and its
module declaration to instructions/create_fund.rs / create_fund and update the
call to create_fund::handler, or keep the file name but change create_fund to
call create_fund_module::handler (and document why the divergence exists).
Ensure the function symbol create_fund calls the handler module whose name
equals the instruction name (e.g., create_fund::handler) and update module
declarations/imports accordingly.
| // Fund parameters round-trip through on-chain state. | ||
| const fundAccount = await program.account.fund.fetch(fund); | ||
| assert(fundAccount.manager.equals(manager.publicKey), "manager mismatch"); | ||
| assert(fundAccount.quoteMint.equals(usdcMint), "quote_mint mismatch"); | ||
| assert.deepEqual( | ||
| Array.from(fundAccount.name as Uint8Array), | ||
| Array.from(name), | ||
| "name mismatch", | ||
| ); | ||
| assert.equal( | ||
| fundAccount.managementFeeBps, | ||
| params.managementFeeBps, | ||
| "management_fee_bps mismatch", | ||
| ); | ||
| assert.equal( | ||
| fundAccount.performanceFeeBps, | ||
| params.performanceFeeBps, | ||
| "performance_fee_bps mismatch", | ||
| ); | ||
| assert(fundAccount.capacity.eq(params.capacity), "capacity mismatch"); | ||
| assert.equal( | ||
| fundAccount.withdrawalDelayDays, | ||
| params.withdrawalDelayDays, | ||
| "withdrawal_delay_days mismatch", | ||
| ); |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial | ⚡ Quick win
Consider verifying the stored PDA bumps.
The spec (SPEC.md line 92) states that the Fund account stores "PDA bumps needed to re-derive Vault / SharesMint cheaply," and the coding guidelines (docs/anchor.md lines 129-131) emphasize that "Account constraints on PDAs always store and reuse the bump."
The test should verify that the bumps stored on the fund account match the canonical bumps derived during PDA creation. This ensures the initialization correctly cached the bumps for future re-derivation.
🧪 Proposed enhancement
assert.equal(
fundAccount.withdrawalDelayDays,
params.withdrawalDelayDays,
"withdrawal_delay_days mismatch",
);
+
+ // Verify stored bumps match derived bumps.
+ const [, vaultBump] = PublicKey.findProgramAddressSync(
+ [Buffer.from("vault"), fund.toBuffer()],
+ program.programId,
+ );
+ const [, sharesMintBump] = PublicKey.findProgramAddressSync(
+ [Buffer.from("shares"), fund.toBuffer()],
+ program.programId,
+ );
+ assert.equal(fundAccount.bumps.vault, vaultBump, "vault bump mismatch");
+ assert.equal(
+ fundAccount.bumps.sharesMint,
+ sharesMintBump,
+ "shares_mint bump mismatch",
+ );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/create-fund.ts` around lines 78 - 102, Add assertions that the bumps
stored on the fetched fund account equal the canonical PDA bumps used to derive
the Vault and SharesMint; derive the PDAs (using the same seeds and program id
as the program) to get their bump values and assert they match
fundAccount.vaultBump and fundAccount.sharesMintBump (or the actual field names
used on fundAccount) so the test verifies the cached bumps are correct.
Summary by CodeRabbit
New Features
create_fundinstruction enabling creation of on-chain funds with configurable parameters (name, fees, capacity, withdrawal delay).Chores