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
//! Shared code between relay runtimes.
18

            
19
extern crate alloc;
20

            
21
use alloc::{vec, vec::Vec};
22
use core::marker::PhantomData;
23
use frame_support::{
24
    pallet_prelude::Zero,
25
    traits::{
26
        fungible::{Inspect, Mutate},
27
        tokens::{Fortitude, Preservation},
28
    },
29
};
30
use frame_system::pallet_prelude::BlockNumberFor;
31
use parity_scale_codec::{DecodeAll, Encode, EncodeLike};
32
use snowbridge_core::Channel;
33
use snowbridge_pallet_inbound_queue::RewardProcessor;
34
use sp_core::{Get, H160, H256};
35
use sp_runtime::{
36
    traits::{Hash as _, MaybeEquivalence},
37
    DispatchError, DispatchResult,
38
};
39
use xcm::latest::{
40
    prelude::*, Asset as XcmAsset, AssetId as XcmAssetId, Assets as XcmAssets, ExecuteXcm,
41
    Fungibility, Junctions::*,
42
};
43
use xcm_builder::{deposit_or_burn_fee, HandleFee};
44
use xcm_executor::traits::{FeeReason, TransactAsset, WeightBounds};
45
use {
46
    snowbridge_inbound_queue_primitives::v1::{
47
        Command, Destination, Envelope, MessageProcessor, MessageV1, VersionedXcmMessage,
48
    },
49
    snowbridge_inbound_queue_primitives::EventProof as Message,
50
};
51

            
52
/// Validates the gateway and channel of an inbound envelope
53
pub struct GatewayAndChannelValidator<T>(PhantomData<T>);
54
impl<T> GatewayAndChannelValidator<T>
55
where
56
    T: snowbridge_pallet_inbound_queue::Config + pallet_ethereum_token_transfers::Config,
57
{
58
69
    pub fn validate_gateway_and_channel(channel: &Channel, envelope: &Envelope) -> bool {
59
        // Ensure that the message is intended for the current channel, para_id and agent_id
60
69
        if let Some(channel_info) = pallet_ethereum_token_transfers::CurrentChannelInfo::<T>::get()
61
        {
62
66
            if envelope.channel_id != channel_info.channel_id
63
64
                || channel.para_id != channel_info.para_id
64
62
                || channel.agent_id != channel_info.agent_id
65
            {
66
6
                log::debug!(
67
                    "Unexpected channel id: {:?} != {:?}",
68
                    (envelope.channel_id, channel.para_id, channel.agent_id),
69
                    (
70
                        channel_info.channel_id,
71
                        channel_info.para_id,
72
                        channel_info.agent_id
73
                    )
74
                );
75
6
                return false;
76
60
            }
77
        } else {
78
3
            log::warn!("CurrentChannelInfo not set in storage");
79
3
            return false;
80
        }
81

            
82
        // Check it is from the right gateway
83
60
        if envelope.gateway != T::GatewayAddress::get() {
84
3
            log::warn!("Wrong gateway address: {:?}", envelope.gateway);
85
3
            return false;
86
57
        }
87
57
        true
88
69
    }
89
}
90

            
91
/// Information needed to process a native token transfer message from ethereum.
92
pub struct NativeTokenTransferData {
93
    pub token_id: H256,
94
    pub destination: Destination,
95
    pub amount: u128,
96
    pub fee: u128,
97
}
98

            
99
impl NativeTokenTransferData {
100
1219
    pub fn decode_native_token_message(mut payload: &[u8]) -> Option<Self> {
101
1219
        match VersionedXcmMessage::decode_all(&mut payload) {
102
            Ok(VersionedXcmMessage::V1(MessageV1 {
103
                command:
104
                    Command::SendNativeToken {
105
1173
                        token_id,
106
1173
                        destination,
107
1173
                        amount,
108
1173
                        fee,
109
                    },
110
                ..
111
1173
            })) => Some(NativeTokenTransferData {
112
1173
                token_id,
113
1173
                destination,
114
1173
                amount,
115
1173
                fee,
116
1173
            }),
117
46
            Ok(msg) => {
118
46
                log::trace!("NativeTokenTransferData: unexpected message: {:?}", msg);
119
46
                None
120
            }
121
            Err(e) => {
122
                log::trace!("NativeTokenTransferData: failed to decode message. This is expected if the message is not related to a SendNativeToken command. Error: {:?}", e);
123
                None
124
            }
125
        }
126
1219
    }
127
}
128

            
129
/// `NativeTokenTransferMessageProcessor` is responsible for receiving and processing the Tanssi
130
/// native token sent from Ethereum. If the message is valid, it performs the token transfer
131
/// from the Ethereum sovereign account to the specified destination account.
132
pub struct NativeTokenTransferMessageProcessor<T>(PhantomData<T>);
133
impl<T> MessageProcessor for NativeTokenTransferMessageProcessor<T>
134
where
135
    T: snowbridge_pallet_inbound_queue::Config
136
        + pallet_ethereum_token_transfers::Config
137
        + snowbridge_pallet_system::Config,
138
    T::AccountId: From<[u8; 32]>,
