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

            
204
macro_rules! match_expression {
205
	($expression:expr, $(|)? $( $pattern:pat_param )|+ $( if $guard: expr )?, $value:expr $(,)?) => {
206
		match $expression {
207
			$( $pattern )|+ $( if $guard )? => Some($value),
208
			_ => None,
209
		}
210
	};
211
}
212

            
213
struct XcmConverter<'a, ConvertAssetId, UniversalLocation, EthereumLocation, Call> {
214
    iter: Peekable<Iter<'a, Instruction<Call>>>,
215
    ethereum_network: NetworkId,
216
    para_id: u32,
217
    _marker: PhantomData<(ConvertAssetId, UniversalLocation, EthereumLocation)>,
218
}
219
impl<'a, ConvertAssetId, UniversalLocation, EthereumLocation, Call>
220
    XcmConverter<'a, ConvertAssetId, UniversalLocation, EthereumLocation, Call>
221
where
222
    ConvertAssetId: MaybeEquivalence<TokenId, Location>,
223
    UniversalLocation: Get<InteriorLocation>,
224
    EthereumLocation: Get<Location>,
225
{
226
16
    fn new(message: &'a Xcm<Call>, ethereum_network: NetworkId, para_id: u32) -> Self {
227
16
        Self {
228
16
            iter: message.inner().iter().peekable(),
229
16
            ethereum_network,
230
16
            para_id,
231
16
            _marker: Default::default(),
232
16
        }
233
16
    }
234

            
235
16
    fn convert(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> {
236
16
        let result = match self.peek() {
237
            // Prepare the command to mint the foreign token.
238
15
            Ok(ReserveAssetDeposited { .. }) => self.make_mint_foreign_token_command(),
239
            Err(e) => {
240
                log::trace!(target: "xcm::convert", "peak error: {:?}", e);
241
                Err(e)
242
            }
243
1
            _ => return Err(XcmConverterError::UnexpectedInstruction),
244
13
        }?;
245

            
246
        // All xcm instructions must be consumed before exit.
247
2
        if self.next().is_ok() {
248
1
            return Err(XcmConverterError::EndOfXcmMessageExpected);
249
1
        }
250
1

            
251
1
        Ok(result)
252
16
    }
253

            
254
62
    fn next(&mut self) -> Result<&'a Instruction<Call>, XcmConverterError> {
255
62
        self.iter
256
62
            .next()
257
62
            .ok_or(XcmConverterError::UnexpectedEndOfXcm)
258
62
    }
259

            
260
16
    fn peek(&mut self) -> Result<&&'a Instruction<Call>, XcmConverterError> {
261
16
        self.iter
262
16
            .peek()
263
16
            .ok_or(XcmConverterError::UnexpectedEndOfXcm)
264
16
    }
265

            
266
12
    fn network_matches(&self, network: &Option<NetworkId>) -> bool {
267
12
        if let Some(network) = network {
268
11
            *network == self.ethereum_network
269
        } else {
270
1
            true
271
        }
272
12
    }
273

            
274
6
    fn check_reserve_asset_para_id(&self, location: &Location) -> Result<(), ()> {
275
6
        if location.parents != 1 {
276
            return Err(());
277
6
        }
278
6

            
279
6
        match &location.interior {
280
4
            Junctions::X3(arc) => {
281
4
                let [j1, j2, _] = arc.as_ref();
282
4
                if let GlobalConsensus(_) = j1 {
283
4
                    if let Parachain(id) = j2 {
284
4
                        if *id == self.para_id {
285
4
                            return Ok(());
286
                        }
287
                    }
288
                }
289
                Err(())
290
            }
291
2
            _ => Err(()),
292
        }
293
6
    }
294

            
295
    /// Convert the xcm for Polkadot-native token from AH into the Command
296
    /// To match transfers of Polkadot-native tokens, we expect an input of the form:
297
    /// # ReserveAssetDeposited
298
    /// # ClearOrigin
299
    /// # BuyExecution
300
    /// # DepositAsset
301
    /// # SetTopic
