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_executor::traits::WeightBounds;
44
use {
45
    snowbridge_inbound_queue_primitives::v1::{
46
        Command, Destination, Envelope, MessageProcessor, MessageV1, VersionedXcmMessage,
47
    },
48
    snowbridge_inbound_queue_primitives::EventProof as Message,
49
};
50

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
592
        (randomness, block_number)
593
    }
594
}
595

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

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

            
621
1212
        random_seed
622
1212
    }
623
}
624

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

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

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

            
679
        false
680
7
    }
681

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

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

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

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

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

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

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

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

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

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

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

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

            
810
5
        Ok(())
811
5
    }
812
}