139
{
140
35
    fn can_process_message(channel: &Channel, envelope: &Envelope) -> bool {
141
35
        if !GatewayAndChannelValidator::<T>::validate_gateway_and_channel(channel, envelope) {
142
10
            log::warn!("NativeTokenTransferMessageProcessor: invalid gateway or channel");
143
10
            return false;
144
25
        }
145

            
146
        // Try decode the message and check the token id is the expected one
147
23
        if let Some(token_data) =
148
25
            NativeTokenTransferData::decode_native_token_message(&envelope.payload)
149
        {
150
23
            let token_location = T::TokenLocationReanchored::get();
151

            
152
12
            if let Some(expected_token_id) =
153
23
                snowbridge_pallet_system::Pallet::<T>::convert_back(&token_location)
154
            {
155
12
                if token_data.token_id == expected_token_id {
156
12
                    true
157
                } else {
158
                    // TODO: ensure this does not warn on container token transfers or other message types, if yes change to debug
159
                    log::warn!(
160
                        "NativeTokenTransferMessageProcessor: unexpected token_id: {:?}",
161
                        token_data.token_id
162
                    );
163
                    false
164
                }
165
            } else {
166
11
                log::warn!(
167
                    "NativeTokenTransferMessageProcessor: token id not found for location: {:?}",
168
                    token_location
169
                );
170
11
                false
171
            }
172
        } else {
173
2
            false
174
        }
175
35
    }
176

            
177
12
    fn process_message(_channel: Channel, envelope: Envelope) -> DispatchResult {
178
        // Decode payload as SendNativeToken using the helper function
179
12
        if let Some(token_data) =
180
12
            NativeTokenTransferData::decode_native_token_message(&envelope.payload)
181
        {
182
12
            log::trace!("NativeTokenTransferMessageProcessor: processing token transfer: token_id={:?}, amount={}, destination={:?}", 
183
                token_data.token_id, token_data.amount, token_data.destination);
184

            
185
12
            match token_data.destination {
186
                Destination::AccountId32 {
187
8
                    id: destination_account,
188
                } => {
189
                    // Transfer the amounts of tokens from Ethereum sov account to the destination
190
8
                    let sovereign_account = T::EthereumSovereignAccount::get();
191

            
192
8
                    if let Err(e) = T::Currency::transfer(
193
8
                        &sovereign_account,
194
8
                        &destination_account.into(),
195
8
                        token_data.amount.into(),
196
8
                        Preservation::Preserve,
197
8
                    ) {
198
2
                        log::warn!(
199
                            "NativeTokenTransferMessageProcessor: Error transferring tokens: {:?}",
200
                            e
201
                        );
202
6
                    }
203

            
204
8
                    Ok(())
205
                }
206
                _ => {
207
4
                    log::warn!(
208
                        "NativeTokenTransferMessageProcessor: unsupported destination type: {:?}",
209
                        token_data.destination
210
                    );
211
4
                    Ok(())
212
                }
213
            }
214
        } else {
215
            log::trace!("NativeTokenTransferMessageProcessor: failed to decode message. This is expected if the message is not for this processor.");
216
            Err(DispatchError::Other("unable to parse the envelope payload"))
217
        }
218
12
    }
219
}
220

            
221
/// `NativeContainerTokensProcessor` is responsible for receiving and processing native container
222
/// chain tokens coming from Ethereum and forwarding them to the container chain via Tanssi through XCM.
223
pub struct NativeContainerTokensProcessor<
224
    T,
225
    AssetTransactor,
226
    EthereumLocation,
227
    EthereumNetwork,
228
    InboundQueuePalletInstance,
229
    TanssiLocationReanchored,
230
>(
231
    PhantomData<(
232
        T,
233
        AssetTransactor,
234
        EthereumLocation,
235
        EthereumNetwork,
236
        InboundQueuePalletInstance,
237
        TanssiLocationReanchored,
238
    )>,
239
);
240

            
241
impl<
242
        T,
243
        AssetTransactor,
244
        EthereumLocation,
245
        EthereumNetwork,
246
        InboundQueuePalletInstance,
247
        TanssiLocationReanchored,
248
    > MessageProcessor
249
    for NativeContainerTokensProcessor<
250
        T,
251
        AssetTransactor,
252
        EthereumLocation,
253
        EthereumNetwork,
254
        InboundQueuePalletInstance,
255
        TanssiLocationReanchored,
256
    >
257
where
258
    T: snowbridge_pallet_inbound_queue::Config
259
        + pallet_ethereum_token_transfers::Config
260
        + snowbridge_pallet_system::Config
261
        + pallet_xcm::Config,
262
    <T as frame_system::Config>::RuntimeEvent: From<pallet_xcm::Event<T>>,
263
    <T as frame_system::Config>::AccountId: Into<Location>,
264
    AssetTransactor: TransactAsset,
265
    EthereumLocation: Get<Location>,
266
    EthereumNetwork: Get<NetworkId>,
267
    InboundQueuePalletInstance: Get<u8>,
268
    TanssiLocationReanchored: Get<Location>,