302
15
    fn make_mint_foreign_token_command(
303
15
        &mut self,
304
15
    ) -> Result<(Command, [u8; 32]), XcmConverterError> {
305
        use XcmConverterError::*;
306

            
307
        // Get the reserve assets.
308
15
        let reserve_assets = match_expression!(
309
15
            self.next()?,
310
15
            ReserveAssetDeposited(reserve_assets),
311
15
            reserve_assets
312
        )
313
15
        .ok_or(ReserveAssetDepositedExpected)?;
314

            
315
        // Check if clear origin exists
316
15
        match_expression!(self.next(), Ok(ClearOrigin), ()).ok_or(ClearOriginExpected)?;
317

            
318
        // Get the fee asset item from BuyExecution
319
14
        let fee_asset = match_expression!(self.next(), Ok(BuyExecution { fees, .. }), fees)
320
14
            .ok_or(BuyExecutionExpected)?;
321

            
322
13
        let (deposit_assets, beneficiary) = match self.next()? {
323
            Instruction::DepositAsset {
324
12
                assets,
325
12
                beneficiary,
326
12
            } => (assets, beneficiary),
327
            _ => return Err(DepositAssetExpected),
328
        };
329

            
330
        // assert that the beneficiary is AccountKey20.
331
12
        let (parents, interior) = beneficiary.unpack();
332

            
333
12
        let recipient = match (parents, interior) {
334
12
            (0, [AccountKey20 { network, key }]) if self.network_matches(network) => H160(*key),
335
            _ => return Err(BeneficiaryResolutionFailed),
336
        };
337

            
338
        // Make sure there are reserved assets.
339
12
        if reserve_assets.len() == 0 {
340
1
            return Err(NoReserveAssets);
341
11
        }
342
11

            
343
11
        // Check the the deposit asset filter matches what was reserved.
344
11
        if reserve_assets
345
11
            .inner()
346
11
            .iter()
347
11
            .any(|asset| !deposit_assets.matches(asset))
348
        {
349
1
            return Err(FilterDoesNotConsumeAllAssets);
350
10
        }
351
10

            
352
10
        // We only support a single asset at a time.
353
10
        ensure!(reserve_assets.len() == 1, TooManyAssets);
354
10
        let reserve_asset = reserve_assets.get(0).ok_or(AssetResolutionFailed)?;
355

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

            
366
7
        let (asset_id, amount) = match reserve_asset {
367
            Asset {
368
6
                id: AssetId(inner_location),
369
6
                fun: Fungible(amount),
370
6
            } => Some((inner_location.clone(), *amount)),
371
1
            _ => None,
372
        }
373
7
        .ok_or(AssetResolutionFailed)?;
374

            
375
6
        self.check_reserve_asset_para_id(&asset_id)
376
6
            .map_err(|_| ParaIdMismatch)?;
377

            
378
        // transfer amount must be greater than 0.
379
4
        ensure!(amount > 0, ZeroAssetTransfer);
380

            
381
3
        let asset_location = Location {
382
3
            parents: 0,
383
3
            interior: Junctions::X2([Parachain(self.para_id), PalletInstance(10)].into()),
384
3
        };
385

            
386
3
        let reanchored_location = asset_location
387
3
            .reanchored(&EthereumLocation::get(), &UniversalLocation::get())
388
3
            .map_err(|_| AssetReanchorFailed)?;
389

            
390
        // TODO: Move to trace
391
3
        log::info!("reanchored_location={reanchored_location:?}");
392

            
393
3
        let token_id = ConvertAssetId::convert_back(&reanchored_location).ok_or(InvalidAsset)?;
394

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

            
398
2
        Ok((
399
2
            Command::MintForeignToken {
400
2
                token_id,
401
2
                recipient,
402
2
                amount,
403
2
            },
404
2
            *topic_id,
405
2
        ))
406
15
    }
407
}
408

            
409
#[cfg(test)]
410
mod tests {
411
    use {
412
        super::*,
413
        frame_support::parameter_types,
414
        hex_literal::hex,
415
        snowbridge_outbound_queue_primitives::{v1::Fee, SendError, SendMessageFeeProvider},
416
        sp_core::H256,
417
    };
418

            
419
    parameter_types! {
420
        pub EthereumLocation: Location = Location::new(1, EthereumNetwork::get());
421
        const EthereumNetwork: NetworkId = Ethereum { chain_id: 11155111 };
422
        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 ])), Parachain(2001)].into();
