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
41
    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
41
        if let Some(channel_info) = pallet_ethereum_token_transfers::CurrentChannelInfo::<T>::get()
61
        {
62
38
            if envelope.channel_id != channel_info.channel_id
63
36
                || channel.para_id != channel_info.para_id
64
34
                || 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
32
            }
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
32
        if envelope.gateway != T::GatewayAddress::get() {
84
3
            log::warn!("Wrong gateway address: {:?}", envelope.gateway);
85
3
            return false;
86
29
        }
87
29
        true
88
41
    }
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
1032
    pub fn decode_native_token_message(mut payload: &[u8]) -> Option<Self> {
101
1032
        match VersionedXcmMessage::decode_all(&mut payload) {
102
            Ok(VersionedXcmMessage::V1(MessageV1 {
103
                command:
104
                    Command::SendNativeToken {
105
984
                        token_id,
106
984
                        destination,
107
984
                        amount,
108
984
                        fee,
109
984
                    },
110
984
                ..
111
984
            })) => Some(NativeTokenTransferData {
112
984
                token_id,
113
984
                destination,
114
984
                amount,
115
984
                fee,
116
984
            }),
117
48
            Ok(msg) => {
118
48
                log::trace!("NativeTokenTransferData: unexpected message: {:?}", msg);
119
48
                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
1032
    }
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
31
    fn can_process_message(channel: &Channel, envelope: &Envelope) -> bool {
141
31
        if !GatewayAndChannelValidator::<T>::validate_gateway_and_channel(channel, envelope) {
142
10
            log::warn!("NativeTokenTransferMessageProcessor: invalid gateway or channel");
143
10
            return false;
144
21
        }
145

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

            
152
12
            if let Some(expected_token_id) =
153
19
                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
7
                log::warn!(
167
                    "NativeTokenTransferMessageProcessor: token id not found for location: {:?}",
168
                    token_location
169
                );
170
7
                false
171
            }
172
        } else {
173
2
            false
174
        }
175
31
    }
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
8
                } => {
189
8
                    // 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
    EthereumLocation,
226
    EthereumNetwork,
227
    InboundQueuePalletInstance,
228
    TanssiLocationReanchored,
229
>(
230
    PhantomData<(
231
        T,
232
        EthereumLocation,
233
        EthereumNetwork,
234
        InboundQueuePalletInstance,
235
        TanssiLocationReanchored,
236
    )>,
237
);
238

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

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

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

            
324
/// Result of token data and location extraction
325
enum TokenDataResult {
326
    /// Successfully extracted both token data and location
327
    Success(NativeTokenTransferData, Location),
328
    /// Failed to decode token data from payload
329
    DecodeFailure,
330
    /// Token data decoded but location not found
331
    LocationNotFound(NativeTokenTransferData),
332
    /// Token data decoded but the token is not supported
333
    UnsupportedToken,
334
}
335

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

            
382
    /// Validates that the destination para_id owns the token being transferred.
383
7
    fn validate_destination_owns_token(
384
7
        token_location: &Location,
385
7
        destination: &Destination,
386
7
    ) -> bool {
387
        // Extract para_id from destination - only foreign destinations are supported
388
7
        let expected_para_id = match destination {
389
2
            Destination::ForeignAccountId32 { para_id, .. } => *para_id,
390
4
            Destination::ForeignAccountId20 { para_id, .. } => *para_id,
391
            _ => {
392
1
                log::error!(
393
1
                    "NativeContainerTokensProcessor: unsupported destination type: {:?}",
394
                    destination
395
                );
396
1
                return false;
397
            }
398
        };
399

            
400
6
        let chain_part = token_location.interior().clone().split_global().ok();
401
6

            
402
6
        match chain_part {
403
6
            Some((_, interior)) => {
404
6
                if let Some(Parachain(id)) = interior.first() {
405
6
                    expected_para_id == *id
406
                } else {
407
                    log::error!(
408
                        "NativeContainerTokensProcessor: destination doesn't own the token!"
409
                    );
410
                    false
411
                }
412
            }
413
            _ => {
414
                log::error!("NativeContainerTokensProcessor: invalid chain part");
415
                false
416
            }
417
        }
418
7
    }
419

            
420
    /// Process a native container token transfer by creating and sending an XCM message to the destination parachain.
421
2
    fn process_native_container_token_transfer(
422
2
        token_data: NativeTokenTransferData,
423
2
        token_location: Location,
424
2
    ) {
425
2
        let token_split = token_location.interior().clone().split_global().ok();
426
2
        if let Some((_, interior)) = token_split {
427
2
            let (beneficiary, container_fee, container_para_id) = match token_data.destination {
428
                Destination::ForeignAccountId32 { para_id, id, fee } => {
429
                    let beneficiary = Location::new(0, [AccountId32 { network: None, id }]);
430
                    (beneficiary, fee, para_id)
431
                }
432
2
                Destination::ForeignAccountId20 { para_id, id, fee } => {
433
2
                    let beneficiary = Location::new(
434
2
                        0,
435
2
                        [AccountKey20 {
436
2
                            network: None,
437
2
                            key: id,
438
2
                        }],
439
2
                    );
440
2
                    (beneficiary, fee, para_id)
441
                }
442
                _ => {
443
                    log::error!("NativeContainerTokensProcessor::process_native_token_transfer: invalid destination");
444
                    return;
445
                }
446
            };
447

            
448
2
            let container_location = Location::new(0, [Parachain(container_para_id)]);
449
2

            
450
2
            let container_token_from_tanssi = Location::new(0, interior);
451
2
            let reanchor_result = container_token_from_tanssi.reanchored(
452
2
                &container_location,
453
2
                &<T as pallet_xcm::Config>::UniversalLocation::get(),
454
2
            );
455

            
456
2
            if let Ok(token_location_reanchored) = reanchor_result {
457
2
                let network = EthereumNetwork::get();
458
2

            
459
2
                let total_container_asset = token_data.amount.saturating_add(container_fee);
460
2
                let container_asset_to_withdraw: Asset =
461
2
                    (token_location_reanchored.clone(), total_container_asset).into();
462
2
                let container_asset_fee: Asset =
463
2
                    (token_location_reanchored.clone(), container_fee).into();
464
2
                let container_asset_to_deposit: Asset =
465
2
                    (token_location_reanchored.clone(), token_data.amount).into();
466
2

            
467
2
                let inbound_queue_pallet_index = InboundQueuePalletInstance::get();
468
2

            
469
2
                let remote_xcm = Xcm::<()>(vec![
470
2
                    DescendOrigin(PalletInstance(inbound_queue_pallet_index).into()),
471
2
                    UniversalOrigin(GlobalConsensus(network)),
472
2
                    WithdrawAsset(vec![container_asset_to_withdraw.clone()].into()),
473
2
                    BuyExecution {
474
2
                        fees: container_asset_fee,
475
2
                        weight_limit: Unlimited,
476
2
                    },
477
2
                    DepositAsset {
478
2
                        assets: Definite(container_asset_to_deposit.into()),
479
2
                        beneficiary,
480
2
                    },
481
2
                ]);
482
2

            
483
2
                send_xcm::<<T as pallet_xcm::Config>::XcmRouter>(
484
2
                    container_location.clone(),
485
2
                    remote_xcm.clone(),
486
2
                )
487
2
                .map(|(message_id, _price)| {
488
1
                    let xcm_event: pallet_xcm::Event<T> = pallet_xcm::Event::Sent {
489
1
                        origin: Here.into_location(),
490
1
                        destination: container_location,
491
1
                        message: remote_xcm,
492
1
                        message_id,
493
1
                    };
494
1
                    frame_system::Pallet::<T>::deposit_event(
495
1
                        <T as frame_system::Config>::RuntimeEvent::from(xcm_event),
496
1
                    );
497
2
                })
498
2
                .map_err(|e| {
499
1
                    log::error!(
500
1
                        "NativeContainerTokensProcessor: XCM send failed with error: {:?}",
501
                        e
502
                    );
503
2
                })
504
2
                .ok();
505
2
            } else {
506
                log::error!("NativeContainerTokensProcessor: failed to reanchor token location");
507
            }
508
        } else {
509
            log::error!("NativeContainerTokensProcessor: failed to reanchor token location");
510
        }
511
2
    }
512
}
513

            
514
/// Rewards the relayer that processed a native token transfer message
515
/// using the FeesAccount configured in pallet_ethereum_token_transfers
516
pub struct RewardThroughFeesAccount<T>(PhantomData<T>);
517

            
518
impl<T> RewardProcessor<T> for RewardThroughFeesAccount<T>
519
where
520
    T: snowbridge_pallet_inbound_queue::Config + pallet_ethereum_token_transfers::Config,
