From 0691582f6fb8d15a4ae54475f87b597a6cbcfb63 Mon Sep 17 00:00:00 2001 From: xeno097 Date: Wed, 22 Apr 2026 17:04:10 -0400 Subject: [PATCH 1/2] 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`. 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. --- .../hyperlane-sealevel-token/src/processor.rs | 175 +++++++++----- .../src/processor.rs | 214 ++---------------- 2 files changed, 139 insertions(+), 250 deletions(-) diff --git a/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs b/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs index b8c1ad989be..fea31f9a946 100644 --- a/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs +++ b/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs @@ -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; @@ -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, @@ -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. + 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: {}, router: {}", + xfer.destination_domain, + xfer.recipient, + router + ); + + 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, + 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, + ) -> ProgramResult { + // 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); @@ -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); @@ -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. @@ -412,8 +469,7 @@ where }; // The amount denominated in the local decimals. - let local_amount: u64 = xfer - .amount_or_id + let local_amount: u64 = amount_or_id .try_into() .map_err(|_| Error::IntegerOverflow)?; // Convert to the remote number of decimals, which is universally understood @@ -423,7 +479,7 @@ where // Transfer `local_amount` of tokens in... T::transfer_in( program_id, - &*token, + token, sender_wallet, accounts_iter, local_amount, @@ -433,6 +489,16 @@ where 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), @@ -452,42 +518,49 @@ 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(()) } diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-cross-collateral/src/processor.rs b/rust/sealevel/programs/hyperlane-sealevel-token-cross-collateral/src/processor.rs index aa01e28c6f0..551afe10f3c 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-cross-collateral/src/processor.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-cross-collateral/src/processor.rs @@ -1,24 +1,14 @@ //! Program processor. -use borsh::BorshDeserialize; - use access_control::AccessControl; use account_utils::{ create_pda_account, DiscriminatorDecode, DiscriminatorEncode, SizedData, SPL_NOOP_PROGRAM_ID, }; use hyperlane_core::{Decode, Encode, H256}; -use hyperlane_sealevel_connection_client::gas_router::HyperlaneGasRouter; -use hyperlane_sealevel_connection_client::{ - HyperlaneConnectionClient, HyperlaneConnectionClientRecipient, -}; -use hyperlane_sealevel_igp::{ - accounts::InterchainGasPaymasterType, - instruction::{Instruction as IgpInstruction, PayForGas as IgpPayForGas}, -}; +use hyperlane_sealevel_connection_client::HyperlaneConnectionClientRecipient; use hyperlane_sealevel_mailbox::{ - accounts::Outbox, - instruction::{Instruction as MailboxInstruction, OutboxDispatch as MailboxOutboxDispatch}, - mailbox_message_dispatch_authority_pda_seeds, mailbox_process_authority_pda_seeds, + accounts::Outbox, mailbox_message_dispatch_authority_pda_seeds, + mailbox_process_authority_pda_seeds, }; use hyperlane_sealevel_message_recipient_interface::{ HandleInstruction, MessageRecipientInstruction, @@ -35,7 +25,7 @@ use solana_program::{ entrypoint::ProgramResult, instruction::{AccountMeta, Instruction}, msg, - program::{get_return_data, invoke, invoke_signed, set_return_data}, + program::{invoke_signed, set_return_data}, program_error::ProgramError, pubkey::Pubkey, rent::Rent, @@ -519,16 +509,10 @@ fn transfer_remote_to( /// /// Accounts consumed from iterator: /// 3. `[executable]` SPL Noop -/// 4. `[executable]` mailbox program -/// 5. `[]` mailbox outbox -/// 6. `[]` dispatch authority PDA -/// 7. `[signer]` sender wallet / mailbox payer -/// 8. `[signer]` unique message account -/// 9. `[writable]` dispatched message PDA -/// 10..N IGP accounts (optional), then plugin transfer_in accounts. +/// 4..N Shared remote-dispatch accounts (mailbox, outbox, dispatch authority, sender, +/// unique message, dispatched message PDA, optional IGP, plugin transfer_in). /// SAFETY: This function must only be called from `transfer_remote_to`, which /// validates that the destination is an authorized remote router. Do not call directly. -#[allow(clippy::too_many_lines)] fn transfer_remote_to_remote<'account_info_slice, 'account_info>( program_id: &Pubkey, hyperlane_token: &HyperlaneToken, @@ -542,191 +526,23 @@ fn transfer_remote_to_remote<'account_info_slice, 'account_info>( return Err(ProgramError::InvalidArgument); } - // Account 4: Mailbox program - let mailbox_info = next_account_info(accounts_iter)?; - if mailbox_info.key != &hyperlane_token.mailbox { - return Err(ProgramError::IncorrectProgramId); - } - - // Account 5: Mailbox outbox (verified by mailbox) - let mailbox_outbox_account = next_account_info(accounts_iter)?; - - // Account 6: Dispatch authority PDA - let dispatch_authority_account = next_account_info(accounts_iter)?; - let dispatch_authority_seeds: &[&[u8]] = - mailbox_message_dispatch_authority_pda_seeds!(hyperlane_token.dispatch_authority_bump); - let dispatch_authority_key = - Pubkey::create_program_address(dispatch_authority_seeds, program_id)?; - if *dispatch_authority_account.key != dispatch_authority_key { - return Err(ProgramError::InvalidArgument); - } - - // Account 7: Sender wallet (signer + payer) - let sender_wallet = next_account_info(accounts_iter)?; - if !sender_wallet.is_signer { - return Err(ProgramError::MissingRequiredSignature); - } - - // Account 8: Unique message / gas payment account - let unique_message_account = next_account_info(accounts_iter)?; - - // Account 9: Dispatched message PDA - let dispatched_message_pda = next_account_info(accounts_iter)?; - - // Accounts 10..N: IGP accounts (optional) - let igp_payment_accounts = if let Some((igp_program_id, igp_account_type)) = - hyperlane_token.interchain_gas_paymaster() - { - let igp_program_account = next_account_info(accounts_iter)?; - if igp_program_account.key != igp_program_id { - return Err(ProgramError::InvalidArgument); - } - - let igp_program_data_account = next_account_info(accounts_iter)?; - let igp_payment_pda_account = next_account_info(accounts_iter)?; - - let configured_igp_account = next_account_info(accounts_iter)?; - if configured_igp_account.key != igp_account_type.key() { - return Err(ProgramError::InvalidArgument); - } - - let mut igp_payment_account_metas = vec![ - AccountMeta::new_readonly(system_program::ID, false), - AccountMeta::new(*sender_wallet.key, true), - AccountMeta::new(*igp_program_data_account.key, false), - AccountMeta::new_readonly(*unique_message_account.key, true), - AccountMeta::new(*igp_payment_pda_account.key, false), - ]; - let mut igp_payment_account_infos = vec![ - system_program_account.clone(), - sender_wallet.clone(), - igp_program_data_account.clone(), - unique_message_account.clone(), - igp_payment_pda_account.clone(), - ]; - - match igp_account_type { - InterchainGasPaymasterType::Igp(_) => { - igp_payment_account_metas - .push(AccountMeta::new(*configured_igp_account.key, false)); - igp_payment_account_infos.push(configured_igp_account.clone()); - } - InterchainGasPaymasterType::OverheadIgp(_) => { - let inner_igp_account = next_account_info(accounts_iter)?; - igp_payment_account_metas.extend([ - AccountMeta::new(*inner_igp_account.key, false), - AccountMeta::new_readonly(*configured_igp_account.key, false), - ]); - igp_payment_account_infos - .extend([inner_igp_account.clone(), configured_igp_account.clone()]); - } - }; - - Some((igp_payment_account_metas, igp_payment_account_infos)) - } else { - None - }; - - // Convert amount to local and remote decimals - let local_amount: u64 = xfer - .amount_or_id - .try_into() - .map_err(|_| ProgramError::InvalidArgument)?; - let remote_amount = hyperlane_token.local_amount_to_remote_amount(local_amount)?; - - // Transfer tokens into escrow via plugin - CollateralPlugin::transfer_in( + // Delegate to the shared remote-dispatch helper in token-lib. + HyperlaneSealevelToken::::transfer_remote_to( program_id, hyperlane_token, - sender_wallet, + system_program_account, + spl_noop, accounts_iter, - local_amount, - )?; - - // Extraneous account check - if accounts_iter.next().is_some() { - return Err(TokenError::ExtraneousAccount.into()); - } - - // Build token message body - let token_transfer_message = TokenMessage::new(xfer.recipient, remote_amount, vec![]).to_vec(); - - // Build mailbox dispatch CPI with target_router as recipient (not self.router(domain)) - let dispatch_instruction = MailboxInstruction::OutboxDispatch(MailboxOutboxDispatch { - sender: *program_id, - destination_domain: xfer.destination_domain, - recipient: xfer.target_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), - AccountMeta::new_readonly(system_program::ID, false), - AccountMeta::new_readonly(SPL_NOOP_PROGRAM_ID, false), - AccountMeta::new(*sender_wallet.key, true), - AccountMeta::new_readonly(*unique_message_account.key, true), - AccountMeta::new(*dispatched_message_pda.key, false), - ]; - let dispatch_account_infos = &[ - mailbox_outbox_account.clone(), - dispatch_authority_account.clone(), - system_program_account.clone(), - spl_noop.clone(), - sender_wallet.clone(), - unique_message_account.clone(), - dispatched_message_pda.clone(), - ]; - - let mailbox_ixn = Instruction { - program_id: hyperlane_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], + xfer.destination_domain, + xfer.recipient, + xfer.target_router, + xfer.amount_or_id, )?; - // Parse message ID from mailbox return data - let (returning_program_id, returned_data) = - get_return_data().ok_or(ProgramError::InvalidArgument)?; - if returning_program_id != hyperlane_token.mailbox { - return Err(ProgramError::InvalidArgument); - } - - // IGP payment if configured - if let Some((igp_payment_account_metas, igp_payment_account_infos)) = igp_payment_accounts { - let (igp_program_id, _) = hyperlane_token - .interchain_gas_paymaster() - .ok_or(ProgramError::InvalidArgument)?; - - let message_id = hyperlane_core::H256::try_from_slice(&returned_data) - .map_err(|_| ProgramError::InvalidArgument)?; - - let destination_gas = hyperlane_token - .destination_gas(xfer.destination_domain) - .ok_or(ProgramError::InvalidArgument)?; - - let igp_ixn = Instruction::new_with_borsh( - *igp_program_id, - &IgpInstruction::PayForGas(IgpPayForGas { - message_id, - destination_domain: xfer.destination_domain, - gas_amount: destination_gas, - }), - igp_payment_account_metas, - ); - invoke(&igp_ixn, &igp_payment_account_infos)?; - } - msg!( - "CC transfer_remote_to completed to destination: {}, target_router: {:?}, remote_amount: {}", + "CC transfer_remote_to completed to destination: {}, target_router: {:?}", xfer.destination_domain, xfer.target_router, - remote_amount ); Ok(()) From eda639b46e5fb696ed55d214ee2d7de3c8097807 Mon Sep 17 00:00:00 2001 From: xeno097 Date: Wed, 22 Apr 2026 17:39:48 -0400 Subject: [PATCH 2/2] refactor(svm): extract convert_and_transfer_in shared helper Extracted amount conversion + plugin transfer_in into `convert_and_transfer_in` on `HyperlaneSealevelToken`. Both the remote dispatch path and CC's same-chain local path now delegate to it. Also fixed transfer_remote_to to return remote_amount so callers can restore the original log messages that were lost in the prior commit. --- .../hyperlane-sealevel-token/src/processor.rs | 52 +++++++++++++------ .../src/processor.rs | 17 ++---- 2 files changed, 41 insertions(+), 28 deletions(-) diff --git a/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs b/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs index fea31f9a946..194c0e7cd45 100644 --- a/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs +++ b/rust/sealevel/libraries/hyperlane-sealevel-token/src/processor.rs @@ -315,7 +315,7 @@ where .ok_or(ProgramError::InvalidArgument)?; // Delegate to the shared remote-dispatch helper. - Self::transfer_remote_to( + let remote_amount = Self::transfer_remote_to( program_id, &token, system_program_account, @@ -328,10 +328,10 @@ where )?; msg!( - "Warp route transfer completed to destination: {}, recipient: {}, router: {}", + "Warp route transfer completed to destination: {}, recipient: {}, remote_amount: {}", xfer.destination_domain, xfer.recipient, - router + remote_amount ); Ok(()) @@ -362,7 +362,7 @@ where recipient: H256, router: H256, amount_or_id: U256, - ) -> ProgramResult { + ) -> Result { // Mailbox program let mailbox_info = next_account_info(accounts_iter)?; if mailbox_info.key != &token.mailbox { @@ -468,21 +468,12 @@ where None }; - // 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)?; - - // Transfer `local_amount` of tokens in... - T::transfer_in( + let remote_amount = Self::convert_and_transfer_in( program_id, token, sender_wallet, accounts_iter, - local_amount, + amount_or_id, )?; if accounts_iter.next().is_some() { @@ -561,7 +552,36 @@ where invoke(&igp_ixn, &igp_payment_account_infos)?; } - Ok(()) + Ok(remote_amount) + } + + /// 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, + sender_wallet: &'a AccountInfo<'b>, + accounts_iter: &mut std::slice::Iter<'a, AccountInfo<'b>>, + amount_or_id: U256, + ) -> Result { + // 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: diff --git a/rust/sealevel/programs/hyperlane-sealevel-token-cross-collateral/src/processor.rs b/rust/sealevel/programs/hyperlane-sealevel-token-cross-collateral/src/processor.rs index 551afe10f3c..bf571682f76 100644 --- a/rust/sealevel/programs/hyperlane-sealevel-token-cross-collateral/src/processor.rs +++ b/rust/sealevel/programs/hyperlane-sealevel-token-cross-collateral/src/processor.rs @@ -527,7 +527,7 @@ fn transfer_remote_to_remote<'account_info_slice, 'account_info>( } // Delegate to the shared remote-dispatch helper in token-lib. - HyperlaneSealevelToken::::transfer_remote_to( + let remote_amount = HyperlaneSealevelToken::::transfer_remote_to( program_id, hyperlane_token, system_program_account, @@ -540,9 +540,10 @@ fn transfer_remote_to_remote<'account_info_slice, 'account_info>( )?; msg!( - "CC transfer_remote_to completed to destination: {}, target_router: {:?}", + "CC transfer_remote_to completed to destination: {}, target_router: {:?}, remote_amount: {}", xfer.destination_domain, xfer.target_router, + remote_amount ); Ok(()) @@ -593,20 +594,12 @@ fn transfer_remote_to_local( return Err(ProgramError::InvalidAccountData); } - // Convert amount - let local_amount: u64 = xfer - .amount_or_id - .try_into() - .map_err(|_| ProgramError::InvalidArgument)?; - let remote_amount = hyperlane_token.local_amount_to_remote_amount(local_amount)?; - - // Transfer tokens into escrow via plugin - CollateralPlugin::transfer_in( + let remote_amount = HyperlaneSealevelToken::::convert_and_transfer_in( program_id, hyperlane_token, sender_wallet, accounts_iter, - local_amount, + xfer.amount_or_id, )?; // Build HandleLocal instruction data