acceptance policy

This commit is contained in:
thesn10
2026-05-25 19:59:04 +02:00
parent 4a7af8dc37
commit e093c9aa21

View File

@@ -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`