521
    T::AccountId: From<sp_runtime::AccountId32>,
522
    <T::Token as Inspect<T::AccountId>>::Balance: core::fmt::Debug,
523
{
524
28
    fn process_reward(who: T::AccountId, _channel: Channel, message: Message) -> DispatchResult {
525
28
        let reward_amount = snowbridge_pallet_inbound_queue::Pallet::<T>::calculate_delivery_cost(
526
28
            message.encode().len() as u32,
527
28
        );
528
28

            
529
28
        let fees_account: T::AccountId = T::FeesAccount::get();
530
28

            
531
28
        let amount =
532
28
            T::Token::reducible_balance(&fees_account, Preservation::Preserve, Fortitude::Polite)
533
28
                .min(reward_amount);
534
28

            
535
28
        if amount != reward_amount {
536
10
            log::warn!(
537
                "RewardThroughFeesAccount: fees account running low on funds {:?}: {:?}",
538
                fees_account,
539
                amount
540
            );
541
18
        }
542

            
543
28
        if !amount.is_zero() {
544
18
            T::Token::transfer(&fees_account, &who, amount, Preservation::Preserve)?;
545
10
        }
546

            
547
28
        Ok(())
548
28
    }
549
}
550

            
551
pub struct BabeSlotBeacon<T>(PhantomData<T>);
552
impl<T: pallet_babe::Config> sp_runtime::traits::BlockNumberProvider for BabeSlotBeacon<T> {
553
    type BlockNumber = u32;
554

            
555
18
    fn current_block_number() -> Self::BlockNumber {
556
18
        // TODO: nimbus_primitives::SlotBeacon requires u32, but this is a u64 in pallet_babe, and
557
18
        // also it gets converted to u64 in pallet_author_noting, so let's do something to remove
558
18
        // this intermediate u32 conversion, such as using a different trait
559
18
        u64::from(pallet_babe::CurrentSlot::<T>::get()) as u32
560
18
    }
561
}
562

            
563
/// Combines the vrf output of the previous block with the provided subject.
564
/// This ensures that the randomness will be different on different pallets, as long as the subject is different.
565
5
pub fn mix_randomness<T: frame_system::Config>(vrf_output: [u8; 32], subject: &[u8]) -> T::Hash {
566
5
    let mut digest = Vec::new();
567
5
    digest.extend_from_slice(vrf_output.as_ref());
568
5
    digest.extend_from_slice(subject);
569
5

            
570
5
    T::Hashing::hash(digest.as_slice())
571
5
}
572

            
573
pub struct BabeAuthorVrfBlockRandomness<T>(PhantomData<T>);
574
impl<T: pallet_babe::Config + frame_system::Config> BabeAuthorVrfBlockRandomness<T> {
575
697
    pub fn get_block_randomness() -> Option<[u8; 32]> {
576
697
        // In a relay context we get block randomness from Babe's AuthorVrfRandomness
577
697
        pallet_babe::Pallet::<T>::author_vrf_randomness()
578
697
    }
579

            
580
697
    pub fn get_block_randomness_mixed(subject: &[u8]) -> Option<T::Hash> {
581
697
        Self::get_block_randomness().map(|random_hash| mix_randomness::<T>(random_hash, subject))
582
697
    }
583
}
584

            
585
impl<T: pallet_babe::Config + frame_system::Config>
586
    frame_support::traits::Randomness<T::Hash, BlockNumberFor<T>>