269
{
270
14
    fn can_process_message(channel: &Channel, envelope: &Envelope) -> bool {
271
        // Validate channel and gateway
272
14
        if !GatewayAndChannelValidator::<T>::validate_gateway_and_channel(channel, envelope) {
273
2
            log::warn!(
274
                "NativeContainerTokensProcessor::can_process_message: invalid gateway or channel"
275
            );
276
2
            return false;
277
12
        }
278

            
279
        // Try decoding the message and check if the destination owns the token being transferred
280
12
        match Self::get_token_data_and_location(&envelope.payload) {
281
11
            TokenDataResult::Success(token_data, token_location) => {
282
11
                Self::validate_destination_owns_token(&token_location, &token_data.destination)
283
            }
284
            TokenDataResult::DecodeFailure => {
285
                log::error!(
286
                    "NativeContainerTokensProcessor::can_process_message: failed to decode token data"
287
                );
288
                false
289
            }
290
1
            TokenDataResult::LocationNotFound(token_data) => {
291
1
                log::error!(
292
1
                    "NativeContainerTokensProcessor::can_process_message: token location not found for token_id: {:?}",
293
                    token_data.token_id
294
                );
295
1
                false
296
            }
297
            TokenDataResult::UnsupportedToken => {
298
                log::error!(
299
                    "NativeContainerTokensProcessor::can_process_message: unsupported token"
300
                );
301
                false
302
            }
303
        }
304
14
    }
305

            
306
4
    fn process_message(_channel: Channel, envelope: Envelope) -> DispatchResult {
307
4
        match Self::get_token_data_and_location(&envelope.payload) {
308
4
            TokenDataResult::Success(token_data, token_location) => {
309
4
                Self::process_native_container_token_transfer(token_data, token_location);
310
4
                Ok(())
311
            }
312
            TokenDataResult::DecodeFailure => Err(DispatchError::Other(
313
                "NativeContainerTokensProcessor: unexpected message",
314
            )),
315
            TokenDataResult::LocationNotFound(token_data) => {
316
                log::warn!(
317
                    "NativeContainerTokensProcessor::process_message: token location not found for token_id: {:?}",
318
                    token_data.token_id
319
                );
320
                Ok(())
321
            }
322
            TokenDataResult::UnsupportedToken => {
323
                log::error!("NativeContainerTokensProcessor::process_message: unsupported token");
324
                Ok(())
325
            }
326
        }
327
4
    }
328
}
329

            
330
/// Result of token data and location extraction
331
enum TokenDataResult {
332
    /// Successfully extracted both token data and location
333
    Success(NativeTokenTransferData, Location),
334
    /// Failed to decode token data from payload
335
    DecodeFailure,
336
    /// Token data decoded but location not found
337
    LocationNotFound(NativeTokenTransferData),
338
    /// Token data decoded but the token is not supported
339
    UnsupportedToken,
340
}
341

            
342
impl<
343
        T,
344
        AssetTransactor,
345
        EthereumLocation,
346
        EthereumNetwork,
347
        InboundQueuePalletInstance,
348
        TanssiLocationReanchored,
349
    >
350
    NativeContainerTokensProcessor<
351
        T,
352
        AssetTransactor,
353
        EthereumLocation,
354
        EthereumNetwork,
355
        InboundQueuePalletInstance,
356
        TanssiLocationReanchored,
357
    >
358
where
359
    T: snowbridge_pallet_inbound_queue::Config
360
        + pallet_ethereum_token_transfers::Config
361
        + snowbridge_pallet_system::Config
362
        + pallet_xcm::Config,
363
    <T as frame_system::Config>::RuntimeEvent: From<pallet_xcm::Event<T>>,
364
    <T as frame_system::Config>::AccountId: Into<Location>,
365
    AssetTransactor: TransactAsset,
366
    EthereumLocation: Get<Location>,
367
    EthereumNetwork: Get<NetworkId>,
368
    InboundQueuePalletInstance: Get<u8>,
369
    TanssiLocationReanchored: Get<Location>,