423
        const BridgeChannelInfo: Option<(ChannelId, AgentId)> = Some((ChannelId::new([1u8; 32]), H256([2u8; 32])));
424
    }
425

            
426
    pub struct MockTokenIdConvert;
427
    impl MaybeEquivalence<TokenId, Location> for MockTokenIdConvert {
428
        fn convert(_id: &TokenId) -> Option<Location> {
429
            Some(Location::parent())
430
        }
431
3
        fn convert_back(_loc: &Location) -> Option<TokenId> {
432
3
            Some(H256::from_low_u64_be(123))
433
3
        }
434
    }
435

            
436
    struct MockOkOutboundQueue;
437
    impl SendMessage for MockOkOutboundQueue {
438
        type Ticket = ();
439

            
440
1
        fn validate(_: &Message) -> Result<(Self::Ticket, Fee<Self::Balance>), SendError> {
441
1
            Ok((
442
1
                (),
443
1
                Fee {
444
1
                    local: 1,
445
1
                    remote: 1,
446
1
                },
447
1
            ))
448
1
        }
449

            
450
        fn deliver(_: Self::Ticket) -> Result<H256, SendError> {
451
            Ok(H256::zero())
452
        }
453
    }
454

            
455
    impl SendMessageFeeProvider for MockOkOutboundQueue {
456
        type Balance = u128;
457

            
458
        fn local_fee() -> Self::Balance {
459
            1
460
        }
461
    }
462
    struct MockErrOutboundQueue;
463
    impl SendMessage for MockErrOutboundQueue {
464
        type Ticket = ();
465

            
466
        fn validate(_: &Message) -> Result<(Self::Ticket, Fee<Self::Balance>), SendError> {
467
            Err(SendError::MessageTooLarge)
468
        }
469

            
470
        fn deliver(_: Self::Ticket) -> Result<H256, SendError> {
471
            Err(SendError::MessageTooLarge)
472
        }
473
    }
474

            
475
    impl SendMessageFeeProvider for MockErrOutboundQueue {
476
        type Balance = u128;
477

            
478
        fn local_fee() -> Self::Balance {
479
            1
480
        }
481
    }
482

            
483
    #[test]
484
1
    fn exporter_validate_with_unknown_network_yields_not_applicable() {
485
1
        let network = Ethereum { chain_id: 12345 };
486
1
        let channel: u32 = 0;
487
1
        let mut universal_source: Option<InteriorLocation> = None;
488
1
        let mut destination: Option<InteriorLocation> = None;
489
1
        let mut message: Option<Xcm<()>> = None;
490
1

            
491
1
        let result = ContainerEthereumBlobExporter::<
492
1
            UniversalLocation,
493
1
            EthereumNetwork,
494
1
            EthereumLocation,
495
1
            MockOkOutboundQueue,
496
1
            MockTokenIdConvert,
497
1
            BridgeChannelInfo,
498
1
        >::validate(
499
1
            network,
500
1
            channel,
501
1
            &mut universal_source,
502
1
            &mut destination,
503
1
            &mut message,
504
1
        );
505
1
        assert_eq!(result, Err(NotApplicable));
506
1
    }
507

            
508
    #[test]
509
1
    fn exporter_validate_with_empty_destination_yields_missing_argument() {
510
1
        let network = Ethereum { chain_id: 11155111 };
511
1
        let channel: u32 = 0;
512
1
        let mut universal_source: Option<InteriorLocation> = None;
513
1
        let mut destination: Option<InteriorLocation> = None;
514
1
        let mut message: Option<Xcm<()>> = None;
515
1

            
516
1
        let result = ContainerEthereumBlobExporter::<
517
1
            UniversalLocation,
518
1
            EthereumNetwork,
519
1
            EthereumLocation,
520
1
            MockOkOutboundQueue,
521
1
            MockTokenIdConvert,
522
1
            BridgeChannelInfo,
523
1
        >::validate(
524
1
            network,
525
1
            channel,
526
1
            &mut universal_source,
527
1
            &mut destination,
528
1
            &mut message,
529
1
        );
530
1
        assert_eq!(result, Err(MissingArgument));
531
1
    }