587
    for BabeAuthorVrfBlockRandomness<T>
588
{
589
    fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
590
        let block_number = frame_system::Pallet::<T>::block_number();
591
        let randomness = Self::get_block_randomness_mixed(subject).unwrap_or_default();
592

            
593
        (randomness, block_number)
594
    }
595
}
596

            
597
pub struct BabeGetCollatorAssignmentRandomness<T>(PhantomData<T>);
598
impl<T: pallet_babe::Config + frame_system::Config> Get<[u8; 32]>
599
    for BabeGetCollatorAssignmentRandomness<T>
600
{
601
1248
    fn get() -> [u8; 32] {
602
1248
        let block_number = frame_system::Pallet::<T>::block_number();
603
1248
        let random_seed = if !block_number.is_zero() {
604
696
            if let Some(random_hash) = {
605
696
                BabeAuthorVrfBlockRandomness::<T>::get_block_randomness_mixed(b"CollatorAssignment")
606
696
            } {
607
                // Return random_hash as a [u8; 32] instead of a Hash
608
4
                let mut buf = [0u8; 32];
609
4
                let len = core::cmp::min(32, random_hash.as_ref().len());
610
4
                buf[..len].copy_from_slice(&random_hash.as_ref()[..len]);
611
4

            
612
4
                buf
613
            } else {
614
                // If there is no randomness return [0; 32]
615
692
                [0; 32]
616
            }
617
        } else {
618
            // In block 0 (genesis) there is no randomness
619
552
            [0; 32]
620
        };
621

            
622
1248
        random_seed
623
1248
    }
624
}
625

            
626
/// `EthTokensLocalProcessor` is responsible for receiving and processing the ETH native
627
/// token and ERC20s coming from Ethereum with Tanssi chain or container-chains as final destinations.
628
/// TODO: add support for container transfers
629
pub struct EthTokensLocalProcessor<T, XcmProcessor, XcmWeigher, EthereumLocation, EthereumNetwork>(
630
    PhantomData<(
631
        T,
632
        XcmProcessor,
633
        XcmWeigher,
634
        EthereumLocation,
635
        EthereumNetwork,
636
    )>,
637
);
638
impl<T, XcmProcessor, XcmWeigher, EthereumLocation, EthereumNetwork> MessageProcessor
639
    for EthTokensLocalProcessor<T, XcmProcessor, XcmWeigher, EthereumLocation, EthereumNetwork>
