Files
solisting/programs/solisting/tests/common/mod.rs
2026-06-14 15:57:01 +02:00

372 lines
12 KiB
Rust

#![allow(dead_code, unused_imports)]
use anchor_lang::{
solana_program::{instruction::Instruction, system_program},
AccountDeserialize, InstructionData, ToAccountMetas,
};
use litesvm::LiteSVM;
use solana_keypair::Keypair;
use solana_message::{Message, VersionedMessage};
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;
pub use anchor_lang::prelude::Pubkey;
pub use solisting::state::{AltCurrencyConfig, Currency, ListingAccount, OrderAccount};
pub fn setup() -> (LiteSVM, Keypair, Keypair) {
let mut svm = LiteSVM::new();
let solisting_bytes = include_bytes!("../../../../target/deploy/solisting.so");
svm.add_program(solisting::id(), solisting_bytes).unwrap();
let descro_bytes = include_bytes!("../../../../../descro/target/deploy/descro.so");
svm.add_program(descro::id(), descro_bytes).unwrap();
let seller = Keypair::new();
let buyer = Keypair::new();
svm.airdrop(&seller.pubkey(), 10_000_000_000).unwrap();
svm.airdrop(&buyer.pubkey(), 10_000_000_000).unwrap();
(svm, seller, buyer)
}
pub fn listing_pda(seller: &Pubkey, listing_id: u64) -> Pubkey {
Pubkey::find_program_address(
&[b"listing", seller.as_ref(), &listing_id.to_le_bytes()],
&solisting::id(),
)
.0
}
pub fn order_pda(listing: Pubkey, buyer: Pubkey, order_id: u64) -> Pubkey {
Pubkey::find_program_address(
&[b"order", listing.as_ref(), buyer.as_ref(), &order_id.to_le_bytes()],
&solisting::id(),
)
.0
}
pub 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
}
pub fn vault_pda(escrow: &Pubkey) -> Pubkey {
Pubkey::find_program_address(&[b"vault", escrow.as_ref()], &descro::id()).0
}
pub fn read_listing(svm: &LiteSVM, seller: &Pubkey, listing_id: u64) -> ListingAccount {
let pda = listing_pda(seller, listing_id);
let account = svm.get_account(&pda).expect("listing account not found");
ListingAccount::try_deserialize(&mut account.data.as_slice()).unwrap()
}
pub fn read_order(svm: &LiteSVM, listing: Pubkey, buyer: Pubkey, order_id: u64) -> OrderAccount {
let pda = order_pda(listing, buyer, order_id);
let account = svm.get_account(&pda).expect("order account not found");
OrderAccount::try_deserialize(&mut account.data.as_slice()).unwrap()
}
pub fn send(svm: &mut LiteSVM, ixs: &[Instruction], signers: &[&Keypair]) {
let blockhash = svm.latest_blockhash();
let msg = Message::new_with_blockhash(ixs, Some(&signers[0].pubkey()), &blockhash);
let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), signers).unwrap();
svm.send_transaction(tx).expect("transaction failed");
}
pub fn try_send(
svm: &mut LiteSVM,
ixs: &[Instruction],
signers: &[&Keypair],
) -> Result<(), Box<dyn std::error::Error>> {
let blockhash = svm.latest_blockhash();
let msg = Message::new_with_blockhash(ixs, Some(&signers[0].pubkey()), &blockhash);
let tx = VersionedTransaction::try_new(VersionedMessage::Legacy(msg), signers)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?;
svm.send_transaction(tx)
.map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
}
// --- Listing instruction builders ---
pub fn ix_create_listing(
seller: &Pubkey,
listing_id: u64,
canonical_currency: Currency,
price: u64,
canonical_oracle: Option<Pubkey>,
alt_currencies: Vec<AltCurrencyConfig>,
accepted_resolvers: Vec<Pubkey>,
quantity: u32,
metadata_uri: String,
) -> Instruction {
let listing_account = listing_pda(seller, listing_id);
Instruction::new_with_bytes(
solisting::id(),
&solisting::instruction::CreateListing {
listing_id,
canonical_currency,
price,
canonical_oracle,
alt_currencies,
accepted_resolvers,
quantity,
metadata_uri,
}
.data(),
solisting::accounts::CreateListing {
seller: *seller,
listing_account,
system_program: system_program::ID,
}
.to_account_metas(None),
)
}
pub fn ix_update_listing(
seller: &Pubkey,
listing_id: u64,
canonical_currency: Currency,
price: u64,
canonical_oracle: Option<Pubkey>,
alt_currencies: Vec<AltCurrencyConfig>,
accepted_resolvers: Vec<Pubkey>,
quantity: u32,
metadata_uri: String,
) -> Instruction {
let listing_account = listing_pda(seller, listing_id);
Instruction::new_with_bytes(
solisting::id(),
&solisting::instruction::UpdateListing {
canonical_currency,
price,
canonical_oracle,
alt_currencies,
accepted_resolvers,
quantity,
metadata_uri,
}
.data(),
solisting::accounts::UpdateListing {
seller: *seller,
listing_account,
}
.to_account_metas(None),
)
}
pub fn ix_close_listing(seller: &Pubkey, listing_id: u64) -> Instruction {
let listing_account = listing_pda(seller, listing_id);
Instruction::new_with_bytes(
solisting::id(),
&solisting::instruction::CloseListing {}.data(),
solisting::accounts::CloseListing {
seller: *seller,
listing_account,
}
.to_account_metas(None),
)
}
// --- Order instruction builders ---
/// Build a create_order instruction paying in the canonical currency (no oracle accounts needed).
/// For alt-currency orders, use ix_create_order_with_resolver with explicit oracle keys.
pub fn ix_create_order(
buyer: &Pubkey,
seller: &Pubkey,
listing_id: u64,
order_id: u64,
payment_currency: Currency,
max_slippage_bps: u16,
canonical_oracle: Option<Pubkey>,
target_oracle: Option<Pubkey>,
) -> Instruction {
let resolver = Pubkey::new_unique();
ix_create_order_with_resolver(
buyer,
seller,
listing_id,
order_id,
payment_currency,
max_slippage_bps,
canonical_oracle,
target_oracle,
resolver,
)
}
pub fn ix_create_order_with_resolver(
buyer: &Pubkey,
seller: &Pubkey,
listing_id: u64,
order_id: u64,
payment_currency: Currency,
max_slippage_bps: u16,
canonical_oracle: Option<Pubkey>,
target_oracle: Option<Pubkey>,
resolver: Pubkey,
) -> Instruction {
let listing = listing_pda(seller, listing_id);
let order = order_pda(listing, *buyer, order_id);
// escrow_id is derived from the first 8 bytes of the order PDA to guarantee uniqueness
let escrow_id = u64::from_le_bytes(order.to_bytes()[0..8].try_into().unwrap());
let escrow_account = escrow_pda(seller, escrow_id);
let descro_vault = vault_pda(&escrow_account);
let canonical_oracle_key = canonical_oracle.unwrap_or(system_program::ID);
let target_oracle_key = target_oracle.unwrap_or(system_program::ID);
// expected_amount = 0 disables slippage check (tests compute it externally or don't need it)
let expected_amount: u64 = 0;
Instruction::new_with_bytes(
solisting::id(),
&solisting::instruction::CreateOrder {
order_id,
escrow_id,
resolver,
payment_currency,
expected_amount,
max_slippage_bps,
}
.data(),
solisting::accounts::CreateOrder {
buyer: *buyer,
seller: *seller,
listing_account: listing,
order_account: order,
escrow_account,
descro_vault,
canonical_oracle: canonical_oracle_key,
target_oracle: target_oracle_key,
descro_program: descro::id(),
system_program: system_program::ID,
}
.to_account_metas(None),
)
}
pub fn ix_accept_order(
seller: &Pubkey,
listing_id: u64,
buyer: &Pubkey,
order_id: u64,
escrow_id: u64,
resolver: Pubkey,
) -> Instruction {
let listing_account = listing_pda(seller, listing_id);
let order_account = order_pda(listing_account, *buyer, order_id);
let escrow_account = escrow_pda(seller, escrow_id);
// Pass a fresh key as resolver_entry — it won't exist, so descro skips the policy check
let resolver_entry = Pubkey::new_unique();
Instruction::new_with_bytes(
solisting::id(),
&solisting::instruction::AcceptOrder {}.data(),
solisting::accounts::AcceptOrder {
seller: *seller,
resolver,
listing_account,
order_account,
escrow_account,
resolver_entry,
descro_program: descro::id(),
system_program: system_program::ID,
}
.to_account_metas(None),
)
}
pub fn ix_reject_order(
seller: &Pubkey,
listing_id: u64,
buyer: &Pubkey,
order_id: u64,
escrow_id: u64,
) -> Instruction {
let listing_account = listing_pda(seller, listing_id);
let order_account = order_pda(listing_account, *buyer, order_id);
let escrow_account = escrow_pda(seller, escrow_id);
let vault = vault_pda(&escrow_account);
Instruction::new_with_bytes(
solisting::id(),
&solisting::instruction::RejectOrder {}.data(),
solisting::accounts::RejectOrder {
seller: *seller,
buyer: *buyer,
listing_account,
order_account,
escrow_account,
vault,
descro_program: descro::id(),
system_program: system_program::ID,
}
.to_account_metas(None),
)
}
pub fn ix_cancel_order(
buyer: &Pubkey,
listing_id: u64,
seller: &Pubkey,
order_id: u64,
escrow_id: u64,
) -> Instruction {
let listing_account = listing_pda(seller, listing_id);
let order_account = order_pda(listing_account, *buyer, order_id);
let escrow_account = escrow_pda(seller, escrow_id);
let vault = vault_pda(&escrow_account);
Instruction::new_with_bytes(
solisting::id(),
&solisting::instruction::CancelOrder {}.data(),
solisting::accounts::CancelOrder {
buyer: *buyer,
listing_account,
order_account,
escrow_account,
vault,
descro_program: descro::id(),
system_program: system_program::ID,
}
.to_account_metas(None),
)
}
pub fn ix_close_stale_order(
caller: &Pubkey,
listing: Pubkey,
buyer: Pubkey,
order_id: u64,
escrow_account: Pubkey,
) -> Instruction {
let order_account = order_pda(listing, buyer, order_id);
Instruction::new_with_bytes(
solisting::id(),
&solisting::instruction::CloseStaleOrder {}.data(),
solisting::accounts::CloseStaleOrder {
caller: *caller,
order_account,
escrow_account,
}
.to_account_metas(None),
)
}
/// Build a descro cancel instruction directly (bypasses solisting — used to test defensive paths)
pub fn ix_descro_cancel(buyer: &Pubkey, seller: &Pubkey, escrow_id: u64) -> Instruction {
let escrow_account = escrow_pda(seller, escrow_id);
let vault = vault_pda(&escrow_account);
Instruction::new_with_bytes(
descro::id(),
&descro::instruction::Cancel {}.data(),
descro::accounts::Cancel {
canceller: *buyer,
buyer: *buyer,
escrow_account,
vault,
system_program: system_program::ID,
}
.to_account_metas(None),
)
}