1
// Copyright (C) Moondance Labs Ltd.
2
// This file is part of Tanssi.
3

            
4
// Tanssi is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Tanssi is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Tanssi.  If not, see <http://www.gnu.org/licenses/>
16

            
17
use {
18
    alloc::vec::Vec,
19
    core::{iter::Peekable, marker::PhantomData, slice::Iter},
20
    frame_support::{ensure, traits::Get},
21
    parity_scale_codec::{Decode, Encode},
22
    snowbridge_core::{AgentId, ChannelId, TokenId},
23
    snowbridge_outbound_queue_primitives::v1::message::{Command, Message, SendMessage},
24
    sp_core::H160,
25
    sp_runtime::traits::MaybeEquivalence,
26
    xcm::latest::SendError::{MissingArgument, NotApplicable, Unroutable},
27
    xcm::prelude::*,
28
    xcm_executor::traits::ExportXcm,
29
};
30

            
31
pub struct ContainerEthereumBlobExporter<
32
    UniversalLocation,
33
    EthereumNetwork,
34
    EthereumLocation,
35
    OutboundQueue,
36
    ConvertAssetId,
37
    BridgeChannelInfo,
38
>(
39
    PhantomData<(
40
        UniversalLocation,
41
        EthereumNetwork,
42
        EthereumLocation,
43
        OutboundQueue,
44
        ConvertAssetId,
45
        BridgeChannelInfo,
46
    )>,
47
);
48

            
49
impl<
50
        UniversalLocation,
51
        EthereumNetwork,
52
        EthereumLocation,
53
        OutboundQueue,
54
        ConvertAssetId,
55
        BridgeChannelInfo,
56
    > ExportXcm
57
    for ContainerEthereumBlobExporter<
58
        UniversalLocation,
59
        EthereumNetwork,
60
        EthereumLocation,
61
        OutboundQueue,
62
        ConvertAssetId,
63
        BridgeChannelInfo,
64
    >
65
where
66
    UniversalLocation: Get<InteriorLocation>,
67
    EthereumNetwork: Get<NetworkId>,
68
    EthereumLocation: Get<Location>,
69
    OutboundQueue: SendMessage<Balance = u128>,
70
    ConvertAssetId: MaybeEquivalence<TokenId, Location>,
71
    BridgeChannelInfo: Get<Option<(ChannelId, AgentId)>>,
72
{
73
    type Ticket = (Vec<u8>, XcmHash);
74

            
75
23
    fn validate(
76
23
        network: NetworkId,
77
23
        _channel: u32,
78
23
        universal_source: &mut Option<InteriorLocation>,
79
23
        destination: &mut Option<InteriorLocation>,
80
23
        message: &mut Option<Xcm<()>>,
81
23
    ) -> SendResult<Self::Ticket> {
82
23
        let expected_network = EthereumNetwork::get();
83
23
        let universal_location = UniversalLocation::get();
84
23

            
85
23
        log::info!("validate params: network={network:?}, _channel={_channel:?}, universal_source={universal_source:?}, destination={destination:?}, message={message:?}, ");
86

            
87
23
        if network != expected_network {
88
1
            log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched bridge network {network:?}.");
89
1
            return Err(NotApplicable);
90
22
        }
91

            
92
        // Cloning destination to avoid modifying the value so subsequent exporters can use it.
93
22
        let dest = destination.clone().take().ok_or(MissingArgument)?;
94
21
        if dest != Here {
95
1
            log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched remote destination {dest:?}.");
96
1
            return Err(NotApplicable);
97
20
        }
98

            
99
        // Cloning universal_source to avoid modifying the value so subsequent exporters can use it.
100
20
        let (local_net, local_sub) = universal_source.clone()
101
20
            .take()
102
20
            .ok_or_else(|| {
103
1
                log::error!(target: "xcm::ethereum_blob_exporter", "universal source not provided.");
104
1
                MissingArgument
105
20
            })?
106
19
            .split_global()
107
19
            .map_err(|()| {
108
1
                log::error!(target: "xcm::ethereum_blob_exporter", "could not get global consensus from universal source '{universal_source:?}'.");
109
1
                NotApplicable
110
19
            })?;
111

            
112
18
        if Ok(local_net) != universal_location.global_consensus() {
113
            log::trace!(target: "xcm::ethereum_blob_exporter", "skipped due to unmatched relay network {local_net:?}.");
114
            return Err(NotApplicable);
115
18
        }
116

            
117
18
        let para_id = match local_sub.as_slice() {
118
17
            [Parachain(para_id)] => *para_id,
119
            _ => {
120
1
                log::error!(target: "xcm::ethereum_blob_exporter", "could not get parachain id from universal source '{local_sub:?}'.");
121
1
                return Err(NotApplicable);
122
            }
123
        };
124

            
125
17
        let message = message.take().ok_or_else(|| {
126
1
            log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided.");
127
1
            MissingArgument
128
17
        })?;
129

            
130
16
        let mut converter =
131
16
            XcmConverter::<ConvertAssetId, UniversalLocation, EthereumLocation, ()>::new(
132
16
                &message,
133
16
                expected_network,
134
16
                para_id,
135
16
            );
136
16
        let (command, message_id) = converter.convert().map_err(|err|{
137
15
            log::error!(target: "xcm::ethereum_blob_exporter", "unroutable due to pattern matching error '{err:?}'.");
138
15
            Unroutable
139
16
        })?;
140

            
141
1
        let (channel_id, _) = BridgeChannelInfo::get().ok_or_else(|| {
142
            log::error!(target: "xcm::ethereum_blob_exporter", "channel id and agent id cannot be fetched");
143
            Unroutable
144
1
        })?;
145

            
146
1
        let outbound_message = Message {
147
1
            id: Some(message_id.into()),
148
1
            channel_id,
149
1
            command,
150
1
        };
151

            
152
        // validate the message
153
1
        let (ticket, fee) = OutboundQueue::validate(&outbound_message).map_err(|err| {
154
            log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue validation of message failed. {err:?}");
155
            Unroutable
156
1
        })?;
157

            
158
        // convert fee to Asset (specify native tokens)
159
1
        let fee = Asset::from((Location::new(0, []), fee.total())).into();
160
1

            
161
1
        Ok(((ticket.encode(), message_id), fee))
162
23
    }
163

            
164
    fn deliver(blob: (Vec<u8>, XcmHash)) -> Result<XcmHash, SendError> {
165
        let ticket: OutboundQueue::Ticket = OutboundQueue::Ticket::decode(&mut blob.0.as_ref())
166
            .map_err(|_| {
167
                log::trace!(target: "xcm::ethereum_blob_exporter", "undeliverable due to decoding error");
168
                NotApplicable
169
            })?;
170

            
171
        let message_id = OutboundQueue::deliver(ticket).map_err(|_| {
172
            log::error!(target: "xcm::ethereum_blob_exporter", "OutboundQueue submit of message failed");
173
            SendError::Transport("other transport error")
174
        })?;
175

            
176
        log::info!(target: "xcm::ethereum_blob_exporter", "message delivered {message_id:#?}.");
177
        Ok(message_id.into())
178
    }
179
}
180

            
181
#[cfg(not(feature = "runtime-benchmarks"))]
182
/// Errors that can be thrown to the pattern matching step.
183
#[derive(PartialEq, Debug)]
184
enum XcmConverterError {
185
    UnexpectedEndOfXcm,
186
    EndOfXcmMessageExpected,
187
    DepositAssetExpected,
188
    ClearOriginExpected,
189
    BuyExecutionExpected,
190
    NoReserveAssets,
191
    FilterDoesNotConsumeAllAssets,
192
    TooManyAssets,
193
    ZeroAssetTransfer,
194
    BeneficiaryResolutionFailed,
195
    AssetResolutionFailed,
196
    InvalidFeeAsset,
197
    SetTopicExpected,
198
    ReserveAssetDepositedExpected,
199
    InvalidAsset,
200
    ParaIdMismatch,
201
    UnexpectedInstruction,
202
}
203

            
204
#[cfg(not(feature = "runtime-benchmarks"))]
205
macro_rules! match_expression {
206
	($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => {
207
		match $expression {
208
			$( $pattern )|+ $( if $guard )? => Some($value),
209
			_ => None,
210
		}
211
	};
212
}
213

            
214
#[cfg(feature = "runtime-benchmarks")]
215
struct XcmConverter<'a, ConvertAssetId, UniversalLocation, EthereumLocation, Call> {
216
    iter: Peekable<Iter<'a, Instruction<Call>>>,
217
    _ethereum_network: NetworkId,
218
    _para_id: u32,
219
    _marker: PhantomData<(ConvertAssetId, UniversalLocation, EthereumLocation)>,
220
}
221
#[cfg(feature = "runtime-benchmarks")]
222
impl<'a, ConvertAssetId, UniversalLocation, EthereumLocation, Call>
223
    XcmConverter<'a, ConvertAssetId, UniversalLocation, EthereumLocation, Call>