370
{
371
    /// Decodes token data from payload and gets the corresponding token location.
372
    /// Returns different outcomes based on what succeeded or failed.
373
16
    fn get_token_data_and_location(payload: &[u8]) -> TokenDataResult {
374
16
        if let Some(token_data) = NativeTokenTransferData::decode_native_token_message(payload) {
375
15
            if let Some(token_location) =
376
16
                snowbridge_pallet_system::Pallet::<T>::convert(&token_data.token_id)
377
            {
378
15
                if token_location == TanssiLocationReanchored::get() {
379
                    // Extra safety check to forbid native Tanssi token for this processor
380
                    TokenDataResult::UnsupportedToken
381
                } else {
382
15
                    TokenDataResult::Success(token_data, token_location)
383
                }
384
            } else {
385
1
                TokenDataResult::LocationNotFound(token_data)
386
            }
387
        } else {
388
            TokenDataResult::DecodeFailure
389
        }
390
16
    }
391

            
392
    /// Validates that the destination para_id owns the token being transferred.
393
11
    fn validate_destination_owns_token(
394
11
        token_location: &Location,
395
11
        destination: &Destination,
396
11
    ) -> bool {
397
        // Extract para_id from destination - only foreign destinations are supported
398
11
        let expected_para_id = match destination {
399
4
            Destination::ForeignAccountId32 { para_id, .. } => *para_id,
400
6
            Destination::ForeignAccountId20 { para_id, .. } => *para_id,
401
            _ => {
402
1
                log::error!(
403
1
                    "NativeContainerTokensProcessor: unsupported destination type: {:?}",
404
                    destination
405
                );
406
1
                return false;
407
            }
408
        };
409

            
410
10
        let chain_part = token_location.interior().clone().split_global().ok();
411

            
412
10
        match chain_part {
413
10
            Some((_, interior)) => {
414
10
                if let Some(Parachain(id)) = interior.first() {
415
10
                    expected_para_id == *id
416
                } else {
417
                    log::error!(
418
                        "NativeContainerTokensProcessor: destination doesn't own the token!"
419
                    );
420
                    false
421
                }
422
            }
423
            _ => {
424
                log::error!("NativeContainerTokensProcessor: invalid chain part");
425
                false
426
            }
427
        }
428
11
    }
429

            
430
    /// Process a native container token transfer by creating and sending an XCM message to the destination parachain.
431
4
    fn process_native_container_token_transfer(
432
4
        token_data: NativeTokenTransferData,
433
4
        token_location: Location,
434
4
    ) {
435
4
        let interior = match token_location.interior().clone().split_global().ok() {
436
4
            Some((_, interior)) => interior,
437
            None => {
438
                log::error!(
439
                    "NativeContainerTokensProcessor: failed to split global on token location"
440
                );
441
                return;
442
            }
443
        };
444

            
445
4
        let (beneficiary, container_fee, container_para_id) = match token_data.destination {
446
1
            Destination::ForeignAccountId32 { para_id, id, fee } => {
447
1
                let beneficiary = Location::new(0, [AccountId32 { network: None, id }]);
448
1
                (beneficiary, fee, para_id)
449
            }
450
3
            Destination::ForeignAccountId20 { para_id, id, fee } => {
451
3
                let beneficiary = Location::new(
452
                    0,
453
3
                    [AccountKey20 {
454
3
                        network: None,
455
3
                        key: id,
456
3
                    }],
457
                );
458
3
                (beneficiary, fee, para_id)
459
            }
460
            _ => {
461
                log::error!("NativeContainerTokensProcessor::process_native_token_transfer: invalid destination");
462
                return;
463
            }
464
        };
465

            
466
4
        let container_location = Location::new(0, [Parachain(container_para_id)]);
467

            
468
4
        let container_token_from_tanssi = Location::new(0, interior);
469
4
        let token_location_reanchored = match container_token_from_tanssi.reanchored(
470
4
            &container_location,
471
4
            &<T as pallet_xcm::Config>::UniversalLocation::get(),
472
4
        ) {
473
4
            Ok(loc) => loc,
474
            Err(e) => {
475
                log::error!(
476
                        "NativeContainerTokensProcessor: failed to reanchor container token location: {:?}",
477
                        e
478
                    );
479
                return;
480
            }
481
        };
482

            
483
        // Fees are going to be paid with the native relay token on the container chain
484
4
        let asset_fee_relay: Asset = (Location::here(), container_fee).into();
485

            
486
        // Reanchor the asset fee to the container chain location
487
4
        let relay_asset_fee_container_context = match asset_fee_relay.clone().reanchored(
488
4
            &container_location,
489
4
            &<T as pallet_xcm::Config>::UniversalLocation::get(),
490
4
        ) {
491
4
            Ok(loc) => loc,
492
            Err(e) => {
493
                log::error!(
494
                    "NativeContainerTokensProcessor: failed to reanchor relay token location: {:?}",
495
                    e
496
                );
497
                return;
498
            }
499
        };
500

            
501
4
        let dummy_context = XcmContext {
502
4
            origin: None,
503
4
            message_id: Default::default(),
504
4
            topic: None,
505
4
        };
506

            
507
        // Transfer fee from FeesAccount to container sovereign account
508
4
        if let Err(e) = AssetTransactor::transfer_asset(
509
4
            &asset_fee_relay,
510
4
            &T::FeesAccount::get().into(),
511
4
            &container_location,
512
4
            &dummy_context,
513
4
        ) {
514
1
            log::error!(
515
1
                "NativeContainerTokensProcessor: failed to transfer fee from FeesAccount to container sovereign account: {:?}",
516
                e
517
            );
518
1
            return;
519
3
        }
520

            
521
        // Reanchor Ethereum location to the container chain's point of view
522
3
        let bridge_location = match EthereumLocation::get().clone().reanchored(
523
3
            &container_location,
524
3
            &<T as pallet_xcm::Config>::UniversalLocation::get(),
525
3
        ) {
526
3
            Ok(loc) => loc,
527
            Err(e) => {
528
                log::error!(
529
                    "NativeContainerTokensProcessor: failed to reanchor bridge location: {:?}",
530
                    e
531
                );
532
                return;
533
            }
534
        };
535

            
536
3
        let container_asset: Asset = (token_location_reanchored.clone(), token_data.amount).into();
537
3
        let inbound_queue_pallet_index = InboundQueuePalletInstance::get();
538

            
539
3
        let remote_xcm = Xcm::<()>(vec![
540
3
            ReserveAssetDeposited(vec![relay_asset_fee_container_context.clone()].into()),
541
3
            BuyExecution {
542
3
                fees: relay_asset_fee_container_context,
543
3
                weight_limit: Unlimited,
544
3
            },
545
3
            DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()),
546
3
            UniversalOrigin(GlobalConsensus(EthereumNetwork::get())),
547
3
            WithdrawAsset(vec![container_asset.clone()].into()),
548
3
            DepositAsset {
549
3
                assets: Definite(container_asset.into()),
550
3
                beneficiary,
551
3
            },
552
3
            // When the execution finishes deposit any leftover fees to the ETH
553
3
            // sovereign account on destination.
554
3
            SetAppendix(Xcm(vec![DepositAsset {
555
3
                assets: Wild(AllOf {
556
3
                    id: Location::parent().into(),
557
3
                    fun: WildFungibility::Fungible,
558
3
                }),
559
3
                beneficiary: bridge_location,
560
3
            }])),
561
3
        ]);
562

            
563
3
        send_xcm::<<T as pallet_xcm::Config>::XcmRouter>(
564
3
            container_location.clone(),
565
3
            remote_xcm.clone(),
566
        )
567
3
        .map(|(message_id, _price)| {
568
3
            let xcm_event: pallet_xcm::Event<T> = pallet_xcm::Event::Sent {
569
3
                origin: Here.into_location(),
570
3
                destination: container_location,
571
3
                message: remote_xcm,
572
3
                message_id,
573
3
            };
574
3
            frame_system::Pallet::<T>::deposit_event(
575
3
                <T as frame_system::Config>::RuntimeEvent::from(xcm_event),
576
            );
577
3
        })
578
3
        .map_err(|e| {
579
            log::error!(
580
                "NativeContainerTokensProcessor: XCM send failed with error: {:?}",
581
                e
582
            );
583
        })
584
3
        .ok();
585
4
    }
