feat(descro): add create_escrow_prefunded instruction
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
pub mod cancel;
|
pub mod cancel;
|
||||||
pub mod complete;
|
pub mod complete;
|
||||||
pub mod create_escrow;
|
pub mod create_escrow;
|
||||||
|
pub mod create_escrow_prefunded;
|
||||||
pub mod deposit;
|
pub mod deposit;
|
||||||
pub mod dispute;
|
pub mod dispute;
|
||||||
pub mod resolve;
|
pub mod resolve;
|
||||||
@@ -10,6 +11,7 @@ pub mod resolve;
|
|||||||
pub use cancel::*;
|
pub use cancel::*;
|
||||||
pub use complete::*;
|
pub use complete::*;
|
||||||
pub use create_escrow::*;
|
pub use create_escrow::*;
|
||||||
|
pub use create_escrow_prefunded::*;
|
||||||
pub use deposit::*;
|
pub use deposit::*;
|
||||||
pub use dispute::*;
|
pub use dispute::*;
|
||||||
pub use resolve::*;
|
pub use resolve::*;
|
||||||
|
|||||||
54
programs/descro/src/instructions/create_escrow_prefunded.rs
Normal file
54
programs/descro/src/instructions/create_escrow_prefunded.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use crate::state::{EscrowAccount, EscrowState};
|
||||||
|
use crate::error::EscrowError;
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
#[instruction(amount: u64, dispute_resolver: Option<Pubkey>, escrow_id: u64)]
|
||||||
|
pub struct CreateEscrowPrefunded<'info> {
|
||||||
|
#[account(mut)]
|
||||||
|
pub seller: Signer<'info>,
|
||||||
|
|
||||||
|
/// CHECK: Only stored as pubkey, no ownership check needed
|
||||||
|
pub buyer: UncheckedAccount<'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: Pre-funded vault PDA — balance verified in handler
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
seeds = [b"vault", escrow_account.key().as_ref()],
|
||||||
|
bump
|
||||||
|
)]
|
||||||
|
pub vault: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
pub system_program: Program<'info, System>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handler(
|
||||||
|
ctx: Context<CreateEscrowPrefunded>,
|
||||||
|
amount: u64,
|
||||||
|
dispute_resolver: Option<Pubkey>,
|
||||||
|
escrow_id: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
let vault_lamports = ctx.accounts.vault.to_account_info().lamports();
|
||||||
|
require!(vault_lamports >= amount, EscrowError::InsufficientVaultBalance);
|
||||||
|
|
||||||
|
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::Active;
|
||||||
|
escrow.bump = ctx.bumps.escrow_account;
|
||||||
|
escrow.vault_bump = ctx.bumps.vault;
|
||||||
|
escrow.escrow_id = escrow_id;
|
||||||
|
escrow.dispute_raised_at = None;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -26,6 +26,15 @@ pub mod descro {
|
|||||||
create_escrow::handler(ctx, amount, dispute_resolver, escrow_id)
|
create_escrow::handler(ctx, amount, dispute_resolver, escrow_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_escrow_prefunded(
|
||||||
|
ctx: Context<CreateEscrowPrefunded>,
|
||||||
|
amount: u64,
|
||||||
|
dispute_resolver: Option<Pubkey>,
|
||||||
|
escrow_id: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
create_escrow_prefunded::handler(ctx, amount, dispute_resolver, escrow_id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deposit(ctx: Context<Deposit>) -> Result<()> {
|
pub fn deposit(ctx: Context<Deposit>) -> Result<()> {
|
||||||
deposit::handler(ctx)
|
deposit::handler(ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,3 +199,31 @@ pub fn read_escrow(svm: &LiteSVM, seller: &Pubkey, escrow_id: u64) -> EscrowAcco
|
|||||||
let account = svm.get_account(&escrow).expect("escrow account not found");
|
let account = svm.get_account(&escrow).expect("escrow account not found");
|
||||||
EscrowAccount::try_deserialize(&mut account.data.as_slice()).unwrap()
|
EscrowAccount::try_deserialize(&mut account.data.as_slice()).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ix_create_escrow_prefunded(
|
||||||
|
seller: &Pubkey,
|
||||||
|
buyer: &Pubkey,
|
||||||
|
amount: u64,
|
||||||
|
dispute_resolver: Option<Pubkey>,
|
||||||
|
escrow_id: u64,
|
||||||
|
) -> Instruction {
|
||||||
|
let escrow = escrow_pda(seller, escrow_id);
|
||||||
|
let vault = vault_pda(&escrow);
|
||||||
|
Instruction::new_with_bytes(
|
||||||
|
descro::id(),
|
||||||
|
&descro::instruction::CreateEscrowPrefunded {
|
||||||
|
amount,
|
||||||
|
dispute_resolver,
|
||||||
|
escrow_id,
|
||||||
|
}
|
||||||
|
.data(),
|
||||||
|
descro::accounts::CreateEscrowPrefunded {
|
||||||
|
seller: *seller,
|
||||||
|
buyer: *buyer,
|
||||||
|
escrow_account: escrow,
|
||||||
|
vault,
|
||||||
|
system_program: system_program::ID,
|
||||||
|
}
|
||||||
|
.to_account_metas(None),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
107
programs/descro/tests/test_create_escrow_prefunded.rs
Normal file
107
programs/descro/tests/test_create_escrow_prefunded.rs
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
mod common;
|
||||||
|
use common::*;
|
||||||
|
use solana_signer::Signer;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefunded_creates_active_escrow() {
|
||||||
|
let (mut svm, seller, buyer, resolver) = setup();
|
||||||
|
|
||||||
|
// Pre-fund the vault PDA before the instruction
|
||||||
|
let escrow = escrow_pda(&seller.pubkey(), ESCROW_ID);
|
||||||
|
let vault = vault_pda(&escrow);
|
||||||
|
svm.airdrop(&vault, AMOUNT).unwrap();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow_prefunded(
|
||||||
|
&seller.pubkey(),
|
||||||
|
&buyer.pubkey(),
|
||||||
|
AMOUNT,
|
||||||
|
Some(resolver.pubkey()),
|
||||||
|
ESCROW_ID,
|
||||||
|
),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
|
||||||
|
let escrow_account = read_escrow(&svm, &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert_eq!(escrow_account.state, EscrowState::Active);
|
||||||
|
assert_eq!(escrow_account.seller, seller.pubkey());
|
||||||
|
assert_eq!(escrow_account.buyer, buyer.pubkey());
|
||||||
|
assert_eq!(escrow_account.amount, AMOUNT);
|
||||||
|
assert_eq!(escrow_account.dispute_raised_at, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefunded_fails_if_vault_underfunded() {
|
||||||
|
let (mut svm, seller, buyer, resolver) = setup();
|
||||||
|
|
||||||
|
// Fund vault with less than AMOUNT
|
||||||
|
let escrow = escrow_pda(&seller.pubkey(), ESCROW_ID);
|
||||||
|
let vault = vault_pda(&escrow);
|
||||||
|
svm.airdrop(&vault, AMOUNT - 1).unwrap();
|
||||||
|
|
||||||
|
assert!(!try_send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow_prefunded(
|
||||||
|
&seller.pubkey(),
|
||||||
|
&buyer.pubkey(),
|
||||||
|
AMOUNT,
|
||||||
|
Some(resolver.pubkey()),
|
||||||
|
ESCROW_ID,
|
||||||
|
),
|
||||||
|
&seller,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefunded_without_resolver_still_works() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
let escrow = escrow_pda(&seller.pubkey(), ESCROW_ID);
|
||||||
|
let vault = vault_pda(&escrow);
|
||||||
|
svm.airdrop(&vault, AMOUNT).unwrap();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow_prefunded(
|
||||||
|
&seller.pubkey(),
|
||||||
|
&buyer.pubkey(),
|
||||||
|
AMOUNT,
|
||||||
|
None,
|
||||||
|
ESCROW_ID,
|
||||||
|
),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
|
||||||
|
let escrow_account = read_escrow(&svm, &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert_eq!(escrow_account.state, EscrowState::Active);
|
||||||
|
assert_eq!(escrow_account.dispute_resolver, None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn prefunded_escrow_can_be_completed() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
let escrow = escrow_pda(&seller.pubkey(), ESCROW_ID);
|
||||||
|
let vault = vault_pda(&escrow);
|
||||||
|
svm.airdrop(&vault, AMOUNT).unwrap();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow_prefunded(
|
||||||
|
&seller.pubkey(),
|
||||||
|
&buyer.pubkey(),
|
||||||
|
AMOUNT,
|
||||||
|
None,
|
||||||
|
ESCROW_ID,
|
||||||
|
),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
|
||||||
|
let seller_before = svm.get_account(&seller.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
send(&mut svm, ix_complete(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
let seller_after = svm.get_account(&seller.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
assert!(seller_after > seller_before);
|
||||||
|
assert!(svm.get_account(&escrow_pda(&seller.pubkey(), ESCROW_ID)).is_none());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user