uip-solana-sdk 0.16.0

Universal Interoperability Protocol Solana SDK
Documentation
use bon::bon;
use solana_invoke::{invoke, invoke_signed};
use solana_program::{
    account_info::AccountInfo,
    entrypoint::ProgramResult,
    instruction::{AccountMeta, Instruction},
    pubkey::Pubkey,
};

use crate::UipEndpoint;

/// Commitment option, corresponds to EVM `blockFinalizationOption`.
#[repr(u8)]
pub enum Commitment {
    /// Confirmed commitment. Corresponds to EVM Fast finalization option.
    Confirmed,
    /// Finalized commitment. Corresponds to EVM Standard finalization option.
    Finalized,
}

const REGISTER_EXTENSION_DISCRIMINATOR: &[u8; 8] = b"reg_extn";
const PROPOSE_DISCRIMINATOR: &[u8; 8] = b"propose2";

#[bon]
impl UipEndpoint {
    /// Perform a CPI to register the protocol extension.
    #[builder]
    pub fn register_extension<'info>(
        extension: AccountInfo<'info>,
        program_signer: AccountInfo<'info>,
        payer: AccountInfo<'info>,
        system_program: AccountInfo<'info>,
        program_signer_bump: u8,
        program_id: &Pubkey,
        ipfs_cid: [u8; 36],
    ) -> ProgramResult {
        const DISC_LEN: usize = REGISTER_EXTENSION_DISCRIMINATOR.len();
        let mut data = [0; { DISC_LEN + 32 + 36 }];
        data[..DISC_LEN].copy_from_slice(REGISTER_EXTENSION_DISCRIMINATOR);
        data[DISC_LEN..DISC_LEN + 32].copy_from_slice(program_id.as_ref());
        data[DISC_LEN + 32..DISC_LEN + 32 + 36].copy_from_slice(&ipfs_cid);

        let accounts = vec![
            AccountMeta::new(*payer.key, true),
            AccountMeta::new(*extension.key, false),
            AccountMeta::new_readonly(*program_signer.key, true),
            AccountMeta::new_readonly(*system_program.key, false),
        ];
        let ix = Instruction::new_with_bytes(UipEndpoint::id(), &data, accounts);

        invoke_signed(
            &ix,
            &[extension, program_signer, payer, system_program],
            &[&[b"UIP_SIGNER", &[program_signer_bump]]],
        )
    }

    /// Perform a CPI to propose a cross-chain message.
    #[builder]
    pub fn propose<'info, 'a>(
        proposer: AccountInfo<'info>,
        endpoint_config: AccountInfo<'info>,
        uts_connector: AccountInfo<'info>,
        system_program: AccountInfo<'info>,
        total_fee: u64,
        dest_chain_id: u128,
        selector: Option<&'a [u8; 32]>,
        dest_addr: &'a [u8],
        payload: &'a [u8],
        proposal_commitment: Commitment,
        custom_gas_limit: u128,
    ) -> ProgramResult {
        const DISC_LEN: usize = PROPOSE_DISCRIMINATOR.len();
        let mut data = Vec::with_capacity(
            DISC_LEN + 8 + 16 + 1 + 16 + 32 + 4 + dest_addr.len() + 4 + payload.len(),
        );
        data.extend(PROPOSE_DISCRIMINATOR);
        data.extend(total_fee.to_le_bytes());
        data.extend(dest_chain_id.to_le_bytes());
        data.push(proposal_commitment as u8);
        data.extend(custom_gas_limit.to_le_bytes());
        data.extend(selector.unwrap_or(&Default::default()));
        data.extend((dest_addr.len() as u32).to_le_bytes());
        data.extend(dest_addr);
        data.extend((payload.len() as u32).to_le_bytes());
        data.extend(payload);

        let accounts = vec![
            AccountMeta::new(*proposer.key, true),
            AccountMeta::new_readonly(*endpoint_config.key, false),
            AccountMeta::new(*uts_connector.key, false),
            AccountMeta::new_readonly(*system_program.key, false),
        ];
        let ix = Instruction::new_with_bytes(UipEndpoint::id(), &data, accounts);

        invoke(&ix, &[proposer, endpoint_config, uts_connector, system_program])
    }
}

#[cfg(test)]
mod tests {
    use anchor_lang::prelude::*;
    use uip_endpoint::instruction::{Propose, RegisterExtension};

    use super::*;

    #[test]
    fn test_discriminators() {
        assert_eq!(REGISTER_EXTENSION_DISCRIMINATOR, RegisterExtension::DISCRIMINATOR);
        assert_eq!(PROPOSE_DISCRIMINATOR, Propose::DISCRIMINATOR);
    }
}