use bitcoin::hashes::Hash;
use bitcoin::Transaction;
use bitcoin_slices::{bsl, Error, Visit, Visitor};
use borsh::{BorshDeserialize, BorshSerialize};
use crate::input_to_sign::InputToSign;
use crate::instruction::Instruction;
use crate::program_error::ProgramError;
use crate::rune::{RuneAmount, RuneInfo};
#[cfg(target_os = "solana")]
use crate::stable_layout::stable_ins::StableInstruction;
use crate::{msg, MAX_BTC_RUNE_OUTPUT_SIZE, MAX_BTC_TX_SIZE};
use crate::clock::Clock;
use crate::transaction_to_sign::TransactionToSign;
use crate::utxo::UtxoMeta;
use crate::{account::AccountInfo, entrypoint::ProgramResult, pubkey::Pubkey};
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct FixedSizeBuffer<const N: usize> {
data: [u8; N],
size: usize,
}
impl<const N: usize> FixedSizeBuffer<N> {
pub fn new(data: [u8; N], size: usize) -> Self {
Self { data, size }
}
pub fn size(&self) -> usize {
self.size
}
pub fn as_slice(&self) -> &[u8] {
&self.data[..self.size]
}
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.data.as_mut_ptr()
}
pub fn capacity(&self) -> usize {
N
}
pub fn set_size(&mut self, new_size: usize) {
debug_assert!(
new_size <= N,
"new_size ({}) exceeds buffer capacity ({})",
new_size,
N
);
self.size = new_size;
}
}
impl<const N: usize> AsRef<[u8]> for FixedSizeBuffer<N> {
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl<const N: usize> std::ops::Deref for FixedSizeBuffer<N> {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl<const N: usize> Default for FixedSizeBuffer<N> {
fn default() -> Self {
Self {
data: [0u8; N],
size: 0,
}
}
}
pub type BitcoinTransaction = FixedSizeBuffer<MAX_BTC_TX_SIZE>;
pub type BitcoinRuneOutput = FixedSizeBuffer<MAX_BTC_RUNE_OUTPUT_SIZE>;
pub type ReturnedData = FixedSizeBuffer<MAX_RETURN_DATA>;
pub type RuneInfoBuf = FixedSizeBuffer<64>;
pub fn invoke(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {
invoke_signed(instruction, account_infos, &[])
}
pub fn invoke_unchecked(instruction: &Instruction, account_infos: &[AccountInfo]) -> ProgramResult {
invoke_signed_unchecked(instruction, account_infos, &[])
}
pub fn invoke_signed(
instruction: &Instruction,
account_infos: &[AccountInfo],
signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
for account_meta in instruction.accounts.iter() {
for account_info in account_infos.iter() {
if account_meta.pubkey == *account_info.key {
if account_meta.is_writable {
let _ = account_info.try_borrow_mut_data()?;
} else {
let _ = account_info.try_borrow_data()?;
}
break;
}
}
}
invoke_signed_unchecked(instruction, account_infos, signers_seeds)
}
pub fn invoke_signed_unchecked(
instruction: &Instruction,
account_infos: &[AccountInfo],
signers_seeds: &[&[&[u8]]],
) -> ProgramResult {
#[cfg(target_os = "solana")]
{
let instruction = StableInstruction::from(instruction.clone());
let result = unsafe {
crate::syscalls::sol_invoke_signed_rust(
&instruction as *const _ as *const u8,
account_infos as *const _ as *const u8,
account_infos.len() as u64,
signers_seeds as *const _ as *const u8,
signers_seeds.len() as u64,
)
};
match result {
crate::entrypoint::SUCCESS => Ok(()),
_ => Err(result.into()),
}
}
#[cfg(not(target_os = "solana"))]
crate::program_stubs::sol_invoke_signed(instruction, account_infos, signers_seeds)
}
pub fn next_account_info<'a, 'b, I: Iterator<Item = &'a AccountInfo<'b>>>(
iter: &mut I,
) -> Result<I::Item, ProgramError> {
iter.next().ok_or(ProgramError::NotEnoughAccountKeys)
}
pub const MAX_TRANSACTION_TO_SIGN: usize = 4 * 1024;
pub fn set_transaction_to_sign<'info, T>(
accounts: &[T],
tx: &Transaction,
inputs_to_sign: &[InputToSign],
) -> ProgramResult
where
T: AsRef<AccountInfo<'info>>,
{
msg!("setting tx to sign");
let serialized_tx_bytes = bitcoin::consensus::serialize(tx);
let serialized_inputs_to_sign = TransactionToSign::serialise_inputs_to_sign(inputs_to_sign);
#[cfg(target_os = "solana")]
let set_tx_result = unsafe {
crate::syscalls::arch_set_transaction_to_sign(
serialized_tx_bytes.as_ptr(),
serialized_tx_bytes.len() as u64,
)
};
#[cfg(not(target_os = "solana"))]
let set_tx_result = crate::program_stubs::arch_set_transaction_to_sign(
serialized_tx_bytes.as_ptr(),
serialized_tx_bytes.len(),
);
#[cfg(target_os = "solana")]
let set_inputs_to_sign_result = unsafe {
crate::syscalls::arch_set_inputs_to_sign(
serialized_inputs_to_sign.as_ptr(),
serialized_inputs_to_sign.len() as u64,
)
};
#[cfg(not(target_os = "solana"))]
let set_inputs_to_sign_result = crate::program_stubs::arch_set_inputs_to_sign(
serialized_inputs_to_sign.as_ptr(),
serialized_inputs_to_sign.len(),
);
match set_tx_result {
crate::entrypoint::SUCCESS => match set_inputs_to_sign_result {
crate::entrypoint::SUCCESS => {
let txid = tx.compute_txid();
let mut txid_bytes: [u8; 32] = txid.as_raw_hash().to_byte_array();
txid_bytes.reverse();
for input in inputs_to_sign {
if let Some(account) = accounts
.iter()
.find(|account| *account.as_ref().key == input.signer)
{
account
.as_ref()
.set_utxo(&UtxoMeta::from(txid_bytes, input.index));
}
}
Ok(())
}
_ => Err(set_inputs_to_sign_result.into()),
},
_ => Err(set_tx_result.into()),
}
}
pub fn set_input_to_sign(
accounts: &[AccountInfo],
txid: [u8; 32],
inputs_to_sign: &[InputToSign],
) -> ProgramResult {
msg!("setting inputs to sign");
let serialized_inputs_to_sign = TransactionToSign::serialise_inputs_to_sign(inputs_to_sign);
#[cfg(target_os = "solana")]
let set_inputs_to_sign_result = unsafe {
crate::syscalls::arch_set_inputs_to_sign(
serialized_inputs_to_sign.as_ptr(),
serialized_inputs_to_sign.len() as u64,
)
};
#[cfg(not(target_os = "solana"))]
let set_inputs_to_sign_result = crate::program_stubs::arch_set_inputs_to_sign(
serialized_inputs_to_sign.as_ptr(),
serialized_inputs_to_sign.len(),
);
match set_inputs_to_sign_result {
crate::entrypoint::SUCCESS => {
for input in inputs_to_sign {
if let Some(account) = accounts.iter().find(|account| *account.key == input.signer)
{
account
.as_ref()
.set_utxo(&UtxoMeta::from(txid, input.index));
}
}
Ok(())
}
_ => Err(set_inputs_to_sign_result.into()),
}
}
pub const MAX_RETURN_DATA: usize = 1024;
pub fn set_return_data(data: &[u8]) {
unsafe { crate::syscalls::sol_set_return_data(data.as_ptr(), data.len() as u64) };
}
#[inline(never)]
pub fn get_return_data() -> Option<(Pubkey, ReturnedData)> {
use std::cmp::min;
let mut buf = [0u8; MAX_RETURN_DATA];
let mut program_id = Pubkey::default();
let size = unsafe {
crate::syscalls::sol_get_return_data(buf.as_mut_ptr(), buf.len() as u64, &mut program_id)
};
if size == 0 {
None
} else {
let size = min(size as usize, MAX_RETURN_DATA);
Some((program_id, ReturnedData::new(buf, size)))
}
}
#[inline(never)]
pub fn get_bitcoin_tx(txid: [u8; 32]) -> Option<BitcoinTransaction> {
let mut buf: BitcoinTransaction = Default::default();
#[cfg(target_os = "solana")]
let size = unsafe {
crate::syscalls::arch_get_bitcoin_tx(buf.as_mut_ptr(), buf.capacity() as u64, &txid)
};
#[cfg(not(target_os = "solana"))]
let size = crate::program_stubs::arch_get_bitcoin_tx(buf.as_mut_ptr(), buf.capacity(), &txid);
if size == 0 {
return None;
}
buf.set_size(core::cmp::min(size as usize, MAX_BTC_TX_SIZE));
Some(buf)
}
#[inline(never)]
pub fn get_bitcoin_tx_output_value(txid: [u8; 32], vout: u32) -> Option<u64> {
let mut buf: BitcoinTransaction = Default::default();
#[cfg(target_os = "solana")]
let size = unsafe {
crate::syscalls::arch_get_bitcoin_tx(buf.as_mut_ptr(), buf.capacity() as u64, &txid)
};
#[cfg(not(target_os = "solana"))]
let size = crate::program_stubs::arch_get_bitcoin_tx(buf.as_mut_ptr(), buf.capacity(), &txid);
if size == 0 {
return None;
}
buf.set_size(core::cmp::min(size as usize, MAX_BTC_TX_SIZE));
extract_output_value(buf.as_slice(), vout as usize)
}
#[inline(never)]
fn extract_output_value(tx: &[u8], output_index: usize) -> Option<u64> {
struct OutputExtractor {
target_index: usize,
value: Option<u64>,
}
impl Visitor for OutputExtractor {
fn visit_tx_out(&mut self, vout: usize, tx_out: &bsl::TxOut) -> core::ops::ControlFlow<()> {
if vout == self.target_index {
let value = tx_out.value();
self.value = Some(value);
return core::ops::ControlFlow::Break(());
}
core::ops::ControlFlow::Continue(())
}
}
let mut extractor = OutputExtractor {
target_index: output_index,
value: None,
};
match bsl::Transaction::visit(tx, &mut extractor) {
Ok(_) | Err(Error::VisitBreak) => extractor.value,
Err(_) => None,
}
}
#[inline(never)]
pub fn get_runes_from_output(txid: [u8; 32], output_index: u32) -> Option<Vec<RuneAmount>> {
use std::cmp::min;
if txid == [0u8; 32] {
return None;
}
let mut result: BitcoinRuneOutput = Default::default();
#[cfg(target_os = "solana")]
let size = unsafe {
crate::syscalls::arch_get_runes_from_output(
result.as_mut_ptr(),
result.capacity() as u64,
&txid,
output_index,
)
};
#[cfg(not(target_os = "solana"))]
let size = crate::program_stubs::arch_get_runes_from_output(
result.as_mut_ptr(),
result.capacity(),
&txid,
output_index,
);
if size == 0 {
None
} else {
result.set_size(min(size as usize, MAX_BTC_RUNE_OUTPUT_SIZE));
borsh::from_slice::<Vec<RuneAmount>>(result.as_slice()).ok()
}
}
#[inline(never)]
pub fn get_rune_info(block: u64, tx: u64) -> Option<RuneInfo> {
use std::cmp::min;
let mut result: RuneInfoBuf = Default::default();
#[cfg(target_os = "solana")]
let size = unsafe {
crate::syscalls::arch_get_rune_info(
result.as_mut_ptr(),
result.capacity() as u64,
block,
tx,
)
};
#[cfg(not(target_os = "solana"))]
let size =
crate::program_stubs::arch_get_rune_info(result.as_mut_ptr(), result.capacity(), block, tx);
if size == 0 {
None
} else {
result.set_size(min(size as usize, result.capacity()));
borsh::from_slice::<RuneInfo>(result.as_slice()).ok()
}
}
pub fn get_remaining_compute_units() -> u64 {
#[cfg(target_os = "solana")]
unsafe {
crate::syscalls::get_remaining_compute_units()
}
#[cfg(not(target_os = "solana"))]
crate::program_stubs::get_remaining_compute_units()
}
pub fn get_network_xonly_pubkey() -> [u8; 32] {
let mut buf = [0u8; 32];
#[cfg(target_os = "solana")]
let _ = unsafe { crate::syscalls::arch_get_network_xonly_pubkey(buf.as_mut_ptr()) };
#[cfg(not(target_os = "solana"))]
crate::program_stubs::arch_get_network_xonly_pubkey(buf.as_mut_ptr());
buf
}
pub fn validate_utxo_ownership(utxo: &UtxoMeta, owner: &Pubkey) -> bool {
#[cfg(target_os = "solana")]
unsafe {
crate::syscalls::arch_validate_utxo_ownership(utxo, owner) != 0
}
#[cfg(not(target_os = "solana"))]
{
crate::program_stubs::arch_validate_utxo_ownership(utxo, owner) != 0
}
}
pub fn get_account_script_pubkey(pubkey: &Pubkey) -> [u8; 34] {
let mut buf = [0u8; 34];
#[cfg(target_os = "solana")]
let _ = unsafe { crate::syscalls::arch_get_account_script_pubkey(buf.as_mut_ptr(), pubkey) };
#[cfg(not(target_os = "solana"))]
crate::program_stubs::arch_get_account_script_pubkey(&mut buf, pubkey);
buf
}
pub fn get_bitcoin_block_height() -> u64 {
#[cfg(target_os = "solana")]
unsafe {
crate::syscalls::arch_get_bitcoin_block_height()
}
#[cfg(not(target_os = "solana"))]
crate::program_stubs::arch_get_bitcoin_block_height()
}
pub fn get_clock() -> Clock {
let mut clock = Clock::default();
#[cfg(target_os = "solana")]
unsafe {
crate::syscalls::arch_get_clock(&mut clock)
};
#[cfg(not(target_os = "solana"))]
let _ = crate::program_stubs::arch_get_clock(&mut clock);
clock
}
pub fn get_stack_height() -> u64 {
#[cfg(target_os = "solana")]
unsafe {
crate::syscalls::arch_get_stack_height()
}
#[cfg(not(target_os = "solana"))]
crate::program_stubs::arch_get_stack_height()
}
pub fn get_bitcoin_tx_confirmation(txid: [u8; 32]) -> bool {
let mut buf = [0u8; 1];
#[cfg(target_os = "solana")]
let _ = unsafe { crate::syscalls::arch_get_bitcoin_tx_confirmation(&txid, buf.as_mut_ptr()) };
#[cfg(not(target_os = "solana"))]
let _ = crate::program_stubs::arch_get_bitcoin_tx_confirmation(&txid, buf.as_mut_ptr());
buf[0] == 1
}
pub fn get_transaction_to_sign() -> [u8; 1024] {
let mut buf = [0u8; 1024];
#[cfg(target_os = "solana")]
unsafe {
crate::syscalls::arch_get_transaction_to_sign(buf.as_mut_ptr(), buf.len() as u64)
};
#[cfg(not(target_os = "solana"))]
let _ = crate::program_stubs::arch_get_transaction_to_sign(buf.as_mut_ptr(), buf.len());
buf
}