acceptance policy
This commit is contained in:
@@ -37,6 +37,7 @@ From Active (unchanged):
|
|||||||
| Modify | `programs/descro/src/state.rs` |
|
| Modify | `programs/descro/src/state.rs` |
|
||||||
| Create | `programs/descro/src/instructions/buyer_create_escrow.rs` |
|
| Create | `programs/descro/src/instructions/buyer_create_escrow.rs` |
|
||||||
| Create | `programs/descro/src/instructions/seller_confirm.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/instructions/cancel.rs` |
|
||||||
| Modify | `programs/descro/src/lib.rs` |
|
| Modify | `programs/descro/src/lib.rs` |
|
||||||
| Delete | `programs/descro/src/instructions/create_escrow_prefunded.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)
|
// 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**
|
- [ ] **Step 3: Write test**
|
||||||
|
|
||||||
In `programs/descro/tests/` (new file `test_buyer_flow.rs` or add to existing):
|
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.
|
> **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`**
|
- [ ] **Step 2: Register in `lib.rs`**
|
||||||
|
|
||||||
```rust
|
```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
|
## Task 4: Extend `cancel` — Symmetric Pre-Active Cancellation
|
||||||
|
|
||||||
**File:** `programs/descro/src/instructions/cancel.rs`
|
**File:** `programs/descro/src/instructions/cancel.rs`
|
||||||
|
|||||||
Reference in New Issue
Block a user