use palinurus_core::base58::Pubkey;
use palinurus_core::versioned_tx::{build_unsigned, serialize, serialize_message, AccountMeta, Instruction};
fn sol_pubkey(b: [u8; 32]) -> solana_program::pubkey::Pubkey {
solana_program::pubkey::Pubkey::new_from_array(b)
}
fn sol_hash(b: [u8; 32]) -> solana_program::hash::Hash {
solana_program::hash::Hash::new_from_array(b)
}
fn sol_meta(m: &AccountMeta) -> solana_program::instruction::AccountMeta {
solana_program::instruction::AccountMeta {
pubkey: sol_pubkey(m.pubkey.to_bytes()),
is_signer: m.is_signer,
is_writable: m.is_writable,
}
}
fn sol_ix(ix: &Instruction) -> solana_program::instruction::Instruction {
solana_program::instruction::Instruction {
program_id: sol_pubkey(ix.program_id.to_bytes()),
accounts: ix.accounts.iter().map(sol_meta).collect(),
data: ix.data.clone(),
}
}
fn oracle_unsigned_tx_bytes(ixs: &[Instruction], payer: Pubkey, blockhash: [u8; 32]) -> Vec<u8> {
let sol_ixs: Vec<_> = ixs.iter().map(sol_ix).collect();
let v0 = solana_program::message::v0::Message::try_compile(
&sol_pubkey(payer.to_bytes()),
&sol_ixs,
&[],
sol_hash(blockhash),
)
.expect("oracle: try_compile");
let msg_bytes = bincode::serialize(&solana_program::message::VersionedMessage::V0(v0))
.expect("oracle: bincode serialize VersionedMessage::V0");
let mut out = vec![0x00];
out.extend_from_slice(&msg_bytes);
out
}
fn pk(b: u8) -> Pubkey { Pubkey::from_bytes([b; 32]) }
fn blockhash() -> [u8; 32] { [0x11; 32] }
fn sas_attest_ix(payer: Pubkey, authority: Pubkey, credential: Pubkey, schema: Pubkey, attestation: Pubkey, system: Pubkey, data: Vec<u8>) -> Instruction {
Instruction {
program_id: Pubkey::SAS,
accounts: vec![
AccountMeta { pubkey: payer, is_signer: true, is_writable: true }, AccountMeta { pubkey: authority, is_signer: true, is_writable: false }, AccountMeta { pubkey: credential, is_signer: false, is_writable: false }, AccountMeta { pubkey: schema, is_signer: false, is_writable: false }, AccountMeta { pubkey: attestation, is_signer: false, is_writable: true }, AccountMeta { pubkey: system, is_signer: false, is_writable: false }, ],
data,
}
}
#[test]
fn unsigned_tx_matches_oracle_single_sas_ix() {
let payer = pk(0x01);
let authority = pk(0x02);
let credential = pk(0x03);
let schema = pk(0x04);
let attestation = pk(0x05);
let system = Pubkey::SYSTEM;
let data = vec![6u8, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE]; let ix = sas_attest_ix(payer, authority, credential, schema, attestation, system, data);
let ixs = vec![ix];
let my = serialize(&build_unsigned(&ixs, payer, blockhash()));
let refb = oracle_unsigned_tx_bytes(&ixs, payer, blockhash());
assert_eq!(my, refb, "unsigned tx bytes must match solana_program oracle byte-for-byte (single SAS ix)");
}
#[test]
fn unsigned_tx_matches_oracle_two_ixs_with_shared_accounts() {
let payer = pk(0x01);
let a = pk(0x10);
let b = pk(0x20);
let prog1 = pk(0x30);
let prog2 = pk(0x40);
let ixs = vec![
Instruction { program_id: prog1, accounts: vec![
AccountMeta { pubkey: payer, is_signer: true, is_writable: true },
AccountMeta { pubkey: a, is_signer: false, is_writable: true },
AccountMeta { pubkey: b, is_signer: false, is_writable: false },
], data: vec![1, 2, 3] },
Instruction { program_id: prog2, accounts: vec![
AccountMeta { pubkey: a, is_signer: false, is_writable: true }, AccountMeta { pubkey: b, is_signer: false, is_writable: false }, AccountMeta { pubkey: payer, is_signer: true, is_writable: true }, ], data: vec![9] },
];
let my = serialize(&build_unsigned(&ixs, payer, blockhash()));
let refb = oracle_unsigned_tx_bytes(&ixs, payer, blockhash());
assert_eq!(my, refb, "unsigned tx bytes must match oracle (two ixs, shared accounts, dedup + ordering)");
}
#[test]
fn account_key_ordering_is_payer_then_signers_then_writable_then_readonly() {
let payer = pk(0x01);
let readonly_signer = pk(0x07);
let writable_non_signer = pk(0x03);
let readonly_non_signer = pk(0x09);
let program = pk(0xFF);
let ixs = vec![Instruction {
program_id: program,
accounts: vec![
AccountMeta { pubkey: readonly_non_signer, is_signer: false, is_writable: false },
AccountMeta { pubkey: writable_non_signer, is_signer: false, is_writable: true },
AccountMeta { pubkey: readonly_signer, is_signer: true, is_writable: false },
AccountMeta { pubkey: payer, is_signer: true, is_writable: true },
],
data: vec![],
}];
let my = serialize(&build_unsigned(&ixs, payer, blockhash()));
let refb = oracle_unsigned_tx_bytes(&ixs, payer, blockhash());
assert_eq!(my, refb, "account-key ordering must match canonical 4-category sort, not insertion order");
}
#[test]
fn empty_accounts_ix_matches_oracle() {
let payer = pk(0x01);
let ixs = vec![Instruction {
program_id: Pubkey::MEMO,
accounts: vec![],
data: b"sensor ok".to_vec(),
}];
let my = serialize(&build_unsigned(&ixs, payer, blockhash()));
let refb = oracle_unsigned_tx_bytes(&ixs, payer, blockhash());
assert_eq!(my, refb, "empty-accounts ix must match oracle");
}
#[test]
fn unsigned_tx_has_zero_signatures_prefix() {
let payer = pk(0x01);
let ixs = vec![Instruction { program_id: Pubkey::MEMO, accounts: vec![], data: vec![] }];
let my = serialize(&build_unsigned(&ixs, payer, blockhash()));
assert_eq!(my[0], 0x00, "unsigned tx: short-vec(0) signatures prefix = 0x00");
assert_eq!(my[1], 0x80, "V0 message prefix byte = 0x80");
}
#[test]
fn v0_message_header_counts_match_oracle() {
let payer = pk(0x01);
let authority = pk(0x02);
let credential = pk(0x03);
let schema = pk(0x04);
let attestation = pk(0x05);
let ix = sas_attest_ix(payer, authority, credential, schema, attestation, Pubkey::SYSTEM, vec![6]);
let tx = build_unsigned(&[ix], payer, blockhash());
let h = &tx.message.header;
assert_eq!(h.num_required_signatures, 2, "payer + authority");
assert_eq!(h.num_readonly_signed_accounts, 1, "authority is readonly signer");
assert_eq!(h.num_readonly_unsigned_accounts, 4, "credential + schema + system + SAS program");
}
#[test]
fn serialize_message_is_tx_bytes_minus_zero_sig_prefix() {
let payer = pk(0x01);
let ixs = vec![sas_attest_ix(payer, pk(0x02), pk(0x03), pk(0x04), pk(0x05), Pubkey::SYSTEM, vec![6])];
let tx = build_unsigned(&ixs, payer, blockhash());
let tx_bytes = serialize(&tx);
let msg_bytes = serialize_message(&tx.message);
assert_eq!(tx_bytes[0], 0x00, "zero sigs prefix");
assert_eq!(&tx_bytes[1..], &msg_bytes[..], "tx bytes (after sig prefix) == serialize_message");
assert_eq!(msg_bytes[0], 0x80, "message starts with V0 prefix");
}