586
}
587

            
588
/// Rewards the relayer that processed a native token transfer message
589
/// using the FeesAccount configured in pallet_ethereum_token_transfers
590
pub struct RewardThroughFeesAccount<T>(PhantomData<T>);
591

            
592
impl<T> RewardProcessor<T> for RewardThroughFeesAccount<T>
593
where
594
    T: snowbridge_pallet_inbound_queue::Config + pallet_ethereum_token_transfers::Config,
595
    T::AccountId: From<sp_runtime::AccountId32>,
596
    <T::Token as Inspect<T::AccountId>>::Balance: core::fmt::Debug,
597
{
598
43
    fn process_reward(who: T::AccountId, _channel: Channel, message: Message) -> DispatchResult {
599
43
        let reward_amount = snowbridge_pallet_inbound_queue::Pallet::<T>::calculate_delivery_cost(
600
43
            message.encode().len() as u32,
601
        );
602

            
603
43
        let fees_account: T::AccountId = T::FeesAccount::get();
604

            
605
43
        let amount =
606
43
            T::Token::reducible_balance(&fees_account, Preservation::Preserve, Fortitude::Polite)
607
43
                .min(reward_amount);
608

            
609
43
        if amount != reward_amount {
610
12
            log::warn!(
611
                "RewardThroughFeesAccount: fees account running low on funds {:?}: {:?}",
612
                fees_account,
613
                amount
614
            );
615
31
        }
616

            
617
43
        if !amount.is_zero() {
618
31
            T::Token::transfer(&fees_account, &who, amount, Preservation::Preserve)?;
619
12
        }
620

            
621
43
        Ok(())
622
43
    }
623
}
624

            
625
pub struct BabeSlotBeacon<T>(PhantomData<T>);
626
impl<T: pallet_babe::Config> sp_runtime::traits::BlockNumberProvider for BabeSlotBeacon<T> {
627
    type BlockNumber = u32;
628

            
629
18
    fn current_block_number() -> Self::BlockNumber {
630
        // TODO: nimbus_primitives::SlotBeacon requires u32, but this is a u64 in pallet_babe, and
631
        // also it gets converted to u64 in pallet_author_noting, so let's do something to remove
632
        // this intermediate u32 conversion, such as using a different trait
633
18
        u64::from(pallet_babe::CurrentSlot::<T>::get()) as u32
634
18
    }
635
}
636

            
637
/// Combines the vrf output of the previous block with the provided subject.
638
/// This ensures that the randomness will be different on different pallets, as long as the subject is different.
639
5
pub fn mix_randomness<T: frame_system::Config>(vrf_output: [u8; 32], subject: &[u8]) -> T::Hash {
640
5
    let mut digest = Vec::new();
641
5
    digest.extend_from_slice(vrf_output.as_ref());
642
5
    digest.extend_from_slice(subject);
643

            
644
5
    T::Hashing::hash(digest.as_slice())
645
5
}
646

            
647
pub struct BabeAuthorVrfBlockRandomness<T>(PhantomData<T>);
648
impl<T: pallet_babe::Config + frame_system::Config> BabeAuthorVrfBlockRandomness<T> {
649
703
    pub fn get_block_randomness() -> Option<[u8; 32]> {
650
        // In a relay context we get block randomness from Babe's AuthorVrfRandomness
651
703
        pallet_babe::Pallet::<T>::author_vrf_randomness()
652
703
    }
653

            
654
703
    pub fn get_block_randomness_mixed(subject: &[u8]) -> Option<T::Hash> {
655
703
        Self::get_block_randomness().map(|random_hash| mix_randomness::<T>(random_hash, subject))
656
703
    }
657
}
658

            
659
impl<T: pallet_babe::Config + frame_system::Config>
660
    frame_support::traits::Randomness<T::Hash, BlockNumberFor<T>>
661
    for BabeAuthorVrfBlockRandomness<T>
662
{
663
    fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
664
        let block_number = frame_system::Pallet::<T>::block_number();
665
        let randomness = Self::get_block_randomness_mixed(subject).unwrap_or_default();
666

            
667
        (randomness, block_number)
668
    }
669
}
670

            
671
pub struct BabeGetCollatorAssignmentRandomness<T>(PhantomData<T>);
672
impl<T: pallet_babe::Config + frame_system::Config> Get<[u8; 32]>
673
    for BabeGetCollatorAssignmentRandomness<T>
