Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
215 changes: 154 additions & 61 deletions rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
use access_control::AccessControl;
use account_utils::{create_pda_account, SizedData};
use borsh::{BorshDeserialize, BorshSerialize};
use hyperlane_core::{Decode, Encode};
use hyperlane_core::{Decode, Encode, H256, U256};
use hyperlane_sealevel_connection_client::{
gas_router::{GasRouterConfig, HyperlaneGasRouterAccessControl, HyperlaneGasRouterDispatch},
gas_router::{GasRouterConfig, HyperlaneGasRouter, HyperlaneGasRouterAccessControl},
router::{
HyperlaneRouterAccessControl, HyperlaneRouterDispatch, HyperlaneRouterMessageRecipient,
HyperlaneRouter, HyperlaneRouterAccessControl, HyperlaneRouterMessageRecipient,
RemoteRouterConfig,
},
HyperlaneConnectionClient, HyperlaneConnectionClientSetterAccessControl,
};
use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType;
use hyperlane_sealevel_igp::{
accounts::InterchainGasPaymasterType,
instruction::{Instruction as IgpInstruction, PayForGas as IgpPayForGas},
};
use hyperlane_sealevel_mailbox::{
instruction::{Instruction as MailboxInstruction, OutboxDispatch as MailboxOutboxDispatch},
mailbox_message_dispatch_authority_pda_seeds, mailbox_process_authority_pda_seeds,
};
use hyperlane_sealevel_message_recipient_interface::HandleInstruction;
Expand All @@ -22,9 +26,9 @@ use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData};
use solana_program::{
account_info::{next_account_info, AccountInfo},
entrypoint::ProgramResult,
instruction::AccountMeta,
instruction::{AccountMeta, Instruction},
msg,
program::set_return_data,
program::{get_return_data, invoke, invoke_signed, set_return_data},
program_error::ProgramError,
pubkey::Pubkey,
rent::Rent,
Expand Down Expand Up @@ -305,17 +309,71 @@ where
return Err(ProgramError::IncorrectProgramId);
}

// Account 3: Mailbox program
// Resolve the enrolled router for the destination domain.
let router = *token
.router(xfer.destination_domain)
.ok_or(ProgramError::InvalidArgument)?;

// Delegate to the shared remote-dispatch helper.
let remote_amount = Self::transfer_remote_to(
program_id,
&token,
system_program_account,
spl_noop,
accounts_iter,
xfer.destination_domain,
xfer.recipient,
router,
xfer.amount_or_id,
)?;

msg!(
"Warp route transfer completed to destination: {}, recipient: {}, remote_amount: {}",
xfer.destination_domain,
xfer.recipient,
remote_amount
);

Ok(())
}

/// Shared remote-dispatch helper: validates mailbox accounts, converts
/// amounts, calls plugin `transfer_in`, dispatches via mailbox CPI, and
/// optionally pays IGP. The caller is responsible for validating
/// system_program, spl_noop, loading the token, and resolving the router.
///
/// Accounts consumed from the iterator (starting after spl_noop / token PDA):
/// - mailbox program
/// - mailbox outbox
/// - dispatch authority PDA
/// - sender wallet (signer)
/// - unique message account
/// - dispatched message PDA
/// - optional IGP accounts
/// - plugin transfer_in accounts
#[allow(clippy::too_many_arguments)]
pub fn transfer_remote_to<'a, 'b>(
program_id: &Pubkey,
token: &HyperlaneToken<T>,
system_program_account: &'a AccountInfo<'b>,
spl_noop: &'a AccountInfo<'b>,
accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>,
destination_domain: u32,
recipient: H256,
router: H256,
amount_or_id: U256,
) -> Result<U256, ProgramError> {
// Mailbox program
let mailbox_info = next_account_info(accounts_iter)?;
if mailbox_info.key != &token.mailbox {
return Err(ProgramError::IncorrectProgramId);
}

// Account 4: Mailbox Outbox data account.
// Mailbox Outbox data account.
// No verification is performed here, the Mailbox will do that.
let mailbox_outbox_account = next_account_info(accounts_iter)?;

// Account 5: Message dispatch authority
// Message dispatch authority
let dispatch_authority_account = next_account_info(accounts_iter)?;
let dispatch_authority_seeds: &[&[u8]] =
mailbox_message_dispatch_authority_pda_seeds!(token.dispatch_authority_bump);
Expand All @@ -325,37 +383,37 @@ where
return Err(ProgramError::InvalidArgument);
}

// Account 6: Sender account / mailbox payer
// Sender account / mailbox payer
let sender_wallet = next_account_info(accounts_iter)?;
if !sender_wallet.is_signer {
return Err(ProgramError::MissingRequiredSignature);
}

// Account 7: Unique message / gas payment account
// Unique message / gas payment account
// Defer to the checks in the Mailbox / IGP, no need to verify anything here.
let unique_message_account = next_account_info(accounts_iter)?;

// Account 8: Message storage PDA.
// Message storage PDA.
// Similarly defer to the checks in the Mailbox to ensure account validity.
let dispatched_message_pda = next_account_info(accounts_iter)?;

