Skip to content

Commit 7d97862

Browse files
committed
refactor(svm): deduplicate transfer_remote dispatch logic via shared helper
Extracted the common remote-dispatch tail (mailbox CPI, IGP payment, amount conversion, plugin transfer_in) into a new `transfer_remote_to` method on `HyperlaneSealevelToken<T>`. The existing `transfer_remote` resolves the enrolled router then delegates, while the cross-collateral token passes its explicit `target_router` directly. ~180 lines removed from the CC processor with no account layout or ordering changes.
1 parent 886177c commit 7d97862

2 files changed

Lines changed: 139 additions & 250 deletions

File tree

  • rust/sealevel

rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs

Lines changed: 124 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,21 @@
33
use access_control::AccessControl;
44
use account_utils::{create_pda_account, SizedData};
55
use borsh::{BorshDeserialize, BorshSerialize};
6-
use hyperlane_core::{Decode, Encode};
6+
use hyperlane_core::{Decode, Encode, H256, U256};
77
use hyperlane_sealevel_connection_client::{
8-
gas_router::{GasRouterConfig, HyperlaneGasRouterAccessControl, HyperlaneGasRouterDispatch},
8+
gas_router::{GasRouterConfig, HyperlaneGasRouter, HyperlaneGasRouterAccessControl},
99
router::{
10-
HyperlaneRouterAccessControl, HyperlaneRouterDispatch, HyperlaneRouterMessageRecipient,
10+
HyperlaneRouter, HyperlaneRouterAccessControl, HyperlaneRouterMessageRecipient,
1111
RemoteRouterConfig,
1212
},
1313
HyperlaneConnectionClient, HyperlaneConnectionClientSetterAccessControl,
1414
};
15-
use hyperlane_sealevel_igp::accounts::InterchainGasPaymasterType;
15+
use hyperlane_sealevel_igp::{
16+
accounts::InterchainGasPaymasterType,
17+
instruction::{Instruction as IgpInstruction, PayForGas as IgpPayForGas},
18+
};
1619
use hyperlane_sealevel_mailbox::{
20+
instruction::{Instruction as MailboxInstruction, OutboxDispatch as MailboxOutboxDispatch},
1721
mailbox_message_dispatch_authority_pda_seeds, mailbox_process_authority_pda_seeds,
1822
};
1923
use hyperlane_sealevel_message_recipient_interface::HandleInstruction;
@@ -22,9 +26,9 @@ use serializable_account_meta::{SerializableAccountMeta, SimulationReturnData};
2226
use solana_program::{
2327
account_info::{next_account_info, AccountInfo},
2428
entrypoint::ProgramResult,
25-
instruction::AccountMeta,
29+
instruction::{AccountMeta, Instruction},
2630
msg,
27-
program::set_return_data,
31+
program::{get_return_data, invoke, invoke_signed, set_return_data},
2832
program_error::ProgramError,
2933
pubkey::Pubkey,
3034
rent::Rent,
@@ -305,17 +309,71 @@ where
305309
return Err(ProgramError::IncorrectProgramId);
306310
}
307311