674
{
675
1431
    fn get() -> [u8; 32] {
676
1431
        let block_number = frame_system::Pallet::<T>::block_number();
677
1431
        let random_seed = if !block_number.is_zero() {
678
702
            if let Some(random_hash) = {
679
702
                BabeAuthorVrfBlockRandomness::<T>::get_block_randomness_mixed(b"CollatorAssignment")
680
702
            } {
681
                // Return random_hash as a [u8; 32] instead of a Hash
682
4
                let mut buf = [0u8; 32];
683
4
                let len = core::cmp::min(32, random_hash.as_ref().len());
684
4
                buf[..len].copy_from_slice(&random_hash.as_ref()[..len]);
685

            
686
4
                buf
687
            } else {
688
                // If there is no randomness return [0; 32]
689
698
                [0; 32]
690
            }
691
        } else {
692
            // In block 0 (genesis) there is no randomness
693
729
            [0; 32]
694
        };
695

            
696
1431
        random_seed
697
1431
    }
698
}
699

            
700
/// `EthTokensLocalProcessor` is responsible for receiving and processing the ETH native
701
/// token and ERC20s coming from Ethereum with Tanssi chain or container-chains as final destinations.
702
/// TODO: add support for container transfers
703
pub struct EthTokensLocalProcessor<
704
    T,
705
    XcmProcessor,
706
    XcmWeigher,
707
    AssetTransactor,
708
    EthereumLocation,
709
    EthereumNetwork,
710
    ContainerTransfersEnabled, // TODO: remove this when all runtimes support container transfers
711
>(
712
    PhantomData<(
713
        T,
714
        XcmProcessor,
715
        XcmWeigher,
716
        AssetTransactor,
717
        EthereumLocation,
718
        EthereumNetwork,
719
        ContainerTransfersEnabled,
720
    )>,
721
);
722

            
723
impl<
724
        T,
725
        XcmProcessor,
726
        XcmWeigher,
727
        AssetTransactor,
728
        EthereumLocation,
729
        EthereumNetwork,
730
        ContainerTransfersEnabled,
731
    > MessageProcessor
732
    for EthTokensLocalProcessor<
733
        T,
734
        XcmProcessor,
735
        XcmWeigher,
736
        AssetTransactor,
737
        EthereumLocation,
738
        EthereumNetwork,
739
        ContainerTransfersEnabled,
740
    >
741
where
742
    T: snowbridge_pallet_inbound_queue::Config
743
        + pallet_ethereum_token_transfers::Config
744
        + pallet_foreign_asset_creator::Config
745
        + pallet_xcm::Config,
746
    <T as frame_system::Config>::RuntimeEvent: From<pallet_xcm::Event<T>>,
747
    <T as frame_system::Config>::AccountId: Into<Location>,
748
    XcmProcessor: ExecuteXcm<<T as pallet_xcm::Config>::RuntimeCall>,
749
    XcmWeigher: WeightBounds<<T as pallet_xcm::Config>::RuntimeCall>,
750
    AssetTransactor: TransactAsset,
751
    EthereumLocation: Get<Location>,
752
    EthereumNetwork: Get<NetworkId>,
753
    ContainerTransfersEnabled: Get<bool>,
754
    cumulus_primitives_core::Location:
755
        EncodeLike<<T as pallet_foreign_asset_creator::Config>::ForeignAsset>,
756
{
757
20
    fn can_process_message(channel: &Channel, envelope: &Envelope) -> bool {
758
        // Validate channel and gateway
759
20
        if !GatewayAndChannelValidator::<T>::validate_gateway_and_channel(channel, envelope) {
760
            log::warn!("EthTokensLocalProcessor::can_process_message: invalid gateway or channel");
761
            return false;
762
20
        }
763

            
764
20
        if let Some(eth_transfer_data) =
765
20
            Self::decode_message_for_eth_transfer(envelope.payload.as_slice())
766
        {
767
            // Check if the token is registered in the relay
768
20
            match eth_transfer_data.destination {
769
                Destination::AccountId32 { id: _ } => {
770
12
                    return pallet_foreign_asset_creator::ForeignAssetToAssetId::<T>::get(
771
12
                        eth_transfer_data.token_location,
772
12
                    )
773
12
                    .is_some();
774
                }
775
8
                _ => return true,
776
            }
777
        }
778

            
779
        false
780
20
    }
781

            
782
16
    fn process_message(_channel: Channel, envelope: Envelope) -> DispatchResult {
783
16
        let eth_transfer_data = Self::decode_message_for_eth_transfer(envelope.payload.as_slice())
784
16
            .ok_or(DispatchError::Other("unexpected message"))?;
785

            
786
16
        match eth_transfer_data.destination {
787
            Destination::AccountId32 { id: _ } => {
788
8
                Self::process_xcm_local_native_eth_transfer(eth_transfer_data)
789
            }
790
            Destination::ForeignAccountId32 { .. } | Destination::ForeignAccountId20 { .. } => {
791
8
                if ContainerTransfersEnabled::get() {
792
7
                    Self::process_xcm_container_eth_transfer(eth_transfer_data)
793
                } else {
794
1
                    log::error!("EthTokensLocalProcessor: container transfers not supported yet");
795
1
                    return Ok(());
796
                }
797
            }
798
        }
799
16
    }
800
}
801

            
802
pub struct EthTransferData {
803
    pub token_location: Location,
804
    pub destination: Destination,
805
    pub amount: u128,
806
}
807

            
808
impl<
809
        T,
810
        XcmProcessor,
811
        XcmWeigher,
812
        AssetTransactor,
813
        EthereumLocation,
814
        EthereumNetwork,
815
        ContainerTransfersEnabled,
816
    >
817
    EthTokensLocalProcessor<
818
        T,
819
        XcmProcessor,
820
        XcmWeigher,
821
        AssetTransactor,
822
        EthereumLocation,
823
        EthereumNetwork,
