#![cfg(test)]
use super::super::shard::StateShard;
use arch_program::program_pack::{Pack, Sealed};
use arch_program::utxo;
use arch_program::{account::AccountInfo, pubkey::Pubkey, utxo::UtxoMeta};
use bytemuck::{Pod, Zeroable};
use satellite_bitcoin::utxo_info::UtxoInfoTrait;
use satellite_bitcoin::utxo_info::{SingleRuneSet, UtxoInfo};
use satellite_lang::prelude::Owner;
use satellite_lang::prelude::{AccountLoader, ProgramError};
use satellite_lang::Discriminator;
use satellite_lang::ZeroCopy;
pub const MAX_BTC_UTXOS: usize = 64;
#[repr(C)]
#[derive(Clone, Copy, Zeroable)]
pub struct MockShardZc {
btc_utxos: [UtxoInfo<SingleRuneSet>; MAX_BTC_UTXOS],
rune_utxo: UtxoInfo<SingleRuneSet>,
btc_utxo_len: u8,
has_rune: u8,
_padding: [u8; 5],
}
impl ZeroCopy for MockShardZc {}
impl Sealed for MockShardZc {}
impl Pack for MockShardZc {
const LEN: usize = std::mem::size_of::<MockShardZc>();
fn pack_into_slice(&self, dst: &mut [u8]) {
dst.copy_from_slice(unsafe {
std::slice::from_raw_parts(self as *const MockShardZc as *const u8, Self::LEN)
});
}
fn unpack_from_slice(src: &[u8]) -> std::result::Result<Self, ProgramError> {
Ok(unsafe { *(src.as_ptr() as *const MockShardZc) })
}
}
impl Discriminator for MockShardZc {
const DISCRIMINATOR: &'static [u8] = b"mockshard_zc____";
}
impl Owner for MockShardZc {
fn owner() -> Pubkey {
Pubkey::default()
}
}
impl Default for MockShardZc {
fn default() -> Self {
Self::zeroed()
}
}
impl StateShard<UtxoInfo<SingleRuneSet>, SingleRuneSet> for MockShardZc {
fn btc_utxos(&self) -> &[UtxoInfo<SingleRuneSet>] {
let len = self.btc_utxo_len as usize;
&self.btc_utxos[..len]
}
fn btc_utxos_mut(&mut self) -> &mut [UtxoInfo<SingleRuneSet>] {
let len = self.btc_utxo_len as usize;
&mut self.btc_utxos[..len]
}
fn btc_utxos_retain(&mut self, f: &mut dyn FnMut(&UtxoInfo<SingleRuneSet>) -> bool) {
let len = self.btc_utxo_len as usize;
let mut write_idx = 0usize;
for read_idx in 0..len {
let keep = f(&self.btc_utxos[read_idx]);
if keep {
if write_idx != read_idx {
self.btc_utxos[write_idx] = self.btc_utxos[read_idx];
}
write_idx += 1;
}
}
self.btc_utxo_len = write_idx as u8;
}
fn add_btc_utxo(&mut self, utxo: UtxoInfo<SingleRuneSet>) -> Option<usize> {
let len = self.btc_utxo_len as usize;
if len >= MAX_BTC_UTXOS {
return None;
}
self.btc_utxos[len] = utxo;
self.btc_utxo_len += 1;
Some(len)
}
fn btc_utxos_len(&self) -> usize {
self.btc_utxo_len as usize
}
fn btc_utxos_max_len(&self) -> usize {
MAX_BTC_UTXOS
}
fn rune_utxo(&self) -> Option<&UtxoInfo<SingleRuneSet>> {
if self.has_rune == 1 {
Some(&self.rune_utxo)
} else {
None
}
}
fn rune_utxo_mut(&mut self) -> Option<&mut UtxoInfo<SingleRuneSet>> {
if self.has_rune == 1 {
Some(&mut self.rune_utxo)
} else {
None
}
}
fn clear_rune_utxo(&mut self) {
self.has_rune = 0;
}
fn set_rune_utxo(&mut self, utxo: UtxoInfo<SingleRuneSet>) {
self.rune_utxo = utxo;
self.has_rune = 1;
}
}
unsafe impl Pod for MockShardZc {}
pub fn create_loader() -> AccountLoader<'static, MockShardZc> {
let key = Box::leak(Box::new(Pubkey::default()));
let owner = Box::leak(Box::new(Pubkey::default()));
let utxo = Box::leak(Box::new(UtxoMeta::default()));
let lamports = Box::leak(Box::new(0u64));
let disc = MockShardZc::DISCRIMINATOR;
let struct_size = core::mem::size_of::<MockShardZc>();
let struct_align = core::mem::align_of::<MockShardZc>();
let padding = (struct_align - (disc.len() % struct_align)) % struct_align;
let offset = disc.len() + padding;
let total_len = offset + struct_size;
use std::alloc::{alloc_zeroed, Layout};
let data: &'static mut [u8] = unsafe {
let layout = Layout::from_size_align(total_len, struct_align).expect("layout");
let ptr = alloc_zeroed(layout);
if ptr.is_null() {
std::alloc::handle_alloc_error(layout);
}
std::ptr::copy_nonoverlapping(disc.as_ptr(), ptr, disc.len());
let slice_ptr = std::ptr::slice_from_raw_parts_mut(ptr, total_len);
let boxed_slice: Box<[u8]> = Box::from_raw(slice_ptr);
Box::leak(boxed_slice)
};
let account_info = AccountInfo::new(
key, lamports, data, owner, utxo, false,
true, false,
);
let account_ref: &'static AccountInfo<'static> = Box::leak(Box::new(account_info));
AccountLoader::try_from_unchecked(account_ref).expect("create loader")
}
pub fn random_utxo_meta(vout: u32) -> UtxoMeta {
UtxoMeta::from([vout as u8; 32], vout)
}
pub fn create_btc_utxo(value: u64, vout: u32) -> UtxoInfo<SingleRuneSet> {
let utxo_info = UtxoInfo::new(random_utxo_meta(vout), value);
utxo_info
}
pub fn create_shard(initial_btc: u64) -> MockShardZc {
let mut shard = MockShardZc::default();
if initial_btc > 0 {
shard.add_btc_utxo(create_btc_utxo(initial_btc, 0));
}
shard
}
pub fn create_loader_from(shard: &MockShardZc) -> AccountLoader<'static, MockShardZc> {
let loader = create_loader();
{
let mut mut_ref = loader.load_mut().expect("zero-copy borrow");
*mut_ref = *shard;
}
loader
}
pub fn leak_loaders_from_vec(
shards: Vec<MockShardZc>,
) -> &'static [AccountLoader<'static, MockShardZc>] {
let mut boxed_vec: Vec<AccountLoader<'static, MockShardZc>> = Vec::with_capacity(shards.len());
for shard in shards {
let loader = create_loader_from(&shard);
boxed_vec.push(loader);
}
Box::leak(boxed_vec.into_boxed_slice())
}
pub fn add_btc_utxos_bulk(shard: &mut MockShardZc, sats_values: &[u64]) {
let mut next_vout = shard.btc_utxos_len() as u32;
for &value in sats_values {
if shard
.add_btc_utxo(create_btc_utxo(value, next_vout))
.is_none()
{
break;
}
next_vout = next_vout.saturating_add(1);
}
}
#[cfg(feature = "runes")]
pub fn create_rune_utxo(amount: u128, vout: u32) -> UtxoInfo<SingleRuneSet> {
use arch_program::rune::{RuneAmount, RuneId};
use satellite_bitcoin::constants::DUST_LIMIT;
let mut runes = SingleRuneSet::default();
let _ = runes.insert(RuneAmount {
id: RuneId::new(1, 1),
amount,
});
UtxoInfo::<SingleRuneSet> {
meta: random_utxo_meta(vout),
value: DUST_LIMIT, runes,
#[cfg(feature = "utxo-consolidation")]
needs_consolidation: Default::default(),
}
}