224
where
225
    ConvertAssetId: MaybeEquivalence<TokenId, Location>,
226
    UniversalLocation: Get<InteriorLocation>,
227
    EthereumLocation: Get<Location>,
228
{
229
    fn new(message: &'a Xcm<Call>, _ethereum_network: NetworkId, _para_id: u32) -> Self {
230
        Self {
231
            iter: message.inner().iter().peekable(),
232
            _ethereum_network,
233
            _para_id,
234
            _marker: Default::default(),
235
        }
236
    }
237

            
238
    fn convert(&mut self) -> Result<(Command, [u8; 32]), sp_runtime::DispatchError> {
239
        ensure!(self.iter.len() > 0, "Should have at least one instruction");
240

            
241
        return Ok((
242
            Command::MintForeignToken {
243
                token_id: Default::default(),
244
                recipient: H160::repeat_byte(0),
245
                amount: 0,
246
            },
247
            [0u8; 32],
248
        ));
249
    }
250
}
251

            
252
#[cfg(not(feature = "runtime-benchmarks"))]
253
struct XcmConverter<'a, ConvertAssetId, UniversalLocation, EthereumLocation, Call> {
254
    iter: Peekable<Iter<'a, Instruction<Call>>>,
255
    ethereum_network: NetworkId,
256
    para_id: u32,
257
    _marker: PhantomData<(ConvertAssetId, UniversalLocation, EthereumLocation)>,
258
}
259
#[cfg(not(feature = "runtime-benchmarks"))]
260
impl<'a, ConvertAssetId, UniversalLocation, EthereumLocation, Call>
261
    XcmConverter<'a, ConvertAssetId, UniversalLocation, EthereumLocation, Call>
262
where
263
    ConvertAssetId: MaybeEquivalence<TokenId, Location>,
264
    UniversalLocation: Get<InteriorLocation>,
265
    EthereumLocation: Get<Location>,
266
{
267
16
    fn new(message: &'a Xcm<Call>, ethereum_network: NetworkId, para_id: u32) -> Self {
268
16
        Self {
269
16
            iter: message.inner().iter().peekable(),
270
16
            ethereum_network,
271
16
            para_id,
272
16
            _marker: Default::default(),
273
16
        }
274
16
    }
275

            
276
16
    fn convert(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> {
277
16
        let result = match self.peek() {
278
            // Prepare the command to mint the foreign token.
279
15
            Ok(ReserveAssetDeposited { .. }) => self.make_mint_foreign_token_command(),
280
            Err(e) => {
281
                log::trace!(target: "xcm::convert", "peak error: {:?}", e);
282
                Err(e)
283
            }
284
1
            _ => return Err(XcmConverterError::UnexpectedInstruction),
285
13
        }?;
286

            
287
        // All xcm instructions must be consumed before exit.
288
2
        if self.next().is_ok() {
289
1
            return Err(XcmConverterError::EndOfXcmMessageExpected);
290
1
        }
291
1

            
292
1
        Ok(result)
293
16
    }
294

            
295
62
    fn next(&mut self) -> Result<&'a Instruction<Call>, XcmConverterError> {
296
62
        self.iter
297
62
            .next()
298
62
            .ok_or(XcmConverterError::UnexpectedEndOfXcm)
299
62
    }
300

            
301
16
    fn peek(&mut self) -> Result<&&'a Instruction<Call>, XcmConverterError> {
302
16
        self.iter
303
16
            .peek()
304
16
            .ok_or(XcmConverterError::UnexpectedEndOfXcm)
305
16
    }
