#[cfg(feature = "anchor-lang")]
use anchor_lang::{prelude::*, Bumps};
use solana_program::{
account_info::AccountInfo, program_error::ProgramError, pubkey, pubkey::Pubkey,
};
pub use self::{cpi::*, deser::MessageDataRef};
use self::{deser::DeserializeRef, utils::split_at_checked};
pub mod chains;
mod cpi;
mod deser;
#[cfg(feature = "extension")]
pub mod extension;
mod utils;
#[deprecated(note = "Please use `UipEndpoint::id()` instead")]
pub const UIP_PROGRAM_ID: Pubkey = pubkey!("uipby67GWuDDt1jZTWFdXNrsSu83kcxt9r5CLPTKGhX");
pub const EXECUTE_DISCRIMINATOR: &[u8] = b"execute\0";
const MESSAGE_DISCRIMINATOR: &[u8] = b"message\0";
pub struct UipEndpoint;
impl UipEndpoint {
pub const fn id() -> Pubkey {
pubkey!("uipby67GWuDDt1jZTWFdXNrsSu83kcxt9r5CLPTKGhX")
}
}
#[cfg(feature = "anchor-lang")]
impl Id for UipEndpoint {
fn id() -> Pubkey {
Self::id()
}
}
pub fn parse_uip_message<'a>(
message: &'a AccountInfo,
message_data: &'a [u8],
expected_dest_addr: &Pubkey,
) -> core::result::Result<MessageDataRef<'a>, ProgramError> {
if message.owner != &UipEndpoint::id() {
return Err(ProgramError::InvalidAccountOwner);
}
if !message.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}
let Some(rest) = message_data.strip_prefix(MESSAGE_DISCRIMINATOR) else {
return Err(ProgramError::InvalidAccountData);
};
deserialize_message(rest, expected_dest_addr)
}
fn deserialize_message<'a>(
data: &'a [u8],
expected_dest_addr: &Pubkey,
) -> core::result::Result<MessageDataRef<'a>, ProgramError> {
let data = &mut &data[..];
let mut read_data = |count| -> core::result::Result<&[u8], ProgramError> {
let (left, right) =
split_at_checked(data, count).ok_or(ProgramError::InvalidAccountData)?;
*data = right;
Ok(left)
};
let _message_status = read_data(1)?;
let option_tag = read_data(1)?[0];
if option_tag == 0 {
return Err(ProgramError::InvalidAccountData);
}
let _payer = read_data(32)?;
let _loaded_at = read_data(8)?;
let msg_data = MessageDataRef::deserialize_ref(data).ok_or(ProgramError::InvalidAccountData)?;
if &msg_data.dest_addr != expected_dest_addr {
return Err(ProgramError::InvalidAccountData);
}
Ok(msg_data)
}
#[cfg(feature = "anchor-lang")]
pub fn route_instruction<'info, Handler, Accs, InstructionData, HandlerParams>(
program_id: &Pubkey,
handler: Handler,
accounts: &'info [AccountInfo<'info>],
instruction_data: InstructionData,
params: HandlerParams,
) -> Result<()>
where
Handler: FnOnce(Context<Accs>, HandlerParams) -> Result<()>,
Accs: Accounts<'info, Accs::Bumps> + Bumps + AccountsExit<'info>,
Accs::Bumps: Default,
InstructionData: AnchorSerialize,
{
let mut data = Vec::new();
instruction_data.serialize(&mut data)?;
let remaining_accounts = &mut &accounts[..];
let mut bumps = Default::default();
let mut accounts = Accs::try_accounts(
program_id,
remaining_accounts,
&data,
&mut bumps,
&mut Default::default(),
)?;
handler(Context::new(program_id, &mut accounts, remaining_accounts, bumps), params)?;
accounts.exit(program_id)
}
#[cfg(test)]
mod tests {
#[cfg(feature = "anchor-lang")]
use rand::{random, thread_rng, Rng};
#[cfg(feature = "anchor-lang")]
use uip_endpoint::state::{
Message, MessageData, MessageInfo, MessageStatus, Proposal, SrcChainData,
};
use super::*;
#[test]
fn test_program_id() {
assert_eq!(UipEndpoint::id(), uip_endpoint::ID_CONST);
}
#[test]
fn test_discriminators() {
assert_eq!(EXECUTE_DISCRIMINATOR, uip_endpoint::DESTINATION_EXECUTE_DISCRIMINATOR);
}
#[test]
#[cfg(feature = "anchor-lang")]
fn test_anchor_discriminators() {
assert_eq!(MESSAGE_DISCRIMINATOR, Message::DISCRIMINATOR);
}
#[test]
#[cfg(feature = "anchor-lang")]
fn test_deserialize_message() {
let mut rng = thread_rng();
for _ in 0..100 {
let expected_dest_addr = Pubkey::new_from_array(random());
let expected_total_fee = random();
let expected_selector = random();
let expected_payload: Vec<_> = (0..rng.gen_range(0..1024)).map(|_| random()).collect();
let expected_src_chain_id = random();
let expected_sender_addr: Vec<_> =
(0..rng.gen_range(0..32)).map(|_| random()).collect();
let expected_reserved: Vec<_> = (0..rng.gen_range(0..100)).map(|_| random()).collect();
let expected_transmitter_params: Vec<_> =
(0..rng.gen_range(0..100)).map(|_| random()).collect();
let expected_src_block_number = random();
let expected_src_op_tx_id = [random(); 2];
let expected_msg_hash = random();
let msg = Message {
status: MessageStatus::Signed,
info: Some(MessageInfo {
payer: Pubkey::new_from_array(random()),
hash: expected_msg_hash,
loaded_at: random(),
msg_data: MessageData {
initial_proposal: Proposal {
total_fee: expected_total_fee,
selector: expected_selector,
sender_addr: expected_sender_addr.clone(),
dest_addr: expected_dest_addr,
payload: expected_payload.clone(),
reserved: expected_reserved.clone(),
transmitter_params: expected_transmitter_params.clone(),
},
src_chain_data: SrcChainData {
src_chain_id: expected_src_chain_id,
src_block_number: expected_src_block_number,
src_op_tx_id: expected_src_op_tx_id,
},
},
}),
};
let data = msg.try_to_vec().unwrap();
let MessageDataRef {
payload,
src_chain_id,
sender_addr,
msg_hash,
total_fee,
selector,
reserved,
dest_addr,
transmitter_params,
src_block_number,
src_op_tx_id,
} = deserialize_message(&data, &expected_dest_addr).unwrap();
assert_eq!(payload, expected_payload);
assert_eq!(src_chain_id, expected_src_chain_id);
assert_eq!(sender_addr, expected_sender_addr);
assert_eq!(msg_hash, &expected_msg_hash);
assert_eq!(total_fee, expected_total_fee);
assert_eq!(selector, &expected_selector);
assert_eq!(reserved, expected_reserved);
assert_eq!(dest_addr, expected_dest_addr);
assert_eq!(transmitter_params, expected_transmitter_params);
assert_eq!(src_block_number, expected_src_block_number);
assert_eq!(src_op_tx_id, &expected_src_op_tx_id);
}
}
}