308-
// Account 3: Mailbox program
312+
// Resolve the enrolled router for the destination domain.
313+
let router = *token
314+
.router(xfer.destination_domain)
315+
.ok_or(ProgramError::InvalidArgument)?;
316+
317+
// Delegate to the shared remote-dispatch helper.
318+
Self::transfer_remote_to(
319+
program_id,
320+
&token,
321+
system_program_account,
322+
spl_noop,
323+
accounts_iter,
324+
xfer.destination_domain,
325+
xfer.recipient,
326+
router,
327+
xfer.amount_or_id,
328+
)?;
329+
330+
msg!(
331+
"Warp route transfer completed to destination: {}, recipient: {}, router: {}",
332+
xfer.destination_domain,
333+
xfer.recipient,
334+
router
335+
);
336+
337+
Ok(())
338+
}
339+
340+
/// Shared remote-dispatch helper: validates mailbox accounts, converts
341+
/// amounts, calls plugin `transfer_in`, dispatches via mailbox CPI, and
342+
/// optionally pays IGP. The caller is responsible for validating
343+
/// system_program, spl_noop, loading the token, and resolving the router.
344+
///
345+
/// Accounts consumed from the iterator (starting after spl_noop / token PDA):
346+
/// - mailbox program
347+
/// - mailbox outbox
348+
/// - dispatch authority PDA
349+
/// - sender wallet (signer)
350+
/// - unique message account
351+
/// - dispatched message PDA
352+
/// - optional IGP accounts
353+
/// - plugin transfer_in accounts
354+
#[allow(clippy::too_many_arguments)]
355+
pub fn transfer_remote_to<'a, 'b>(
356+
program_id: &Pubkey,
357+
token: &HyperlaneToken<T>,
358+
system_program_account: &'a AccountInfo<'b>,
359+
spl_noop: &'a AccountInfo<'b>,
360+
accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>,
361+
destination_domain: u32,
362+
recipient: H256,
363+
router: H256,
364+
amount_or_id: U256,
365+
) -> ProgramResult {
366+
// Mailbox program
309367
let mailbox_info = next_account_info(accounts_iter)?;
310368
if mailbox_info.key != &token.mailbox {
311369
return Err(ProgramError::IncorrectProgramId);
312370
}
313371

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

318-
// Account 5: Message dispatch authority
376+
// Message dispatch authority
319377
let dispatch_authority_account = next_account_info(accounts_iter)?;
320378
let dispatch_authority_seeds: &[&[u8]] =
321379
mailbox_message_dispatch_authority_pda_seeds!(token.dispatch_authority_bump);
@@ -325,37 +383,37 @@ where
325383
return Err(ProgramError::InvalidArgument);
326384
}
327385

328-
// Account 6: Sender account / mailbox payer
386+
// Sender account / mailbox payer
329387
let sender_wallet = next_account_info(accounts_iter)?;
330388
if !sender_wallet.is_signer {
331389
return Err(ProgramError::MissingRequiredSignature);
332390
}
333391

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

338-
// Account 8: Message storage PDA.
396+
// Message storage PDA.
339397
// Similarly defer to the checks in the Mailbox to ensure account validity.
340398
let dispatched_message_pda = next_account_info(accounts_iter)?;
341399