640
where
641
    T: snowbridge_pallet_inbound_queue::Config
642
        + pallet_ethereum_token_transfers::Config
643
        + pallet_foreign_asset_creator::Config,
644
    XcmProcessor: ExecuteXcm<T::RuntimeCall>,
645
    XcmWeigher: WeightBounds<T::RuntimeCall>,
646
    EthereumLocation: Get<Location>,
647
    EthereumNetwork: Get<NetworkId>,
648
    cumulus_primitives_core::Location:
649
        EncodeLike<<T as pallet_foreign_asset_creator::Config>::ForeignAsset>,
650
{
651
7
    fn can_process_message(channel: &Channel, envelope: &Envelope) -> bool {
652
        // Ensure that the message is intended for the current channel, para_id and agent_id
653
7
        if let Some(channel_info) = pallet_ethereum_token_transfers::CurrentChannelInfo::<T>::get()
654
        {
655
7
            if envelope.channel_id != channel_info.channel_id
656
7
                || channel.para_id != channel_info.para_id
657
7
                || channel.agent_id != channel_info.agent_id
658
            {
659
                return false;
660
7
            }
661
        } else {
662
            return false;
663
        }
664

            
665
        // Check it is from the right gateway
666
7
        if envelope.gateway != T::GatewayAddress::get() {
667
            return false;
668
7
        }
669

            
670
7
        if let Some(eth_transfer_data) =
671
7
            Self::decode_message_for_eth_transfer(envelope.payload.as_slice())
672
        {
673
            // Check if the token location is a foreign asset included in ForeignAssetCreator
674
7
            return pallet_foreign_asset_creator::ForeignAssetToAssetId::<T>::get(
675
7
                eth_transfer_data.token_location,
676
7
            )
677
7
            .is_some();
678
        }
679

            
680
        false
681
7
    }
682

            
683
5
    fn process_message(_channel: Channel, envelope: Envelope) -> DispatchResult {
684
5
        let eth_transfer_data = Self::decode_message_for_eth_transfer(envelope.payload.as_slice())
685
5
            .ok_or(DispatchError::Other("unexpected message"))?;
686

            
687
5
        match eth_transfer_data.destination {
688
            Destination::AccountId32 { id: _ } => {
689
5
                Self::process_xcm_local_native_eth_transfer(eth_transfer_data)
690
            }
691
            // TODO: Add support for container transfers here
692
            _ => {
693
                log::error!("EthTokensLocalProcessor: container transfers not supported yet");
694
                return Ok(());
695
            }
696
        }
697
5
    }
698
}
699

            
700
/// Information needed to process an eth transfer message or check its validity.
701
pub struct EthTransferData {
702
    pub token_location: Location,
703
    pub destination: Destination,
704
    pub amount: u128,
705
}
706

            
707
impl<T, XcmProcessor, XcmWeigher, EthereumLocation, EthereumNetwork>
708
    EthTokensLocalProcessor<T, XcmProcessor, XcmWeigher, EthereumLocation, EthereumNetwork>
709
where
710
    T: frame_system::Config,
711
    XcmProcessor: ExecuteXcm<T::RuntimeCall>,
712
    XcmWeigher: WeightBounds<T::RuntimeCall>,
713
    EthereumLocation: Get<Location>,
714
    EthereumNetwork: Get<NetworkId>,