306

            
307
12
    fn network_matches(&self, network: &Option<NetworkId>) -> bool {
308
12
        if let Some(network) = network {
309
11
            *network == self.ethereum_network
310
        } else {
311
1
            true
312
        }
313
12
    }
314

            
315
6
    fn check_reserve_asset_para_id(&self, location: &Location) -> Result<(), ()> {
316
6
        if location.parents != 1 {
317
            return Err(());
318
6
        }
319
6

            
320
6
        match &location.interior {
321
4
            Junctions::X3(arc) => {
322
4
                let [j1, j2, _] = arc.as_ref();
323
4
                if let GlobalConsensus(_) = j1 {
324
4
                    if let Parachain(id) = j2 {
325
4
                        if *id == self.para_id {
326
4
                            return Ok(());
327
                        }
328
                    }
329
                }
330
                Err(())
331
            }
332
2
            _ => Err(()),
333
        }
334
6
    }
335

            
336
    /// Convert the xcm for Polkadot-native token from AH into the Command
337
    /// To match transfers of Polkadot-native tokens, we expect an input of the form:
338
    /// # ReserveAssetDeposited
339
    /// # ClearOrigin
340
    /// # BuyExecution
341
    /// # DepositAsset
342
    /// # SetTopic
343
15
    fn make_mint_foreign_token_command(
344
15
        &mut self,
345
15
    ) -> Result<(Command, [u8; 32]), XcmConverterError> {
346
        use XcmConverterError::*;
347

            
348
        // Get the reserve assets.
349
15
        let reserve_assets = match_expression!(
350
15
            self.next()?,
351
15
            ReserveAssetDeposited(reserve_assets),
352
15
            reserve_assets
353
        )
354
15
        .ok_or(ReserveAssetDepositedExpected)?;
355

            
356
        // Check if clear origin exists
357
15
        match_expression!(self.next(), Ok(ClearOrigin), ()).ok_or(ClearOriginExpected)?;
358

            
359
        // Get the fee asset item from BuyExecution
360
14
        let fee_asset = match_expression!(self.next(), Ok(BuyExecution { fees, .. }), fees)
361
14
            .ok_or(BuyExecutionExpected)?;
362

            
363
13
        let (deposit_assets, beneficiary) = match self.next()? {
364
            Instruction::DepositAsset {
365
12
                assets,
366
12
                beneficiary,
367
12
            } => (assets, beneficiary),
368
            _ => return Err(DepositAssetExpected),
369
        };
370

            
371
        // assert that the beneficiary is AccountKey20.
372
12
        let (parents, interior) = beneficiary.unpack();
373

            
374
12
        let recipient = match (parents, interior) {
375
12
            (0, [AccountKey20 { network, key }]) if self.network_matches(network) => H160(*key),
376
            _ => return Err(BeneficiaryResolutionFailed),
377
        };
378

            
379
        // Make sure there are reserved assets.
380
12
        if reserve_assets.len() == 0 {
381
1
            return Err(NoReserveAssets);
382
11
        }
383
11

            
384
11
        // Check the the deposit asset filter matches what was reserved.
385
11
        if reserve_assets
386
11
            .inner()
387
11
            .iter()
388
11
            .any(|asset| !deposit_assets.matches(asset))
389
        {
390
1
            return Err(FilterDoesNotConsumeAllAssets);
391
10
        }
392
10

            
393
10
        // We only support a single asset at a time.
394
10
        ensure!(reserve_assets.len() == 1, TooManyAssets);
395
10
        let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?;
396

            
397
        // Fees are collected on AH, up front and directly from the user, to cover the
398
        // complete cost of the transfer. Any additional fees provided in the XCM program are
399
        // refunded to the beneficiary. We only validate the fee here if its provided to make sure
400
        // the XCM program is well formed. Another way to think about this from an XCM perspective
401
        // would be that the user offered to pay X amount in fees, but we charge 0 of that X amount
402
        // (no fee) and refund X to the user.
403
10
        if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun {
404
3
            return Err(InvalidFeeAsset);
405
7
        }
406

            
407
7
        let (asset_id, amount) = match reserve_asset {
408
            Asset {
409
6
                id: AssetId(inner_location),
410
6
                fun: Fungible(amount),
411
6
            } => Some((inner_location.clone(), *amount)),
412
1
            _ => None,
413
        }
414
7
        .ok_or(AssetResolutionFailed)?;
415

            
416
6
        self.check_reserve_asset_para_id(&asset_id)
417
6
            .map_err(|_| ParaIdMismatch)?;
418

            
419
        // transfer amount must be greater than 0.
420
4
        ensure!(amount > 0, ZeroAssetTransfer);
421

            
422
3
        log::trace!(target: "xcm::make_mint_foreign_token_command", "asset_id={asset_id:?}");
423

            
424
        // NOTE: For now we have hardcoded RelayNetwork to the DANCELIGHT_GENESIS_HASH,
425
        // so asset_id won't work with Starlight runtime, but after we add pallet parameters and make the
426
        // RelayNetwork parameter dynamic, it will work with both
427
3
        let token_id = ConvertAssetId::convert_back(&asset_id).ok_or(InvalidAsset)?;
428

            
429
        // Check if there is a SetTopic and skip over it if found.
430
3
        let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?;
431

            
432
2
        Ok((
433
2
            Command::MintForeignToken {
434
2
                token_id,
435
2
                recipient,
436
2
                amount,
437
2
            },
438
2
            *topic_id,
439
2
        ))
440
15
    }
