buyer initiated flow
This commit is contained in:
@@ -18,4 +18,6 @@ pub enum EscrowError {
|
|||||||
InsufficientVaultBalance,
|
InsufficientVaultBalance,
|
||||||
#[msg("Amount must be at least rent-exempt minimum for the vault (~890880 lamports)")]
|
#[msg("Amount must be at least rent-exempt minimum for the vault (~890880 lamports)")]
|
||||||
AmountBelowRentMinimum,
|
AmountBelowRentMinimum,
|
||||||
|
#[msg("Resolver must co-sign this transaction (SignatureGated acceptance policy)")]
|
||||||
|
ResolverSignatureRequired,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
#![allow(ambiguous_glob_reexports)]
|
#![allow(ambiguous_glob_reexports)]
|
||||||
|
|
||||||
|
pub mod buyer_create_escrow;
|
||||||
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 emergency_resolve;
|
pub mod emergency_resolve;
|
||||||
pub mod resolve;
|
pub mod resolve;
|
||||||
|
pub mod seller_confirm;
|
||||||
|
|
||||||
|
pub use buyer_create_escrow::*;
|
||||||
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 emergency_resolve::*;
|
pub use emergency_resolve::*;
|
||||||
pub use resolve::*;
|
pub use resolve::*;
|
||||||
|
pub use seller_confirm::*;
|
||||||
|
|||||||
@@ -1,26 +1,27 @@
|
|||||||
use anchor_lang::prelude::*;
|
|
||||||
use crate::state::{EscrowAccount, EscrowState};
|
use crate::state::{EscrowAccount, EscrowState};
|
||||||
use crate::error::EscrowError;
|
use crate::EscrowError;
|
||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_lang::system_program::{self, Transfer};
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
#[instruction(amount: u64, dispute_resolver: Option<Pubkey>, escrow_id: u64)]
|
#[instruction(amount: u64, dispute_resolver: Option<Pubkey>, escrow_id: u64)]
|
||||||
pub struct CreateEscrowPrefunded<'info> {
|
pub struct BuyerCreateEscrow<'info> {
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
pub seller: Signer<'info>,
|
pub buyer: Signer<'info>,
|
||||||
|
|
||||||
/// CHECK: Only stored as pubkey, no ownership check needed
|
/// CHECK: Only stored as pubkey, no ownership check needed
|
||||||
pub buyer: UncheckedAccount<'info>,
|
pub seller: UncheckedAccount<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
init,
|
init,
|
||||||
payer = seller,
|
payer = buyer,
|
||||||
space = 8 + EscrowAccount::INIT_SPACE,
|
space = 8 + EscrowAccount::INIT_SPACE,
|
||||||
seeds = [b"escrow", seller.key().as_ref(), &escrow_id.to_le_bytes()],
|
seeds = [b"escrow", seller.key().as_ref(), &escrow_id.to_le_bytes()],
|
||||||
bump
|
bump
|
||||||
)]
|
)]
|
||||||
pub escrow_account: Account<'info, EscrowAccount>,
|
pub escrow_account: Account<'info, EscrowAccount>,
|
||||||
|
|
||||||
/// CHECK: Pre-funded vault PDA — balance verified in handler
|
/// CHECK: PDA vault for holding escrow SOL; created implicitly on deposit
|
||||||
#[account(
|
#[account(
|
||||||
mut,
|
mut,
|
||||||
seeds = [b"vault", escrow_account.key().as_ref()],
|
seeds = [b"vault", escrow_account.key().as_ref()],
|
||||||
@@ -32,20 +33,31 @@ pub struct CreateEscrowPrefunded<'info> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn handler(
|
pub fn handler(
|
||||||
ctx: Context<CreateEscrowPrefunded>,
|
ctx: Context<BuyerCreateEscrow>,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
dispute_resolver: Option<Pubkey>,
|
dispute_resolver: Option<Pubkey>,
|
||||||
escrow_id: u64,
|
escrow_id: u64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let vault_lamports = ctx.accounts.vault.to_account_info().lamports();
|
let rent_min = Rent::get()?.minimum_balance(0);
|
||||||
require!(vault_lamports >= amount, EscrowError::InsufficientVaultBalance);
|
require!(amount >= rent_min, EscrowError::AmountBelowRentMinimum);
|
||||||
|
|
||||||
|
system_program::transfer(
|
||||||
|
CpiContext::new(
|
||||||
|
system_program::ID,
|
||||||
|
Transfer {
|
||||||
|
from: ctx.accounts.buyer.to_account_info(),
|
||||||
|
to: ctx.accounts.vault.to_account_info(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
amount,
|
||||||
|
)?;
|
||||||
|
|
||||||
let escrow = &mut ctx.accounts.escrow_account;
|
let escrow = &mut ctx.accounts.escrow_account;
|
||||||
escrow.seller = ctx.accounts.seller.key();
|
escrow.seller = ctx.accounts.seller.key();
|
||||||
escrow.buyer = ctx.accounts.buyer.key();
|
escrow.buyer = ctx.accounts.buyer.key();
|
||||||
escrow.amount = amount;
|
escrow.amount = amount;
|
||||||
escrow.dispute_resolver = dispute_resolver;
|
escrow.dispute_resolver = dispute_resolver;
|
||||||
escrow.state = EscrowState::Active;
|
escrow.state = EscrowState::AwaitingSellerConfirm;
|
||||||
escrow.bump = ctx.bumps.escrow_account;
|
escrow.bump = ctx.bumps.escrow_account;
|
||||||
escrow.vault_bump = ctx.bumps.vault;
|
escrow.vault_bump = ctx.bumps.vault;
|
||||||
escrow.escrow_id = escrow_id;
|
escrow.escrow_id = escrow_id;
|
||||||
@@ -1,25 +1,66 @@
|
|||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
|
use anchor_lang::system_program::{self, Transfer};
|
||||||
use crate::state::{EscrowAccount, EscrowState};
|
use crate::state::{EscrowAccount, EscrowState};
|
||||||
use crate::error::EscrowError;
|
use crate::error::EscrowError;
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
pub struct Cancel<'info> {
|
pub struct Cancel<'info> {
|
||||||
#[account(mut)]
|
#[account(mut)]
|
||||||
pub seller: Signer<'info>,
|
pub canceller: Signer<'info>,
|
||||||
|
|
||||||
|
/// CHECK: Buyer receives vault refund if funds are present
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
constraint = buyer.key() == escrow_account.buyer @ EscrowError::Unauthorized,
|
||||||
|
)]
|
||||||
|
pub buyer: UncheckedAccount<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
mut,
|
mut,
|
||||||
seeds = [b"escrow", escrow_account.seller.as_ref(), &escrow_account.escrow_id.to_le_bytes()],
|
seeds = [b"escrow", escrow_account.seller.as_ref(), &escrow_account.escrow_id.to_le_bytes()],
|
||||||
bump = escrow_account.bump,
|
bump = escrow_account.bump,
|
||||||
constraint = seller.key() == escrow_account.seller @ EscrowError::Unauthorized,
|
constraint = (
|
||||||
constraint = escrow_account.state == EscrowState::AwaitingDeposit @ EscrowError::InvalidState,
|
canceller.key() == escrow_account.buyer ||
|
||||||
close = seller,
|
canceller.key() == escrow_account.seller
|
||||||
|
) @ EscrowError::Unauthorized,
|
||||||
|
constraint = (
|
||||||
|
escrow_account.state == EscrowState::AwaitingDeposit ||
|
||||||
|
escrow_account.state == EscrowState::AwaitingSellerConfirm
|
||||||
|
) @ EscrowError::InvalidState,
|
||||||
|
close = canceller,
|
||||||
)]
|
)]
|
||||||
pub escrow_account: Account<'info, EscrowAccount>,
|
pub escrow_account: Account<'info, EscrowAccount>,
|
||||||
|
|
||||||
|
/// CHECK: Vault PDA — may hold funds (AwaitingSellerConfirm) or be empty (AwaitingDeposit)
|
||||||
|
#[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 system_program: Program<'info, System>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handler(_ctx: Context<Cancel>) -> Result<()> {
|
pub fn handler(ctx: Context<Cancel>) -> Result<()> {
|
||||||
|
let vault_lamports = ctx.accounts.vault.to_account_info().lamports();
|
||||||
|
|
||||||
|
if vault_lamports > 0 {
|
||||||
|
let escrow_key = ctx.accounts.escrow_account.key();
|
||||||
|
let vault_bump = ctx.accounts.escrow_account.vault_bump;
|
||||||
|
|
||||||
|
system_program::transfer(
|
||||||
|
CpiContext::new_with_signer(
|
||||||
|
system_program::ID,
|
||||||
|
Transfer {
|
||||||
|
from: ctx.accounts.vault.to_account_info(),
|
||||||
|
to: ctx.accounts.buyer.to_account_info(),
|
||||||
|
},
|
||||||
|
&[&[b"vault", escrow_key.as_ref(), &[vault_bump]]],
|
||||||
|
),
|
||||||
|
vault_lamports,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ pub struct CreateEscrow<'info> {
|
|||||||
/// CHECK: Only stored as pubkey, no ownership check needed
|
/// CHECK: Only stored as pubkey, no ownership check needed
|
||||||
pub buyer: UncheckedAccount<'info>,
|
pub buyer: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
/// CHECK: Resolver — may need to co-sign if AcceptancePolicy is SignatureGated
|
||||||
|
pub resolver: UncheckedAccount<'info>,
|
||||||
|
|
||||||
#[account(
|
#[account(
|
||||||
init,
|
init,
|
||||||
payer = seller,
|
payer = seller,
|
||||||
@@ -28,6 +31,9 @@ pub struct CreateEscrow<'info> {
|
|||||||
)]
|
)]
|
||||||
pub vault: UncheckedAccount<'info>,
|
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 system_program: Program<'info, System>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,6 +46,23 @@ pub fn handler(
|
|||||||
let rent_min = Rent::get()?.minimum_balance(0);
|
let rent_min = Rent::get()?.minimum_balance(0);
|
||||||
require!(amount >= rent_min, EscrowError::AmountBelowRentMinimum);
|
require!(amount >= rent_min, EscrowError::AmountBelowRentMinimum);
|
||||||
|
|
||||||
|
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 => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let escrow = &mut ctx.accounts.escrow_account;
|
let escrow = &mut ctx.accounts.escrow_account;
|
||||||
escrow.seller = ctx.accounts.seller.key();
|
escrow.seller = ctx.accounts.seller.key();
|
||||||
escrow.buyer = ctx.accounts.buyer.key();
|
escrow.buyer = ctx.accounts.buyer.key();
|
||||||
|
|||||||
47
programs/descro/src/instructions/seller_confirm.rs
Normal file
47
programs/descro/src/instructions/seller_confirm.rs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
use anchor_lang::prelude::*;
|
||||||
|
use crate::state::{EscrowAccount, EscrowState};
|
||||||
|
use crate::error::EscrowError;
|
||||||
|
|
||||||
|
#[derive(Accounts)]
|
||||||
|
pub struct SellerConfirm<'info> {
|
||||||
|
pub seller: Signer<'info>,
|
||||||
|
|
||||||
|
/// CHECK: Resolver — may need to co-sign if AcceptancePolicy is SignatureGated
|
||||||
|
pub resolver: UncheckedAccount<'info>,
|
||||||
|
|
||||||
|
#[account(
|
||||||
|
mut,
|
||||||
|
seeds = [b"escrow", seller.key().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::AwaitingSellerConfirm @ EscrowError::InvalidState,
|
||||||
|
)]
|
||||||
|
pub escrow_account: Account<'info, EscrowAccount>,
|
||||||
|
|
||||||
|
/// CHECK: Optional resolver registry entry — read to determine AcceptancePolicy
|
||||||
|
pub resolver_entry: UncheckedAccount<'info>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handler(ctx: Context<SellerConfirm>) -> Result<()> {
|
||||||
|
if ctx.accounts.escrow_account.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 => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.accounts.escrow_account.state = EscrowState::Active;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -26,13 +26,13 @@ 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(
|
pub fn buyer_create_escrow(
|
||||||
ctx: Context<CreateEscrowPrefunded>,
|
ctx: Context<BuyerCreateEscrow>,
|
||||||
amount: u64,
|
amount: u64,
|
||||||
dispute_resolver: Option<Pubkey>,
|
dispute_resolver: Option<Pubkey>,
|
||||||
escrow_id: u64,
|
escrow_id: u64,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
create_escrow_prefunded::handler(ctx, amount, dispute_resolver, escrow_id)
|
buyer_create_escrow::handler(ctx, amount, dispute_resolver, escrow_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deposit(ctx: Context<Deposit>) -> Result<()> {
|
pub fn deposit(ctx: Context<Deposit>) -> Result<()> {
|
||||||
@@ -51,6 +51,10 @@ pub mod descro {
|
|||||||
resolve::handler(ctx, winner)
|
resolve::handler(ctx, winner)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn seller_confirm(ctx: Context<SellerConfirm>) -> Result<()> {
|
||||||
|
seller_confirm::handler(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn cancel(ctx: Context<Cancel>) -> Result<()> {
|
pub fn cancel(ctx: Context<Cancel>) -> Result<()> {
|
||||||
cancel::handler(ctx)
|
cancel::handler(ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ pub struct EscrowAccount {
|
|||||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, InitSpace, Debug)]
|
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, InitSpace, Debug)]
|
||||||
pub enum EscrowState {
|
pub enum EscrowState {
|
||||||
AwaitingDeposit,
|
AwaitingDeposit,
|
||||||
|
AwaitingSellerConfirm,
|
||||||
Active,
|
Active,
|
||||||
Disputed,
|
Disputed,
|
||||||
Complete,
|
Complete,
|
||||||
|
|||||||
@@ -81,6 +81,24 @@ pub fn ix_create_escrow(
|
|||||||
amount: u64,
|
amount: u64,
|
||||||
dispute_resolver: Option<Pubkey>,
|
dispute_resolver: Option<Pubkey>,
|
||||||
escrow_id: u64,
|
escrow_id: u64,
|
||||||
|
) -> Instruction {
|
||||||
|
// Default: derive the resolver_entry PDA from the resolver (won't exist → empty account → policy check skipped)
|
||||||
|
let dummy = Pubkey::new_from_array([1u8; 32]);
|
||||||
|
let (resolver, resolver_entry) = match dispute_resolver {
|
||||||
|
Some(r) => (r, resolver_entry_pda(&r)),
|
||||||
|
None => (dummy, dummy),
|
||||||
|
};
|
||||||
|
ix_create_escrow_with_resolver(seller, buyer, amount, dispute_resolver, escrow_id, &resolver, &resolver_entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ix_create_escrow_with_resolver(
|
||||||
|
seller: &Pubkey,
|
||||||
|
buyer: &Pubkey,
|
||||||
|
amount: u64,
|
||||||
|
dispute_resolver: Option<Pubkey>,
|
||||||
|
escrow_id: u64,
|
||||||
|
resolver: &Pubkey,
|
||||||
|
resolver_entry: &Pubkey,
|
||||||
) -> Instruction {
|
) -> Instruction {
|
||||||
let escrow = escrow_pda(seller, escrow_id);
|
let escrow = escrow_pda(seller, escrow_id);
|
||||||
let vault = vault_pda(&escrow);
|
let vault = vault_pda(&escrow);
|
||||||
@@ -95,8 +113,10 @@ pub fn ix_create_escrow(
|
|||||||
descro::accounts::CreateEscrow {
|
descro::accounts::CreateEscrow {
|
||||||
seller: *seller,
|
seller: *seller,
|
||||||
buyer: *buyer,
|
buyer: *buyer,
|
||||||
|
resolver: *resolver,
|
||||||
escrow_account: escrow,
|
escrow_account: escrow,
|
||||||
vault,
|
vault,
|
||||||
|
resolver_entry: *resolver_entry,
|
||||||
system_program: system_program::ID,
|
system_program: system_program::ID,
|
||||||
}
|
}
|
||||||
.to_account_metas(None),
|
.to_account_metas(None),
|
||||||
@@ -182,20 +202,66 @@ pub fn ix_resolve(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ix_cancel(seller: &Pubkey, escrow_id: u64) -> Instruction {
|
pub fn ix_cancel(canceller: &Pubkey, buyer: &Pubkey, seller: &Pubkey, escrow_id: u64) -> Instruction {
|
||||||
let escrow = escrow_pda(seller, escrow_id);
|
let escrow = escrow_pda(seller, escrow_id);
|
||||||
|
let vault = vault_pda(&escrow);
|
||||||
Instruction::new_with_bytes(
|
Instruction::new_with_bytes(
|
||||||
descro::id(),
|
descro::id(),
|
||||||
&descro::instruction::Cancel {}.data(),
|
&descro::instruction::Cancel {}.data(),
|
||||||
descro::accounts::Cancel {
|
descro::accounts::Cancel {
|
||||||
|
canceller: *canceller,
|
||||||
|
buyer: *buyer,
|
||||||
|
escrow_account: escrow,
|
||||||
|
vault,
|
||||||
|
system_program: system_program::ID,
|
||||||
|
}
|
||||||
|
.to_account_metas(None),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ix_buyer_create_escrow(
|
||||||
|
buyer: &Pubkey,
|
||||||
|
seller: &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::BuyerCreateEscrow {
|
||||||
|
amount,
|
||||||
|
dispute_resolver,
|
||||||
|
escrow_id,
|
||||||
|
}
|
||||||
|
.data(),
|
||||||
|
descro::accounts::BuyerCreateEscrow {
|
||||||
|
buyer: *buyer,
|
||||||
seller: *seller,
|
seller: *seller,
|
||||||
escrow_account: escrow,
|
escrow_account: escrow,
|
||||||
|
vault,
|
||||||
system_program: system_program::ID,
|
system_program: system_program::ID,
|
||||||
}
|
}
|
||||||
.to_account_metas(None),
|
.to_account_metas(None),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ix_seller_confirm(seller: &Pubkey, resolver: &Pubkey, resolver_entry: &Pubkey, escrow_id: u64) -> Instruction {
|
||||||
|
let escrow = escrow_pda(seller, escrow_id);
|
||||||
|
Instruction::new_with_bytes(
|
||||||
|
descro::id(),
|
||||||
|
&descro::instruction::SellerConfirm {}.data(),
|
||||||
|
descro::accounts::SellerConfirm {
|
||||||
|
seller: *seller,
|
||||||
|
resolver: *resolver,
|
||||||
|
escrow_account: escrow,
|
||||||
|
resolver_entry: *resolver_entry,
|
||||||
|
}
|
||||||
|
.to_account_metas(None),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_escrow(svm: &LiteSVM, seller: &Pubkey, escrow_id: u64) -> EscrowAccount {
|
pub fn read_escrow(svm: &LiteSVM, seller: &Pubkey, escrow_id: u64) -> EscrowAccount {
|
||||||
let escrow = escrow_pda(seller, escrow_id);
|
let escrow = escrow_pda(seller, escrow_id);
|
||||||
let account = svm.get_account(&escrow).expect("escrow account not found");
|
let account = svm.get_account(&escrow).expect("escrow account not found");
|
||||||
@@ -247,30 +313,3 @@ pub fn ix_emergency_resolve(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
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),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
74
programs/descro/tests/test_buyer_flow.rs
Normal file
74
programs/descro/tests/test_buyer_flow.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
mod common;
|
||||||
|
use common::*;
|
||||||
|
use anchor_lang::solana_program::system_program;
|
||||||
|
use solana_signer::Signer;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn buyer_can_create_and_fund_escrow() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_buyer_create_escrow(&buyer.pubkey(), &seller.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&buyer,
|
||||||
|
);
|
||||||
|
|
||||||
|
let escrow = read_escrow(&svm, &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert_eq!(escrow.state, EscrowState::AwaitingSellerConfirm);
|
||||||
|
assert_eq!(escrow.buyer, buyer.pubkey());
|
||||||
|
assert_eq!(escrow.seller, seller.pubkey());
|
||||||
|
assert_eq!(escrow.amount, AMOUNT);
|
||||||
|
|
||||||
|
let vault = vault_pda(&escrow_pda(&seller.pubkey(), ESCROW_ID));
|
||||||
|
let vault_lamports = svm.get_account(&vault).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
assert_eq!(vault_lamports, AMOUNT);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn buyer_create_escrow_fails_below_rent_minimum() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
assert!(!try_send(
|
||||||
|
&mut svm,
|
||||||
|
ix_buyer_create_escrow(&buyer.pubkey(), &seller.pubkey(), 0, None, ESCROW_ID),
|
||||||
|
&buyer,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seller_can_confirm_buyer_escrow() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_buyer_create_escrow(&buyer.pubkey(), &seller.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&buyer,
|
||||||
|
);
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_seller_confirm(&seller.pubkey(), &seller.pubkey(), &system_program::ID, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
|
||||||
|
let escrow = read_escrow(&svm, &seller.pubkey(), ESCROW_ID);
|
||||||
|
assert_eq!(escrow.state, EscrowState::Active);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seller_confirm_fails_from_wrong_state() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
// Seller-initiated → AwaitingDeposit — seller_confirm must fail
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_create_escrow(&seller.pubkey(), &buyer.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert!(!try_send(
|
||||||
|
&mut svm,
|
||||||
|
ix_seller_confirm(&seller.pubkey(), &seller.pubkey(), &system_program::ID, ESCROW_ID),
|
||||||
|
&seller,
|
||||||
|
));
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ use common::*;
|
|||||||
use solana_signer::Signer;
|
use solana_signer::Signer;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cancel_returns_rent_to_seller() {
|
fn seller_can_cancel_awaiting_deposit() {
|
||||||
let (mut svm, seller, buyer, _resolver) = setup();
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
send(
|
send(
|
||||||
@@ -14,7 +14,7 @@ fn cancel_returns_rent_to_seller() {
|
|||||||
|
|
||||||
let seller_before = svm.get_account(&seller.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
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);
|
send(&mut svm, ix_cancel(&seller.pubkey(), &buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &seller);
|
||||||
|
|
||||||
assert!(svm.get_account(&escrow_pda(&seller.pubkey(), ESCROW_ID)).is_none());
|
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);
|
let seller_after = svm.get_account(&seller.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
@@ -22,7 +22,60 @@ fn cancel_returns_rent_to_seller() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn cancel_after_deposit_fails() {
|
fn buyer_can_cancel_awaiting_deposit() {
|
||||||
|
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_cancel(&buyer.pubkey(), &buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
assert!(svm.get_account(&escrow_pda(&seller.pubkey(), ESCROW_ID)).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn seller_can_cancel_awaiting_seller_confirm() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_buyer_create_escrow(&buyer.pubkey(), &seller.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&buyer,
|
||||||
|
);
|
||||||
|
|
||||||
|
let buyer_before = svm.get_account(&buyer.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
|
||||||
|
send(&mut svm, ix_cancel(&seller.pubkey(), &buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &seller);
|
||||||
|
|
||||||
|
assert!(svm.get_account(&escrow_pda(&seller.pubkey(), ESCROW_ID)).is_none());
|
||||||
|
let buyer_after = svm.get_account(&buyer.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
assert!(buyer_after > buyer_before, "buyer should have received vault refund");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn buyer_can_cancel_awaiting_seller_confirm() {
|
||||||
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
|
send(
|
||||||
|
&mut svm,
|
||||||
|
ix_buyer_create_escrow(&buyer.pubkey(), &seller.pubkey(), AMOUNT, None, ESCROW_ID),
|
||||||
|
&buyer,
|
||||||
|
);
|
||||||
|
|
||||||
|
let buyer_before = svm.get_account(&buyer.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
|
||||||
|
send(&mut svm, ix_cancel(&buyer.pubkey(), &buyer.pubkey(), &seller.pubkey(), ESCROW_ID), &buyer);
|
||||||
|
|
||||||
|
assert!(svm.get_account(&escrow_pda(&seller.pubkey(), ESCROW_ID)).is_none());
|
||||||
|
let buyer_after = svm.get_account(&buyer.pubkey()).map(|a| a.lamports).unwrap_or(0);
|
||||||
|
assert!(buyer_after > buyer_before, "buyer should have received vault refund");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn cannot_cancel_active_escrow() {
|
||||||
let (mut svm, seller, buyer, _resolver) = setup();
|
let (mut svm, seller, buyer, _resolver) = setup();
|
||||||
|
|
||||||
send(
|
send(
|
||||||
@@ -34,7 +87,7 @@ fn cancel_after_deposit_fails() {
|
|||||||
|
|
||||||
assert!(!try_send(
|
assert!(!try_send(
|
||||||
&mut svm,
|
&mut svm,
|
||||||
ix_cancel(&seller.pubkey(), ESCROW_ID),
|
ix_cancel(&seller.pubkey(), &buyer.pubkey(), &seller.pubkey(), ESCROW_ID),
|
||||||
&seller,
|
&seller,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
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());
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ use common::*;
|
|||||||
use anchor_lang::InstructionData;
|
use anchor_lang::InstructionData;
|
||||||
use anchor_lang::ToAccountMetas;
|
use anchor_lang::ToAccountMetas;
|
||||||
use anchor_lang::solana_program::{instruction::Instruction, system_program};
|
use anchor_lang::solana_program::{instruction::Instruction, system_program};
|
||||||
use descro_ext_resolvers::{ResolverType, ResolverEntry};
|
use descro_ext_resolvers::{AcceptancePolicy, ResolverType, ResolverEntry};
|
||||||
use anchor_lang::AccountDeserialize;
|
use anchor_lang::AccountDeserialize;
|
||||||
use litesvm::LiteSVM;
|
use litesvm::LiteSVM;
|
||||||
use solana_keypair::Keypair;
|
use solana_keypair::Keypair;
|
||||||
@@ -37,6 +37,7 @@ fn ix_register_resolver(authority: &Pubkey) -> Instruction {
|
|||||||
descro_ext_resolvers::id(),
|
descro_ext_resolvers::id(),
|
||||||
&descro_ext_resolvers::instruction::RegisterResolver {
|
&descro_ext_resolvers::instruction::RegisterResolver {
|
||||||
resolver_type: ResolverType::CentralAuthority,
|
resolver_type: ResolverType::CentralAuthority,
|
||||||
|
acceptance_policy: AcceptancePolicy::Open,
|
||||||
name: "Test Resolver".to_string(),
|
name: "Test Resolver".to_string(),
|
||||||
description: "Integration test resolver".to_string(),
|
description: "Integration test resolver".to_string(),
|
||||||
fee_bps: 100,
|
fee_bps: 100,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use anchor_lang::prelude::*;
|
use anchor_lang::prelude::*;
|
||||||
use crate::state::{ResolverEntry, ResolverType};
|
use crate::state::{AcceptancePolicy, ResolverEntry, ResolverType};
|
||||||
use crate::error::RegistryError;
|
use crate::error::RegistryError;
|
||||||
|
|
||||||
#[derive(Accounts)]
|
#[derive(Accounts)]
|
||||||
@@ -22,6 +22,7 @@ pub struct RegisterResolver<'info> {
|
|||||||
pub fn handler(
|
pub fn handler(
|
||||||
ctx: Context<RegisterResolver>,
|
ctx: Context<RegisterResolver>,
|
||||||
resolver_type: ResolverType,
|
resolver_type: ResolverType,
|
||||||
|
acceptance_policy: AcceptancePolicy,
|
||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
fee_bps: u16,
|
fee_bps: u16,
|
||||||
@@ -34,6 +35,7 @@ pub fn handler(
|
|||||||
let entry = &mut ctx.accounts.resolver_entry;
|
let entry = &mut ctx.accounts.resolver_entry;
|
||||||
entry.authority = ctx.accounts.authority.key();
|
entry.authority = ctx.accounts.authority.key();
|
||||||
entry.resolver_type = resolver_type;
|
entry.resolver_type = resolver_type;
|
||||||
|
entry.acceptance_policy = acceptance_policy;
|
||||||
entry.name = name;
|
entry.name = name;
|
||||||
entry.description = description;
|
entry.description = description;
|
||||||
entry.fee_bps = fee_bps;
|
entry.fee_bps = fee_bps;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#![allow(clippy::diverging_sub_expression)]
|
#![allow(clippy::diverging_sub_expression)]
|
||||||
|
#![allow(clippy::too_many_arguments)]
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
pub mod instructions;
|
pub mod instructions;
|
||||||
@@ -23,6 +24,7 @@ pub mod descro_ext_resolvers {
|
|||||||
pub fn register_resolver(
|
pub fn register_resolver(
|
||||||
ctx: Context<RegisterResolver>,
|
ctx: Context<RegisterResolver>,
|
||||||
resolver_type: ResolverType,
|
resolver_type: ResolverType,
|
||||||
|
acceptance_policy: AcceptancePolicy,
|
||||||
name: String,
|
name: String,
|
||||||
description: String,
|
description: String,
|
||||||
fee_bps: u16,
|
fee_bps: u16,
|
||||||
@@ -32,6 +34,7 @@ pub mod descro_ext_resolvers {
|
|||||||
register::handler(
|
register::handler(
|
||||||
ctx,
|
ctx,
|
||||||
resolver_type,
|
resolver_type,
|
||||||
|
acceptance_policy,
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
fee_bps,
|
fee_bps,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ use anchor_lang::prelude::*;
|
|||||||
pub struct ResolverEntry {
|
pub struct ResolverEntry {
|
||||||
pub authority: Pubkey,
|
pub authority: Pubkey,
|
||||||
pub resolver_type: ResolverType,
|
pub resolver_type: ResolverType,
|
||||||
|
pub acceptance_policy: AcceptancePolicy,
|
||||||
#[max_len(64)]
|
#[max_len(64)]
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[max_len(256)]
|
#[max_len(256)]
|
||||||
@@ -28,6 +29,13 @@ pub enum ResolverType {
|
|||||||
Multisig,
|
Multisig,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq, InitSpace, Debug)]
|
||||||
|
pub enum AcceptancePolicy {
|
||||||
|
Open,
|
||||||
|
SignatureGated,
|
||||||
|
ProgramGated,
|
||||||
|
}
|
||||||
|
|
||||||
/// Passed to update_stats; mirrors the Escrow program's Winner enum.
|
/// Passed to update_stats; mirrors the Escrow program's Winner enum.
|
||||||
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
|
#[derive(AnchorSerialize, AnchorDeserialize, Clone, PartialEq)]
|
||||||
pub enum Ruling {
|
pub enum Ruling {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#![allow(dead_code, unused_imports)]
|
#![allow(dead_code, unused_imports, clippy::too_many_arguments)]
|
||||||
|
|
||||||
pub use anchor_lang::prelude::Pubkey;
|
pub use anchor_lang::prelude::Pubkey;
|
||||||
use {
|
use {
|
||||||
@@ -6,7 +6,7 @@ use {
|
|||||||
solana_program::{instruction::Instruction, system_program},
|
solana_program::{instruction::Instruction, system_program},
|
||||||
AccountDeserialize, InstructionData, ToAccountMetas,
|
AccountDeserialize, InstructionData, ToAccountMetas,
|
||||||
},
|
},
|
||||||
descro_ext_resolvers::{ResolverEntry, ResolverType},
|
descro_ext_resolvers::{AcceptancePolicy, ResolverEntry, ResolverType},
|
||||||
litesvm::LiteSVM,
|
litesvm::LiteSVM,
|
||||||
solana_keypair::Keypair,
|
solana_keypair::Keypair,
|
||||||
solana_message::{Message, VersionedMessage},
|
solana_message::{Message, VersionedMessage},
|
||||||
@@ -51,6 +51,7 @@ pub fn try_send(svm: &mut LiteSVM, ix: Instruction, payer: &Keypair) -> bool {
|
|||||||
pub fn ix_register(
|
pub fn ix_register(
|
||||||
authority: &Pubkey,
|
authority: &Pubkey,
|
||||||
resolver_type: ResolverType,
|
resolver_type: ResolverType,
|
||||||
|
acceptance_policy: AcceptancePolicy,
|
||||||
name: &str,
|
name: &str,
|
||||||
description: &str,
|
description: &str,
|
||||||
fee_bps: u16,
|
fee_bps: u16,
|
||||||
@@ -62,6 +63,7 @@ pub fn ix_register(
|
|||||||
descro_ext_resolvers::id(),
|
descro_ext_resolvers::id(),
|
||||||
&descro_ext_resolvers::instruction::RegisterResolver {
|
&descro_ext_resolvers::instruction::RegisterResolver {
|
||||||
resolver_type,
|
resolver_type,
|
||||||
|
acceptance_policy,
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
description: description.to_string(),
|
description: description.to_string(),
|
||||||
fee_bps,
|
fee_bps,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
mod common;
|
mod common;
|
||||||
use common::*;
|
use common::*;
|
||||||
use descro_ext_resolvers::ResolverType;
|
use descro_ext_resolvers::{AcceptancePolicy, ResolverType};
|
||||||
use solana_keypair::Keypair;
|
use solana_keypair::Keypair;
|
||||||
use solana_signer::Signer;
|
use solana_signer::Signer;
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ fn registers_entry_with_correct_state() {
|
|||||||
ix_register(
|
ix_register(
|
||||||
&authority.pubkey(),
|
&authority.pubkey(),
|
||||||
ResolverType::CentralAuthority,
|
ResolverType::CentralAuthority,
|
||||||
|
AcceptancePolicy::Open,
|
||||||
"Acme Resolvers",
|
"Acme Resolvers",
|
||||||
"Fast and fair dispute resolution",
|
"Fast and fair dispute resolution",
|
||||||
100, // 1%
|
100, // 1%
|
||||||
@@ -25,6 +26,7 @@ fn registers_entry_with_correct_state() {
|
|||||||
let entry = read_entry(&svm, &authority.pubkey());
|
let entry = read_entry(&svm, &authority.pubkey());
|
||||||
assert_eq!(entry.authority, authority.pubkey());
|
assert_eq!(entry.authority, authority.pubkey());
|
||||||
assert_eq!(entry.resolver_type, ResolverType::CentralAuthority);
|
assert_eq!(entry.resolver_type, ResolverType::CentralAuthority);
|
||||||
|
assert_eq!(entry.acceptance_policy, AcceptancePolicy::Open);
|
||||||
assert_eq!(entry.name, "Acme Resolvers");
|
assert_eq!(entry.name, "Acme Resolvers");
|
||||||
assert_eq!(entry.fee_bps, 100);
|
assert_eq!(entry.fee_bps, 100);
|
||||||
assert_eq!(entry.total_resolved, 0);
|
assert_eq!(entry.total_resolved, 0);
|
||||||
@@ -41,6 +43,7 @@ fn duplicate_register_fails() {
|
|||||||
ix_register(
|
ix_register(
|
||||||
&authority.pubkey(),
|
&authority.pubkey(),
|
||||||
ResolverType::CentralAuthority,
|
ResolverType::CentralAuthority,
|
||||||
|
AcceptancePolicy::Open,
|
||||||
"Acme",
|
"Acme",
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
@@ -54,6 +57,7 @@ fn duplicate_register_fails() {
|
|||||||
ix_register(
|
ix_register(
|
||||||
&authority.pubkey(),
|
&authority.pubkey(),
|
||||||
ResolverType::CentralAuthority,
|
ResolverType::CentralAuthority,
|
||||||
|
AcceptancePolicy::Open,
|
||||||
"Acme Again",
|
"Acme Again",
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
@@ -73,6 +77,7 @@ fn register_empty_name_fails() {
|
|||||||
ix_register(
|
ix_register(
|
||||||
&authority.pubkey(),
|
&authority.pubkey(),
|
||||||
ResolverType::CentralAuthority,
|
ResolverType::CentralAuthority,
|
||||||
|
AcceptancePolicy::Open,
|
||||||
"",
|
"",
|
||||||
"",
|
"",
|
||||||
0,
|
0,
|
||||||
@@ -92,6 +97,7 @@ fn register_invalid_fee_bps_fails() {
|
|||||||
ix_register(
|
ix_register(
|
||||||
&authority.pubkey(),
|
&authority.pubkey(),
|
||||||
ResolverType::CentralAuthority,
|
ResolverType::CentralAuthority,
|
||||||
|
AcceptancePolicy::Open,
|
||||||
"Acme",
|
"Acme",
|
||||||
"",
|
"",
|
||||||
10_001, // > 100%
|
10_001, // > 100%
|
||||||
@@ -110,12 +116,12 @@ fn different_authorities_have_separate_entries() {
|
|||||||
|
|
||||||
send(
|
send(
|
||||||
&mut svm,
|
&mut svm,
|
||||||
ix_register(&auth1.pubkey(), ResolverType::CentralAuthority, "Auth1", "", 0, auth1.pubkey(), ""),
|
ix_register(&auth1.pubkey(), ResolverType::CentralAuthority, AcceptancePolicy::Open, "Auth1", "", 0, auth1.pubkey(), ""),
|
||||||
&auth1,
|
&auth1,
|
||||||
);
|
);
|
||||||
send(
|
send(
|
||||||
&mut svm,
|
&mut svm,
|
||||||
ix_register(&auth2.pubkey(), ResolverType::JuryDAO, "Auth2", "", 50, auth2.pubkey(), ""),
|
ix_register(&auth2.pubkey(), ResolverType::JuryDAO, AcceptancePolicy::Open, "Auth2", "", 50, auth2.pubkey(), ""),
|
||||||
&auth2,
|
&auth2,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
mod common;
|
mod common;
|
||||||
use common::*;
|
use common::*;
|
||||||
use descro_ext_resolvers::ResolverType;
|
use descro_ext_resolvers::{AcceptancePolicy, ResolverType};
|
||||||
use solana_keypair::Keypair;
|
use solana_keypair::Keypair;
|
||||||
use solana_signer::Signer;
|
use solana_signer::Signer;
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@ fn register_default(svm: &mut litesvm::LiteSVM, authority: &Keypair) {
|
|||||||
ix_register(
|
ix_register(
|
||||||
&authority.pubkey(),
|
&authority.pubkey(),
|
||||||
ResolverType::CentralAuthority,
|
ResolverType::CentralAuthority,
|
||||||
|
AcceptancePolicy::Open,
|
||||||
"Original Name",
|
"Original Name",
|
||||||
"Original description",
|
"Original description",
|
||||||
100,
|
100,
|
||||||
|
|||||||
Reference in New Issue
Block a user