Skip to main content

arch_program/
program.rs

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/// A generic wrapper for fixed-size data that avoids heap allocation.
20///
21/// This type holds raw bytes in a fixed-size array and tracks the actual size of the data.
22/// The array size is determined by the const generic parameter `N`.
23#[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    /// Creates a new FixedSizeBuffer from a buffer and size.
31    pub fn new(data: [u8; N], size: usize) -> Self {
32        Self { data, size }
33    }
34
35    /// Returns the actual size of the data.
36    pub fn size(&self) -> usize {
37        self.size
38    }
39
40    /// Returns a slice of the actual data.
41    pub fn as_slice(&self) -> &[u8] {
42        &self.data[..self.size]
43    }
44
45    /// Returns a mutable raw pointer to the underlying buffer (for FFI/syscall writes).
46    pub fn as_mut_ptr(&mut self) -> *mut u8 {
47        self.data.as_mut_ptr()
48    }
49
50    /// Returns the total capacity of the buffer.
51    pub fn capacity(&self) -> usize {
52        N
53    }
54
55    /// Sets the length of the valid data written into the buffer.
56    ///
57    /// # Safety
58    /// The caller must guarantee that `new_size` bytes starting from the
59    /// pointer returned by `as_mut_ptr` have been initialised.
60    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
95/// Type alias for Bitcoin transaction data with a fixed 3976-byte buffer.
96pub type BitcoinTransaction = FixedSizeBuffer<MAX_BTC_TX_SIZE>;
97
98/// Type alias for Bitcoin rune output data with a fixed 2048-byte buffer.
99pub type BitcoinRuneOutput = FixedSizeBuffer<MAX_BTC_RUNE_OUTPUT_SIZE>;
100
101/// Type alias for Returned Data with a fixed 1024-byte buffer.
102pub type ReturnedData = FixedSizeBuffer<MAX_RETURN_DATA>;
103
104/// Type alias for RuneInfo data with a tight 64-byte buffer.
105/// Borsh-encoded `RuneInfo` is 53 bytes; 64 is a safe upper bound.
106pub type RuneInfoBuf = FixedSizeBuffer<64>;
107
108/// Invokes a program instruction through cross-program invocation.
109///
110/// This function processes the provided instruction by dispatching control to another program
111/// using the account information provided.
112///
113/// # Arguments
114/// * `instruction` - The instruction to process
115/// * `account_infos` - The accounts required to process the instruction
116///
117/// # Returns
118/// * `ProgramResult` - Ok(()) if successful, or an error if the operation fails
119pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {
120    invoke_signed(instruction, account_infos, &[])
121}
122
123/// Invokes a program instruction without checking account permissions.
124///
125/// Similar to `invoke`, but skips the account permission checking step.
126/// This is generally less safe than `invoke` and should be used carefully.
127///
128/// # Arguments
129/// * `instruction` - The instruction to process
130/// * `account_infos` - The accounts required to process the instruction
131///
132/// # Returns
133/// * `ProgramResult` - Ok(()) if successful, or an error if the operation fails
134pub fn invoke_unchecked(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {
135    invoke_signed_unchecked(instruction, account_infos, &[])
136}
137
138/// Invokes a program instruction with additional signing authority.
139///
140/// This function processes the provided instruction by dispatching control to another program,
141/// while also providing program-derived address signing authority.
142/// It performs permission checks on the accounts before invoking.
143///
144/// # Arguments
145/// * `instruction` - The instruction to process
146/// * `account_infos` - The accounts required to process the instruction
147/// * `signers_seeds` - Seeds used to sign the transaction as a program-derived address
148///
149/// # Returns
150/// * `ProgramResult` - Ok(()) if successful, or an error if the operation fails
151///
152/// # Errors
153/// Returns an error if any required account cannot be borrowed according to its stated permissions
154pub fn invoke_signed(
155    instruction: &Instruction,
156    account_infos: &[AccountInfo],
157    signers_seeds: &[&[&[u8]]],
158) -> ProgramResult {
159    // Check that the account RefCells are consistent with the request
160    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
176/// Invokes a program instruction with additional signing authority without checking account permissions.
177///
178/// Similar to `invoke_signed`, but skips the account permission checking step.
179/// This is generally less safe than `invoke_signed` and should be used carefully.
180///
181/// # Arguments
182/// * `instruction` - The instruction to process
183/// * `account_infos` - The accounts required to process the instruction
184/// * `signers_seeds` - Seeds used to sign the transaction as a program-derived address
185///
186/// # Returns
187/// * `ProgramResult` - Ok(()) if successful, or an error if the operation fails
188pub 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
215/// Gets the next account from an account iterator.
216///
217/// A utility function that advances the iterator and returns the next `AccountInfo`,
218/// or returns a `NotEnoughAccountKeys` error if there are no more accounts.
219///
220/// # Arguments
221/// * `iter` - Mutable reference to an iterator yielding references to `AccountInfo`
222///
223/// # Returns
224/// * `Result<&AccountInfo, ProgramError>` - The next account info or an error if depleted
225///
226/// # Errors
227/// Returns `ProgramError::NotEnoughAccountKeys` if the iterator has no more items
228pub 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
236/// Returns whether registered input `input` re-anchors `account`'s state carrier.
237///
238/// Mirrors the runtime's classification: a defined account moves its carrier only through
239/// the input spending its exact current outpoint; an unanchored account first-anchors
240/// through its first registered input. Every other input is an additional owned-UTXO
241/// spend that leaves the carrier untouched.
242fn 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
261/// Sets an Arch transaction to be signed by the program.
262///
263/// This function takes a transaction and its associated signing metadata and prepares it
264/// for signing through the runtime. It also re-anchors each signer's UTXO metadata at its
265/// state-carrying input's index; additional owned-UTXO spends leave the metadata untouched
266/// (see [`input_moves_state_carrier`]).
267///
268/// # Arguments
269/// * `accounts` - Slice of account information required for the transaction
270/// * `tx` - The transaction
271/// * `inputs_to_sign` - The inputs to sign
272///
273/// # Returns
274/// * `ProgramResult` - Ok(()) if successful, or an error if the operation fails
275pub 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    // Use the new method that avoids double allocation
285
286    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
341/// Registers additional inputs on the pending transaction to sign and re-anchors each
342/// signer's UTXO metadata at `OutPoint(txid, index)`.
343///
344/// Unlike [`set_transaction_to_sign`], this helper cannot see the transaction's inputs and
345/// therefore treats **every** registered input as a state-carrier move (or first anchor).
346/// Programs spending additional owned UTXOs must not route them through this helper: use
347/// [`set_transaction_to_sign`], or register them separately and leave the account UTXO
348/// untouched.
349pub 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
387/// Maximum size that can be set using [`set_return_data`].
388pub const MAX_RETURN_DATA: usize = 1024;
389
390/// Set the running program's return data.
391///
392/// Return data is a dedicated per-transaction buffer for data passed
393/// from cross-program invoked programs back to their caller.
394///
395/// The maximum size of return data is [`MAX_RETURN_DATA`]. Return data is
396/// retrieved by the caller with [`get_return_data`].
397pub 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/// Get the return data from an invoked program.
408///
409/// For every transaction there is a single buffer with maximum length
410/// [`MAX_RETURN_DATA`], paired with a [`Pubkey`] representing the program ID of
411/// the program that most recently set the return data. Thus the return data is
412/// a global resource and care must be taken to ensure that it represents what
413/// is expected: called programs are free to set or not set the return data; and
414/// the return data may represent values set by programs multiple calls down the
415/// call stack, depending on the circumstances of transaction execution.
416///
417/// Return data is set by the callee with [`set_return_data`].
418///
419/// Return data is cleared before every CPI invocation &mdash; a program that
420/// has invoked no other programs can expect the return data to be `None`; if no
421/// return data was set by the previous CPI invocation, then this function
422/// returns `None`.
423///
424/// Return data is not cleared after returning from CPI invocations &mdash; a
425/// program that has called another program may retrieve return data that was
426/// not set by the called program, but instead set by a program further down the
427/// call stack; or, if a program calls itself recursively, it is possible that
428/// the return data was not set by the immediate call to that program, but by a
429/// subsequent recursive call to that program. Likewise, an external RPC caller
430/// may see return data that was not set by the program it is directly calling,
431/// but by a program that program called.
432///
433/// For more about return data see the [documentation for the return data proposal][rdp].
434///
435/// [rdp]: https://docs.solanalabs.com/proposals/return-data
436#[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/// Retrieves a Bitcoin transaction by its transaction ID.
464///
465/// # Arguments
466/// * `txid` - 32-byte array containing the Bitcoin transaction ID
467///
468/// # Returns
469/// * `Option<BitcoinTransaction>` - The transaction if found, None if not found
470#[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/// Extracts the value of a specific output from a serialized Bitcoin transaction.
491///
492/// This function is used to extract the value of a specific output from a serialized Bitcoin transaction.
493///
494/// # Arguments
495/// * `tx` - The transaction bytes
496/// * `output_index` - The output index to retrieve
497///
498/// # Returns
499/// * `Option<u64>` - The output value if found, None if not found
500#[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                // Calculate the position within the original transaction bytes
531                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    // Parse transaction and visit outputs
545    match bsl::Transaction::visit(tx, &mut extractor) {
546        Ok(_) | Err(Error::VisitBreak) => extractor.value,
547        Err(_) => None,
548    }
549}
550
551/// Retrieves the runes from a Bitcoin output by its transaction ID and output index.
552///
553/// # Arguments
554/// * `txid` - 32-byte array containing the Bitcoin transaction ID
555/// * `output_index` - The output index to retrieve
556///
557/// # Returns
558/// * `Option<Vec<RuneAmount>>` - The runes if found, None if not found
559#[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/// Retrieves the runes from a Bitcoin output by its transaction ID and output index.
595///
596/// # Arguments
597/// * `txid` - 32-byte array containing the Bitcoin transaction ID
598/// * `output_index` - The output index to retrieve
599///
600/// # Returns
601/// * `Option<RuneInfo>` - The runes if found, None if not found
602#[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}
639/// Retrieves the network's X-only public key.
640///
641/// This function fetches the X-only public key associated with the current network configuration.
642///
643/// # Returns
644/// * `[u8; 32]` - The 32-byte X-only public key
645pub 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
656/// Validates if a UTXO is owned by the specified public key.
657///
658/// # Arguments
659/// * `utxo` - The UTXO metadata to validate
660/// * `owner` - The public key to check ownership against
661///
662/// # Returns
663/// * `bool` - true if the UTXO is owned by the specified public key, false otherwise
664pub 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
676/// Gets the script public key for a given account.
677///
678/// # Arguments
679/// * `pubkey` - The public key of the account
680///
681/// # Returns
682/// * `[u8; 34]` - The 34-byte script public key
683pub 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
694/// Retrieves the current Bitcoin block height from the runtime.
695///
696/// # Returns
697/// * `u64` - The current Bitcoin block height
698pub 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
708/// Gets the current clock information from the runtime.
709///
710/// # Returns
711/// * `Clock` - The current clock state containing timing information
712pub 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
725/// Gets the current stack height from the runtime.
726///
727/// # Returns
728/// * `u64` - The current stack height
729pub 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
739/// Retrieves the confirmation status of a Bitcoin transaction by its transaction ID.
740///
741/// # Arguments
742/// * `txid` - 32-byte array containing the Bitcoin transaction ID
743///
744/// # Returns
745/// * `bool` - The confirmation status of the transaction, false if not found
746pub 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    // setting it to 2048 as large allocation can cause stack allocation errors as we have limited
760    // stack space, we expect it to be useful only when debugging code
761    // in case you need more space, use the syscall directly
762
763    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}