Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,15 @@ A configurable escrow program for Solana that enables receipt-based token deposi
- **Configurable timelocks** - Set lock durations that must pass before withdrawals
- **Custom hook programs** - Invoke external programs pre/post deposit and withdrawal

## Hook Warning

If a hook is configured and the escrow is later made immutable, that hook configuration becomes permanent.

- The hook cannot be changed or removed after immutability is set.
- Hook callbacks run at all four hook points: PreDeposit, PostDeposit, PreWithdraw, PostWithdraw.
- Any hook revert aborts the escrow instruction.
- A buggy or malicious hook can permanently block deposit and/or withdraw flows.

## Account Types

| Account | PDA Seeds | Description |
Expand Down
1 change: 1 addition & 0 deletions apps/web/src/components/instructions/SetHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export function SetHook() {
value={hookProgram}
onChange={setHookProgram}
placeholder="Program ID implementing the transfer hook"
hint="Warning: if this escrow is later set immutable, this hook dependency becomes permanent and hook reverts will block operations."
required
/>
<SendButton sending={sending} />
Expand Down
3 changes: 2 additions & 1 deletion apps/web/src/components/instructions/SetImmutable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export function SetImmutable() {
>
<div>
<Badge variant="warning">
This action is one-way. Escrow configuration becomes permanently immutable.
This action is one-way. Escrow configuration becomes permanently immutable. Any configured hook also
becomes permanent, and hook reverts will block escrow operations.
</Badge>
</div>
<FormField
Expand Down
12 changes: 11 additions & 1 deletion docs/PROGRAM_OVERVIEW.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,10 @@ Adds or updates the timelock extension.

Sets the hook program for deposit/withdraw callbacks.

**Warning:**

If an escrow is made immutable after setting a hook, the hook configuration is permanent and cannot be changed or removed.

**Accounts:**

| # | Name | Signer | Writable | Description |
Expand Down Expand Up @@ -387,6 +391,12 @@ Withdrawals blocked until `deposited_at + lock_duration`.

Hook receives 1-byte instruction data (hook point) and accounts: escrow, actor, mint, receipt, vault, plus any remaining accounts.

**Warning:**

- Hook execution is fail-closed. Any revert aborts the parent escrow instruction.
- All four hook points are enforced when a hook is configured.
- For immutable escrows, hook behavior is permanently embedded.

---

### BlockedTokenExtensions (type = 2)
Expand Down Expand Up @@ -432,7 +442,7 @@ During `AllowMint`, the program checks if the mint has any blocked Token-2022 ex
## Security Considerations

1. **Token-2022 blocking** - PermanentDelegate, NonTransferable, and Pausable are always blocked to prevent token manipulation
2. **Hook validation** - Hook programs must be passed correctly; mismatches cause HookProgramMismatch error
2. **Hook validation and liveness dependency** - Hook programs must be passed correctly; mismatches cause HookProgramMismatch, and hook reverts abort escrow operations (`HookRejected`)
3. **Receipt ownership** - Only the original depositor can withdraw using their receipt
4. **Timelock enforcement** - Clock sysvar used to verify lock duration has passed
5. **PDA validation** - All PDAs validated against expected seeds and bumps
5 changes: 5 additions & 0 deletions program/src/state/extensions/hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ impl HookData {

/// Validates and invokes the hook program.
///
/// Important: hook execution is fail-closed. Any hook CPI error aborts the
/// parent escrow instruction. If an escrow is set immutable with a hook
/// configured, this external dependency is permanently embedded.
///
/// # Arguments
/// * `hook_point` - The hook point discriminator
/// * `remaining_accounts` - Remaining accounts slice: [hook_program, extra_accounts...]
Expand Down Expand Up @@ -81,6 +85,7 @@ impl HookData {
data: &instruction_data,
};

// Preserve a stable escrow error surface for all hook CPI failures.
invoke_with_bounds::<16>(&instruction, &all_accounts).map_err(|_| EscrowProgramError::HookRejected.into())
}
}
Expand Down
Loading