@@ -328,40 +328,58 @@ impl<T: Config<I>, I: 'static> SendXcm for Pallet<T, I> {
328328 xcm : & mut Option < Xcm < ( ) > > ,
329329 ) -> SendResult < Self :: Ticket > {
330330 log:: trace!( target: LOG_TARGET , "validate - msg: {xcm:?}, destination: {dest:?}" ) ;
331- // `dest` and `xcm` are required here
332- let dest_ref = dest. as_ref ( ) . ok_or ( SendError :: MissingArgument ) ?;
333- let xcm_ref = xcm. as_ref ( ) . ok_or ( SendError :: MissingArgument ) ?;
334-
335- // we won't have an access to `dest` and `xcm` in the `deliver` method, so precompute
336- // everything required here
337- let message_size = xcm_ref. encoded_size ( ) as _ ;
338-
339- // bridge doesn't support oversized/overweight messages now. So it is better to drop such
340- // messages here than at the bridge hub. Let's check the message size.
341- if message_size > HARD_MESSAGE_SIZE_LIMIT {
342- return Err ( SendError :: ExceedsMaxMessageSize )
343- }
344331
345- // We need to ensure that the known `dest`'s XCM version can comprehend the current `xcm`
346- // program. This may seem like an additional, unnecessary check, but it is not. A similar
347- // check is probably performed by the `ViaBridgeHubExporter`, which attempts to send a
348- // versioned message to the sibling bridge hub. However, the local bridge hub may have a
349- // higher XCM version than the remote `dest`. Once again, it is better to discard such
350- // messages here than at the bridge hub (e.g., to avoid losing funds).
351- let destination_version = T :: DestinationVersion :: get_version_for ( dest_ref)
352- . ok_or ( SendError :: DestinationUnsupported ) ?;
353- let _ = VersionedXcm :: from ( xcm_ref. clone ( ) )
354- . into_version ( destination_version)
355- . map_err ( |( ) | SendError :: DestinationUnsupported ) ?;
356-
357- // just use exporter to validate destination and insert instructions to pay message fee
358- // at the sibling/child bridge hub
359- //
360- // the cost will include both cost of: (1) to-sibling bridge hub delivery (returned by
361- // the `Config::ToBridgeHubSender`) and (2) to-bridged bridge hub delivery (returned by
362- // `Self::exporter_for`)
363- ViaBridgeHubExporter :: < T , I > :: validate ( dest, xcm)
364- . map ( |( ticket, cost) | ( ( message_size, ticket) , cost) )
332+ // In case of success, the `ViaBridgeHubExporter` can modify XCM instructions and consume
333+ // `dest` / `xcm`, so we retain the clone of original message and the destination for later
334+ // `DestinationVersion` validation.
335+ let xcm_to_dest_clone = xcm. clone ( ) ;
336+ let dest_clone = dest. clone ( ) ;
337+
338+ // First, use the inner exporter to validate the destination to determine if it is even
339+ // routable. If it is not, return an error. If it is, then the XCM is extended with
340+ // instructions to pay the message fee at the sibling/child bridge hub. The cost will
341+ // include both the cost of (1) delivery to the sibling bridge hub (returned by
342+ // `Config::ToBridgeHubSender`) and (2) delivery to the bridged bridge hub (returned by
343+ // `Self::exporter_for`).
344+ match ViaBridgeHubExporter :: < T , I > :: validate ( dest, xcm) {
345+ Ok ( ( ticket, cost) ) => {
346+ // If the ticket is ok, it means we are routing with this router, so we need to
347+ // apply more validations to the cloned `dest` and `xcm`, which are required here.
348+ let xcm_to_dest_clone = xcm_to_dest_clone. ok_or ( SendError :: MissingArgument ) ?;
349+ let dest_clone = dest_clone. ok_or ( SendError :: MissingArgument ) ?;
350+
351+ // We won't have access to `dest` and `xcm` in the `deliver` method, so we need to
352+ // precompute everything required here. However, `dest` and `xcm` were consumed by
353+ // `ViaBridgeHubExporter`, so we need to use their clones.
354+ let message_size = xcm_to_dest_clone. encoded_size ( ) as _ ;
355+
356+ // The bridge doesn't support oversized or overweight messages. Therefore, it's
357+ // better to drop such messages here rather than at the bridge hub. Let's check the
358+ // message size."
359+ if message_size > HARD_MESSAGE_SIZE_LIMIT {
360+ return Err ( SendError :: ExceedsMaxMessageSize )
361+ }
362+
363+ // We need to ensure that the known `dest`'s XCM version can comprehend the current
364+ // `xcm` program. This may seem like an additional, unnecessary check, but it is
365+ // not. A similar check is probably performed by the `ViaBridgeHubExporter`, which
366+ // attempts to send a versioned message to the sibling bridge hub. However, the
367+ // local bridge hub may have a higher XCM version than the remote `dest`. Once
368+ // again, it is better to discard such messages here than at the bridge hub (e.g.,
369+ // to avoid losing funds).
370+ let destination_version = T :: DestinationVersion :: get_version_for ( & dest_clone)
371+ . ok_or ( SendError :: DestinationUnsupported ) ?;
372+ let _ = VersionedXcm :: from ( xcm_to_dest_clone)
373+ . into_version ( destination_version)
374+ . map_err ( |( ) | SendError :: DestinationUnsupported ) ?;
375+
376+ Ok ( ( ( message_size, ticket) , cost) )
377+ } ,
378+ Err ( e) => {
379+ log:: trace!( target: LOG_TARGET , "validate - ViaBridgeHubExporter - error: {e:?}" ) ;
380+ Err ( e)
381+ } ,
382+ }
365383 }
366384
367385 fn deliver ( ticket : Self :: Ticket ) -> Result < XcmHash , SendError > {
@@ -452,24 +470,51 @@ mod tests {
452470 #[ test]
453471 fn not_applicable_if_destination_is_within_other_network ( ) {
454472 run_test ( || {
473+ // unroutable dest
474+ let dest = Location :: new ( 2 , [ GlobalConsensus ( ByGenesis ( [ 0 ; 32 ] ) ) , Parachain ( 1000 ) ] ) ;
475+ let xcm: Xcm < ( ) > = vec ! [ ClearOrigin ] . into ( ) ;
476+
477+ // check that router does not consume when `NotApplicable`
478+ let mut xcm_wrapper = Some ( xcm. clone ( ) ) ;
455479 assert_eq ! (
456- send_xcm:: <XcmBridgeHubRouter >(
457- Location :: new( 2 , [ GlobalConsensus ( Rococo ) , Parachain ( 1000 ) ] ) ,
458- vec![ ] . into( ) ,
459- ) ,
480+ XcmBridgeHubRouter :: validate( & mut Some ( dest. clone( ) ) , & mut xcm_wrapper) ,
460481 Err ( SendError :: NotApplicable ) ,
461482 ) ;
483+ // XCM is NOT consumed and untouched
484+ assert_eq ! ( Some ( xcm. clone( ) ) , xcm_wrapper) ;
485+
486+ // check the full `send_xcm`
487+ assert_eq ! ( send_xcm:: <XcmBridgeHubRouter >( dest, xcm, ) , Err ( SendError :: NotApplicable ) , ) ;
462488 } ) ;
463489 }
464490
465491 #[ test]
466492 fn exceeds_max_message_size_if_size_is_above_hard_limit ( ) {
467493 run_test ( || {
494+ // routable dest with XCM version
495+ let dest =
496+ Location :: new ( 2 , [ GlobalConsensus ( BridgedNetworkId :: get ( ) ) , Parachain ( 1000 ) ] ) ;
497+ // oversized XCM
498+ let xcm: Xcm < ( ) > = vec ! [ ClearOrigin ; HARD_MESSAGE_SIZE_LIMIT as usize ] . into ( ) ;
499+
500+ // dest is routable with the inner router
501+ assert_ok ! ( ViaBridgeHubExporter :: <TestRuntime , ( ) >:: validate(
502+ & mut Some ( dest. clone( ) ) ,
503+ & mut Some ( xcm. clone( ) )
504+ ) ) ;
505+
506+ // check for oversized message
507+ let mut xcm_wrapper = Some ( xcm. clone ( ) ) ;
508+ assert_eq ! (
509+ XcmBridgeHubRouter :: validate( & mut Some ( dest. clone( ) ) , & mut xcm_wrapper) ,
510+ Err ( SendError :: ExceedsMaxMessageSize ) ,
511+ ) ;
512+ // XCM is consumed by the inner router
513+ assert ! ( xcm_wrapper. is_none( ) ) ;
514+
515+ // check the full `send_xcm`
468516 assert_eq ! (
469- send_xcm:: <XcmBridgeHubRouter >(
470- Location :: new( 2 , [ GlobalConsensus ( Rococo ) , Parachain ( 1000 ) ] ) ,
471- vec![ ClearOrigin ; HARD_MESSAGE_SIZE_LIMIT as usize ] . into( ) ,
472- ) ,
517+ send_xcm:: <XcmBridgeHubRouter >( dest, xcm, ) ,
473518 Err ( SendError :: ExceedsMaxMessageSize ) ,
474519 ) ;
475520 } ) ;
@@ -478,11 +523,28 @@ mod tests {
478523 #[ test]
479524 fn destination_unsupported_if_wrap_version_fails ( ) {
480525 run_test ( || {
526+ // routable dest but we don't know XCM version
527+ let dest = UnknownXcmVersionForRoutableLocation :: get ( ) ;
528+ let xcm: Xcm < ( ) > = vec ! [ ClearOrigin ] . into ( ) ;
529+
530+ // dest is routable with the inner router
531+ assert_ok ! ( ViaBridgeHubExporter :: <TestRuntime , ( ) >:: validate(
532+ & mut Some ( dest. clone( ) ) ,
533+ & mut Some ( xcm. clone( ) )
534+ ) ) ;
535+
536+ // check that it does not pass XCM version check
537+ let mut xcm_wrapper = Some ( xcm. clone ( ) ) ;
538+ assert_eq ! (
539+ XcmBridgeHubRouter :: validate( & mut Some ( dest. clone( ) ) , & mut xcm_wrapper) ,
540+ Err ( SendError :: DestinationUnsupported ) ,
541+ ) ;
542+ // XCM is consumed by the inner router
543+ assert ! ( xcm_wrapper. is_none( ) ) ;
544+
545+ // check the full `send_xcm`
481546 assert_eq ! (
482- send_xcm:: <XcmBridgeHubRouter >(
483- UnknownXcmVersionLocation :: get( ) ,
484- vec![ ClearOrigin ] . into( ) ,
485- ) ,
547+ send_xcm:: <XcmBridgeHubRouter >( dest, xcm, ) ,
486548 Err ( SendError :: DestinationUnsupported ) ,
487549 ) ;
488550 } ) ;
0 commit comments