1use bitcoin::hashes::Hash;
2use bitcoin::Transaction;
3use bitcoin_slices::{bsl, Error, Visit, Visitor};
4use borsh::{BorshDeserialize, BorshSerialize};
5
6use crate::input_to_sign::InputToSign;
7use crate::instruction::Instruction;
8use crate::program_error::ProgramError;
9use crate::rune::{RuneAmount, RuneInfo};
10#[cfg(target_os = "solana")]
11use crate::stable_layout::stable_ins::StableInstruction;
12use crate::{msg, MAX_BTC_RUNE_OUTPUT_SIZE, MAX_BTC_TX_SIZE};
13
14use crate::clock::Clock;
15use crate::transaction_to_sign::TransactionToSign;
16use crate::utxo::UtxoMeta;
17use crate::{account::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
18
19#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
24pub struct FixedSizeBuffer<const N: usize> {
25 data: [u8; N],
26 size: usize,
27}
28
29impl<const N: usize> FixedSizeBuffer<N> {
30 pub fn new(data: [u8; N], size: usize) -> Self {
32 Self { data, size }
33 }
34
35 pub fn size(&self) -> usize {
37 self.size
38 }
39
40 pub fn as_slice(&self) -> &[u8] {
42 &self.data[..self.size]
43 }
44
45 pub fn as_mut_ptr(&mut self) -> *mut u8 {
47 self.data.as_mut_ptr()
48 }
49
50 pub fn capacity(&self) -> usize {
52 N
53 }
54
55 pub fn set_size(&mut self, new_size: usize) {
61 debug_assert!(
62 new_size <= N,
63 "new_size ({}) exceeds buffer capacity ({})",
64 new_size,
65 N
66 );
67
68 self.size = new_size;
69 }
70}
71
72impl<const N: usize> AsRef<[u8]> for FixedSizeBuffer<N> {
73 fn as_ref(&self) -> &[u8] {
74 self.as_slice()
75 }
76}
77
78impl<const N: usize> std::ops::Deref for FixedSizeBuffer<N> {
79 type Target = [u8];
80
81 fn deref(&self) -> &Self::Target {
82 self.as_slice()
83 }
84}
85
86impl<const N: usize> Default for FixedSizeBuffer<N> {
87 fn default() -> Self {
88 Self {
89 data: [0u8; N],
90 size: 0,
91 }
92 }
93}
94
95pub type BitcoinTransaction = FixedSizeBuffer<MAX_BTC_TX_SIZE>;
97
98pub type BitcoinRuneOutput = FixedSizeBuffer<MAX_BTC_RUNE_OUTPUT_SIZE>;
100
101pub type ReturnedData = FixedSizeBuffer<MAX_RETURN_DATA>;
103
104pub type RuneInfoBuf = FixedSizeBuffer<64>;
107
108pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {
120 invoke_signed(instruction, account_infos, &[])
121}
122
123pub fn invoke_unchecked(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {
135 invoke_signed_unchecked(instruction, account_infos, &[])
136}
137
138pub fn invoke_signed(
155 instruction: &Instruction,
156 account_infos: &[AccountInfo],
157 signers_seeds: &[&[&[u8]]],
158) -> ProgramResult {
159 for account_meta in instruction.accounts.iter() {
161 for account_info in account_infos.iter() {
162 if account_meta.pubkey == *account_info.key {
163 if account_meta.is_writable {
164 let _ = account_info.try_borrow_mut_data()?;
165 } else {
166 let _ = account_info.try_borrow_data()?;
167 }
168 break;
169 }
170 }
171 }
172
173 invoke_signed_unchecked(instruction, account_infos, signers_seeds)
174}
175
176pub fn invoke_signed_unchecked(
189 instruction: &Instruction,
190 account_infos: &[AccountInfo],
191 signers_seeds: &[&[&[u8]]],
192) -> ProgramResult {
193 #[cfg(target_os = "solana")]
194 {
195 let instruction = StableInstruction::from(instruction.clone());
196 let result = unsafe {
197 crate::syscalls::sol_invoke_signed_rust(
198 &instruction as *const _ as *const u8,
199 account_infos as *const _ as *const u8,
200 account_infos.len() as u64,
201 signers_seeds as *const _ as *const u8,
202 signers_seeds.len() as u64,
203 )
204 };
205 match result {
206 crate::entrypoint::SUCCESS => Ok(()),
207 _ => Err(result.into()),
208 }
209 }
210
211 #[cfg(not(target_os = "solana"))]
212 crate::program_stubs::sol_invoke_signed(instruction, account_infos, signers_seeds)
213}
214
215pub fn next_account_info<'a, 'b, I: Iterator<Item = &'a AccountInfo<'b>>>(
229 iter: &mut I,
230) -> Result<I::Item, ProgramError> {
231 iter.next().ok_or(ProgramError::NotEnoughAccountKeys)
232}
233
234pub const MAX_TRANSACTION_TO_SIGN: usize = 4 * 1024;
235
236fn input_moves_state_carrier(
243 account_utxo: &UtxoMeta,
244 input: &InputToSign,
245 tx: &Transaction,
246 inputs_to_sign: &[InputToSign],
247) -> bool {
248 if account_utxo.is_defined() {
249 tx.input.get(input.index as usize).is_some_and(|txin| {
250 *account_utxo
251 == UtxoMeta::from_outpoint(txin.previous_output.txid, txin.previous_output.vout)
252 })
253 } else {
254 inputs_to_sign
255 .iter()
256 .find(|candidate| candidate.signer == input.signer)
257 .is_some_and(|first| first.index == input.index)
258 }
259}
260
261pub fn set_transaction_to_sign<'info, T>(
276 accounts: &[T],
277 tx: &Transaction,
278 inputs_to_sign: &[InputToSign],
279) -> ProgramResult
280where
281 T: AsRef<AccountInfo<'info>>,
282{
283 msg!("setting tx to sign");
284 let serialized_tx_bytes = bitcoin::consensus::serialize(tx);
287 let serialized_inputs_to_sign = TransactionToSign::serialise_inputs_to_sign(inputs_to_sign);
288
289 #[cfg(target_os = "solana")]
290 let set_tx_result = unsafe {
291 crate::syscalls::arch_set_transaction_to_sign(
292 serialized_tx_bytes.as_ptr(),
293 serialized_tx_bytes.len() as u64,
294 )
295 };
296 #[cfg(not(target_os = "solana"))]
297 let set_tx_result = crate::program_stubs::arch_set_transaction_to_sign(
298 serialized_tx_bytes.as_ptr(),
299 serialized_tx_bytes.len(),
300 );
301
302 #[cfg(target_os = "solana")]
303 let set_inputs_to_sign_result = unsafe {
304 crate::syscalls::arch_set_inputs_to_sign(
305 serialized_inputs_to_sign.as_ptr(),
306 serialized_inputs_to_sign.len() as u64,
307 )
308 };
309 #[cfg(not(target_os = "solana"))]
310 let set_inputs_to_sign_result = crate::program_stubs::arch_set_inputs_to_sign(
311 serialized_inputs_to_sign.as_ptr(),
312 serialized_inputs_to_sign.len(),
313 );
314
315 match set_tx_result {
316 crate::entrypoint::SUCCESS => match set_inputs_to_sign_result {
317 crate::entrypoint::SUCCESS => {
318 let txid = tx.compute_txid();
319 let mut txid_bytes: [u8; 32] = txid.as_raw_hash().to_byte_array();
320 txid_bytes.reverse();
321
322 for input in inputs_to_sign {
323 if let Some(account) = accounts
324 .iter()
325 .map(AsRef::as_ref)
326 .find(|account| *account.key == input.signer)
327 {
328 if input_moves_state_carrier(account.utxo, input, tx, inputs_to_sign) {
329 account.set_utxo(&UtxoMeta::from(txid_bytes, input.index));
330 }
331 }
332 }
333 Ok(())
334 }
335 _ => Err(set_inputs_to_sign_result.into()),
336 },
337 _ => Err(set_tx_result.into()),
338 }
339}
340
341pub fn set_input_to_sign(
350 accounts: &[AccountInfo],
351 txid: [u8; 32],
352 inputs_to_sign: &[InputToSign],
353) -> ProgramResult {
354 msg!("setting inputs to sign");
355
356 let serialized_inputs_to_sign = TransactionToSign::serialise_inputs_to_sign(inputs_to_sign);
357
358 #[cfg(target_os = "solana")]
359 let set_inputs_to_sign_result = unsafe {
360 crate::syscalls::arch_set_inputs_to_sign(
361 serialized_inputs_to_sign.as_ptr(),
362 serialized_inputs_to_sign.len() as u64,
363 )
364 };
365 #[cfg(not(target_os = "solana"))]
366 let set_inputs_to_sign_result = crate::program_stubs::arch_set_inputs_to_sign(
367 serialized_inputs_to_sign.as_ptr(),
368 serialized_inputs_to_sign.len(),
369 );
370
371 match set_inputs_to_sign_result {
372 crate::entrypoint::SUCCESS => {
373 for input in inputs_to_sign {
374 if let Some(account) = accounts.iter().find(|account| *account.key == input.signer)
375 {
376 account
377 .as_ref()
378 .set_utxo(&UtxoMeta::from(txid, input.index));
379 }
380 }
381 Ok(())
382 }
383 _ => Err(set_inputs_to_sign_result.into()),
384 }
385}
386
387pub const MAX_RETURN_DATA: usize = 1024;
389
390pub fn set_return_data(data: &[u8]) {
398 #[cfg(target_os = "solana")]
399 unsafe {
400 crate::syscalls::sol_set_return_data(data.as_ptr(), data.len() as u64)
401 };
402
403 #[cfg(not(target_os = "solana"))]
404 crate::program_stubs::_sol_set_return_data(data.as_ptr(), data.len() as u64);
405}
406
407#[inline(never)]
437pub fn get_return_data() -> Option<(Pubkey, ReturnedData)> {
438 use std::cmp::min;
439
440 let mut buf = [0u8; MAX_RETURN_DATA];
441 let mut program_id = Pubkey::default();
442
443 #[cfg(target_os = "solana")]
444 let size = unsafe {
445 crate::syscalls::sol_get_return_data(buf.as_mut_ptr(), buf.len() as u64, &mut program_id)
446 };
447
448 #[cfg(not(target_os = "solana"))]
449 let size = crate::program_stubs::_sol_get_return_data(
450 buf.as_mut_ptr(),
451 buf.len() as u64,
452 &mut program_id,
453 );
454
455 if size == 0 {
456 None
457 } else {
458 let size = min(size as usize, MAX_RETURN_DATA);
459 Some((program_id, ReturnedData::new(buf, size)))
460 }
461}
462
463#[inline(never)]
471pub fn get_bitcoin_tx(txid: [u8; 32]) -> Option<BitcoinTransaction> {
472 let mut buf: BitcoinTransaction = Default::default();
473
474 #[cfg(target_os = "solana")]
475 let size = unsafe {
476 crate::syscalls::arch_get_bitcoin_tx(buf.as_mut_ptr(), buf.capacity() as u64, &txid)
477 };
478 #[cfg(not(target_os = "solana"))]
479 let size = crate::program_stubs::arch_get_bitcoin_tx(buf.as_mut_ptr(), buf.capacity(), &txid);
480
481 if size == 0 {
482 return None;
483 }
484
485 buf.set_size(core::cmp::min(size as usize, MAX_BTC_TX_SIZE));
486
487 Some(buf)
488}
489
490#[inline(never)]
501pub fn get_bitcoin_tx_output_value(txid: [u8; 32], vout: u32) -> Option<u64> {
502 let mut buf: BitcoinTransaction = Default::default();
503
504 #[cfg(target_os = "solana")]
505 let size = unsafe {
506 crate::syscalls::arch_get_bitcoin_tx(buf.as_mut_ptr(), buf.capacity() as u64, &txid)
507 };
508 #[cfg(not(target_os = "solana"))]
509 let size = crate::program_stubs::arch_get_bitcoin_tx(buf.as_mut_ptr(), buf.capacity(), &txid);
510
511 if size == 0 {
512 return None;
513 }
514
515 buf.set_size(core::cmp::min(size as usize, MAX_BTC_TX_SIZE));
516
517 extract_output_value(buf.as_slice(), vout as usize)
518}
519
520#[inline(never)]
521fn extract_output_value(tx: &[u8], output_index: usize) -> Option<u64> {
522 struct OutputExtractor {
523 target_index: usize,
524 value: Option<u64>,
525 }
526
527 impl Visitor for OutputExtractor {
528 fn visit_tx_out(&mut self, vout: usize, tx_out: &bsl::TxOut) -> core::ops::ControlFlow<()> {
529 if vout == self.target_index {
530 let value = tx_out.value();
532 self.value = Some(value);
533 return core::ops::ControlFlow::Break(());
534 }
535 core::ops::ControlFlow::Continue(())
536 }
537 }
538
539 let mut extractor = OutputExtractor {
540 target_index: output_index,
541 value: None,
542 };
543
544 match bsl::Transaction::visit(tx, &mut extractor) {
546 Ok(_) | Err(Error::VisitBreak) => extractor.value,
547 Err(_) => None,
548 }
549}
550
551#[inline(never)]
560pub fn get_runes_from_output(txid: [u8; 32], output_index: u32) -> Option<Vec<RuneAmount>> {
561 use std::cmp::min;
562 if txid == [0u8; 32] {
563 return None;
564 }
565
566 let mut result: BitcoinRuneOutput = Default::default();
567
568 #[cfg(target_os = "solana")]
569 let size = unsafe {
570 crate::syscalls::arch_get_runes_from_output(
571 result.as_mut_ptr(),
572 result.capacity() as u64,
573 &txid,
574 output_index,
575 )
576 };
577
578 #[cfg(not(target_os = "solana"))]
579 let size = crate::program_stubs::arch_get_runes_from_output(
580 result.as_mut_ptr(),
581 result.capacity(),
582 &txid,
583 output_index,
584 );
585
586 if size == 0 {
587 None
588 } else {
589 result.set_size(min(size as usize, MAX_BTC_RUNE_OUTPUT_SIZE));
590 borsh::from_slice::<Vec<RuneAmount>>(result.as_slice()).ok()
591 }
592}
593
594#[inline(never)]
603pub fn get_rune_info(block: u64, tx: u64) -> Option<RuneInfo> {
604 use std::cmp::min;
605
606 let mut result: RuneInfoBuf = Default::default();
607
608 #[cfg(target_os = "solana")]
609 let size = unsafe {
610 crate::syscalls::arch_get_rune_info(
611 result.as_mut_ptr(),
612 result.capacity() as u64,
613 block,
614 tx,
615 )
616 };
617
618 #[cfg(not(target_os = "solana"))]
619 let size =
620 crate::program_stubs::arch_get_rune_info(result.as_mut_ptr(), result.capacity(), block, tx);
621
622 if size == 0 {
623 None
624 } else {
625 result.set_size(min(size as usize, result.capacity()));
626 borsh::from_slice::<RuneInfo>(result.as_slice()).ok()
627 }
628}
629
630pub fn get_remaining_compute_units() -> u64 {
631 #[cfg(target_os = "solana")]
632 unsafe {
633 crate::syscalls::get_remaining_compute_units()
634 }
635
636 #[cfg(not(target_os = "solana"))]
637 crate::program_stubs::get_remaining_compute_units()
638}
639pub fn get_network_xonly_pubkey() -> [u8; 32] {
646 let mut buf = [0u8; 32];
647
648 #[cfg(target_os = "solana")]
649 let _ = unsafe { crate::syscalls::arch_get_network_xonly_pubkey(buf.as_mut_ptr()) };
650
651 #[cfg(not(target_os = "solana"))]
652 crate::program_stubs::arch_get_network_xonly_pubkey(buf.as_mut_ptr());
653 buf
654}
655
656pub fn validate_utxo_ownership(utxo: &UtxoMeta, owner: &Pubkey) -> bool {
665 #[cfg(target_os = "solana")]
666 unsafe {
667 crate::syscalls::arch_validate_utxo_ownership(utxo, owner) != 0
668 }
669
670 #[cfg(not(target_os = "solana"))]
671 {
672 crate::program_stubs::arch_validate_utxo_ownership(utxo, owner) != 0
673 }
674}
675
676pub fn get_account_script_pubkey(pubkey: &Pubkey) -> [u8; 34] {
684 let mut buf = [0u8; 34];
685
686 #[cfg(target_os = "solana")]
687 let _ = unsafe { crate::syscalls::arch_get_account_script_pubkey(buf.as_mut_ptr(), pubkey) };
688
689 #[cfg(not(target_os = "solana"))]
690 crate::program_stubs::arch_get_account_script_pubkey(&mut buf, pubkey);
691 buf
692}
693
694pub fn get_bitcoin_block_height() -> u64 {
699 #[cfg(target_os = "solana")]
700 unsafe {
701 crate::syscalls::arch_get_bitcoin_block_height()
702 }
703
704 #[cfg(not(target_os = "solana"))]
705 crate::program_stubs::arch_get_bitcoin_block_height()
706}
707
708pub fn get_clock() -> Clock {
713 let mut clock = Clock::default();
714 #[cfg(target_os = "solana")]
715 unsafe {
716 crate::syscalls::arch_get_clock(&mut clock)
717 };
718
719 #[cfg(not(target_os = "solana"))]
720 let _ = crate::program_stubs::arch_get_clock(&mut clock);
721
722 clock
723}
724
725pub fn get_stack_height() -> u64 {
730 #[cfg(target_os = "solana")]
731 unsafe {
732 crate::syscalls::arch_get_stack_height()
733 }
734
735 #[cfg(not(target_os = "solana"))]
736 crate::program_stubs::arch_get_stack_height()
737}
738
739pub fn get_bitcoin_tx_confirmation(txid: [u8; 32]) -> bool {
747 let mut buf = [0u8; 1];
748
749 #[cfg(target_os = "solana")]
750 let _ = unsafe { crate::syscalls::arch_get_bitcoin_tx_confirmation(&txid, buf.as_mut_ptr()) };
751
752 #[cfg(not(target_os = "solana"))]
753 let _ = crate::program_stubs::arch_get_bitcoin_tx_confirmation(&txid, buf.as_mut_ptr());
754
755 buf[0] == 1
756}
757
758pub fn get_transaction_to_sign() -> [u8; 1024] {
759 let mut buf = [0u8; 1024];
764
765 #[cfg(target_os = "solana")]
766 unsafe {
767 crate::syscalls::arch_get_transaction_to_sign(buf.as_mut_ptr(), buf.len() as u64)
768 };
769
770 #[cfg(not(target_os = "solana"))]
771 let _ = crate::program_stubs::arch_get_transaction_to_sign(buf.as_mut_ptr(), buf.len());
772
773 buf
774}
775
776#[cfg(test)]
777mod carrier_classification_tests {
778 use super::*;
779 use bitcoin::{absolute::LockTime, transaction::Version, OutPoint, Sequence, TxIn, Txid};
780
781 fn tx_spending(outpoints: &[(Txid, u32)]) -> Transaction {
782 Transaction {
783 version: Version::TWO,
784 lock_time: LockTime::ZERO,
785 input: outpoints
786 .iter()
787 .map(|&(txid, vout)| TxIn {
788 previous_output: OutPoint { txid, vout },
789 script_sig: Default::default(),
790 sequence: Sequence::MAX,
791 witness: Default::default(),
792 })
793 .collect(),
794 output: vec![],
795 }
796 }
797
798 #[test]
799 fn defined_account_moves_carrier_only_through_exact_outpoint() {
800 let signer = Pubkey::new_unique();
801 let carrier_txid = Txid::from_slice(&[7u8; 32]).unwrap();
802 let other_txid = Txid::from_slice(&[8u8; 32]).unwrap();
803 let carrier = UtxoMeta::from_outpoint(carrier_txid, 1);
804 let tx = tx_spending(&[(other_txid, 0), (carrier_txid, 1)]);
805 let inputs = [
806 InputToSign { index: 0, signer },
807 InputToSign { index: 1, signer },
808 ];
809
810 assert!(!input_moves_state_carrier(
811 &carrier, &inputs[0], &tx, &inputs
812 ));
813 assert!(input_moves_state_carrier(
814 &carrier, &inputs[1], &tx, &inputs
815 ));
816 }
817
818 #[test]
819 fn defined_account_ignores_out_of_bounds_index() {
820 let signer = Pubkey::new_unique();
821 let carrier = UtxoMeta::from_outpoint(Txid::from_slice(&[7u8; 32]).unwrap(), 1);
822 let tx = tx_spending(&[]);
823 let input = InputToSign { index: 5, signer };
824
825 assert!(!input_moves_state_carrier(
826 &carrier,
827 &input,
828 &tx,
829 &[input.clone()]
830 ));
831 }
832
833 #[test]
834 fn unanchored_account_first_registered_input_wins() {
835 let signer = Pubkey::new_unique();
836 let other = Pubkey::new_unique();
837 let txid = Txid::from_slice(&[9u8; 32]).unwrap();
838 let tx = tx_spending(&[(txid, 0), (txid, 1), (txid, 2)]);
839 let inputs = [
840 InputToSign {
841 index: 0,
842 signer: other,
843 },
844 InputToSign { index: 1, signer },
845 InputToSign { index: 2, signer },
846 ];
847 let undefined = UtxoMeta::default();
848
849 assert!(input_moves_state_carrier(
850 &undefined, &inputs[1], &tx, &inputs
851 ));
852 assert!(!input_moves_state_carrier(
853 &undefined, &inputs[2], &tx, &inputs
854 ));
855 }
856}