372 lines
12 KiB
Rust
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),
|
|
)
|
|
}
|