acceptance policy
This commit is contained in:
@@ -37,6 +37,7 @@ From Active (unchanged):
|
||||
| Modify | `programs/descro/src/state.rs` |
|
||||
| Create | `programs/descro/src/instructions/buyer_create_escrow.rs` |
|
||||
| Create | `programs/descro/src/instructions/seller_confirm.rs` |
|
||||
| Modify | `programs/descro/src/instructions/create_escrow.rs` |
|
||||
| Modify | `programs/descro/src/instructions/cancel.rs` |
|
||||
| Modify | `programs/descro/src/lib.rs` |
|
||||
| Delete | `programs/descro/src/instructions/create_escrow_prefunded.rs` |
|
||||
@@ -179,6 +180,8 @@ And add to the `cpi::accounts` module for CPI callers (solisting needs this):
|
||||
// but verify the IDL exports BuyerCreateEscrow accounts correctly after build)
|
||||
```
|
||||
|
||||
> **Note on AcceptancePolicy:** `buyer_create_escrow` intentionally has no AcceptancePolicy check. The resolver's primary relationship is with the seller (the seller selects and configures the resolver). The check therefore happens on the seller's transaction: `seller_confirm` in the buyer-initiated flow, `create_escrow` in the seller-initiated flow. `buyer_create_escrow` is the buyer's transaction — the seller is not yet involved and a resolver co-sign here would be premature. See Task 3 and Task 3b.
|
||||
|
||||
- [ ] **Step 3: Write test**
|
||||
|
||||
In `programs/descro/tests/` (new file `test_buyer_flow.rs` or add to existing):
|
||||
@@ -273,6 +276,8 @@ pub fn handler(ctx: Context<SellerConfirm>) -> Result<()> {
|
||||
|
||||
> **Note:** Add `ResolverSignatureRequired` to `EscrowError` if not already present.
|
||||
|
||||
> **Why here and not at `buyer_create_escrow`:** The resolver co-signs the seller's transaction because the seller holds the primary relationship with the resolver (the seller chose and configured them). This is symmetric across both flows: `seller_confirm` is the seller's transaction in the buyer-initiated flow; `create_escrow` is the seller's transaction in the seller-initiated flow. See Task 3b for the seller-initiated counterpart.
|
||||
|
||||
- [ ] **Step 2: Register in `lib.rs`**
|
||||
|
||||
```rust
|
||||
@@ -315,6 +320,127 @@ cargo test --manifest-path programs/descro/Cargo.toml 2>&1 | tail -15
|
||||
|
||||
---
|
||||
|
||||
## Task 3b: Extend `create_escrow` — Add AcceptancePolicy Check
|
||||
|
||||
**File:** `programs/descro/src/instructions/create_escrow.rs`
|
||||
|
||||
**Why here:** `create_escrow` is the seller's transaction in the seller-initiated flow — symmetric to `seller_confirm` in the buyer-initiated flow. The resolver co-signs alongside the seller, the party who chose and configured the resolver.
|
||||
|
||||
- [ ] **Step 1: Add `resolver` and `resolver_entry` accounts and policy check**
|
||||
|
||||
```rust
|
||||
use anchor_lang::prelude::*;
|
||||
use crate::state::{EscrowAccount, EscrowState};
|
||||
use crate::EscrowError;
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(amount: u64, dispute_resolver: Option<Pubkey>, escrow_id: u64)]
|
||||
pub struct CreateEscrow<'info> {
|
||||
#[account(mut)]
|
||||
pub seller: Signer<'info>,
|
||||
|
||||
/// CHECK: Only stored as pubkey, no ownership check needed
|
||||
pub buyer: UncheckedAccount<'info>,
|
||||
|
||||
/// CHECK: Resolver — may need to co-sign if AcceptancePolicy is SignatureGated
|
||||
pub resolver: AccountInfo<'info>,
|
||||
|
||||
#[account(
|
||||
init,
|
||||
payer = seller,
|
||||
space = 8 + EscrowAccount::INIT_SPACE,
|
||||
seeds = [b"escrow", seller.key().as_ref(), &escrow_id.to_le_bytes()],
|
||||
bump
|
||||
)]
|
||||
pub escrow_account: Account<'info, EscrowAccount>,
|
||||
|
||||
/// CHECK: PDA vault for holding escrow SOL; created implicitly on deposit
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [b"vault", escrow_account.key().as_ref()],
|
||||
bump
|
||||
)]
|
||||
pub vault: UncheckedAccount<'info>,
|
||||
|
||||
/// CHECK: Optional resolver registry entry — read to determine AcceptancePolicy
|
||||
pub resolver_entry: UncheckedAccount<'info>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
||||
pub fn handler(
|
||||
ctx: Context<CreateEscrow>,
|
||||
amount: u64,
|
||||
dispute_resolver: Option<Pubkey>,
|
||||
escrow_id: u64,
|
||||
) -> Result<()> {
|
||||
let rent_min = Rent::get()?.minimum_balance(0);
|
||||
require!(amount >= rent_min, EscrowError::AmountBelowRentMinimum);
|
||||
|
||||
// AcceptancePolicy check — only if resolver is set and has a registry entry
|
||||
if dispute_resolver.is_some() && !ctx.accounts.resolver_entry.data_is_empty() {
|
||||
let entry_data = ctx.accounts.resolver_entry.try_borrow_data()?;
|
||||
let entry = descro_ext_resolvers::state::ResolverEntry::try_deserialize(
|
||||
&mut entry_data.as_ref(),
|
||||
)?;
|
||||
match entry.acceptance_policy {
|
||||
descro_ext_resolvers::state::AcceptancePolicy::Open => {}
|
||||
descro_ext_resolvers::state::AcceptancePolicy::SignatureGated => {
|
||||
require!(
|
||||
ctx.accounts.resolver.is_signer,
|
||||
EscrowError::ResolverSignatureRequired
|
||||
);
|
||||
}
|
||||
descro_ext_resolvers::state::AcceptancePolicy::ProgramGated => {
|
||||
// Future: CPI to resolver program's accept_escrow instruction
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let escrow = &mut ctx.accounts.escrow_account;
|
||||
escrow.seller = ctx.accounts.seller.key();
|
||||
escrow.buyer = ctx.accounts.buyer.key();
|
||||
escrow.amount = amount;
|
||||
escrow.dispute_resolver = dispute_resolver;
|
||||
escrow.state = EscrowState::AwaitingDeposit;
|
||||
escrow.bump = ctx.bumps.escrow_account;
|
||||
escrow.vault_bump = ctx.bumps.vault;
|
||||
escrow.escrow_id = escrow_id;
|
||||
escrow.dispute_raised_at = None;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update existing tests that call `create_escrow`**
|
||||
|
||||
The accounts struct now requires `resolver` and `resolver_entry`. For tests without a registered resolver, pass a dummy account for `resolver` (no signer needed for `Open` or absent resolver) and an empty/system account for `resolver_entry`.
|
||||
|
||||
- [ ] **Step 3: Add policy tests**
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn create_escrow_open_resolver_no_cosign_needed() {
|
||||
// Register resolver with AcceptancePolicy::Open
|
||||
// create_escrow without resolver signing → should succeed
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_escrow_signature_gated_requires_resolver_cosign() {
|
||||
// Register resolver with AcceptancePolicy::SignatureGated
|
||||
// create_escrow WITHOUT resolver signing → must fail with ResolverSignatureRequired
|
||||
// create_escrow WITH resolver co-signing → AwaitingDeposit
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Build and test**
|
||||
|
||||
```bash
|
||||
cargo build-sbf --manifest-path programs/descro/Cargo.toml && \
|
||||
cargo test --manifest-path programs/descro/Cargo.toml 2>&1 | tail -15
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Extend `cancel` — Symmetric Pre-Active Cancellation
|
||||
|
||||
**File:** `programs/descro/src/instructions/cancel.rs`
|
||||
|
||||
Reference in New Issue
Block a user