342400
let igp_payment_accounts =
343401
if let Some((igp_program_id, igp_account_type)) = token.interchain_gas_paymaster() {
344-
// Account 9: The IGP program
402+
// The IGP program
345403
let igp_program_account = next_account_info(accounts_iter)?;
346404
if igp_program_account.key != igp_program_id {
347405
return Err(ProgramError::InvalidArgument);
348406
}
349407

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

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

358-
// Account 12: The configured IGP account.
416+
// The configured IGP account.
359417
let configured_igp_account = next_account_info(accounts_iter)?;
360418
if configured_igp_account.key != igp_account_type.key() {
361419
return Err(ProgramError::InvalidArgument);
@@ -393,7 +451,6 @@ where
393451
igp_payment_account_infos.push(configured_igp_account.clone());
394452
}
395453
InterchainGasPaymasterType::OverheadIgp(_) => {
396-
// Account 13: The inner IGP account.
397454
let inner_igp_account = next_account_info(accounts_iter)?;
398455

399456
// The inner IGP is expected first, then the overhead IGP.
@@ -412,8 +469,7 @@ where
412469
};
413470

414471
// The amount denominated in the local decimals.
415-
let local_amount: u64 = xfer
416-
.amount_or_id
472+
let local_amount: u64 = amount_or_id
417473
.try_into()
418474
.map_err(|_| Error::IntegerOverflow)?;
419475
// Convert to the remote number of decimals, which is universally understood
@@ -423,7 +479,7 @@ where
423479
// Transfer `local_amount` of tokens in...
424480
T::transfer_in(
425481
program_id,
426-
&*token,
482+
token,
427483
sender_wallet,
428484
accounts_iter,
429485
local_amount,
@@ -433,6 +489,16 @@ where
433489
return Err(ProgramError::from(Error::ExtraneousAccount));
434490
}
435491

492+
// The token message body, which specifies the remote_amount.
493+
let token_transfer_message = TokenMessage::new(recipient, remote_amount, vec![]).to_vec();
494+
495+
// Build mailbox dispatch CPI with explicit router as recipient.
496+
let dispatch_instruction = MailboxInstruction::OutboxDispatch(MailboxOutboxDispatch {
497+
sender: *program_id,
498+
destination_domain,
499+
recipient: router,
500+
message_body: token_transfer_message,
501+
});
436502
let dispatch_account_metas = vec![
437503
AccountMeta::new(*mailbox_outbox_account.key, false),
438504
AccountMeta::new_readonly(*dispatch_authority_account.key, true),
@@ -452,42 +518,49 @@ where
452518
dispatched_message_pda.clone(),
453519
];
454520

455-
// The token message body, which specifies the remote_amount.
456-
let token_transfer_message =
457-
TokenMessage::new(xfer.recipient, remote_amount, vec![]).to_vec();
521+
let mailbox_ixn = Instruction {
522+
program_id: token.mailbox,
523+
data: dispatch_instruction
524+
.into_instruction_data()
525+
.map_err(|_| ProgramError::BorshIoError)?,
526+
accounts: dispatch_account_metas,
527+
};
528+
invoke_signed(
529+
&mailbox_ixn,
530+
dispatch_account_infos,
531+
&[dispatch_authority_seeds],
532+
)?;
458533

534+
// Pay for gas if IGP is configured.
459535
if let Some((igp_payment_account_metas, igp_payment_account_infos)) = igp_payment_accounts {
460-
// Dispatch the message and pay for gas.
461-
HyperlaneGasRouterDispatch::dispatch_with_gas(
462-
&*token,
463-
program_id,
464-
dispatch_authority_seeds,
465-
xfer.destination_domain,
466-
token_transfer_message,
467-
dispatch_account_metas,
468-
dispatch_account_infos,
536+
let (igp_program_id, _) = token
537+
.interchain_gas_paymaster()
538+
.ok_or(ProgramError::InvalidArgument)?;
539+
540+
let (returning_program_id, returned_data) =
541+
get_return_data().ok_or(ProgramError::InvalidArgument)?;
542+
if returning_program_id != token.mailbox {
543+
return Err(ProgramError::InvalidArgument);
544+
}
545+
let message_id =
546+
H256::try_from_slice(&returned_data).map_err(|_| ProgramError::InvalidArgument)?;
547+
548+
let destination_gas = token
549+
.destination_gas(destination_domain)
550+
.ok_or(ProgramError::InvalidArgument)?;
551+
552+
let igp_ixn = Instruction::new_with_borsh(
553+
*igp_program_id,
554+
&IgpInstruction::PayForGas(IgpPayForGas {
555+
message_id,
556+
destination_domain,
557+
gas_amount: destination_gas,
558+
}),
469559
igp_payment_account_metas,
470-
&igp_payment_account_infos,
471-
)?;
472-
} else {
473-
// Dispatch the message.
474-
token.dispatch(
475-
program_id,
476-
dispatch_authority_seeds,
477-
xfer.destination_domain,
478-
token_transfer_message,
479-
dispatch_account_metas,
480-
dispatch_account_infos,
481-
)?;
560+
);
561+
invoke(&igp_ixn, &igp_payment_account_infos)?;
482562
}
483563

484-
msg!(
485-
"Warp route transfer completed to destination: {}, recipient: {}, remote_amount: {}",
486-
xfer.destination_domain,
487-
xfer.recipient,
488-
remote_amount
489-
);
490-
491564
Ok(())
492565
}
493566

0 commit comments

Comments
 (0)