441
}
442

            
443
#[cfg(feature = "runtime-benchmarks")]
444
pub struct ToEthDeliveryHelper<XcmConfig, ExistentialDeposit, PriceForDelivery>(
445
    core::marker::PhantomData<(XcmConfig, ExistentialDeposit, PriceForDelivery)>,
446
);
447

            
448
#[cfg(feature = "runtime-benchmarks")]
449
impl<
450
        XcmConfig: xcm_executor::Config,
451
        ExistentialDeposit: Get<Option<Asset>>,
452
        PriceForDelivery: Get<u128>,
453
    > xcm_builder::EnsureDelivery
454
    for ToEthDeliveryHelper<XcmConfig, ExistentialDeposit, PriceForDelivery>
455
{
456
    fn ensure_successful_delivery(
457
        origin_ref: &Location,
458
        dest: &Location,
459
        fee_reason: xcm_executor::traits::FeeReason,
460
    ) -> (Option<xcm_executor::FeesMode>, Option<Assets>) {
461
        log::trace!(target: "xcm::delivery_helper",
462
            "ensure_successful_delivery params: {origin_ref:?} {dest:?} {fee_reason:?} "
463
        );
464

            
465
        use xcm_executor::{
466
            traits::{FeeManager, TransactAsset},
467
            FeesMode,
468
        };
469

            
470
        if !dest.is_here() {
471
            log::trace!(target: "xcm::delivery_helper",
472
                "skipped due to unmatched remote destination {dest:?}."
473
            );
474
            return (None, None);
475
        }
476

            
477
        let mut fees_mode = None;
478
        if !XcmConfig::FeeManager::is_waived(Some(origin_ref), fee_reason) {
479
            // if not waived, we need to set up accounts for paying and receiving fees
480

            
481
            // overestimate delivery fee
482
            let overestimated_fees = PriceForDelivery::get();
483
            log::debug!(target: "xcm::delivery_helper", "fees to deposit {overestimated_fees:?} for origin: {origin_ref:?}");
484

            
485
            // mint overestimated fee to origin
486
            XcmConfig::AssetTransactor::deposit_asset(
487
                &Asset {
488
                    id: AssetId(Location::new(0, Here)),
489
                    fun: Fungible(overestimated_fees),
490
                },
491
                &origin_ref,
492
                None,
493
            )
494
            .unwrap();
495

            
496
            // expected worst case - direct withdraw
497
            fees_mode = Some(FeesMode { jit_withdraw: true });
498
        }
499
        (fees_mode, None)
500
    }
501
}
502

            
503
#[cfg(test)]
504
mod tests {
505
    use {
506
        super::*,
507
        frame_support::parameter_types,
508
        hex_literal::hex,
509
        snowbridge_outbound_queue_primitives::{v1::Fee, SendError, SendMessageFeeProvider},
510
        sp_core::H256,
511
    };
512

            
513
    parameter_types! {
514
        pub EthereumLocation: Location = Location::new(1, EthereumNetwork::get());
515
        const EthereumNetwork: NetworkId = Ethereum { chain_id: 11155111 };
516
        UniversalLocation: InteriorLocation = [GlobalConsensus(ByGenesis([ 152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10 ]))].into();
517
        const BridgeChannelInfo: Option<(ChannelId, AgentId)> = Some((ChannelId::new([1u8; 32]), H256([2u8; 32])));
518
    }
519

            
520
    pub struct MockTokenIdConvert;
521
    impl MaybeEquivalence<TokenId, Location> for MockTokenIdConvert {
522
        fn convert(_id: &TokenId) -> Option<Location> {
523
            Some(Location::parent())
524
        }
525
3
        fn convert_back(_loc: &Location) -> Option<TokenId> {
526
3
            Some(H256::from_low_u64_be(123))
527
3
        }
528
    }
529

            
530
    struct MockOkOutboundQueue;
531
    impl SendMessage for MockOkOutboundQueue {
532
        type Ticket = ();
533

            
534
1
        fn validate(_: &Message) -> Result<(Self::Ticket, Fee<Self::Balance>), SendError> {
535
1
            Ok((
536
1
                (),
537
1
                Fee {
538
1
                    local: 1,
539
1
                    remote: 1,
540
1
                },
541
1
            ))
542
1
        }
543

            
544
        fn deliver(_: Self::Ticket) -> Result<H256, SendError> {
545
            Ok(H256::zero())
546
        }
547
    }
548

            
549
    impl SendMessageFeeProvider for MockOkOutboundQueue {
550
        type Balance = u128;
551

            
552
        fn local_fee() -> Self::Balance {
553
            1
554
        }
555
    }
556
    struct MockErrOutboundQueue;
557
    impl SendMessage for MockErrOutboundQueue {
558
        type Ticket = ();
559

            
560
        fn validate(_: &Message) -> Result<(Self::Ticket, Fee<Self::Balance>), SendError> {
561
            Err(SendError::MessageTooLarge)
562
        }
563

            
564
        fn deliver(_: Self::Ticket) -> Result<H256, SendError> {
565
            Err(SendError::MessageTooLarge)
566
        }
567
    }
568

            
569
    impl SendMessageFeeProvider for MockErrOutboundQueue {
570
        type Balance = u128;
571

            
572
        fn local_fee() -> Self::Balance {
573
            1
574
        }
575
    }
576

            
577
    #[test]
578
1
    fn exporter_validate_with_unknown_network_yields_not_applicable() {
579
1
        let network = Ethereum { chain_id: 12345 };
580
1
        let channel: u32 = 0;
581
1
        let mut universal_source: Option<InteriorLocation> = None;
582
1
        let mut destination: Option<InteriorLocation> = None;
583
1
        let mut message: Option<Xcm<()>> = None;
584
1

            
585
1
        let result = ContainerEthereumBlobExporter::<
586
1
            UniversalLocation,
587
1
            EthereumNetwork,
588
1
            EthereumLocation,
589
1
            MockOkOutboundQueue,
590
1
            MockTokenIdConvert,
591
1
            BridgeChannelInfo,
592
1
        >::validate(
593
1
            network,
594
1
            channel,
595
1
            &mut universal_source,
596
1
            &mut destination,
597
1
            &mut message,
598
1
        );
599
1
        assert_eq!(result, Err(NotApplicable));
600
1
    }
601

            
602
    #[test]
603
1
    fn exporter_validate_with_empty_destination_yields_missing_argument() {
604
1
        let network = Ethereum { chain_id: 11155111 };
605
1
        let channel: u32 = 0;
606
1
        let mut universal_source: Option<InteriorLocation> = None;
607
1
        let mut destination: Option<InteriorLocation> = None;
608
1
        let mut message: Option<Xcm<()>> = None;
609
1

            
610
1
        let result = ContainerEthereumBlobExporter::<
611
1
            UniversalLocation,
612
1
            EthereumNetwork,
613
1
            EthereumLocation,
614
1
            MockOkOutboundQueue,
615
1
            MockTokenIdConvert,
616
1
            BridgeChannelInfo,
617
1
        >::validate(
618
1
            network,
619
1
            channel,
620
1
            &mut universal_source,
621
1
            &mut destination,
622
1
            &mut message,
623
1
        );
624
1
        assert_eq!(result, Err(MissingArgument));
625
1
    }
626

            
627
    #[test]
628
1
    fn exporter_validate_with_incorrect_destination_yields_not_applicable() {
629
1
        let network = Ethereum { chain_id: 11155111 };
630
1
        let channel: u32 = 0;
631
1
        let mut universal_source: Option<InteriorLocation> = None;
632
1
        let mut destination: Option<InteriorLocation> = Some(
633
1
            [
634
1
                OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild,
635
1
                OnlyChild,
636
1
            ]
637
1
            .into(),
638
1
        );
639
1
        let mut message: Option<Xcm<()>> = None;
640
1

            
641
1
        let result = ContainerEthereumBlobExporter::<
642
1
            UniversalLocation,
643
1
            EthereumNetwork,
644
1
            EthereumLocation,
645
1
            MockOkOutboundQueue,
646
1
            MockTokenIdConvert,
647
1
            BridgeChannelInfo,
648
1
        >::validate(
649
1
            network,
650
1
            channel,
651
1
            &mut universal_source,
652
1
            &mut destination,
653
1
            &mut message,
654
1
        );
655
1
        assert_eq!(result, Err(NotApplicable));
656
1
    }
657

            
658
    #[test]
659
1
    fn exporter_validate_with_incorrect_universal_source_yields_validation_error() {
660
1
        let network = Ethereum { chain_id: 11155111 };
661
1
        let channel: u32 = 0;
662
1
        let mut universal_source: Option<InteriorLocation> = None;
663
1
        let mut destination: Option<InteriorLocation> = Here.into();
664
1
        let mut message: Option<Xcm<()>> = None;
665
1

            
666
1
        let result = ContainerEthereumBlobExporter::<
667
1
            UniversalLocation,
668
1
            EthereumNetwork,
669
1
            EthereumLocation,
670
1
            MockOkOutboundQueue,
671
1
            MockTokenIdConvert,
672
1
            BridgeChannelInfo,
673
1
        >::validate(
674
1
            network,
675
1
            channel,
676
1
            &mut universal_source,
677
1
            &mut destination,
678
1
            &mut message,
679
1
        );
680
1
        assert_eq!(result, Err(MissingArgument));
681

            
682
1
        let mut universal_source: Option<InteriorLocation> = Here.into();
683
1
        let result = ContainerEthereumBlobExporter::<
684
1
            UniversalLocation,
685
1
            EthereumNetwork,
686
1
            EthereumLocation,
687
1
            MockOkOutboundQueue,
688
1
            MockTokenIdConvert,
689
1
            BridgeChannelInfo,
690
1
        >::validate(
691
1
            network,
692
1
            channel,
693
1
            &mut universal_source,
694
1
            &mut destination,
695
1
            &mut message,
696
1
        );
697
1
        assert_eq!(result, Err(NotApplicable));
698
1
    }
699

            
700
    #[test]
701
1
    fn exporter_validate_with_missing_para_id_universal_source_yields_validation_error() {
702
1
        let network = Ethereum { chain_id: 11155111 };
703
1
        let channel: u32 = 0;
704
1
        let mut universal_source: Option<InteriorLocation> = Some(
705
1
            [GlobalConsensus(ByGenesis([
706
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
707
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
708
1
            ]))]
709
1
            .into(),
710
1
        );
711
1
        let mut destination: Option<InteriorLocation> = Here.into();
712
1
        let mut message: Option<Xcm<()>> = None;
713
1

            
714
1
        let result = ContainerEthereumBlobExporter::<
715
1
            UniversalLocation,
716
1
            EthereumNetwork,
717
1
            EthereumLocation,
718
1
            MockOkOutboundQueue,
719
1
            MockTokenIdConvert,
720
1
            BridgeChannelInfo,
721
1
        >::validate(
722
1
            network,
723
1
            channel,
724
1
            &mut universal_source,
725
1
            &mut destination,
726
1
            &mut message,
727
1
        );
728
1
        assert_eq!(result, Err(NotApplicable));
729
1
    }
730

            
731
    #[test]
732
1
    fn exporter_validate_with_empty_message_yields_missing_argument() {
733
1
        let network = Ethereum { chain_id: 11155111 };
734
1
        let channel: u32 = 0;
735
1
        let mut universal_source: Option<InteriorLocation> = Some(
736
1
            [
737
1
                GlobalConsensus(ByGenesis([
738
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
739
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
740
1
                ])),
741
1
                Parachain(2001),
742
1
            ]
743
1
            .into(),
744
1
        );
745
1
        let mut destination: Option<InteriorLocation> = Here.into();
746
1
        let mut message: Option<Xcm<()>> = None;
747
1

            
748
1
        let result = ContainerEthereumBlobExporter::<
749
1
            UniversalLocation,
750
1
            EthereumNetwork,
751
1
            EthereumLocation,
752
1
            MockOkOutboundQueue,
753
1
            MockTokenIdConvert,
754
1
            BridgeChannelInfo,
755
1
        >::validate(
756
1
            network,
757
1
            channel,
758
1
            &mut universal_source,
759
1
            &mut destination,
760
1
            &mut message,
761
1
        );
762
1
        assert_eq!(result, Err(MissingArgument));
763
1
    }
764

            
765
    #[test]
766
1
    fn exporter_incorrect_message_yields_incorrect_instruction() {
767
1
        let network = Ethereum { chain_id: 11155111 };
768
1
        let channel: u32 = 0;
769
1
        let mut universal_source: Option<InteriorLocation> = Some(
770
1
            [
771
1
                GlobalConsensus(ByGenesis([
772
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
773
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
774
1
                ])),
775
1
                Parachain(2001),
776
1
            ]
777
1
            .into(),
778
1
        );
779
1
        let mut destination: Option<InteriorLocation> = Here.into();
780
1
        let mut message: Option<Xcm<()>> = Some(vec![SetTopic([0; 32])].into());
781
1

            
782
1
        let result = ContainerEthereumBlobExporter::<
783
1
            UniversalLocation,
784
1
            EthereumNetwork,
785
1
            EthereumLocation,
786
1
            MockOkOutboundQueue,
787
1
            MockTokenIdConvert,
788
1
            BridgeChannelInfo,
789
1
        >::validate(
790
1
            network,
791
1
            channel,
792
1
            &mut universal_source,
793
1
            &mut destination,
794
1
            &mut message,
795
1
        );
796
1
        assert_eq!(result, Err(Unroutable));
797
1
    }
798

            
799
    #[test]
800
1
    fn exporter_incorrect_clear_origin_yields_incorrect_instruction() {
801
1
        let network = Ethereum { chain_id: 11155111 };
802
1
        let channel: u32 = 0;
803
1
        let mut universal_source: Option<InteriorLocation> = Some(
804
1
            [
805
1
                GlobalConsensus(ByGenesis([
806
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
807
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
808
1
                ])),
809
1
                Parachain(2001),
810
1
            ]
811
1
            .into(),
812
1
        );
813
1
        let mut destination: Option<InteriorLocation> = Here.into();
814
1
        let asset_location = Location::new(
815
1
            1,
816
1
            [GlobalConsensus(ByGenesis([
817
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
818
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
819
1
            ]))],
820
1
        );
821
1
        let assets: Assets = vec![Asset {
822
1
            id: AssetId(asset_location),
823
1
            fun: Fungible(123321000000000000),
824
1
        }]
825
1
        .into();
826
1

            
827
1
        let mut message: Option<Xcm<()>> = Some(
828
1
            vec![
829
1
                ReserveAssetDeposited(assets.clone()),
830
1
                BuyExecution {
831
1
                    fees: assets.get(0).unwrap().clone(),
832
1
                    weight_limit: Unlimited,
833
1
                },
834
1
            ]
835
1
            .into(),
836
1
        );
837
1

            
838
1
        let result = ContainerEthereumBlobExporter::<
839
1
            UniversalLocation,
840
1
            EthereumNetwork,
841
1
            EthereumLocation,
842
1
            MockOkOutboundQueue,
843
1
            MockTokenIdConvert,
844
1
            BridgeChannelInfo,
845
1
        >::validate(
846
1
            network,
847
1
            channel,
848
1
            &mut universal_source,
849
1
            &mut destination,
850
1
            &mut message,
851
1
        );
852
1
        assert_eq!(result, Err(Unroutable));
853
1
    }
854

            
855
    #[test]
856
1
    fn exporter_incorrect_buy_execution_yields_unroutable() {
857
1
        let network = Ethereum { chain_id: 11155111 };
858
1
        let channel: u32 = 0;
859
1
        let mut universal_source: Option<InteriorLocation> = Some(
860
1
            [
861
1
                GlobalConsensus(ByGenesis([
862
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
863
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
864
1
                ])),
865
1
                Parachain(2001),
866
1
            ]
867
1
            .into(),
868
1
        );
869
1
        let mut destination: Option<InteriorLocation> = Here.into();
870
1
        let asset_location = Location::new(
871
1
            1,
872
1
            [GlobalConsensus(ByGenesis([
873
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
874
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
875
1
            ]))],
876
1
        );
877
1
        let assets: Assets = vec![Asset {
878
1
            id: AssetId(asset_location),
879
1
            fun: Fungible(123321000000000000),
880
1
        }]
881
1
        .into();
882
1

            
883
1
        let mut message: Option<Xcm<()>> = Some(
884
1
            vec![
885
1
                ReserveAssetDeposited(assets.clone()),
886
1
                ClearOrigin,
887
1
                ClearOrigin,
888
1
            ]
889
1
            .into(),
890
1
        );
891
1

            
892
1
        let result = ContainerEthereumBlobExporter::<
893
1
            UniversalLocation,
894
1
            EthereumNetwork,
895
1
            EthereumLocation,
896
1
            MockOkOutboundQueue,
897
1
            MockTokenIdConvert,
898
1
            BridgeChannelInfo,
899
1
        >::validate(
900
1
            network,
901
1
            channel,
902
1
            &mut universal_source,
903
1
            &mut destination,
904
1
            &mut message,
905
1
        );
906
1
        assert_eq!(result, Err(Unroutable));
907
1
    }
908

            
909
    #[test]
910
1
    fn exporter_lack_of_deposit_asset_yields_unroutable() {
911
1
        let network = Ethereum { chain_id: 11155111 };
912
1
        let channel: u32 = 0;
913
1
        let mut universal_source: Option<InteriorLocation> = Some(
914
1
            [
915
1
                GlobalConsensus(ByGenesis([
916
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
917
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
918
1
                ])),
919
1
                Parachain(2001),
920
1
            ]
921
1
            .into(),
922
1
        );
923
1
        let mut destination: Option<InteriorLocation> = Here.into();
924
1
        let asset_location = Location::new(
925
1
            1,
926
1
            [GlobalConsensus(ByGenesis([
927
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
928
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
929
1
            ]))],
930
1
        );
931
1
        let assets: Assets = vec![Asset {
932
1
            id: AssetId(asset_location),
933
1
            fun: Fungible(123321000000000000),
934
1
        }]
935
1
        .into();
936
1

            
937
1
        let mut message: Option<Xcm<()>> = Some(
938
1
            vec![
939
1
                ReserveAssetDeposited(assets.clone()),
940
1
                ClearOrigin,
941
1
                BuyExecution {
942
1
                    fees: assets.get(0).unwrap().clone(),
943
1
                    weight_limit: Unlimited,
944
1
                },
945
1
            ]
946
1
            .into(),
947
1
        );
948
1

            
949
1
        let result = ContainerEthereumBlobExporter::<
950
1
            UniversalLocation,
951
1
            EthereumNetwork,
952
1
            EthereumLocation,
953
1
            MockOkOutboundQueue,
954
1
            MockTokenIdConvert,
955
1
            BridgeChannelInfo,
956
1
        >::validate(
957
1
            network,
958
1
            channel,
959
1
            &mut universal_source,
960
1
            &mut destination,
961
1
            &mut message,
962
1
        );
963
1
        assert_eq!(result, Err(Unroutable));
964
1
    }
965

            
966
    #[test]
967
1
    fn exporter_incorrect_beneficiary_yields_unroutable() {
968
1
        let network = Ethereum { chain_id: 11155111 };
969
1
        let channel: u32 = 0;
970
1
        let mut universal_source: Option<InteriorLocation> = Some(
971
1
            [
972
1
                GlobalConsensus(ByGenesis([
973
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
974
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
975
1
                ])),
976
1
                Parachain(2001),
977
1
            ]
978
1
            .into(),
979
1
        );
980
1
        let mut destination: Option<InteriorLocation> = Here.into();
981
1
        let asset_location = Location::new(
982
1
            1,
983
1
            [GlobalConsensus(ByGenesis([
984
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
985
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
986
1
            ]))],
987
1
        );
988
1
        let assets: Assets = vec![Asset {
989
1
            id: AssetId(asset_location),
990
1
            fun: Fungible(123321000000000000),
991
1
        }]
992
1
        .into();
993
1

            
994
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
995
1
        let filter: AssetFilter = assets.clone().into();
996
1
        let mut message: Option<Xcm<()>> = Some(
997
1
            vec![
998
1
                ReserveAssetDeposited(assets.clone()),
999
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: None,
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_empty_reserve_assets_yields_unroutable() {
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(
1
            1,
1
            [GlobalConsensus(ByGenesis([
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
            ]))],
1
        );
1
        let assets: Assets = vec![Asset {
1
            id: AssetId(asset_location),
1
            fun: Fungible(123321000000000000),
1
        }]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1
        let filter: AssetFilter = assets.clone().into();
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(vec![].into()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_reserve_not_match_deposit_yields_unroutable() {
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(
1
            1,
1
            [GlobalConsensus(ByGenesis([
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
            ]))],
1
        );
1
        let assets: Assets = vec![Asset {
1
            id: AssetId(asset_location.clone()),
1
            fun: Fungible(123321000000000000),
1
        }]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1
        let assets1: Assets = vec![Asset {
1
            id: AssetId(asset_location),
1
            fun: Fungible(999),
1
        }]
1
        .into();
1
        let filter: AssetFilter = assets1.clone().into();
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(assets.clone()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_reserve_assets_more_then_one_yields_unroutable() {
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(
1
            1,
1
            [GlobalConsensus(ByGenesis([
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
            ]))],
1
        );
1
        let assets: Assets = vec![
1
            Asset {
1
                id: AssetId(asset_location.clone()),
1
                fun: Fungible(123321000000000000),
1
            },
1
            Asset {
1
                id: AssetId(asset_location.clone()),
1
                fun: Fungible(123321000000000000),
1
            },
1
        ]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1

            
1
        let filter: AssetFilter = Wild(WildAsset::AllCounted(1));
1

            
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(assets.clone()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_fee_id_does_not_equal_to_reserve_id_yields_unroutable() {
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(
1
            1,
1
            [GlobalConsensus(ByGenesis([
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
            ]))],
1
        );
1
        let assets: Assets = vec![Asset {
1
            id: AssetId(asset_location.clone()),
1
            fun: Fungible(123321000000000000),
1
        }]
1
        .into();
1

            
1
        let asset_location1 = Location::new(
1
            2,
1
            [GlobalConsensus(ByGenesis([
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
            ]))],
1
        );
1
        let assets1: Assets = vec![Asset {
1
            id: AssetId(asset_location1.clone()),
1
            fun: Fungible(123321000000000000),
1
        }]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1

            
1
        let filter: AssetFilter = Wild(WildAsset::AllCounted(1));
1

            
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(assets.clone()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets1.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_fee_amount_greater_then_reserve_amount_yields_unroutable() {
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(
1
            1,
1
            [GlobalConsensus(ByGenesis([
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
            ]))],
1
        );
1
        let assets: Assets = vec![Asset {
1
            id: AssetId(asset_location.clone()),
1
            fun: Fungible(123321000000000000),
1
        }]
1
        .into();
1

            
1
        let asset_location1 = Location::new(
1
            1,
1
            [GlobalConsensus(ByGenesis([
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
            ]))],
1
        );
1
        let assets1: Assets = vec![Asset {
1
            id: AssetId(asset_location1.clone()),
1
            fun: Fungible(123321000000000001),
1
        }]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1

            
1
        let filter: AssetFilter = Wild(WildAsset::AllCounted(1));
1

            
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(assets.clone()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets1.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_reserve_assets_incorrect_resolution_yields_unroutable() {
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(
1
            1,
1
            [GlobalConsensus(ByGenesis([
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
            ]))],
1
        );
1
        let assets: Assets = vec![Asset {
1
            id: AssetId(asset_location.clone()),
1
            fun: NonFungible([42u8; 32].into()),
1
        }]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1

            
1
        let filter: AssetFilter = Wild(WildAsset::AllCounted(1));
1

            
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(assets.clone()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_para_mismatch_yields_unroutable() {
1
        let asset_para_location: InteriorLocation = [
1
            GlobalConsensus(Polkadot),
1
            Parachain(2001),
1
            PalletInstance(10),
1
        ]
1
        .into();
1
        let asset_para_location1: InteriorLocation = [
1
            GlobalConsensus(Polkadot),
1
            Parachain(2002),
1
            PalletInstance(10),
1
        ]
1
        .into();
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(1, asset_para_location);
1
        let asset_location1 = Location::new(1, asset_para_location1);
1
        let assets: Assets = vec![Asset {
1
            id: AssetId(asset_location.clone()),
1
            fun: Fungible(123321000000000000),
1
        }]
1
        .into();
1

            
1
        let assets1: Assets = vec![Asset {
1
            id: AssetId(asset_location1.clone()),
1
            fun: Fungible(123321000000000000),
1
        }]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1

            
1
        let filter: AssetFilter = Wild(WildAsset::AllCounted(1));
1

            
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(assets1.clone()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_incorrect_amount_yields_unroutable() {
1
        let asset_para_location: InteriorLocation = [
1
            GlobalConsensus(Polkadot),
1
            Parachain(2001),
1
            PalletInstance(10),
1
        ]
1
        .into();
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(1, asset_para_location);
1
        let assets: Assets = vec![Asset {
1
            id: AssetId(asset_location.clone()),
1
            fun: Fungible(0),
1
        }]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1

            
1
        let filter: AssetFilter = Wild(WildAsset::AllCounted(1));
1

            
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(assets.clone()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_no_set_topic_yields_unroutable() {
1
        let asset_para_location: InteriorLocation = [
1
            GlobalConsensus(Polkadot),
1
            Parachain(2001),
1
            PalletInstance(10),
1
        ]
1
        .into();
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(1, asset_para_location);
1
        let assets: Assets = vec![Asset {
1
            id: AssetId(asset_location.clone()),
1
            fun: Fungible(123321000000000000),
1
        }]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1

            
1
        let filter: AssetFilter = Wild(WildAsset::AllCounted(1));
1

            
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(assets.clone()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_extra_instruction_yields_unroutable() {
1
        let asset_para_location: InteriorLocation = [
1
            GlobalConsensus(Polkadot),
1
            Parachain(2001),
1
            PalletInstance(10),
1
        ]
1
        .into();
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(1, asset_para_location);
1
        let assets: Assets = vec![Asset {
1
            id: AssetId(asset_location.clone()),
1
            fun: Fungible(123321000000000000),
1
        }]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1

            
1
        let filter: AssetFilter = Wild(WildAsset::AllCounted(1));
1

            
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(assets.clone()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
                SetTopic([
1
                    57, 238, 159, 80, 83, 113, 184, 105, 108, 164, 73, 6, 134, 160, 7, 234, 121,
1
                    88, 234, 173, 250, 136, 18, 29, 1, 204, 109, 70, 45, 3, 160, 251,
1
                ]),
1
                ClearOrigin,
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert_eq!(result, Err(Unroutable));
1
    }
    #[test]
1
    fn exporter_bridge_success() {
1
        let asset_para_location: InteriorLocation = [
1
            GlobalConsensus(Polkadot),
1
            Parachain(2001),
1
            PalletInstance(10),
1
        ]
1
        .into();
1
        let network = Ethereum { chain_id: 11155111 };
1
        let channel: u32 = 0;
1
        let mut universal_source: Option<InteriorLocation> = Some(
1
            [
1
                GlobalConsensus(ByGenesis([
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
1
                ])),
1
                Parachain(2001),
1
            ]
1
            .into(),
1
        );
1
        let mut destination: Option<InteriorLocation> = Here.into();
1
        let asset_location = Location::new(1, asset_para_location);
1
        let assets: Assets = vec![Asset {
1
            id: AssetId(asset_location.clone()),
1
            fun: Fungible(123321000000000000),
1
        }]
1
        .into();
1

            
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
1

            
1
        let filter: AssetFilter = Wild(WildAsset::AllCounted(1));
1

            
1
        let mut message: Option<Xcm<()>> = Some(
1
            vec![
1
                ReserveAssetDeposited(assets.clone()),
1
                ClearOrigin,
1
                BuyExecution {
1
                    fees: assets.get(0).unwrap().clone(),
1
                    weight_limit: Unlimited,
1
                },
1
                DepositAsset {
1
                    assets: filter,
1
                    beneficiary: AccountKey20 {
1
                        network: Some(Ethereum { chain_id: 11155111 }),
1
                        key: beneficiary_address,
1
                    }
1
                    .into(),
1
                },
1
                SetTopic([
1
                    57, 238, 159, 80, 83, 113, 184, 105, 108, 164, 73, 6, 134, 160, 7, 234, 121,
1
                    88, 234, 173, 250, 136, 18, 29, 1, 204, 109, 70, 45, 3, 160, 251,
1
                ]),
1
            ]
1
            .into(),
1
        );
1

            
1
        let result = ContainerEthereumBlobExporter::<
1
            UniversalLocation,
1
            EthereumNetwork,
1
            EthereumLocation,
1
            MockOkOutboundQueue,
1
            MockTokenIdConvert,
1
            BridgeChannelInfo,
1
        >::validate(
1
            network,
1
            channel,
1
            &mut universal_source,
1
            &mut destination,
1
            &mut message,
1
        );
1
        assert!(result.is_ok());
1
    }
}