extern crate std;
use crate::context::Context;
use crate::crypto;
use crate::instruction::InstructionAccount;
use crate::lazy::LazyContext;
use crate::token::{invoke_token_signed, Trailing, MAX_TOKEN_MULTISIG_SIGNERS};
use crate::{AccountView, Address, ProgramError};
use hopper_native::{
AccountView as NativeAccountView, Address as NativeAddress, RuntimeAccount, NOT_BORROWED,
};
use std::vec::Vec;
const OWNER: [u8; 32] = [0xA1; 32];
fn account(key: u8, is_signer: bool, is_writable: bool) -> (Vec<u64>, AccountView<'static>) {
let mut backing = std::vec![0u64; (RuntimeAccount::SIZE + 16).div_ceil(8)];
let raw = backing.as_mut_ptr() as *mut RuntimeAccount;
unsafe {
raw.write(RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: u8::from(is_signer),
is_writable: u8::from(is_writable),
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array([key; 32]),
owner: NativeAddress::new_from_array(OWNER),
lamports: 1,
data_len: 16,
});
}
let backend = unsafe { NativeAccountView::new_unchecked(raw) };
(backing, AccountView::from_backend(backend))
}
#[test]
fn an_address_is_the_same_bytes_on_both_sides_of_the_boundary() {
let ours = Address::new([0x5C; 32]);
let native: &NativeAddress = ours.as_upstream();
assert_eq!(native.as_array(), &[0x5C; 32]);
assert!(core::ptr::eq(
native as *const NativeAddress as *const u8,
&ours as *const Address as *const u8
));
assert_eq!(
core::mem::size_of::<NativeAddress>(),
core::mem::size_of::<Address>()
);
assert_eq!(core::mem::align_of::<NativeAddress>(), 1);
assert_eq!(core::mem::align_of::<Address>(), 1);
let back = Address::from_upstream(native);
assert_eq!(back, &ours);
let same: &Address = ours.as_upstream();
assert!(core::ptr::eq(same, &ours));
}
#[test]
fn the_owner_reference_reads_the_header_in_place() {
let (_backing, view) = account(3, false, true);
let owner = unsafe { crate::native_boundary::account_owner(view.as_backend()) };
assert_eq!(owner, &Address::new(OWNER));
assert_eq!(
crate::native_boundary::read_owner(view.as_backend()),
*owner
);
assert_eq!(
crate::native_boundary::account_address(view.as_backend()),
&Address::new([3; 32])
);
}
#[test]
fn instruction_data_is_read_by_value_at_any_offset() {
let pid = Address::new([9; 32]);
let data: [u8; 13] = [0xFF, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
let ctx = Context::new(&pid, &[], &data);
assert_eq!(
ctx.read_data::<u64>(1).unwrap(),
u64::from_le_bytes([1, 2, 3, 4, 5, 6, 7, 8])
);
assert_eq!(
ctx.read_data::<u32>(9).unwrap(),
u32::from_le_bytes([9, 10, 11, 12])
);
assert_eq!(ctx.read_data::<u8>(12).unwrap(), 12);
assert_eq!(ctx.read_data::<[u8; 13]>(0).unwrap(), data);
assert_eq!(
ctx.read_data::<u32>(10),
Err(ProgramError::InvalidInstructionData)
);
assert_eq!(
ctx.read_data::<u8>(13),
Err(ProgramError::InvalidInstructionData)
);
assert_eq!(
ctx.read_data::<u64>(usize::MAX),
Err(ProgramError::ArithmeticOverflow)
);
let empty = Context::new(&pid, &[], &[]);
assert_eq!(
empty.read_data::<u8>(0),
Err(ProgramError::InvalidInstructionData)
);
}
fn frame(owners: &[[u8; 32]]) -> Vec<u64> {
let mut bytes: Vec<u8> = Vec::new();
bytes.extend_from_slice(&(owners.len() as u64).to_le_bytes());
for (i, owner) in owners.iter().enumerate() {
let header = RuntimeAccount {
borrow_state: NOT_BORROWED,
is_signer: 0,
is_writable: 1,
executable: 0,
resize_delta: 0,
address: NativeAddress::new_from_array([i as u8 + 1; 32]),
owner: NativeAddress::new_from_array(*owner),
lamports: 5,
data_len: 0,
};
bytes.extend_from_slice(unsafe {
core::slice::from_raw_parts(
&header as *const RuntimeAccount as *const u8,
core::mem::size_of::<RuntimeAccount>(),
)
});
bytes.extend_from_slice(&std::vec![0u8; 10 * 1024]);
bytes.extend_from_slice(&u64::MAX.to_le_bytes());
}
bytes.extend_from_slice(&0u64.to_le_bytes());
bytes.extend_from_slice(&[42; 32]);
let mut words = std::vec![0u64; bytes.len().div_ceil(8)];
unsafe {
core::ptr::copy_nonoverlapping(bytes.as_ptr(), words.as_mut_ptr() as *mut u8, bytes.len());
}
words
}
#[test]
fn the_lazy_parser_checks_the_owner_it_was_given() {
let mut input = frame(&[OWNER, [9; 32], OWNER]);
let mut native = unsafe { hopper_native::lazy::lazy_deserialize(input.as_mut_ptr().cast()) };
let mut ctx = LazyContext::from_native(&mut native);
let program = Address::new(OWNER);
let first = ctx.next_owned_by(&program).unwrap();
assert_eq!(first.address(), &Address::new([1; 32]));
assert!(ctx.next_owned_by(&program).is_err());
}
fn refs<'a>(views: &'a [AccountView<'static>]) -> Vec<&'a AccountView<'static>> {
views.iter().collect()
}
#[test]
fn a_token_cpi_lists_fixed_accounts_first_and_trailing_runs_in_order() {
let (_a, source) = account(1, false, true);
let (_b, authority) = account(2, false, false);
let mut keep = Vec::new();
let mut signers = Vec::new();
for key in 10..13 {
let (backing, view) = account(key, true, false);
keep.push(backing);
signers.push(view);
}
let program = Address::new([6; 32]);
let signer_refs = refs(&signers);
let fixed = [
InstructionAccount::writable(source.address()),
InstructionAccount::readonly(authority.address()),
];
let sent = invoke_token_signed(
&program,
&[3],
fixed,
[&source, &authority],
&[Trailing::signers(&signer_refs)],
&[],
);
assert_ne!(sent, Err(ProgramError::InvalidAccountData));
assert_ne!(sent, Err(ProgramError::MissingRequiredSignature));
assert_ne!(sent, Err(ProgramError::Immutable));
let crossed = invoke_token_signed(
&program,
&[3],
fixed,
[&authority, &source],
&[Trailing::signers(&signer_refs)],
&[],
);
assert_eq!(crossed, Err(ProgramError::InvalidAccountData));
let (_c, absent) = account(13, false, false);
let with_absent = [&signers[0], &absent, &signers[2]];
let unsigned = invoke_token_signed(
&program,
&[3],
fixed,
[&source, &authority],
&[Trailing::signers(&with_absent)],
&[],
);
assert_eq!(unsigned, Err(ProgramError::MissingRequiredSignature));
let read_only = [
InstructionAccount::writable(authority.address()),
InstructionAccount::readonly(source.address()),
];
let immutable = invoke_token_signed(&program, &[3], read_only, [&authority, &source], &[], &[]);
assert_eq!(immutable, Err(ProgramError::Immutable));
}
#[test]
fn a_token_cpi_refuses_more_accounts_than_its_buffers_hold() {
let (_a, source) = account(1, false, true);
let mut keep = Vec::new();
let mut many = Vec::new();
for key in 0..crate::cpi::MAX_STATIC_CPI_ACCOUNTS {
let (backing, view) = account(100 + key as u8, true, false);
keep.push(backing);
many.push(view);
}
let program = Address::new([6; 32]);
let fixed = [InstructionAccount::writable(source.address())];
let many_refs = refs(&many);
let twelve = &many_refs[..MAX_TOKEN_MULTISIG_SIGNERS + 1];
assert_eq!(
invoke_token_signed(
&program,
&[3],
fixed,
[&source],
&[Trailing::signers(twelve)],
&[],
),
Err(ProgramError::InvalidArgument)
);
let run = Trailing {
views: &many_refs,
writable: false,
signer: false,
};
assert_eq!(
invoke_token_signed(&program, &[3], fixed, [&source], &[run], &[]),
Err(ProgramError::InvalidArgument)
);
let full = Trailing {
views: &many_refs[..crate::cpi::MAX_STATIC_CPI_ACCOUNTS - 1],
writable: false,
signer: false,
};
assert_ne!(
invoke_token_signed(&program, &[3], fixed, [&source], &[full], &[]),
Err(ProgramError::InvalidArgument)
);
}
#[test]
fn the_edwards_check_runs_on_the_host() {
let mut base = [0x66u8; 32];
base[0] = 0x58;
let mut identity = [0u8; 32];
identity[0] = 1;
assert_eq!(crypto::curve25519_edwards_validate_point(&base), Ok(true));
assert_eq!(
crypto::curve25519_edwards_validate_point(&identity),
Ok(true)
);
let mut off = [0u8; 32];
off[0] = 2;
assert_eq!(crypto::curve25519_edwards_validate_point(&off), Ok(false));
let mut bytes = [0u8; 32];
let mut on = 0;
for round in 0u32..512 {
for (i, byte) in bytes.iter_mut().enumerate() {
*byte = (round.wrapping_mul(2_654_435_761).rotate_left(i as u32) >> 3) as u8 ^ i as u8;
}
let expected = solana_pubkey::Pubkey::new_from_array(bytes).is_on_curve();
assert_eq!(
crypto::curve_validate_point(crypto::CURVE25519_EDWARDS, &bytes),
Ok(expected),
"{bytes:?}"
);
on += expected as u32;
}
assert!(on > 100 && on < 400, "{on} of 512 on the curve");
assert_eq!(
crypto::curve_validate_point(crypto::CURVE25519_RISTRETTO, &base),
Ok(false)
);
}
#[test]
fn syscall_only_operations_fail_on_the_host_instead_of_inventing_a_result() {
assert_eq!(
crypto::secp256k1_recover(&[1; 32], 0, &[2; 64]),
Err(ProgramError::InvalidArgument)
);
assert!(crypto::recover_ethereum_address(&[1; 32], 0, &[2; 64]).is_err());
#[cfg(feature = "crypto-curve")]
{
let point = [0x58; 32];
assert!(crypto::curve_group_add(crypto::CURVE25519_EDWARDS, &point, &point).is_err());
assert!(crypto::curve_group_sub(crypto::CURVE25519_EDWARDS, &point, &point).is_err());
assert!(crypto::curve_group_mul(crypto::CURVE25519_EDWARDS, &[1; 32], &point).is_err());
}
#[cfg(feature = "crypto-bn254")]
{
assert!(crypto::alt_bn128_g1_compress_be(&[0; 64]).is_err());
}
}
#[test]
fn the_sibling_instruction_syscall_reports_failure_on_the_host() {
let mut meta = [0u8; 16];
let mut program_id = [0u8; 32];
let status = unsafe {
crate::syscalls::sol_get_processed_sibling_instruction(
0,
meta.as_mut_ptr(),
program_id.as_mut_ptr(),
core::ptr::null_mut(),
core::ptr::null_mut(),
)
};
assert_ne!(status, 0, "no transaction, so no sibling instruction");
assert_eq!(meta, [0; 16]);
assert_eq!(program_id, [0; 32]);
}