532

            
533
    #[test]
534
1
    fn exporter_validate_with_incorrect_destination_yields_not_applicable() {
535
1
        let network = Ethereum { chain_id: 11155111 };
536
1
        let channel: u32 = 0;
537
1
        let mut universal_source: Option<InteriorLocation> = None;
538
1
        let mut destination: Option<InteriorLocation> = Some(
539
1
            [
540
1
                OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild, OnlyChild,
541
1
                OnlyChild,
542
1
            ]
543
1
            .into(),
544
1
        );
545
1
        let mut message: Option<Xcm<()>> = None;
546
1

            
547
1
        let result = ContainerEthereumBlobExporter::<
548
1
            UniversalLocation,
549
1
            EthereumNetwork,
550
1
            EthereumLocation,
551
1
            MockOkOutboundQueue,
552
1
            MockTokenIdConvert,
553
1
            BridgeChannelInfo,
554
1
        >::validate(
555
1
            network,
556
1
            channel,
557
1
            &mut universal_source,
558
1
            &mut destination,
559
1
            &mut message,
560
1
        );
561
1
        assert_eq!(result, Err(NotApplicable));
562
1
    }
563

            
564
    #[test]
565
1
    fn exporter_validate_with_incorrect_universal_source_yields_validation_error() {
566
1
        let network = Ethereum { chain_id: 11155111 };
567
1
        let channel: u32 = 0;
568
1
        let mut universal_source: Option<InteriorLocation> = None;
569
1
        let mut destination: Option<InteriorLocation> = Here.into();
570
1
        let mut message: Option<Xcm<()>> = None;
571
1

            
572
1
        let result = ContainerEthereumBlobExporter::<
573
1
            UniversalLocation,
574
1
            EthereumNetwork,
575
1
            EthereumLocation,
576
1
            MockOkOutboundQueue,
577
1
            MockTokenIdConvert,
578
1
            BridgeChannelInfo,
579
1
        >::validate(
580
1
            network,
581
1
            channel,
582
1
            &mut universal_source,
583
1
            &mut destination,
584
1
            &mut message,
585
1
        );
586
1
        assert_eq!(result, Err(MissingArgument));
587

            
588
1
        let mut universal_source: Option<InteriorLocation> = Here.into();
589
1
        let result = ContainerEthereumBlobExporter::<
590
1
            UniversalLocation,
591
1
            EthereumNetwork,
592
1
            EthereumLocation,
593
1
            MockOkOutboundQueue,
594
1
            MockTokenIdConvert,
595
1
            BridgeChannelInfo,
596
1
        >::validate(
597
1
            network,
598
1
            channel,
599
1
            &mut universal_source,
600
1
            &mut destination,
601
1
            &mut message,
602
1
        );
603
1
        assert_eq!(result, Err(NotApplicable));
604
1
    }
605

            
606
    #[test]
607
1
    fn exporter_validate_with_missing_para_id_universal_source_yields_validation_error() {
608
1
        let network = Ethereum { chain_id: 11155111 };
609
1
        let channel: u32 = 0;
610
1
        let mut universal_source: Option<InteriorLocation> = Some(
611
1
            [GlobalConsensus(ByGenesis([
612
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
613
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
614
1
            ]))]
615
1
            .into(),
616
1
        );
617
1
        let mut destination: Option<InteriorLocation> = Here.into();
618
1
        let mut message: Option<Xcm<()>> = None;
619
1

            
620
1
        let result = ContainerEthereumBlobExporter::<
621
1
            UniversalLocation,
622
1
            EthereumNetwork,
623
1
            EthereumLocation,
624
1
            MockOkOutboundQueue,
625
1
            MockTokenIdConvert,
626
1
            BridgeChannelInfo,
627
1
        >::validate(
628
1
            network,
629
1
            channel,
630
1
            &mut universal_source,
631
1
            &mut destination,
632
1
            &mut message,
633
1
        );
634
1
        assert_eq!(result, Err(NotApplicable));
635
1
    }
636

            
637
    #[test]