824
        ContainerTransfersEnabled,
825
    >
826
where
827
    T: frame_system::Config + pallet_xcm::Config + pallet_ethereum_token_transfers::Config,
828
    <T as frame_system::Config>::RuntimeEvent: From<pallet_xcm::Event<T>>,
829
    <T as frame_system::Config>::AccountId: Into<Location>,
830
    XcmProcessor: ExecuteXcm<<T as pallet_xcm::Config>::RuntimeCall>,
831
    XcmWeigher: WeightBounds<<T as pallet_xcm::Config>::RuntimeCall>,
832
    AssetTransactor: TransactAsset,
833
    EthereumLocation: Get<Location>,
834
    EthereumNetwork: Get<NetworkId>,
835
    ContainerTransfersEnabled: Get<bool>,
836
{
837
36
    pub fn decode_message_for_eth_transfer(mut payload: &[u8]) -> Option<EthTransferData> {
838
36
        match VersionedXcmMessage::decode_all(&mut payload) {
839
            Ok(VersionedXcmMessage::V1(MessageV1 {
840
                command:
841
                    Command::SendToken {
842
36
                        token: token_address,
843
36
                        destination,
844
36
                        amount,
845
                        fee: _,
846
                    },
847
                ..
848
            })) => {
849
36
                let token_location = if token_address == H160::zero() {
850
6
                    Location {
851
6
                        parents: 1,
852
6
                        interior: X1([GlobalConsensus(EthereumNetwork::get())].into()),
853
6
                    }
854
                } else {
855
30
                    Location {
856
30
                        parents: 1,
857
30
                        interior: X2([
858
30
                            GlobalConsensus(EthereumNetwork::get()),
859
30
                            AccountKey20 {
860
30
                                network: Some(EthereumNetwork::get()),
861
30
                                key: token_address.into(),
862
30
                            },
863
30
                        ]
864
30
                        .into()),
865
30
                    }
866
                };
867

            
868
36
                Some(EthTransferData {
869
36
                    token_location,
870
36
                    destination,
871
36
                    amount,
872
36
                })
873
            }
874
            _ => None,
875
        }
876
36
    }
877

            
878
8
    fn process_xcm_local_native_eth_transfer(eth_transfer_data: EthTransferData) -> DispatchResult {
879
8
        let assets_to_holding: XcmAssets = vec![XcmAsset {
880
8
            id: XcmAssetId::from(eth_transfer_data.token_location),
881
8
            fun: Fungibility::Fungible(eth_transfer_data.amount),
882
8
        }]
883
8
        .into();
884

            
885
8
        let destination_account = match eth_transfer_data.destination {
886
8
            Destination::AccountId32 { id } => id,
887
            _ => {
888
                log::error!("EthTokensLocalProcessor: invalid destination");
889
                return Ok(());
890
            }
891
        };
892

            
893
8
        let mut xcm = Xcm::<<T as pallet_xcm::Config>::RuntimeCall>(vec![
894
8
            ReserveAssetDeposited(assets_to_holding),
895
8
            DepositAsset {
896
8
                assets: AllCounted(1).into(),
897
8
                beneficiary: Location::new(
898
8
                    0,
899
8
                    [AccountId32 {
900
8
                        network: None,
901
8
                        id: destination_account,
902
8
                    }],
903
8
                ),
904
8
            },
905
8
        ]);
906

            
907
8
        let ethereum_location = EthereumLocation::get();
908

            
909
        // Using Weight::MAX here because we don't have a limit, same as they do in pallet-xcm
910
8
        if let Ok(weight) = XcmWeigher::weight(&mut xcm, Weight::MAX) {
911
8
            let mut message_id = xcm.using_encoded(sp_io::hashing::blake2_256);
912

            
913
8
            let outcome = XcmProcessor::prepare_and_execute(
914
8
                ethereum_location,
915
8
                xcm,
916
8
                &mut message_id,
917
8
                weight,
918
8
                weight,
919
            );
920

            
921
8
            if let Err(error) = outcome.ensure_complete() {
922
2
                log::error!(
923
2
                    "EthTokensLocalProcessor: XCM execution failed with error {:?}",
924
                    error
925
                );
926
6
            }
927
        } else {
928
            log::error!("EthTokensLocalProcessor: unweighable message");
929
        }
930

            
931
8
        Ok(())
932
8
    }
933

            
934
7
    fn process_xcm_container_eth_transfer(eth_transfer_data: EthTransferData) -> DispatchResult {
935
        // Get the para_id, beneficiary and fee from the destination
936
7
        let (para_id, beneficiary, fee) = match eth_transfer_data.destination {
937
4
            Destination::ForeignAccountId32 { para_id, id, fee } => (
938
4
                para_id,
939
4
                Location::new(0, [AccountId32 { network: None, id }]),
940
4
                fee,
941
4
            ),
942
3
            Destination::ForeignAccountId20 { para_id, id, fee } => (
943
3
                para_id,
944
3
                Location::new(
945
3
                    0,
946
3
                    [AccountKey20 {
947
3
                        network: None,
948
3
                        key: id,
949
3
                    }],
950
3
                ),
951
3
                fee,
952
3
            ),
953
            _ => {
954
                log::error!(
955
                    "EthTokensLocalProcessor: unsupported destination for container transfer: {:?}",
956
                    eth_transfer_data.destination
957
                );
958
                return Ok(());
959
            }
960
        };
961

            
962
        // Container chain location from relay point of view
963
7
        let container_location = Location::new(0, [Parachain(para_id)]);
964

            
965
        // Reanchor the token location to the container chain location
966
7
        let token_id_dest = match eth_transfer_data.token_location.clone().reanchored(
967
7
            &container_location,
968
7
            &<T as pallet_xcm::Config>::UniversalLocation::get(),
969
7
        ) {
970
7
            Ok(loc) => loc,
971
            Err(e) => {
972
                log::error!(
973
                    "EthTokensLocalProcessor: failed to reanchor token location: {:?}",
974
                    e
975
                );
976
                return Ok(());
977
            }
978
        };
979

            
980
        // Asset to pay fees, in this case native relay token
981
7
        let asset_fee_relay: Asset = (Location::here(), fee).into();
982

            
983
        // Reanchor the asset fee to the container chain location
984
7
        let asset_fee_container = match asset_fee_relay.clone().reanchored(
985
7
            &container_location,
986
7
            &<T as pallet_xcm::Config>::UniversalLocation::get(),
987
7
        ) {
988
7
            Ok(loc) => loc,
989
            Err(e) => {
990
                log::error!(
991
                    "EthTokensLocalProcessor: failed to reanchor relay token location: {:?}",
992
                    e
993
                );
994
                return Ok(());
995
            }
996
        };
997

            
998
        // Ethereum token location from relay point of view
999
7
        let eth_token_location: Asset = (
7
            eth_transfer_data.token_location.clone(),
7
            eth_transfer_data.amount,
7
        )
7
            .into();
        // Asset to deposit into the container chain
7
        let asset_to_deposit: Asset = (token_id_dest.clone(), eth_transfer_data.amount).into();
7
        let dummy_context = XcmContext {
7
            origin: None,
7
            message_id: Default::default(),
7
            topic: None,
7
        };
        // To early check if the token is registered in the relay
1
        if let Err(e) =
7
            AssetTransactor::can_check_in(&container_location, &eth_token_location, &dummy_context)
        {
1
            log::error!("EthTokensLocalProcessor: can_check_in failed: {:?}", e);
1
            return Ok(());
6
        }
        // Transfer fee from FeesAccount to container sovereign account
6
        if let Err(e) = AssetTransactor::transfer_asset(
6
            &asset_fee_relay,
6
            &T::FeesAccount::get().into(),
6
            &container_location,
6
            &dummy_context,
6
        ) {
2
            log::error!(
2
                "EthTokensLocalProcessor: failed to transfer fee from FeesAccount to container sovereign account: {:?}",
                e
            );
2
            return Ok(());
4
        }
        // Mint the ERC20 token into the container sovereign account
4
        AssetTransactor::check_in(&container_location, &eth_token_location, &dummy_context);
        if let Err(e) =
4
            AssetTransactor::deposit_asset(&eth_token_location, &container_location, None)
        {
            log::error!("EthTokensLocalProcessor: deposit_asset failed: {:?}", e);
            return Ok(());
4
        }
        // Send XCM to deposit the ERC20 token into beneficiary account and pay fees
4
        let remote_xcm = Xcm::<()>(vec![
4
            ReserveAssetDeposited(
4
                vec![asset_fee_container.clone(), asset_to_deposit.clone()].into(),
4
            ),
4
            BuyExecution {
4
                fees: asset_fee_container.clone(),
4
                weight_limit: Unlimited,
4
            },
4
            DepositAsset {
4
                assets: Definite(vec![asset_to_deposit].into()),
4
                beneficiary,
4
            },
4
        ]);
4
        match send_xcm::<<T as pallet_xcm::Config>::XcmRouter>(
4
            container_location.clone(),
4
            remote_xcm.clone(),
4
        ) {
4
            Ok((message_id, _price)) => {
4
                let evt: pallet_xcm::Event<T> = pallet_xcm::Event::Sent {
4
                    origin: Here.into_location(),
4
                    destination: container_location,
4
                    message: remote_xcm,
4
                    message_id,
4
                };
4
                frame_system::Pallet::<T>::deposit_event(
4
                    <T as frame_system::Config>::RuntimeEvent::from(evt),
4
                );
4
            }
            Err(e) => {
                log::error!(
                    "EthTokensLocalProcessor: XCM send failed to para_id {} with error: {:?}",
                    para_id,
                    e
                );
            }
        };
4
        Ok(())
7
    }
}
/// Handler for depositing fees to the exporter fees account or a default account based on the reason.
pub struct ExporterFeeHandler<AssetTransactor, ExporterFeesAccount, DefaultAccount>(
    PhantomData<(AssetTransactor, ExporterFeesAccount, DefaultAccount)>,
);
impl<AssetTransactor, ExporterFeesAccount, DefaultAccount> HandleFee
    for ExporterFeeHandler<AssetTransactor, ExporterFeesAccount, DefaultAccount>
where
    AssetTransactor: TransactAsset,
    ExporterFeesAccount: Get<Location>,
    DefaultAccount: Get<Location>,
{
38
    fn handle_fee(fee: XcmAssets, context: Option<&XcmContext>, reason: FeeReason) -> XcmAssets {
38
        match reason {
            FeeReason::Export {
                network: _,
                destination: _,
18
            } => {
18
                deposit_or_burn_fee::<AssetTransactor>(fee, context, ExporterFeesAccount::get());
18
            }
20
            _ => {
20
                deposit_or_burn_fee::<AssetTransactor>(fee, context, DefaultAccount::get());
20
            }
        }
38
        XcmAssets::new()
38
    }
}