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
104
    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
104
        if let Some(channel_info) = pallet_ethereum_token_transfers::CurrentChannelInfo::<T>::get()
61
        {
62
100
            if envelope.channel_id != channel_info.channel_id
63
98
                || channel.para_id != channel_info.para_id
64
96
                || 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
94
            }
77
        } else {
78
4
            log::warn!("CurrentChannelInfo not set in storage");
79
4
            return false;
80
        }
81

            
82
        // Check it is from the right gateway
83
94
        if envelope.gateway != T::GatewayAddress::get() {
84
4
            log::warn!("Wrong gateway address: {:?}", envelope.gateway);
85
4
            return false;
86
90
        }
87
90
        true
88
104
    }
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
1932
    pub fn decode_native_token_message(mut payload: &[u8]) -> Option<Self> {
101
1932
        match VersionedXcmMessage::decode_all(&mut payload) {
102
            Ok(VersionedXcmMessage::V1(MessageV1 {
103
                command:
104
                    Command::SendNativeToken {
105
1886
                        token_id,
106
1886
                        destination,
107
1886
                        amount,
108
1886
                        fee,
109
                    },
110
                ..
111
1886
            })) => Some(NativeTokenTransferData {
112
1886
                token_id,
113
1886
                destination,
114
1886
                amount,
115
1886
                fee,
116
1886
            }),
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
1932
    }
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
50
    fn can_process_message(channel: &Channel, envelope: &Envelope) -> bool {
141
50
        if !GatewayAndChannelValidator::<T>::validate_gateway_and_channel(channel, envelope) {
142
10
            log::warn!("NativeTokenTransferMessageProcessor: invalid gateway or channel");
143
10
            return false;
144
40
        }
145

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

            
152
16
            if let Some(expected_token_id) =
153
38
                snowbridge_pallet_system::Pallet::<T>::convert_back(&token_location)
154
            {
155
16
                if token_data.token_id == expected_token_id {
156
16
                    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
22
                log::warn!(
167
                    "NativeTokenTransferMessageProcessor: token id not found for location: {:?}",
168
                    token_location
169
                );
170
22
                false
171
            }
172
        } else {
173
2
            false
174
        }
175
50
    }
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
28
    fn can_process_message(channel: &Channel, envelope: &Envelope) -> bool {
271
        // Validate channel and gateway
272
28
        if !GatewayAndChannelValidator::<T>::validate_gateway_and_channel(channel, envelope) {
273
4
            log::warn!(
274
                "NativeContainerTokensProcessor::can_process_message: invalid gateway or channel"
275
            );
276
4
            return false;
277
24
        }
278

            
279
        // Try decoding the message and check if the destination owns the token being transferred
280
24
        match Self::get_token_data_and_location(&envelope.payload) {
281
22
            TokenDataResult::Success(token_data, token_location) => {
282
22
                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
2
            TokenDataResult::LocationNotFound(token_data) => {
291
2
                log::error!(
292
2
                    "NativeContainerTokensProcessor::can_process_message: token location not found for token_id: {:?}",
293
                    token_data.token_id
294
                );
295
2
                false
296
            }
297
            TokenDataResult::UnsupportedToken => {
298
                log::error!(
299
                    "NativeContainerTokensProcessor::can_process_message: unsupported token"
300
                );
301
                false
302
            }
303
        }
304
28
    }
305

            
306
8
    fn process_message(_channel: Channel, envelope: Envelope) -> DispatchResult {
307
8
        match Self::get_token_data_and_location(&envelope.payload) {
308
8
            TokenDataResult::Success(token_data, token_location) => {
309
8
                Self::process_native_container_token_transfer(token_data, token_location);
310
8
                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
8
    }
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
32
    fn get_token_data_and_location(payload: &[u8]) -> TokenDataResult {
374
32
        if let Some(token_data) = NativeTokenTransferData::decode_native_token_message(payload) {
375
30
            if let Some(token_location) =
376
32
                snowbridge_pallet_system::Pallet::<T>::convert(&token_data.token_id)
377
            {
378
30
                if token_location == TanssiLocationReanchored::get() {
379
                    // Extra safety check to forbid native Tanssi token for this processor
380
                    TokenDataResult::UnsupportedToken
381
                } else {
382
30
                    TokenDataResult::Success(token_data, token_location)
383
                }
384
            } else {
385
2
                TokenDataResult::LocationNotFound(token_data)
386
            }
387
        } else {
388
            TokenDataResult::DecodeFailure
389
        }
390
32
    }
391

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

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

            
412
20
        match chain_part {
413
20
            Some((_, interior)) => {
414
20
                if let Some(Parachain(id)) = interior.first() {
415
20
                    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
22
    }
429

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

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

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

            
468
8
        let container_token_from_tanssi = Location::new(0, interior);
469
8
        let token_location_reanchored = match container_token_from_tanssi.reanchored(
470
8
            &container_location,
471
8
            &<T as pallet_xcm::Config>::UniversalLocation::get(),
472
8
        ) {
473
8
            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
8
        let asset_fee_relay: Asset = (Location::here(), container_fee).into();
485

            
486
        // Reanchor the asset fee to the container chain location
487
8
        let relay_asset_fee_container_context = match asset_fee_relay.clone().reanchored(
488
8
            &container_location,
489
8
            &<T as pallet_xcm::Config>::UniversalLocation::get(),
490
8
        ) {
491
8
            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
8
        let dummy_context = XcmContext {
502
8
            origin: None,
503
8
            message_id: Default::default(),
504
8
            topic: None,
505
8
        };
506

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

            
521
        // Reanchor Ethereum location to the container chain's point of view
522
6
        let bridge_location = match EthereumLocation::get().clone().reanchored(
523
6
            &container_location,
524
6
            &<T as pallet_xcm::Config>::UniversalLocation::get(),
525
6
        ) {
526
6
            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
6
        let container_asset: Asset = (token_location_reanchored.clone(), token_data.amount).into();
537
6
        let inbound_queue_pallet_index = InboundQueuePalletInstance::get();
538

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

            
563
6
        send_xcm::<<T as pallet_xcm::Config>::XcmRouter>(
564
6
            container_location.clone(),
565
6
            remote_xcm.clone(),
566
        )
567
6
        .map(|(message_id, _price)| {
568
6
            let xcm_event: pallet_xcm::Event<T> = pallet_xcm::Event::Sent {
569
6
                origin: Here.into_location(),
570
6
                destination: container_location,
571
6
                message: remote_xcm,
572
6
                message_id,
573
6
            };
574
6
            frame_system::Pallet::<T>::deposit_event(
575
6
                <T as frame_system::Config>::RuntimeEvent::from(xcm_event),
576
            );
577
6
        })
578
6
        .map_err(|e| {
579
            log::error!(
580
                "NativeContainerTokensProcessor: XCM send failed with error: {:?}",
581
                e
582
            );
583
        })
584
6
        .ok();
585
8
    }
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
56
    fn process_reward(who: T::AccountId, _channel: Channel, message: Message) -> DispatchResult {
599
56
        let reward_amount = snowbridge_pallet_inbound_queue::Pallet::<T>::calculate_delivery_cost(
600
56
            message.encode().len() as u32,
601
        );
602

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

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

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

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

            
621
56
        Ok(())
622
56
    }
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
733
    pub fn get_block_randomness() -> Option<[u8; 32]> {
650
        // In a relay context we get block randomness from Babe's AuthorVrfRandomness
651
733
        pallet_babe::Pallet::<T>::author_vrf_randomness()
652
733
    }
653

            
654
733
    pub fn get_block_randomness_mixed(subject: &[u8]) -> Option<T::Hash> {
655
733
        Self::get_block_randomness().map(|random_hash| mix_randomness::<T>(random_hash, subject))
656
733
    }
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
1732
    fn get() -> [u8; 32] {
676
1732
        let block_number = frame_system::Pallet::<T>::block_number();
677
1732
        let random_seed = if !block_number.is_zero() {
678
732
            if let Some(random_hash) = {
679
732
                BabeAuthorVrfBlockRandomness::<T>::get_block_randomness_mixed(b"CollatorAssignment")
680
732
            } {
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
728
                [0; 32]
690
            }
691
        } else {
692
            // In block 0 (genesis) there is no randomness
693
1000
            [0; 32]
694
        };
695

            
696
1732
        random_seed
697
1732
    }
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
>(
711
    PhantomData<(
712
        T,
713
        XcmProcessor,
714
        XcmWeigher,
715
        AssetTransactor,
716
        EthereumLocation,
717
        EthereumNetwork,
718
    )>,
719
);
720

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

            
753
26
        if let Some(eth_transfer_data) =
754
26
            Self::decode_message_for_eth_transfer(envelope.payload.as_slice())
755
        {
756
            // Check if the token is registered in the relay
757
26
            match eth_transfer_data.destination {
758
                Destination::AccountId32 { id: _ } => {
759
12
                    return pallet_foreign_asset_creator::ForeignAssetToAssetId::<T>::get(
760
12
                        eth_transfer_data.token_location,
761
12
                    )
762
12
                    .is_some();
763
                }
764
14
                _ => return true,
765
            }
766
        }
767

            
768
        false
769
26
    }
770

            
771
22
    fn process_message(_channel: Channel, envelope: Envelope) -> DispatchResult {
772
22
        let eth_transfer_data = Self::decode_message_for_eth_transfer(envelope.payload.as_slice())
773
22
            .ok_or(DispatchError::Other("unexpected message"))?;
774

            
775
22
        match eth_transfer_data.destination {
776
            Destination::AccountId32 { id: _ } => {
777
8
                Self::process_xcm_local_native_eth_transfer(eth_transfer_data)
778
            }
779
            Destination::ForeignAccountId32 { .. } | Destination::ForeignAccountId20 { .. } => {
780
14
                Self::process_xcm_container_eth_transfer(eth_transfer_data)
781
            }
782
        }
783
22
    }
784
}
785

            
786
pub struct EthTransferData {
787
    pub token_location: Location,
788
    pub destination: Destination,
789
    pub amount: u128,
790
}
791

            
792
impl<T, XcmProcessor, XcmWeigher, AssetTransactor, EthereumLocation, EthereumNetwork>
793
    EthTokensLocalProcessor<
794
        T,
795
        XcmProcessor,
796
        XcmWeigher,
797
        AssetTransactor,
798
        EthereumLocation,
799
        EthereumNetwork,
800
    >
801
where
802
    T: frame_system::Config + pallet_xcm::Config + pallet_ethereum_token_transfers::Config,
803
    <T as frame_system::Config>::RuntimeEvent: From<pallet_xcm::Event<T>>,
804
    <T as frame_system::Config>::AccountId: Into<Location>,
805
    XcmProcessor: ExecuteXcm<<T as pallet_xcm::Config>::RuntimeCall>,
806
    XcmWeigher: WeightBounds<<T as pallet_xcm::Config>::RuntimeCall>,
807
    AssetTransactor: TransactAsset,
808
    EthereumLocation: Get<Location>,
809
    EthereumNetwork: Get<NetworkId>,
810
{
811
48
    pub fn decode_message_for_eth_transfer(mut payload: &[u8]) -> Option<EthTransferData> {
812
48
        match VersionedXcmMessage::decode_all(&mut payload) {
813
            Ok(VersionedXcmMessage::V1(MessageV1 {
814
                command:
815
                    Command::SendToken {
816
48
                        token: token_address,
817
48
                        destination,
818
48
                        amount,
819
                        fee: _,
820
                    },
821
                ..
822
            })) => {
823
48
                let token_location = if token_address == H160::zero() {
824
6
                    Location {
825
6
                        parents: 1,
826
6
                        interior: X1([GlobalConsensus(EthereumNetwork::get())].into()),
827
6
                    }
828
                } else {
829
42
                    Location {
830
42
                        parents: 1,
831
42
                        interior: X2([
832
42
                            GlobalConsensus(EthereumNetwork::get()),
833
42
                            AccountKey20 {
834
42
                                network: Some(EthereumNetwork::get()),
835
42
                                key: token_address.into(),
836
42
                            },
837
42
                        ]
838
42
                        .into()),
839
42
                    }
840
                };
841

            
842
48
                Some(EthTransferData {
843
48
                    token_location,
844
48
                    destination,
845
48
                    amount,
846
48
                })
847
            }
848
            _ => None,
849
        }
850
48
    }
851

            
852
8
    fn process_xcm_local_native_eth_transfer(eth_transfer_data: EthTransferData) -> DispatchResult {
853
8
        let assets_to_holding: XcmAssets = vec![XcmAsset {
854
8
            id: XcmAssetId::from(eth_transfer_data.token_location),
855
8
            fun: Fungibility::Fungible(eth_transfer_data.amount),
856
8
        }]
857
8
        .into();
858

            
859
8
        let destination_account = match eth_transfer_data.destination {
860
8
            Destination::AccountId32 { id } => id,
861
            _ => {
862
                log::error!("EthTokensLocalProcessor: invalid destination");
863
                return Ok(());
864
            }
865
        };
866

            
867
8
        let mut xcm = Xcm::<<T as pallet_xcm::Config>::RuntimeCall>(vec![
868
8
            ReserveAssetDeposited(assets_to_holding),
869
8
            DepositAsset {
870
8
                assets: AllCounted(1).into(),
871
8
                beneficiary: Location::new(
872
8
                    0,
873
8
                    [AccountId32 {
874
8
                        network: None,
875
8
                        id: destination_account,
876
8
                    }],
877
8
                ),
878
8
            },
879
8
        ]);
880

            
881
8
        let ethereum_location = EthereumLocation::get();
882

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

            
887
8
            let outcome = XcmProcessor::prepare_and_execute(
888
8
                ethereum_location,
889
8
                xcm,
890
8
                &mut message_id,
891
8
                weight,
892
8
                weight,
893
            );
894

            
895
8
            if let Err(error) = outcome.ensure_complete() {
896
2
                log::error!(
897
2
                    "EthTokensLocalProcessor: XCM execution failed with error {:?}",
898
                    error
899
                );
900
6
            }
901
        } else {
902
            log::error!("EthTokensLocalProcessor: unweighable message");
903
        }
904

            
905
8
        Ok(())
906
8
    }
907

            
908
14
    fn process_xcm_container_eth_transfer(eth_transfer_data: EthTransferData) -> DispatchResult {
909
        // Get the para_id, beneficiary and fee from the destination
910
14
        let (para_id, beneficiary, fee) = match eth_transfer_data.destination {
911
8
            Destination::ForeignAccountId32 { para_id, id, fee } => (
912
8
                para_id,
913
8
                Location::new(0, [AccountId32 { network: None, id }]),
914
8
                fee,
915
8
            ),
916
6
            Destination::ForeignAccountId20 { para_id, id, fee } => (
917
6
                para_id,
918
6
                Location::new(
919
6
                    0,
920
6
                    [AccountKey20 {
921
6
                        network: None,
922
6
                        key: id,
923
6
                    }],
924
6
                ),
925
6
                fee,
926
6
            ),
927
            _ => {
928
                log::error!(
929
                    "EthTokensLocalProcessor: unsupported destination for container transfer: {:?}",
930
                    eth_transfer_data.destination
931
                );
932
                return Ok(());
933
            }
934
        };
935

            
936
        // Container chain location from relay point of view
937
14
        let container_location = Location::new(0, [Parachain(para_id)]);
938

            
939
        // Reanchor the token location to the container chain location
940
14
        let token_id_dest = match eth_transfer_data.token_location.clone().reanchored(
941
14
            &container_location,
942
14
            &<T as pallet_xcm::Config>::UniversalLocation::get(),
943
14
        ) {
944
14
            Ok(loc) => loc,
945
            Err(e) => {
946
                log::error!(
947
                    "EthTokensLocalProcessor: failed to reanchor token location: {:?}",
948
                    e
949
                );
950
                return Ok(());
951
            }
952
        };
953

            
954
        // Asset to pay fees, in this case native relay token
955
14
        let asset_fee_relay: Asset = (Location::here(), fee).into();
956

            
957
        // Reanchor the asset fee to the container chain location
958
14
        let asset_fee_container = match asset_fee_relay.clone().reanchored(
959
14
            &container_location,
960
14
            &<T as pallet_xcm::Config>::UniversalLocation::get(),
961
14
        ) {
962
14
            Ok(loc) => loc,
963
            Err(e) => {
964
                log::error!(
965
                    "EthTokensLocalProcessor: failed to reanchor relay token location: {:?}",
966
                    e
967
                );
968
                return Ok(());
969
            }
970
        };
971

            
972
        // Ethereum token location from relay point of view
973
14
        let eth_token_location: Asset = (
974
14
            eth_transfer_data.token_location.clone(),
975
14
            eth_transfer_data.amount,
976
14
        )
977
14
            .into();
978

            
979
        // Asset to deposit into the container chain
980
14
        let asset_to_deposit: Asset = (token_id_dest.clone(), eth_transfer_data.amount).into();
981

            
982
14
        let dummy_context = XcmContext {
983
14
            origin: None,
984
14
            message_id: Default::default(),
985
14
            topic: None,
986
14
        };
987

            
988
        // To early check if the token is registered in the relay
989
2
        if let Err(e) =
990
14
            AssetTransactor::can_check_in(&container_location, &eth_token_location, &dummy_context)
991
        {
992
2
            log::error!("EthTokensLocalProcessor: can_check_in failed: {:?}", e);
993
2
            return Ok(());
994
12
        }
995

            
996
        // Transfer fee from FeesAccount to container sovereign account
997
12
        if let Err(e) = AssetTransactor::transfer_asset(
998
12
            &asset_fee_relay,
999
12
            &T::FeesAccount::get().into(),
12
            &container_location,
12
            &dummy_context,
12
        ) {
4
            log::error!(
4
                "EthTokensLocalProcessor: failed to transfer fee from FeesAccount to container sovereign account: {:?}",
                e
            );
4
            return Ok(());
8
        }
        // Mint the ERC20 token into the container sovereign account
8
        AssetTransactor::check_in(&container_location, &eth_token_location, &dummy_context);
        if let Err(e) =
8
            AssetTransactor::deposit_asset(&eth_token_location, &container_location, None)
        {
            log::error!("EthTokensLocalProcessor: deposit_asset failed: {:?}", e);
            return Ok(());
8
        }
        // Send XCM to deposit the ERC20 token into beneficiary account and pay fees
8
        let remote_xcm = Xcm::<()>(vec![
8
            ReserveAssetDeposited(
8
                vec![asset_fee_container.clone(), asset_to_deposit.clone()].into(),
8
            ),
8
            BuyExecution {
8
                fees: asset_fee_container.clone(),
8
                weight_limit: Unlimited,
8
            },
8
            DepositAsset {
8
                assets: Definite(vec![asset_to_deposit].into()),
8
                beneficiary,
8
            },
8
        ]);
8
        match send_xcm::<<T as pallet_xcm::Config>::XcmRouter>(
8
            container_location.clone(),
8
            remote_xcm.clone(),
8
        ) {
8
            Ok((message_id, _price)) => {
8
                let evt: pallet_xcm::Event<T> = pallet_xcm::Event::Sent {
8
                    origin: Here.into_location(),
8
                    destination: container_location,
8
                    message: remote_xcm,
8
                    message_id,
8
                };
8
                frame_system::Pallet::<T>::deposit_event(
8
                    <T as frame_system::Config>::RuntimeEvent::from(evt),
8
                );
8
            }
            Err(e) => {
                log::error!(
                    "EthTokensLocalProcessor: XCM send failed to para_id {} with error: {:?}",
                    para_id,
                    e
                );
            }
        };
8
        Ok(())
14
    }
}
/// 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>,
{
84
    fn handle_fee(fee: XcmAssets, context: Option<&XcmContext>, reason: FeeReason) -> XcmAssets {
84
        match reason {
            FeeReason::Export {
                network: _,
                destination: _,
40
            } => {
40
                deposit_or_burn_fee::<AssetTransactor>(fee, context, ExporterFeesAccount::get());
40
            }
44
            _ => {
44
                deposit_or_burn_fee::<AssetTransactor>(fee, context, DefaultAccount::get());
44
            }
        }
84
        XcmAssets::new()
84
    }
}