638
1
    fn exporter_validate_with_empty_message_yields_missing_argument() {
639
1
        let network = Ethereum { chain_id: 11155111 };
640
1
        let channel: u32 = 0;
641
1
        let mut universal_source: Option<InteriorLocation> = Some(
642
1
            [
643
1
                GlobalConsensus(ByGenesis([
644
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
645
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
646
1
                ])),
647
1
                Parachain(2001),
648
1
            ]
649
1
            .into(),
650
1
        );
651
1
        let mut destination: Option<InteriorLocation> = Here.into();
652
1
        let mut message: Option<Xcm<()>> = None;
653
1

            
654
1
        let result = ContainerEthereumBlobExporter::<
655
1
            UniversalLocation,
656
1
            EthereumNetwork,
657
1
            EthereumLocation,
658
1
            MockOkOutboundQueue,
659
1
            MockTokenIdConvert,
660
1
            BridgeChannelInfo,
661
1
        >::validate(
662
1
            network,
663
1
            channel,
664
1
            &mut universal_source,
665
1
            &mut destination,
666
1
            &mut message,
667
1
        );
668
1
        assert_eq!(result, Err(MissingArgument));
669
1
    }
670

            
671
    #[test]
672
1
    fn exporter_incorrect_message_yields_incorrect_instruction() {
673
1
        let network = Ethereum { chain_id: 11155111 };
674
1
        let channel: u32 = 0;
675
1
        let mut universal_source: Option<InteriorLocation> = Some(
676
1
            [
677
1
                GlobalConsensus(ByGenesis([
678
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
679
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
680
1
                ])),
681
1
                Parachain(2001),
682
1
            ]
683
1
            .into(),
684
1
        );
685
1
        let mut destination: Option<InteriorLocation> = Here.into();
686
1
        let mut message: Option<Xcm<()>> = Some(vec![SetTopic([0; 32])].into());
687
1

            
688
1
        let result = ContainerEthereumBlobExporter::<
689
1
            UniversalLocation,
690
1
            EthereumNetwork,
691
1
            EthereumLocation,
692
1
            MockOkOutboundQueue,
693
1
            MockTokenIdConvert,
694
1
            BridgeChannelInfo,
695
1
        >::validate(
696
1
            network,
697
1
            channel,
698
1
            &mut universal_source,
699
1
            &mut destination,
700
1
            &mut message,
701
1
        );
702
1
        assert_eq!(result, Err(Unroutable));
703
1
    }
704

            
705
    #[test]