let igp_payment_accounts =
if let Some((igp_program_id, igp_account_type)) = token.interchain_gas_paymaster() {
// Account 9: The IGP program
// The IGP program
let igp_program_account = next_account_info(accounts_iter)?;
if igp_program_account.key != igp_program_id {
return Err(ProgramError::InvalidArgument);
}

// Account 10: The IGP program data.
// The IGP program data.
// No verification is performed here, the IGP will do that.
let igp_program_data_account = next_account_info(accounts_iter)?;

// Account 11: The gas payment PDA.
// The gas payment PDA.
// No verification is performed here, the IGP will do that.
let igp_payment_pda_account = next_account_info(accounts_iter)?;

// Account 12: The configured IGP account.
// The configured IGP account.
let configured_igp_account = next_account_info(accounts_iter)?;
if configured_igp_account.key != igp_account_type.key() {
return Err(ProgramError::InvalidArgument);
Expand Down Expand Up @@ -393,7 +451,6 @@ where
igp_payment_account_infos.push(configured_igp_account.clone());
}
InterchainGasPaymasterType::OverheadIgp(_) => {
// Account 13: The inner IGP account.
let inner_igp_account = next_account_info(accounts_iter)?;

// The inner IGP is expected first, then the overhead IGP.
Expand All @@ -411,28 +468,28 @@ where
None
};

// The amount denominated in the local decimals.
let local_amount: u64 = xfer
.amount_or_id
.try_into()
.map_err(|_| Error::IntegerOverflow)?;
// Convert to the remote number of decimals, which is universally understood
// by the remote routers as the number of decimals used by the message amount.
let remote_amount = token.local_amount_to_remote_amount(local_amount)?;

// Transfer `local_amount` of tokens in...
T::transfer_in(
let remote_amount = Self::convert_and_transfer_in(
program_id,
&*token,
token,
sender_wallet,
accounts_iter,
local_amount,
amount_or_id,
)?;

if accounts_iter.next().is_some() {
return Err(ProgramError::from(Error::ExtraneousAccount));
}

// The token message body, which specifies the remote_amount.
let token_transfer_message = TokenMessage::new(recipient, remote_amount, vec![]).to_vec();

// Build mailbox dispatch CPI with explicit router as recipient.
let dispatch_instruction = MailboxInstruction::OutboxDispatch(MailboxOutboxDispatch {
sender: *program_id,
destination_domain,
recipient: router,
message_body: token_transfer_message,
});
let dispatch_account_metas = vec![
AccountMeta::new(*mailbox_outbox_account.key, false),
AccountMeta::new_readonly(*dispatch_authority_account.key, true),
Expand All @@ -452,43 +509,79 @@ where
dispatched_message_pda.clone(),
];

// The token message body, which specifies the remote_amount.
let token_transfer_message =
TokenMessage::new(xfer.recipient, remote_amount, vec![]).to_vec();
let mailbox_ixn = Instruction {
program_id: token.mailbox,
data: dispatch_instruction
.into_instruction_data()
.map_err(|_| ProgramError::BorshIoError)?,
accounts: dispatch_account_metas,
};
invoke_signed(
&mailbox_ixn,
dispatch_account_infos,
&[dispatch_authority_seeds],
)?;

// Pay for gas if IGP is configured.
if let Some((igp_payment_account_metas, igp_payment_account_infos)) = igp_payment_accounts {
// Dispatch the message and pay for gas.
HyperlaneGasRouterDispatch::dispatch_with_gas(
&*token,
program_id,
dispatch_authority_seeds,
xfer.destination_domain,
token_transfer_message,
dispatch_account_metas,
dispatch_account_infos,
let (igp_program_id, _) = token
.interchain_gas_paymaster()
.ok_or(ProgramError::InvalidArgument)?;

let (returning_program_id, returned_data) =
get_return_data().ok_or(ProgramError::InvalidArgument)?;
if returning_program_id != token.mailbox {
return Err(ProgramError::InvalidArgument);
}
let message_id =
H256::try_from_slice(&returned_data).map_err(|_| ProgramError::InvalidArgument)?;

let destination_gas = token
.destination_gas(destination_domain)
.ok_or(ProgramError::InvalidArgument)?;

let igp_ixn = Instruction::new_with_borsh(
*igp_program_id,
&IgpInstruction::PayForGas(IgpPayForGas {
message_id,
destination_domain,
gas_amount: destination_gas,
}),
igp_payment_account_metas,
&igp_payment_account_infos,
)?;
} else {
// Dispatch the message.
token.dispatch(
program_id,
dispatch_authority_seeds,
xfer.destination_domain,
token_transfer_message,
dispatch_account_metas,
dispatch_account_infos,
)?;
);
invoke(&igp_ixn, &igp_payment_account_infos)?;
}

msg!(
"Warp route transfer completed to destination: {}, recipient: {}, remote_amount: {}",
xfer.destination_domain,
xfer.recipient,
remote_amount
);
Ok(remote_amount)
}

Ok(())
/// Converts amount from local to remote decimals and calls plugin
/// `transfer_in`. Returns `remote_amount` for the caller to build
/// `TokenMessage`.
pub fn convert_and_transfer_in<'a, 'b>(
program_id: &Pubkey,
token: &HyperlaneToken<T>,
sender_wallet: &'a AccountInfo<'b>,
accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>,
amount_or_id: U256,
) -> Result<U256, ProgramError> {
// The amount denominated in the local decimals.
let local_amount: u64 = amount_or_id
.try_into()
.map_err(|_| Error::IntegerOverflow)?;
// Convert to the remote number of decimals, which is universally understood
// by the remote routers as the number of decimals used by the message amount.
let remote_amount = token.local_amount_to_remote_amount(local_amount)?;

T::transfer_in(
program_id,
token,
sender_wallet,
accounts_iter,
local_amount,
)?;

Ok(remote_amount)
}

/// Accounts:
Expand Down
Loading