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

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

            
87
41
        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
40
        }
91

            
92
        // Cloning destination to avoid modifying the value so subsequent exporters can use it.
93
40
        let dest = destination.clone().take().ok_or(MissingArgument)?;
94
39
        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
38
        }
98

            
99
        // Cloning universal_source to avoid modifying the value so subsequent exporters can use it.
100
38
        let (local_net, local_sub) = universal_source.clone()
101
38
            .take()
102
38
            .ok_or_else(|| {
103
1
                log::error!(target: "xcm::ethereum_blob_exporter", "universal source not provided.");
104
1
                MissingArgument
105
38
            })?
106
37
            .split_global()
107
37
            .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
37
            })?;
111

            
112
36
        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
36
        }
116

            
117
36
        let para_id = match local_sub.as_slice() {
118
35
            [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
35
        let message = message.take().ok_or_else(|| {
126
1
            log::error!(target: "xcm::ethereum_blob_exporter", "xcm message not provided.");
127
1
            MissingArgument
128
35
        })?;
129

            
130
34
        let mut converter =
131
34
            XcmConverter::<ConvertAssetId, UniversalLocation, EthereumLocation, ()>::new(
132
34
                &message,
133
34
                expected_network,
134
34
                para_id,
135
34
            );
136
34
        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
34
        })?;
140

            
141
19
        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
19
        })?;
145

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

            
152
        // validate the message
153
19
        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
19
        })?;
157

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

            
161
19
        Ok(((ticket.encode(), message_id), fee))
162
41
    }
163

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

            
171
18
        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
18
        })?;
175

            
176
18
        log::info!(target: "xcm::ethereum_blob_exporter", "message delivered {message_id:#?}.");
177
18
        Ok(message_id.into())
178
18
    }
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
34
    fn new(message: &'a Xcm<Call>, ethereum_network: NetworkId, para_id: u32) -> Self {
268
34
        Self {
269
34
            iter: message.inner().iter().peekable(),
270
34
            ethereum_network,
271
34
            para_id,
272
34
            _marker: Default::default(),
273
34
        }
274
34
    }
275

            
276
34
    fn convert(&mut self) -> Result<(Command, [u8; 32]), XcmConverterError> {
277
34
        let result = match self.peek() {
278
            // Prepare the command to mint the foreign token.
279
33
            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
20
        if self.next().is_ok() {
289
1
            return Err(XcmConverterError::EndOfXcmMessageExpected);
290
19
        }
291
19

            
292
19
        Ok(result)
293
34
    }
294

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

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

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

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

            
320
24
        match &location.interior {
321
22
            Junctions::X3(arc) => {
322
22
                let [j1, j2, _] = arc.as_ref();
323
22
                if let GlobalConsensus(_) = j1 {
324
22
                    if let Parachain(id) = j2 {
325
22
                        if *id == self.para_id {
326
22
                            return Ok(());
327
                        }
328
                    }
329
                }
330
                Err(())
331
            }
332
2
            _ => Err(()),
333
        }
334
24
    }
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
33
    fn make_mint_foreign_token_command(
344
33
        &mut self,
345
33
    ) -> Result<(Command, [u8; 32]), XcmConverterError> {
346
        use XcmConverterError::*;
347

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

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

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

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

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

            
374
30
        let recipient = match (parents, interior) {
375
30
            (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
30
        if reserve_assets.len() == 0 {
381
1
            return Err(NoReserveAssets);
382
29
        }
383
29

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

            
393
28
        // We only support a single asset at a time.
394
28
        ensure!(reserve_assets.len() == 1, TooManyAssets);
395
28
        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
28
        if fee_asset.id != reserve_asset.id || fee_asset.fun > reserve_asset.fun {
404
3
            return Err(InvalidFeeAsset);
405
25
        }
406

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

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

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

            
422
21
        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
21
        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
21
        let topic_id = match_expression!(self.next()?, SetTopic(id), id).ok_or(SetTopicExpected)?;
431

            
432
20
        Ok((
433
20
            Command::MintForeignToken {
434
20
                token_id,
435
20
                recipient,
436
20
                amount,
437
20
            },
438
20
            *topic_id,
439
20
        ))
440
33
    }
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
    }
}