706
1
    fn exporter_incorrect_clear_origin_yields_incorrect_instruction() {
707
1
        let network = Ethereum { chain_id: 11155111 };
708
1
        let channel: u32 = 0;
709
1
        let mut universal_source: Option<InteriorLocation> = Some(
710
1
            [
711
1
                GlobalConsensus(ByGenesis([
712
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
713
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
714
1
                ])),
715
1
                Parachain(2001),
716
1
            ]
717
1
            .into(),
718
1
        );
719
1
        let mut destination: Option<InteriorLocation> = Here.into();
720
1
        let asset_location = Location::new(
721
1
            1,
722
1
            [GlobalConsensus(ByGenesis([
723
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
724
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
725
1
            ]))],
726
1
        );
727
1
        let assets: Assets = vec![Asset {
728
1
            id: AssetId(asset_location),
729
1
            fun: Fungible(123321000000000000),
730
1
        }]
731
1
        .into();
732
1

            
733
1
        let mut message: Option<Xcm<()>> = Some(
734
1
            vec![
735
1
                ReserveAssetDeposited(assets.clone()),
736
1
                BuyExecution {
737
1
                    fees: assets.get(0).unwrap().clone(),
738
1
                    weight_limit: Unlimited,
739
1
                },
740
1
            ]
741
1
            .into(),
742
1
        );
743
1

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

            
761
    #[test]
762
1
    fn exporter_incorrect_buy_execution_yields_unroutable() {
763
1
        let network = Ethereum { chain_id: 11155111 };
764
1
        let channel: u32 = 0;
765
1
        let mut universal_source: Option<InteriorLocation> = Some(
766
1
            [
767
1
                GlobalConsensus(ByGenesis([
768
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
769
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
770
1
                ])),
771
1
                Parachain(2001),
772
1
            ]
773
1
            .into(),
774
1
        );
775
1
        let mut destination: Option<InteriorLocation> = Here.into();
776
1
        let asset_location = Location::new(
777
1
            1,
778
1
            [GlobalConsensus(ByGenesis([
779
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
780
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
781
1
            ]))],
782
1
        );
783
1
        let assets: Assets = vec![Asset {
784
1
            id: AssetId(asset_location),
785
1
            fun: Fungible(123321000000000000),
786
1
        }]
787
1
        .into();
788
1

            
789
1
        let mut message: Option<Xcm<()>> = Some(
790
1
            vec![
791
1
                ReserveAssetDeposited(assets.clone()),
792
1
                ClearOrigin,
793
1
                ClearOrigin,
794
1
            ]
795
1
            .into(),
796
1
        );
797
1

            
798
1
        let result = ContainerEthereumBlobExporter::<
799
1
            UniversalLocation,
800
1
            EthereumNetwork,
801
1
            EthereumLocation,
802
1
            MockOkOutboundQueue,
803
1
            MockTokenIdConvert,
804
1
            BridgeChannelInfo,
805
1
        >::validate(
806
1
            network,
807
1
            channel,
808
1
            &mut universal_source,
809
1
            &mut destination,
810
1
            &mut message,
811
1
        );
812
1
        assert_eq!(result, Err(Unroutable));
813
1
    }
814

            
815
    #[test]
816
1
    fn exporter_lack_of_deposit_asset_yields_unroutable() {
817
1
        let network = Ethereum { chain_id: 11155111 };
818
1
        let channel: u32 = 0;
819
1
        let mut universal_source: Option<InteriorLocation> = Some(
820
1
            [
821
1
                GlobalConsensus(ByGenesis([
822
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
823
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
824
1
                ])),
825
1
                Parachain(2001),
826
1
            ]
827
1
            .into(),
828
1
        );
829
1
        let mut destination: Option<InteriorLocation> = Here.into();
830
1
        let asset_location = Location::new(
831
1
            1,
832
1
            [GlobalConsensus(ByGenesis([
833
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
834
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
835
1
            ]))],
836
1
        );
837
1
        let assets: Assets = vec![Asset {
838
1
            id: AssetId(asset_location),
839
1
            fun: Fungible(123321000000000000),
840
1
        }]
841
1
        .into();
842
1

            
843
1
        let mut message: Option<Xcm<()>> = Some(
844
1
            vec![
845
1
                ReserveAssetDeposited(assets.clone()),
846
1
                ClearOrigin,
847
1
                BuyExecution {
848
1
                    fees: assets.get(0).unwrap().clone(),
849
1
                    weight_limit: Unlimited,
850
1
                },
851
1
            ]
852
1
            .into(),
853
1
        );
854
1

            
855
1
        let result = ContainerEthereumBlobExporter::<
856
1
            UniversalLocation,
857
1
            EthereumNetwork,
858
1
            EthereumLocation,
859
1
            MockOkOutboundQueue,
860
1
            MockTokenIdConvert,
861
1
            BridgeChannelInfo,
862
1
        >::validate(
863
1
            network,
864
1
            channel,
865
1
            &mut universal_source,
866
1
            &mut destination,
867
1
            &mut message,
868
1
        );
869
1
        assert_eq!(result, Err(Unroutable));
870
1
    }
871

            
872
    #[test]
873
1
    fn exporter_incorrect_beneficiary_yields_unroutable() {
874
1
        let network = Ethereum { chain_id: 11155111 };
875
1
        let channel: u32 = 0;
876
1
        let mut universal_source: Option<InteriorLocation> = Some(
877
1
            [
878
1
                GlobalConsensus(ByGenesis([
879
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
880
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
881
1
                ])),
882
1
                Parachain(2001),
883
1
            ]
884
1
            .into(),
885
1
        );
886
1
        let mut destination: Option<InteriorLocation> = Here.into();
887
1
        let asset_location = Location::new(
888
1
            1,
889
1
            [GlobalConsensus(ByGenesis([
890
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
891
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
892
1
            ]))],
893
1
        );
894
1
        let assets: Assets = vec![Asset {
895
1
            id: AssetId(asset_location),
896
1
            fun: Fungible(123321000000000000),
897
1
        }]
898
1
        .into();
899
1

            
900
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
901
1
        let filter: AssetFilter = assets.clone().into();
902
1
        let mut message: Option<Xcm<()>> = Some(
903
1
            vec![
904
1
                ReserveAssetDeposited(assets.clone()),
905
1
                ClearOrigin,
906
1
                BuyExecution {
907
1
                    fees: assets.get(0).unwrap().clone(),
908
1
                    weight_limit: Unlimited,
909
1
                },
910
1
                DepositAsset {
911
1
                    assets: filter,
912
1
                    beneficiary: AccountKey20 {
913
1
                        network: None,
914
1
                        key: beneficiary_address,
915
1
                    }
916
1
                    .into(),
917
1
                },
918
1
            ]
919
1
            .into(),
920
1
        );
921
1

            
922
1
        let result = ContainerEthereumBlobExporter::<
923
1
            UniversalLocation,
924
1
            EthereumNetwork,
925
1
            EthereumLocation,
926
1
            MockOkOutboundQueue,
927
1
            MockTokenIdConvert,
928
1
            BridgeChannelInfo,
929
1
        >::validate(
930
1
            network,
931
1
            channel,
932
1
            &mut universal_source,
933
1
            &mut destination,
934
1
            &mut message,
935
1
        );
936
1
        assert_eq!(result, Err(Unroutable));
937
1
    }
938

            
939
    #[test]
940
1
    fn exporter_empty_reserve_assets_yields_unroutable() {
941
1
        let network = Ethereum { chain_id: 11155111 };
942
1
        let channel: u32 = 0;
943
1
        let mut universal_source: Option<InteriorLocation> = Some(
944
1
            [
945
1
                GlobalConsensus(ByGenesis([
946
1
                    152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43,
947
1
                    81, 39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
948
1
                ])),
949
1
                Parachain(2001),
950
1
            ]
951
1
            .into(),
952
1
        );
953
1
        let mut destination: Option<InteriorLocation> = Here.into();
954
1
        let asset_location = Location::new(
955
1
            1,
956
1
            [GlobalConsensus(ByGenesis([
957
1
                152, 58, 26, 114, 80, 61, 108, 195, 99, 103, 118, 116, 126, 198, 39, 23, 43, 81,
958
1
                39, 43, 244, 94, 80, 163, 85, 52, 143, 172, 182, 122, 130, 10,
959
1
            ]))],
960
1
        );
961
1
        let assets: Assets = vec![Asset {
962
1
            id: AssetId(asset_location),
963
1
            fun: Fungible(123321000000000000),
964
1
        }]
965
1
        .into();
966
1

            
967
1
        let beneficiary_address: [u8; 20] = hex!("2000000000000000000000000000000000000000");
968
1
        let filter: AssetFilter = assets.clone().into();
969
1
        let mut message: Option<Xcm<()>> = Some(
970
1
            vec![
971
1
                ReserveAssetDeposited(vec![].into()),
972
1
                ClearOrigin,
973
1
                BuyExecution {
974
1
                    fees: assets.get(0).unwrap().clone(),
975
1
                    weight_limit: Unlimited,
976
1
                },
977
1
                DepositAsset {
978
1
                    assets: filter,
979
1
                    beneficiary: AccountKey20 {
980
1
                        network: Some(Ethereum { chain_id: 11155111 }),
981
1
                        key: beneficiary_address,
982
1
                    }
983
1
                    .into(),
984
1
                },
985
1
            ]
986
1
            .into(),
987
1
        );
988
1

            
989
1
        let result = ContainerEthereumBlobExporter::<
990
1
            UniversalLocation,
991
1
            EthereumNetwork,
992
1
            EthereumLocation,
993
1
            MockOkOutboundQueue,
994
1
            MockTokenIdConvert,
995
1
            BridgeChannelInfo,
996
1
        >::validate(
997
1
            network,
998
1
            channel,
999
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
    }
}