**Severity:** Medium
**Title:** Missing PDA seed constraint on fund account in FundContribute allows arbitrary account injection
## Description
The `FundContribute` accounts struct (`lib.rs:119-134`) declares the `fund` account using only the `#[account(mut)]` attribute. It completely lacks `seeds`, `bump`, or `has_one` constraints. Because the `contribute` instruction does not validate that the passed account is the legitimate PDA derived from the correct seeds, any attacker can pass an arbitrary, attacker-controlled account into the instruction.
## Impact
An attacker can perform a phishing attack by passing a malicious look-alike account address into the transaction. When victims sign and execute `contribute()`, their SOL is directed via the internal CPI transfer to the attacker's account rather than the intended campaign contract.
## Proof of Concept
This execution step explains how an attacker passes a rogue account destination to intercept contributor funds:
1. Attacker deploys a look-alike account or malicious address (Fund B).
2. The attacker tricks a contributor into executing a transaction intended for the legitimate campaign (Fund A), but passes the address of Fund B instead.
3. The contract processes the instruction and transfers the contributor's SOL straight to the attacker's address because no seed verification is executed.
```rust
contribute(
fund = attacker_controlled_address,
contributor = victim_signer,
amount = 10 * LAMPORTS_PER_SOL
)
```
## Recommended Mitigation
This mitigation applies Anchor seed constraints to ensure that only valid program-derived addresses matching the creator and campaign name seeds are accepted.
Update the `FundContribute` structural accounts layout to explicitly validate the PDA seeds and bump:
```rust
pub struct FundContribute<'info> {
#[account(
mut,
seeds = [fund.name.as_bytes(), fund.creator.as_ref()],
bump
)]
pub fund: Account<'info, Fund>,
pub contributor: Signer<'info>,
}
```