initial implement
This commit is contained in:
@@ -1,4 +1 @@
|
|||||||
use anchor_lang::prelude::*;
|
// reserved for future constants
|
||||||
|
|
||||||
#[constant]
|
|
||||||
pub const SEED: &str = "anchor";
|
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
#[error_code]
|
#[error_code]
|
||||||
pub enum ErrorCode {
|
pub enum EscrowError {
|
||||||
#[msg("Custom error message")]
|
#[msg("Invalid state for this instruction")]
|
||||||
CustomError,
|
InvalidState,
|
||||||
|
#[msg("Signer is not authorized")]
|
||||||
|
Unauthorized,
|
||||||
|
#[msg("No resolver configured for this escrow")]
|
||||||
|
NoResolverConfigured,
|
||||||
|
#[msg("Signer is not the configured resolver")]
|
||||||
|
UnauthorizedResolver,
|
||||||
|
#[msg("Escrow has expired")]
|
||||||
|
Expired,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,13 @@
|
|||||||
pub mod initialize;
|
pub mod cancel;
|
||||||
|
pub mod complete;
|
||||||
|
pub mod create_escrow;
|
||||||
|
pub mod deposit;
|
||||||
|
pub mod dispute;
|
||||||
|
pub mod resolve;
|
||||||
|
|
||||||
pub use initialize::*;
|
pub use cancel::*;
|
||||||
|
pub use complete::*;
|
||||||
|
pub use create_escrow::*;
|
||||||
|
pub use deposit::*;
|
||||||
|
pub use dispute::*;
|
||||||
|
pub use resolve::*;
|
||||||
|
|||||||
25
programs/descro/src/instructions/cancel.rs
Normal file
25
programs/descro/src/instructions/cancel.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use crate::state::{EscrowAccount, EscrowState};
|
||||||
|
use crate::error::EscrowError;
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct Cancel<'info> {
|
||||||
|
#[account(mut)]
|
||||||
|
pub seller: Signer<'info>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
seeds = [b"escrow", escrow_account.seller.as_ref(), &escrow_account.escrow_id.to_le_bytes()],
|
||||||
|
bump = escrow_account.bump,
|
||||||
|
constraint = seller.key() == escrow_account.seller @ EscrowError::Unauthorized,
|
||||||
|
constraint = escrow_account.state == EscrowState::AwaitingDeposit @ EscrowError::InvalidState,
|
||||||
|
close = seller,
|
||||||
|
)]
|
||||||
|
pub escrow_account: Account<'info, EscrowAccount>,
|
||||||
|
|
||||||
|
pub system_program: Program<'info, System>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handler(_ctx: Context<Cancel>) -> Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
59
programs/descro/src/instructions/complete.rs
Normal file
59
programs/descro/src/instructions/complete.rs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_lang::solana_program::{program::invoke_signed, system_instruction};
|
||||||
|
use crate::state::{EscrowAccount, EscrowState};
|
||||||
|
use crate::error::EscrowError;
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct Complete<'info> {
|
||||||
|
pub buyer: Signer<'info>,
|
||||||
|
|
||||||
|
/// CHECK: Seller receives vault funds and escrow rent
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
constraint = seller.key() == escrow_account.seller @ EscrowError::Unauthorized,
|
||||||
|
)]
|
||||||
|
pub seller: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
seeds = [b"escrow", escrow_account.seller.as_ref(), &escrow_account.escrow_id.to_le_bytes()],
|
||||||
|
bump = escrow_account.bump,
|
||||||
|
constraint = buyer.key() == escrow_account.buyer @ EscrowError::Unauthorized,
|
||||||
|
constraint = escrow_account.state == EscrowState::Active @ EscrowError::InvalidState,
|
||||||
|
close = seller,
|
||||||
|
)]
|
||||||
|
pub escrow_account: Account<'info, EscrowAccount>,
|
||||||
|
|
||||||
|
/// CHECK: PDA vault from which SOL is released
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
seeds = [b"vault", escrow_account.key().as_ref()],
|
||||||
|
bump = escrow_account.vault_bump
|
||||||
|
)]
|
||||||
|
pub vault: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
pub system_program: Program<'info, System>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handler(ctx: Context<Complete>) -> Result<()> {
|
||||||
|
ctx.accounts.escrow_account.state = EscrowState::Complete;
|
||||||
|
|
||||||
|
let escrow_key = ctx.accounts.escrow_account.key();
|
||||||
|
let vault_bump = ctx.accounts.escrow_account.vault_bump;
|
||||||
|
let vault_balance = ctx.accounts.vault.to_account_info().lamports();
|
||||||
|
|
||||||
|
invoke_signed(
|
||||||
|
&system_instruction::transfer(
|
||||||
|
ctx.accounts.vault.to_account_info().key,
|
||||||
|
ctx.accounts.seller.to_account_info().key,
|
||||||
|
vault_balance,
|
||||||
|
),
|
||||||
|
&[
|
||||||
|
ctx.accounts.vault.to_account_info(),
|
||||||
|
ctx.accounts.seller.to_account_info(),
|
||||||
|
],
|
||||||
|
&[&[b"vault", escrow_key.as_ref(), &[vault_bump]]],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
49
programs/descro/src/instructions/create_escrow.rs
Normal file
49
programs/descro/src/instructions/create_escrow.rs
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use crate::state::{EscrowAccount, EscrowState};
|
||||||
|
|
||||||
|
#[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>,
|
||||||
|
|
||||||
|
#[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>,
|
||||||
|
|
||||||
|
pub system_program: Program<'info, System>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handler(
|
||||||
|
ctx: Context<CreateEscrow>,
|
||||||
|
amount: u64,
|
||||||
|
dispute_resolver: Option<Pubkey>,
|
||||||
|
escrow_id: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
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;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
48
programs/descro/src/instructions/deposit.rs
Normal file
48
programs/descro/src/instructions/deposit.rs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_lang::solana_program::{program::invoke, system_instruction};
|
||||||
|
use crate::state::{EscrowAccount, EscrowState};
|
||||||
|
use crate::error::EscrowError;
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct Deposit<'info> {
|
||||||
|
#[account(mut)]
|
||||||
|
pub buyer: Signer<'info>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
seeds = [b"escrow", escrow_account.seller.as_ref(), &escrow_account.escrow_id.to_le_bytes()],
|
||||||
|
bump = escrow_account.bump,
|
||||||
|
constraint = buyer.key() == escrow_account.buyer @ EscrowError::Unauthorized,
|
||||||
|
constraint = escrow_account.state == EscrowState::AwaitingDeposit @ EscrowError::InvalidState,
|
||||||
|
)]
|
||||||
|
pub escrow_account: Account<'info, EscrowAccount>,
|
||||||
|
|
||||||
|
/// CHECK: PDA vault receiving the escrowed SOL
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
seeds = [b"vault", escrow_account.key().as_ref()],
|
||||||
|
bump = escrow_account.vault_bump
|
||||||
|
)]
|
||||||
|
pub vault: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
pub system_program: Program<'info, System>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handler(ctx: Context<Deposit>) -> Result<()> {
|
||||||
|
let amount = ctx.accounts.escrow_account.amount;
|
||||||
|
|
||||||
|
invoke(
|
||||||
|
&system_instruction::transfer(
|
||||||
|
ctx.accounts.buyer.to_account_info().key,
|
||||||
|
ctx.accounts.vault.to_account_info().key,
|
||||||
|
amount,
|
||||||
|
),
|
||||||
|
&[
|
||||||
|
ctx.accounts.buyer.to_account_info(),
|
||||||
|
ctx.accounts.vault.to_account_info(),
|
||||||
|
],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
ctx.accounts.escrow_account.state = EscrowState::Active;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
25
programs/descro/src/instructions/dispute.rs
Normal file
25
programs/descro/src/instructions/dispute.rs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use crate::state::{EscrowAccount, EscrowState};
|
||||||
|
use crate::error::EscrowError;
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct Dispute<'info> {
|
||||||
|
pub initiator: Signer<'info>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
seeds = [b"escrow", escrow_account.seller.as_ref(), &escrow_account.escrow_id.to_le_bytes()],
|
||||||
|
bump = escrow_account.bump,
|
||||||
|
constraint = (
|
||||||
|
initiator.key() == escrow_account.buyer ||
|
||||||
|
initiator.key() == escrow_account.seller
|
||||||
|
) @ EscrowError::Unauthorized,
|
||||||
|
constraint = escrow_account.state == EscrowState::Active @ EscrowError::InvalidState,
|
||||||
|
)]
|
||||||
|
pub escrow_account: Account<'info, EscrowAccount>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handler(ctx: Context<Dispute>) -> Result<()> {
|
||||||
|
ctx.accounts.escrow_account.state = EscrowState::Disputed;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
use anchor_lang::prelude::*;
|
|
||||||
|
|
||||||
#[derive(Accounts)]
|
|
||||||
pub struct Initialize {}
|
|
||||||
|
|
||||||
pub fn handler(ctx: Context<Initialize>) -> Result<()> {
|
|
||||||
msg!("Greetings from: {:?}", ctx.program_id);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
79
programs/descro/src/instructions/resolve.rs
Normal file
79
programs/descro/src/instructions/resolve.rs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_lang::solana_program::{program::invoke_signed, system_instruction};
|
||||||
|
use crate::state::{EscrowAccount, EscrowState, Winner};
|
||||||
|
use crate::error::EscrowError;
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct Resolve<'info> {
|
||||||
|
pub resolver: Signer<'info>,
|
||||||
|
|
||||||
|
/// CHECK: Winner of the dispute, receives vault funds
|
||||||
|
#[account(mut)]
|
||||||
|
pub winner: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
/// CHECK: Seller receives escrow account rent on close
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
constraint = seller.key() == escrow_account.seller @ EscrowError::Unauthorized,
|
||||||
|
)]
|
||||||
|
pub seller: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
seeds = [b"escrow", escrow_account.seller.as_ref(), &escrow_account.escrow_id.to_le_bytes()],
|
||||||
|
bump = escrow_account.bump,
|
||||||
|
constraint = escrow_account.state == EscrowState::Disputed @ EscrowError::InvalidState,
|
||||||
|
close = seller,
|
||||||
|
)]
|
||||||
|
pub escrow_account: Account<'info, EscrowAccount>,
|
||||||
|
|
||||||
|
/// CHECK: PDA vault from which SOL is released to the winner
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
seeds = [b"vault", escrow_account.key().as_ref()],
|
||||||
|
bump = escrow_account.vault_bump
|
||||||
|
)]
|
||||||
|
pub vault: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
pub system_program: Program<'info, System>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handler(ctx: Context<Resolve>, winner: Winner) -> Result<()> {
|
||||||
|
let escrow = &ctx.accounts.escrow_account;
|
||||||
|
|
||||||
|
require!(escrow.dispute_resolver.is_some(), EscrowError::NoResolverConfigured);
|
||||||
|
require!(
|
||||||
|
ctx.accounts.resolver.key() == escrow.dispute_resolver.unwrap(),
|
||||||
|
EscrowError::UnauthorizedResolver
|
||||||
|
);
|
||||||
|
|
||||||
|
let expected_winner = match winner {
|
||||||
|
Winner::Buyer => escrow.buyer,
|
||||||
|
Winner::Seller => escrow.seller,
|
||||||
|
};
|
||||||
|
require!(
|
||||||
|
ctx.accounts.winner.key() == expected_winner,
|
||||||
|
EscrowError::Unauthorized
|
||||||
|
);
|
||||||
|
|
||||||
|
ctx.accounts.escrow_account.state = EscrowState::Complete;
|
||||||
|
|
||||||
|
let escrow_key = ctx.accounts.escrow_account.key();
|
||||||
|
let vault_bump = ctx.accounts.escrow_account.vault_bump;
|
||||||
|
let vault_balance = ctx.accounts.vault.to_account_info().lamports();
|
||||||
|
|
||||||
|
invoke_signed(
|
||||||
|
&system_instruction::transfer(
|
||||||
|
ctx.accounts.vault.to_account_info().key,
|
||||||
|
ctx.accounts.winner.to_account_info().key,
|
||||||
|
vault_balance,
|
||||||
|
),
|
||||||
|
&[
|
||||||
|
ctx.accounts.vault.to_account_info(),
|
||||||
|
ctx.accounts.winner.to_account_info(),
|
||||||
|
],
|
||||||
|
&[&[b"vault", escrow_key.as_ref(), &[vault_bump]]],
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ pub mod state;
|
|||||||
|
|
||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
pub use constants::*;
|
pub use error::*;
|
||||||
pub use instructions::*;
|
pub use instructions::*;
|
||||||
pub use state::*;
|
pub use state::*;
|
||||||
|
|
||||||
@@ -15,7 +15,32 @@ declare_id!("DjVR4EuYV6USMJFfsGZwhZ3y8rtWsmG8EvDY96GTqqi3");
|
|||||||
pub mod descro {
|
pub mod descro {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
|
pub fn create_escrow(
|
||||||
initialize::handler(ctx)
|
ctx: Context<CreateEscrow>,
|
||||||
|
amount: u64,
|
||||||
|
dispute_resolver: Option<Pubkey>,
|
||||||
|
escrow_id: u64,
|
||||||
|
) -> Result<()> {
|
||||||
|
create_escrow::handler(ctx, amount, dispute_resolver, escrow_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deposit(ctx: Context<Deposit>) -> Result<()> {
|
||||||
|
deposit::handler(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn complete(ctx: Context<Complete>) -> Result<()> {
|
||||||
|
complete::handler(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dispute(ctx: Context<Dispute>) -> Result<()> {
|
||||||
|
dispute::handler(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(ctx: Context<Resolve>, winner: Winner) -> Result<()> {
|
||||||
|
resolve::handler(ctx, winner)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cancel(ctx: Context<Cancel>) -> Result<()> {
|
||||||
|
cancel::handler(ctx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
|
||||||
|
#[account]
|
||||||
|
#[derive(InitSpace)]
|
||||||
|
pub struct EscrowAccount {
|
||||||
|
pub seller: Pubkey,
|
||||||
|
pub buyer: Pubkey,
|
||||||
|
pub amount: u64,
|
||||||
|
pub dispute_resolver: Option<Pubkey>,
|
||||||
|
pub state: EscrowState,
|
||||||
|
pub bump: u8,
|
||||||
|
pub vault_bump: u8,
|
||||||
|
pub escrow_id: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, InitSpace, Debug)]
|
||||||
|
pub enum EscrowState {
|
||||||
|
AwaitingDeposit,
|
||||||
|
Active,
|
||||||
|
Disputed,
|
||||||
|
Complete,
|
||||||
|
Cancelled,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
|
||||||
|
pub enum Winner {
|
||||||
|
Buyer,
|
||||||
|
Seller,
|
||||||
|
}
|
||||||
|
|||||||
525
programs/descro/tests/escrow_tests.rs
Normal file
525
programs/descro/tests/escrow_tests.rs
Normal file
@@ -0,0 +1,525 @@
|
|||||||
|
use {
|
||||||
|
anchor_lang::{
|
||||||
|
solana_program::{instruction::Instruction, system_program},
|
||||||
|
AccountDeserialize, InstructionData, ToAccountMetas,
|
||||||
|
},
|
||||||
|
descro::{EscrowAccount, EscrowState, Winner},
|
||||||
|
litesvm::LiteSVM,
|
||||||
|
solana_keypair::Keypair,
|
||||||
|
solana_message::{Message, VersionedMessage},
|
||||||
|
solana_signer::Signer,
|
||||||
|
solana_transaction::versioned::VersionedTransaction,
|
||||||
|
};
|
||||||
|
use anchor_lang::prelude::Pubkey;
|
||||||
|
|
||||||
|
const AMOUNT: u64 = 1_000_000_000; // 1 SOL
|
||||||
|
const ESCROW_ID: u64 = 1;
|
||||||
|
|
||||||
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
fn setup() -> (LiteSVM, Keypair, Keypair, Keypair) {
|
||||||
|
let program_id = descro::id();
|
||||||
|
let mut svm = LiteSVM::new();
|
||||||
|
let bytes = include_bytes!("../../../target/deploy/descro.so");
|
||||||
|
svm.add_program(program_id, bytes).unwrap();
|
||||||
|
|
||||||
|
let seller = Keypair::new();
|
||||||
|
let buyer = Keypair::new();
|
||||||
|
let resolver = Keypair::new();
|
||||||
|
|
||||||
|
svm.airdrop(&seller.pubkey(), 10_000_000_000).unwrap();
|
||||||
|
svm.airdrop(&buyer.pubkey(), 10_000_000_000).unwrap();
|
||||||
|
svm.airdrop(&resolver.pubkey(), 1_000_000_000).unwrap();
|
||||||
|
|
||||||
|
(svm, seller, buyer, resolver)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn escrow_pda(seller: &Pubkey, escrow_id: u64) -> Pubkey {
|
||||||
|
Pubkey::find_program_address(
|
||||||
|
&[b"escrow", seller.as_ref(), &escrow_id.to_le_bytes()],
|
||||||
|
&descro::id(),
|
||||||
|
)
|
||||||
|
.0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vault_pda(escrow: &Pubkey) -> Pubkey {
|
||||||
|
Pubkey::find_program_address(&[b"vault", escrow.as_ref()], &descro::id()).0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send(svm: &mut LiteSVM, ix: Instruction, payer: &Keypair) {
|
||||||
|
let blockhash = svm.latest_blockhash();
|
||||||
|
let msg = Message::new_with_blockhash(&[ix], Some(&payer.pubkey()), &blockhash);
|
||||||
|
let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[payer]).unwrap();
|
||||||
|
svm.send_transaction(tx).expect("transaction failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_send(svm: &mut LiteSVM, ix: Instruction, payer: &Keypair) -> bool {
|
||||||
|
let blockhash = svm.latest_blockhash();
|
||||||
|
let msg = Message::new_with_blockhash(&[ix], Some(&payer.pubkey()), &blockhash);
|
||||||
|
let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[payer]).unwrap();
|
||||||
|
svm.send_transaction(tx).is_ok()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ix_create_escrow(
|
||||||
|
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::CreateEscrow { amount, dispute_resolver, escrow_id }.data(),
|
||||||
|
descro::accounts::CreateEscrow {
|
||||||
|
seller: *seller,
|
||||||
|
buyer: *buyer,
|
||||||
|
escrow_account: escrow,
|
||||||
|
vault,
|
||||||
|
system_program: system_program::ID,
|
||||||
|
}
|
||||||
|
.to_account_metas(None),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ix_deposit(buyer: &Pubkey, seller: &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::Deposit {}.data(),
|
||||||
|
descro::accounts::Deposit {
|
||||||
|
buyer: *buyer,
|
||||||
|
escrow_account: escrow,
|
||||||
|
vault,
|
||||||
|
system_program: system_program::ID,
|
||||||
|
}
|
||||||
|
.to_account_metas(None),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ix_complete(buyer: &Pubkey, seller: &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::Complete {}.data(),
|
||||||
|
descro::accounts::Complete {
|
||||||
|
buyer: *buyer,
|
||||||
|
seller: *seller,
|
||||||
|
escrow_account: escrow,
|
||||||
|
vault,
|
||||||
|
system_program: system_program::ID,
|
||||||
|
}
|
||||||
|
.to_account_metas(None),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ix_dispute(initiator: &Pubkey, seller: &Pubkey, escrow_id: u64) -> Instruction {
|
||||||
|
let escrow = escrow_pda(seller, escrow_id);
|
||||||
|
Instruction::new_with_bytes(
|
||||||
|
descro::id(),
|
||||||
|
&descro::instruction::Dispute {}.data(),
|
||||||
|
descro::accounts::Dispute {
|
||||||
|
initiator: *initiator,
|
||||||
|
escrow_account: escrow,
|
||||||
|
}
|
||||||
|
.to_account_metas(None),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ix_resolve(
|
||||||
|
resolver: &Pubkey,
|
||||||
|
winner_pubkey: &Pubkey,
|
||||||
|
seller: &Pubkey,
|
||||||
|
escrow_id: u64,
|
||||||
|
winner: Winner,
|
||||||
|
) -> Instruction {
|
||||||
|
let escrow = escrow_pda(seller, escrow_id);
|
||||||
|
let vault = vault_pda(&escrow);
|
||||||
|
Instruction::new_with_bytes(
|
||||||
|
descro::id(),
|
||||||
|
&descro::instruction::Resolve { winner }.data(),
|
||||||
|
descro::accounts::Resolve {
|
||||||
|
resolver: *resolver,
|
||||||
|
winner: *winner_pubkey,
|
||||||
|
seller: *seller,
|
||||||
|
escrow_account: escrow,
|
||||||
|
vault,
|
||||||
|
system_program: system_program::ID,
|
||||||
|
}
|
||||||
|
.to_account_metas(None),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ix_cancel(seller: &Pubkey, escrow_id: u64) -> Instruction {
|
||||||
|
let escrow = escrow_pda(seller, escrow_id);
|
||||||
|
Instruction::new_with_bytes(
|
||||||
|
descro::id(),
|
||||||
|
&descro::instruction::Cancel {}.data(),
|
||||||
|
descro::accounts::Cancel {
|
||||||
|
seller: *seller,
|
||||||
|
escrow_account: escrow,
|
||||||
|
system_program: system_program::ID,
|
||||||
|
}
|
||||||
|
.to_account_metas(None),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_escrow(svm: &LiteSVM, seller: &Pubkey, escrow_id: u64) -> EscrowAccount {
|
||||||
|
let escrow = escrow_pda(seller, escrow_id);
|
||||||
|
let account = svm.get_account(&escrow).expect("escrow account not found");
|
||||||
|
EscrowAccount::try_deserialize(&mut account.data.as_slice()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── tests ─────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_escrow() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
let ix = ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID);
|
||||||
|
send(&mut svm, ix, &seller);
|
||||||
|
|
||||||
|
let escrow = read_escrow(&svm, &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert_eq!(escrow.seller, seller.pubkey());
|
||||||
|
assert_eq!(escrow.buyer, buyer.pubkey());
|
||||||
|
assert_eq!(escrow.amount, AMOUNT);
|
||||||
|
assert_eq!(escrow.state, EscrowState::AwaitingDeposit);
|
||||||
|
assert!(escrow.dispute_resolver.is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_create_escrow_with_resolver() {
|
||||||
|
let (mut svm, seller, buyer, resolver) = setup();
|
||||||
|
|
||||||
|
let ix = ix_create_escrow(
|
||||||
|
&seller.pubkey(),
|
||||||
|
&buyer.pubkey(),
|
||||||
|
AMOUNT,
|
||||||
|
Some(resolver.pubkey()),
|
||||||
|
ESCROW_ID,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix, &seller);
|
||||||
|
|
||||||
|
let escrow = read_escrow(&svm, &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert_eq!(escrow.dispute_resolver, Some(resolver.pubkey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deposit() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
let vault = vault_pda(&escrow_pda(&seller.pubkey(), ESCROW_ID));
|
||||||
|
let vault_before = svm.get_account(&vault).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
let escrow = read_escrow(&svm, &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert_eq!(escrow.state, EscrowState::Active);
|
||||||
|
|
||||||
|
let vault_after = svm.get_account(&vault).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
assert_eq!(vault_after - vault_before, AMOUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complete_flow() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Escrow account should be closed
|
||||||
|
assert!(svm.get_account(&escrow_pda(&seller.pubkey(), ESCROW_ID)).is_none());
|
||||||
|
// Seller should have received the funds
|
||||||
|
let seller_after = svm.get_account(&seller.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
assert!(seller_after > seller_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dispute_by_buyer() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
send(&mut svm, ix_dispute(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
let escrow = read_escrow(&svm, &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert_eq!(escrow.state, EscrowState::Disputed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dispute_by_seller() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
send(&mut svm, ix_dispute(&seller.pubkey(), &seller.pubkey(), ESCROW_ID), &seller);
|
||||||
|
|
||||||
|
let escrow = read_escrow(&svm, &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert_eq!(escrow.state, EscrowState::Disputed);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_seller_wins() {
|
||||||
|
let (mut svm, seller, buyer, resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(
|
||||||
|
&seller.pubkey(),
|
||||||
|
&buyer.pubkey(),
|
||||||
|
AMOUNT,
|
||||||
|
Some(resolver.pubkey()),
|
||||||
|
ESCROW_ID,
|
||||||
|
),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
send(&mut svm, ix_dispute(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
let seller_before = svm.get_account(&seller.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_resolve(
|
||||||
|
&resolver.pubkey(),
|
||||||
|
&seller.pubkey(),
|
||||||
|
&seller.pubkey(),
|
||||||
|
ESCROW_ID,
|
||||||
|
Winner::Seller,
|
||||||
|
),
|
||||||
|
&resolver,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(svm.get_account(&escrow_pda(&seller.pubkey(), ESCROW_ID)).is_none());
|
||||||
|
let seller_after = svm.get_account(&seller.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
assert!(seller_after > seller_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_buyer_wins() {
|
||||||
|
let (mut svm, seller, buyer, resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(
|
||||||
|
&seller.pubkey(),
|
||||||
|
&buyer.pubkey(),
|
||||||
|
AMOUNT,
|
||||||
|
Some(resolver.pubkey()),
|
||||||
|
ESCROW_ID,
|
||||||
|
),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
send(&mut svm, ix_dispute(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
let buyer_before = svm.get_account(&buyer.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_resolve(
|
||||||
|
&resolver.pubkey(),
|
||||||
|
&buyer.pubkey(),
|
||||||
|
&seller.pubkey(),
|
||||||
|
ESCROW_ID,
|
||||||
|
Winner::Buyer,
|
||||||
|
),
|
||||||
|
&resolver,
|
||||||
|
);
|
||||||
|
|
||||||
|
let buyer_after = svm.get_account(&buyer.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
assert!(buyer_after > buyer_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cancel() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&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_cancel(&seller.pubkey(), ESCROW_ID), &seller);
|
||||||
|
|
||||||
|
// Escrow account closed, rent returned
|
||||||
|
assert!(svm.get_account(&escrow_pda(&seller.pubkey(), ESCROW_ID)).is_none());
|
||||||
|
let seller_after = svm.get_account(&seller.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
assert!(seller_after > seller_before);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── error cases ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_deposit_wrong_buyer_fails() {
|
||||||
|
let (mut svm, seller, buyer, resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
|
||||||
|
// resolver is NOT the buyer — should fail
|
||||||
|
let bad_ix = ix_deposit(&resolver.pubkey(), &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert!(!try_send(&mut svm, bad_ix, &resolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complete_wrong_state_fails() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
// Not deposited yet — state is AwaitingDeposit, not Active
|
||||||
|
let bad_ix = ix_complete(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert!(!try_send(&mut svm, bad_ix, &buyer));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_complete_wrong_buyer_fails() {
|
||||||
|
let (mut svm, seller, buyer, resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
// resolver is not the buyer
|
||||||
|
let bad_ix = ix_complete(&resolver.pubkey(), &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert!(!try_send(&mut svm, bad_ix, &resolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_no_resolver_fails() {
|
||||||
|
let (mut svm, seller, buyer, resolver) = setup();
|
||||||
|
|
||||||
|
// Create escrow WITHOUT a resolver
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
send(&mut svm, ix_dispute(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
let bad_ix = ix_resolve(
|
||||||
|
&resolver.pubkey(),
|
||||||
|
&seller.pubkey(),
|
||||||
|
&seller.pubkey(),
|
||||||
|
ESCROW_ID,
|
||||||
|
Winner::Seller,
|
||||||
|
);
|
||||||
|
assert!(!try_send(&mut svm, bad_ix, &resolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_resolve_wrong_resolver_fails() {
|
||||||
|
let (mut svm, seller, buyer, resolver) = setup();
|
||||||
|
let wrong_resolver = Keypair::new();
|
||||||
|
svm.airdrop(&wrong_resolver.pubkey(), 1_000_000_000).unwrap();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(
|
||||||
|
&seller.pubkey(),
|
||||||
|
&buyer.pubkey(),
|
||||||
|
AMOUNT,
|
||||||
|
Some(resolver.pubkey()),
|
||||||
|
ESCROW_ID,
|
||||||
|
),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
send(&mut svm, ix_dispute(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
let bad_ix = ix_resolve(
|
||||||
|
&wrong_resolver.pubkey(),
|
||||||
|
&seller.pubkey(),
|
||||||
|
&seller.pubkey(),
|
||||||
|
ESCROW_ID,
|
||||||
|
Winner::Seller,
|
||||||
|
);
|
||||||
|
assert!(!try_send(&mut svm, bad_ix, &wrong_resolver));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_cancel_after_deposit_fails() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
// State is now Active, cancel requires AwaitingDeposit
|
||||||
|
let bad_ix = ix_cancel(&seller.pubkey(), ESCROW_ID);
|
||||||
|
assert!(!try_send(&mut svm, bad_ix, &seller));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dispute_wrong_state_fails() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
// No deposit — state is AwaitingDeposit, dispute requires Active
|
||||||
|
let bad_ix = ix_dispute(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert!(!try_send(&mut svm, bad_ix, &buyer));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_dispute_third_party_fails() {
|
||||||
|
let (mut svm, seller, buyer, resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
send(&mut svm, ix_deposit(&buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
// Resolver is neither buyer nor seller
|
||||||
|
let bad_ix = ix_dispute(&resolver.pubkey(), &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert!(!try_send(&mut svm, bad_ix, &resolver));
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
|
|
||||||
use {
|
|
||||||
anchor_lang::{solana_program::instruction::Instruction, InstructionData, ToAccountMetas},
|
|
||||||
litesvm::LiteSVM,
|
|
||||||
solana_message::{Message, VersionedMessage},
|
|
||||||
solana_signer::Signer,
|
|
||||||
solana_keypair::Keypair,
|
|
||||||
solana_transaction::versioned::VersionedTransaction,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_initialize() {
|
|
||||||
let program_id = descro::id();
|
|
||||||
let payer = Keypair::new();
|
|
||||||
let mut svm = LiteSVM::new();
|
|
||||||
let bytes = include_bytes!("../../../target/deploy/descro.so");
|
|
||||||
svm.add_program(program_id, bytes).unwrap();
|
|
||||||
svm.airdrop(&payer.pubkey(), 1_000_000_000).unwrap();
|
|
||||||
|
|
||||||
let instruction = Instruction::new_with_bytes(
|
|
||||||
program_id,
|
|
||||||
&descro::instruction::Initialize {}.data(),
|
|
||||||
descro::accounts::Initialize {}.to_account_metas(None),
|
|
||||||
);
|
|
||||||
|
|
||||||
let blockhash = svm.latest_blockhash();
|
|
||||||
let msg = Message::new_with_blockhash(&[instruction], Some(&payer.pubkey()), &blockhash);
|
|
||||||
let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), &[payer]).unwrap();
|
|
||||||
|
|
||||||
let res = svm.send_transaction(tx);
|
|
||||||
assert!(res.is_ok());
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user