715
{
716
    /// Retrieve the eth transfer data from the message payload.
717
12
    pub fn decode_message_for_eth_transfer(mut payload: &[u8]) -> Option<EthTransferData> {
718
12
        match VersionedXcmMessage::decode_all(&mut payload) {
719
            Ok(VersionedXcmMessage::V1(MessageV1 {
720
                command:
721
                    Command::SendToken {
722
12
                        token: token_address,
723
12
                        destination,
724
12
                        amount,
725
                        fee: _,
726
                    },
727
                ..
728
            })) => {
729
12
                let token_location = if token_address == H160::zero() {
730
5
                    Location {
731
5
                        parents: 1,
732
5
                        interior: X1([GlobalConsensus(EthereumNetwork::get())].into()),
733
5
                    }
734
                } else {
735
7
                    Location {
736
7
                        parents: 1,
737
7
                        interior: X2([
738
7
                            GlobalConsensus(EthereumNetwork::get()),
739
7
                            AccountKey20 {
740
7
                                network: Some(EthereumNetwork::get()),
741
7
                                key: token_address.into(),
742
7
                            },
743
7
                        ]
744
7
                        .into()),
745
7
                    }
746
                };
747

            
748
12
                Some(EthTransferData {
749
12
                    token_location,
750
12
                    destination,
751
12
                    amount,
752
12
                })
753
            }
754
            _ => None,
755
        }
756
12
    }
757

            
758
    /// Process a native ETH transfer message to a local account in Tanssi chain.
759
5
    fn process_xcm_local_native_eth_transfer(eth_transfer_data: EthTransferData) -> DispatchResult {
760
5
        let assets_to_holding: XcmAssets = vec![XcmAsset {
761
5
            id: XcmAssetId::from(eth_transfer_data.token_location),
762
5
            fun: Fungibility::Fungible(eth_transfer_data.amount),
763
5
        }]
764
5
        .into();
765

            
766
5
        let destination_account = match eth_transfer_data.destination {
767
5
            Destination::AccountId32 { id } => id,
768
            _ => {
769
                log::error!("EthTokensLocalProcessor: invalid destination");
770
                return Ok(());
771
            }
772
        };
773

            
774
5
        let mut xcm = Xcm::<T::RuntimeCall>(vec![
775
5
            ReserveAssetDeposited(assets_to_holding),
776
5
            DepositAsset {
777
5
                assets: AllCounted(1).into(),
778
5
                beneficiary: Location::new(
779
5
                    0,
780
5
                    [AccountId32 {
781
5
                        network: None,
782
5
                        id: destination_account,
783
5
                    }],
784
5
                ),
785
5
            },
786
5
        ]);
787
5

            
788
5
        let ethereum_location = EthereumLocation::get();
789

            
790
5
        if let Ok(weight) = XcmWeigher::weight(&mut xcm) {
791
5
            let mut message_id = xcm.using_encoded(sp_io::hashing::blake2_256);
792
5

            
793
5
            let outcome = XcmProcessor::prepare_and_execute(
794
5
                ethereum_location,
795
5
                xcm,
796
5
                &mut message_id,
797
5
                weight,
798
5
                weight,
799
5
            );
800

            
801
5
            if let Err(error) = outcome.ensure_complete() {
802
1
                log::error!(
803
1
                    "EthTokensLocalProcessor: XCM execution failed with error {:?}",
804
                    error
805
                );
806
4
            }
807
        } else {
808
            log::error!("EthTokensLocalProcessor: unweighable message");
809
        }
810

            
811
5
        Ok(())
812
5
    }
813
}
814

            
815
/// Handler for depositing fees to the exporter fees account or a default account based on the reason.
816
pub struct ExporterFeeHandler<AssetTransactor, ExporterFeesAccount, DefaultAccount>(
817
    PhantomData<(AssetTransactor, ExporterFeesAccount, DefaultAccount)>,
818
);
819
impl<AssetTransactor, ExporterFeesAccount, DefaultAccount> HandleFee
820
    for ExporterFeeHandler<AssetTransactor, ExporterFeesAccount, DefaultAccount>
821
where
822
    AssetTransactor: TransactAsset,
823
    ExporterFeesAccount: Get<Location>,
824
    DefaultAccount: Get<Location>,
825
{
826
20
    fn handle_fee(fee: XcmAssets, context: Option<&XcmContext>, reason: FeeReason) -> XcmAssets {
827
20
        match reason {
828
            FeeReason::Export {
829
                network: _,
830
                destination: _,
831
18
            } => {
832
18
                deposit_or_burn_fee::<AssetTransactor>(fee, context, ExporterFeesAccount::get());
833
18
            }
834
2
            _ => {
835
2
                deposit_or_burn_fee::<AssetTransactor>(fee, context, DefaultAccount::get());
836
2
            }
837
        }
838

            
839
20
        XcmAssets::new()
840
20
    }
841
}