uip_solana_sdk/
lib.rs

1#[cfg(feature = "anchor-lang")]
2use anchor_lang::{prelude::*, Bumps};
3use solana_program::{
4    account_info::AccountInfo, program_error::ProgramError, pubkey, pubkey::Pubkey,
5};
6
7pub use self::{cpi::*, deser::MessageDataRef};
8use self::{deser::DeserializeRef, utils::split_at_checked};
9
10pub mod chains;
11mod cpi;
12mod deser;
13#[cfg(feature = "extension")]
14pub mod extension;
15mod utils;
16
17#[deprecated(note = "Please use `UipEndpoint::id()` instead")]
18pub const UIP_PROGRAM_ID: Pubkey = pubkey!("uipby67GWuDDt1jZTWFdXNrsSu83kcxt9r5CLPTKGhX");
19
20/// Instruction discriminator that should be used when receiving cross-chain
21/// messages.
22pub const EXECUTE_DISCRIMINATOR: &[u8] = b"execute\0";
23const MESSAGE_DISCRIMINATOR: &[u8] = b"message\0";
24
25/// Solana UIP endpoint methods.
26pub struct UipEndpoint;
27
28impl UipEndpoint {
29    /// Program id of the Solana endpoint.
30    pub const fn id() -> Pubkey {
31        pubkey!("uipby67GWuDDt1jZTWFdXNrsSu83kcxt9r5CLPTKGhX")
32    }
33}
34
35#[cfg(feature = "anchor-lang")]
36impl Id for UipEndpoint {
37    fn id() -> Pubkey {
38        Self::id()
39    }
40}
41
42/// Parses the message account to retrieve [`MessageDataRef`]. It also
43/// performs the checks to verify the authenticity of the incoming message.
44/// It is still the protocol's responsibility to verify the sender.
45pub fn parse_uip_message<'a>(
46    message: &'a AccountInfo,
47    message_data: &'a [u8],
48    expected_dest_addr: &Pubkey,
49) -> core::result::Result<MessageDataRef<'a>, ProgramError> {
50    if message.owner != &UipEndpoint::id() {
51        return Err(ProgramError::InvalidAccountOwner);
52    }
53    if !message.is_signer {
54        return Err(ProgramError::MissingRequiredSignature);
55    }
56
57    let Some(rest) = message_data.strip_prefix(MESSAGE_DISCRIMINATOR) else {
58        return Err(ProgramError::InvalidAccountData);
59    };
60
61    deserialize_message(rest, expected_dest_addr)
62}
63
64fn deserialize_message<'a>(
65    data: &'a [u8],
66    expected_dest_addr: &Pubkey,
67) -> core::result::Result<MessageDataRef<'a>, ProgramError> {
68    let data = &mut &data[..];
69    let mut read_data = |count| -> core::result::Result<&[u8], ProgramError> {
70        let (left, right) =
71            split_at_checked(data, count).ok_or(ProgramError::InvalidAccountData)?;
72        *data = right;
73        Ok(left)
74    };
75
76    let _message_status = read_data(1)?;
77
78    let option_tag = read_data(1)?[0];
79    if option_tag == 0 {
80        return Err(ProgramError::InvalidAccountData);
81    }
82
83    let _payer = read_data(32)?;
84
85    let _loaded_at = read_data(8)?;
86
87    let msg_data = MessageDataRef::deserialize_ref(data).ok_or(ProgramError::InvalidAccountData)?;
88
89    if &msg_data.dest_addr != expected_dest_addr {
90        return Err(ProgramError::InvalidAccountData);
91    }
92
93    Ok(msg_data)
94}
95
96/// Generic function to handle account processing and instruction execution.
97#[cfg(feature = "anchor-lang")]
98pub fn route_instruction<'info, Handler, Accs, InstructionData, HandlerParams>(
99    program_id: &Pubkey,
100    handler: Handler,
101    accounts: &'info [AccountInfo<'info>],
102    instruction_data: InstructionData,
103    params: HandlerParams,
104) -> Result<()>
105where
106    Handler: FnOnce(Context<Accs>, HandlerParams) -> Result<()>,
107    Accs: Accounts<'info, Accs::Bumps> + Bumps + AccountsExit<'info>,
108    Accs::Bumps: Default,
109    InstructionData: AnchorSerialize,
110{
111    let mut data = Vec::new();
112    instruction_data.serialize(&mut data)?;
113
114    let remaining_accounts = &mut &accounts[..];
115    let mut bumps = Default::default();
116    let mut accounts = Accs::try_accounts(
117        program_id,
118        remaining_accounts,
119        &data,
120        &mut bumps,
121        &mut Default::default(),
122    )?;
123    handler(Context::new(program_id, &mut accounts, remaining_accounts, bumps), params)?;
124    accounts.exit(program_id)
125}
126
127#[cfg(test)]
128mod tests {
129    #[cfg(feature = "anchor-lang")]
130    use rand::{random, thread_rng, Rng};
131    #[cfg(feature = "anchor-lang")]
132    use uip_endpoint::state::{
133        Message, MessageData, MessageInfo, MessageStatus, Proposal, SrcChainData,
134    };
135
136    use super::*;
137
138    #[test]
139    fn test_program_id() {
140        assert_eq!(UipEndpoint::id(), uip_endpoint::ID_CONST);
141    }
142
143    #[test]
144    fn test_discriminators() {
145        assert_eq!(EXECUTE_DISCRIMINATOR, uip_endpoint::DESTINATION_EXECUTE_DISCRIMINATOR);
146    }
147
148    #[test]
149    #[cfg(feature = "anchor-lang")]
150    fn test_anchor_discriminators() {
151        assert_eq!(MESSAGE_DISCRIMINATOR, Message::DISCRIMINATOR);
152    }
153
154    #[test]
155    #[cfg(feature = "anchor-lang")]
156    fn test_deserialize_message() {
157        let mut rng = thread_rng();
158
159        for _ in 0..100 {
160            let expected_dest_addr = Pubkey::new_from_array(random());
161
162            let expected_total_fee = random();
163            let expected_selector = random();
164            let expected_payload: Vec<_> = (0..rng.gen_range(0..1024)).map(|_| random()).collect();
165            let expected_src_chain_id = random();
166            let expected_sender_addr: Vec<_> =
167                (0..rng.gen_range(0..32)).map(|_| random()).collect();
168            let expected_reserved: Vec<_> = (0..rng.gen_range(0..100)).map(|_| random()).collect();
169            let expected_transmitter_params: Vec<_> =
170                (0..rng.gen_range(0..100)).map(|_| random()).collect();
171            let expected_src_block_number = random();
172            let expected_src_op_tx_id = [random(); 2];
173
174            let expected_msg_hash = random();
175
176            let msg = Message {
177                status: MessageStatus::Signed,
178                info: Some(MessageInfo {
179                    payer: Pubkey::new_from_array(random()),
180                    hash: expected_msg_hash,
181                    loaded_at: random(),
182                    msg_data: MessageData {
183                        initial_proposal: Proposal {
184                            total_fee: expected_total_fee,
185                            selector: expected_selector,
186                            sender_addr: expected_sender_addr.clone(),
187                            dest_addr: expected_dest_addr,
188                            payload: expected_payload.clone(),
189                            reserved: expected_reserved.clone(),
190                            transmitter_params: expected_transmitter_params.clone(),
191                        },
192                        src_chain_data: SrcChainData {
193                            src_chain_id: expected_src_chain_id,
194                            src_block_number: expected_src_block_number,
195                            src_op_tx_id: expected_src_op_tx_id,
196                        },
197                    },
198                }),
199            };
200
201            let data = msg.try_to_vec().unwrap();
202            let MessageDataRef {
203                payload,
204                src_chain_id,
205                sender_addr,
206                msg_hash,
207                total_fee,
208                selector,
209                reserved,
210                dest_addr,
211                transmitter_params,
212                src_block_number,
213                src_op_tx_id,
214            } = deserialize_message(&data, &expected_dest_addr).unwrap();
215            assert_eq!(payload, expected_payload);
216            assert_eq!(src_chain_id, expected_src_chain_id);
217            assert_eq!(sender_addr, expected_sender_addr);
218            assert_eq!(msg_hash, &expected_msg_hash);
219            assert_eq!(total_fee, expected_total_fee);
220            assert_eq!(selector, &expected_selector);
221            assert_eq!(reserved, expected_reserved);
222            assert_eq!(dest_addr, expected_dest_addr);
223            assert_eq!(transmitter_params, expected_transmitter_params);
224            assert_eq!(src_block_number, expected_src_block_number);
225            assert_eq!(src_op_tx_id, &expected_src_op_tx_id);
226        }
227    }
228}