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
use core::marker::PhantomData;
19
use frame_support::{
20
    pallet_prelude::Zero,
21
    traits::{
22
        fungible::{Inspect, Mutate},
23
        tokens::{Fortitude, Preservation},
24
    },
25
};
26
use frame_system::pallet_prelude::BlockNumberFor;
27
use parity_scale_codec::{DecodeAll, Encode, EncodeLike};
28
use scale_info::prelude::vec;
29
use snowbridge_core::Channel;
30
use snowbridge_pallet_inbound_queue::RewardProcessor;
31
use sp_core::{Get, H160};
32
use sp_runtime::Vec;
33
use sp_runtime::{
34
    traits::{Hash as _, MaybeEquivalence},
35
    DispatchError, DispatchResult,
36
};
37
use xcm::latest::{
38
    prelude::*, Asset as XcmAsset, AssetId as XcmAssetId, Assets as XcmAssets, ExecuteXcm,
39
    Fungibility, Junctions::*,
40
};
41
use xcm_executor::traits::WeightBounds;
42
use {
43
    snowbridge_inbound_queue_primitives::v1::{
44
        Command, Destination, Envelope, MessageProcessor, MessageV1, VersionedXcmMessage,
45
    },
46
    snowbridge_inbound_queue_primitives::EventProof as Message,
47
};
48
/// `NativeTokenTransferMessageProcessor` is responsible for receiving and processing the Tanssi
49
/// native token sent from Ethereum. If the message is valid, it performs the token transfer
50
/// from the Ethereum sovereign account to the specified destination account.
51
pub struct NativeTokenTransferMessageProcessor<T>(PhantomData<T>);
52
impl<T> MessageProcessor for NativeTokenTransferMessageProcessor<T>
53
where
54
    T: snowbridge_pallet_inbound_queue::Config
55
        + pallet_ethereum_token_transfers::Config
56
        + snowbridge_pallet_system::Config,
57
    T::AccountId: From<[u8; 32]>,
58
{
59
20
    fn can_process_message(channel: &Channel, envelope: &Envelope) -> bool {
60
        // Ensure that the message is intended for the current channel, para_id and agent_id
61
20
        if let Some(channel_info) = pallet_ethereum_token_transfers::CurrentChannelInfo::<T>::get()
62
        {
63
18
            if envelope.channel_id != channel_info.channel_id
64
16
                || channel.para_id != channel_info.para_id
65
14
                || channel.agent_id != channel_info.agent_id
66
            {
67
6
                log::debug!(
68
                    "Unexpected channel id: {:?} != {:?}",
69
                    (envelope.channel_id, channel.para_id, channel.agent_id),
70
                    (
71
                        channel_info.channel_id,
72
                        channel_info.para_id,
73
                        channel_info.agent_id
74
                    )
75
                );
76
6
                return false;
77
12
            }
78
        } else {
79
2
            log::warn!("CurrentChannelInfo not set in storage");
80
2
            return false;
81
        }
82

            
83
        // Check it is from the right gateway
84
12
        if envelope.gateway != T::GatewayAddress::get() {
85
2
            log::warn!("Wrong gateway address: {:?}", envelope.gateway);
86
2
            return false;
87
10
        }
88
10

            
89
10
        // Try decode the message and check the token id is the expected one
90
10
        match VersionedXcmMessage::decode_all(&mut envelope.payload.as_slice()) {
91
            Ok(VersionedXcmMessage::V1(MessageV1 {
92
8
                command: Command::SendNativeToken { token_id, .. },
93
8
                ..
94
8
            })) => {
95
8
                let token_location = T::TokenLocationReanchored::get();
96

            
97
8
                if let Some(expected_token_id) =
98
8
                    snowbridge_pallet_system::Pallet::<T>::convert_back(&token_location)
99
                {
100
8
                    if token_id == expected_token_id {
101
8
                        true
102
                    } else {
103
                        // TODO: ensure this does not warn on container token transfers or other message types, if yes change to debug
104
                        log::warn!(
105
                            "NativeTokenTransferMessageProcessor: unexpected token_id: {:?}",
106
                            token_id
107
                        );
108
                        false
109
                    }
110
                } else {
111
                    log::warn!("NativeTokenTransferMessageProcessor: token id not found for location: {:?}", token_location);
112

            
113
                    false
114
                }
115
            }
116
2
            Ok(msg) => {
117
2
                log::trace!(
118
                    "NativeTokenTransferMessageProcessor: unexpected message: {:?}",
119
                    msg
120
                );
121
2
                false
122
            }
123
            Err(e) => {
124
                log::trace!("NativeTokenTransferMessageProcessor: failed to decode message. This is expected if the message is not for this processor. Error: {:?}", e);
125
                false
126
            }
127
        }
128
20
    }
129

            
130
12
    fn process_message(_channel: Channel, envelope: Envelope) -> DispatchResult {
131
        // - Decode payload as SendNativeToken
132
12
        let message = VersionedXcmMessage::decode_all(&mut envelope.payload.as_slice())
133
12
        .map_err(|e| {
134
            log::trace!("NativeTokenTransferMessageProcessor: failed to decode message. This is expected if the message is not for this processor. Error: {:?}", e);
135

            
136
            DispatchError::Other("unable to parse the envelope payload")
137
12
        })?;
138

            
139
12
        log::trace!("NativeTokenTransferMessageProcessor: {:?}", message);
140

            
141
12
        match message {
142
            VersionedXcmMessage::V1(MessageV1 {
143
                chain_id: _,
144
                command:
145
                    Command::SendNativeToken {
146
                        destination:
147
                            Destination::AccountId32 {
148
8
                                id: destination_account,
149
8
                            },
150
8
                        amount,
151
8
                        ..
152
8
                    },
153
8
            }) => {
154
8
                // - Transfer the amounts of tokens from Ethereum sov account to the destination
155
8
                let sovereign_account = T::EthereumSovereignAccount::get();
156

            
157
8
                if let Err(e) = T::Currency::transfer(
158
8
                    &sovereign_account,
159
8
                    &destination_account.into(),
160
8
                    amount.into(),
161
8
                    Preservation::Preserve,
162
8
                ) {
163
2
                    log::warn!(
164
                        "NativeTokenTransferMessageProcessor: Error transferring tokens: {:?}",
165
                        e
166
                    );
167
6
                }
168

            
169
8
                Ok(())
170
            }
171
4
            msg => {
172
4
                log::warn!(
173
                    "NativeTokenTransferMessageProcessor: unexpected message: {:?}",
174
                    msg
175
                );
176
4
                Ok(())
177
            }
178
        }
179
12
    }
180
}
181

            
182
/// Rewards the relayer that processed a native token transfer message
183
/// using the FeesAccount configured in pallet_ethereum_token_transfers
184
pub struct RewardThroughFeesAccount<T>(PhantomData<T>);
185

            
186
impl<T> RewardProcessor<T> for RewardThroughFeesAccount<T>
187
where
188
    T: snowbridge_pallet_inbound_queue::Config + pallet_ethereum_token_transfers::Config,
189
    T::AccountId: From<sp_runtime::AccountId32>,
190
    <T::Token as Inspect<T::AccountId>>::Balance: core::fmt::Debug,
191
{
192
23
    fn process_reward(who: T::AccountId, _channel: Channel, message: Message) -> DispatchResult {
193
23
        let reward_amount = snowbridge_pallet_inbound_queue::Pallet::<T>::calculate_delivery_cost(
194
23
            message.encode().len() as u32,
195
23
        );
196
23

            
197
23
        let fees_account: T::AccountId = T::FeesAccount::get();
198
23

            
199
23
        let amount =
200
23
            T::Token::reducible_balance(&fees_account, Preservation::Preserve, Fortitude::Polite)
201
23
                .min(reward_amount);
202
23

            
203
23
        if amount != reward_amount {
204
6
            log::warn!(
205
                "RewardThroughFeesAccount: fees account running low on funds {:?}: {:?}",
206
                fees_account,
207
                amount
208
            );
209
17
        }
210

            
211
23
        if !amount.is_zero() {
212
17
            T::Token::transfer(&fees_account, &who, amount, Preservation::Preserve)?;
213
6
        }
214

            
215
23
        Ok(())
216
23
    }
217
}
218

            
219
pub struct BabeSlotBeacon<T>(PhantomData<T>);
220
impl<T: pallet_babe::Config> sp_runtime::traits::BlockNumberProvider for BabeSlotBeacon<T> {
221
    type BlockNumber = u32;
222

            
223
18
    fn current_block_number() -> Self::BlockNumber {
224
18
        // TODO: nimbus_primitives::SlotBeacon requires u32, but this is a u64 in pallet_babe, and
225
18
        // also it gets converted to u64 in pallet_author_noting, so let's do something to remove
226
18
        // this intermediate u32 conversion, such as using a different trait
227
18
        u64::from(pallet_babe::CurrentSlot::<T>::get()) as u32
228
18
    }
229
}
230

            
231
/// Combines the vrf output of the previous block with the provided subject.
232
/// This ensures that the randomness will be different on different pallets, as long as the subject is different.
233
5
pub fn mix_randomness<T: frame_system::Config>(vrf_output: [u8; 32], subject: &[u8]) -> T::Hash {
234
5
    let mut digest = Vec::new();
235
5
    digest.extend_from_slice(vrf_output.as_ref());
236
5
    digest.extend_from_slice(subject);
237
5

            
238
5
    T::Hashing::hash(digest.as_slice())
239
5
}
240

            
241
pub struct BabeAuthorVrfBlockRandomness<T>(PhantomData<T>);
242
impl<T: pallet_babe::Config + frame_system::Config> BabeAuthorVrfBlockRandomness<T> {
243
697
    pub fn get_block_randomness() -> Option<[u8; 32]> {
244
697
        // In a relay context we get block randomness from Babe's AuthorVrfRandomness
245
697
        pallet_babe::Pallet::<T>::author_vrf_randomness()
246
697
    }
247

            
248
697
    pub fn get_block_randomness_mixed(subject: &[u8]) -> Option<T::Hash> {
249
697
        Self::get_block_randomness().map(|random_hash| mix_randomness::<T>(random_hash, subject))
250
697
    }
251
}
252

            
253
impl<T: pallet_babe::Config + frame_system::Config>
254
    frame_support::traits::Randomness<T::Hash, BlockNumberFor<T>>
255
    for BabeAuthorVrfBlockRandomness<T>
256
{
257
    fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor<T>) {
258
        let block_number = frame_system::Pallet::<T>::block_number();
259
        let randomness = Self::get_block_randomness_mixed(subject).unwrap_or_default();
260

            
261
        (randomness, block_number)
262
    }
263
}
264

            
265
pub struct BabeGetCollatorAssignmentRandomness<T>(PhantomData<T>);
266
impl<T: pallet_babe::Config + frame_system::Config> Get<[u8; 32]>
267
    for BabeGetCollatorAssignmentRandomness<T>
268
{
269
1204
    fn get() -> [u8; 32] {
270
1204
        let block_number = frame_system::Pallet::<T>::block_number();
271
1204
        let random_seed = if !block_number.is_zero() {
272
696
            if let Some(random_hash) = {
273
696
                BabeAuthorVrfBlockRandomness::<T>::get_block_randomness_mixed(b"CollatorAssignment")
274
696
            } {
275
                // Return random_hash as a [u8; 32] instead of a Hash
276
4
                let mut buf = [0u8; 32];
277
4
                let len = core::cmp::min(32, random_hash.as_ref().len());
278
4
                buf[..len].copy_from_slice(&random_hash.as_ref()[..len]);
279
4

            
280
4
                buf
281
            } else {
282
                // If there is no randomness return [0; 32]
283
692
                [0; 32]
284
            }
285
        } else {
286
            // In block 0 (genesis) there is no randomness
287
508
            [0; 32]
288
        };
289

            
290
1204
        random_seed
291
1204
    }
292
}
293

            
294
/// `EthTokensLocalProcessor` is responsible for receiving and processing the ETH native
295
/// token and ERC20s coming from Ethereum with Tanssi chain or container-chains as final destinations.
296
/// TODO: add support for container transfers
297
pub struct EthTokensLocalProcessor<T, XcmProcessor, XcmWeigher, EthereumLocation, EthereumNetwork>(
298
    PhantomData<(
299
        T,
300
        XcmProcessor,
301
        XcmWeigher,
302
        EthereumLocation,
303
        EthereumNetwork,
304
    )>,
305
);
306
impl<T, XcmProcessor, XcmWeigher, EthereumLocation, EthereumNetwork> MessageProcessor
307
    for EthTokensLocalProcessor<T, XcmProcessor, XcmWeigher, EthereumLocation, EthereumNetwork>
308
where
309
    T: snowbridge_pallet_inbound_queue::Config
310
        + pallet_ethereum_token_transfers::Config
311
        + pallet_foreign_asset_creator::Config,
312
    XcmProcessor: ExecuteXcm<T::RuntimeCall>,
313
    XcmWeigher: WeightBounds<T::RuntimeCall>,
314
    EthereumLocation: Get<Location>,
315
    EthereumNetwork: Get<NetworkId>,
316
    cumulus_primitives_core::Location:
317
        EncodeLike<<T as pallet_foreign_asset_creator::Config>::ForeignAsset>,
318
{
319
7
    fn can_process_message(channel: &Channel, envelope: &Envelope) -> bool {
320
        // Ensure that the message is intended for the current channel, para_id and agent_id
321
7
        if let Some(channel_info) = pallet_ethereum_token_transfers::CurrentChannelInfo::<T>::get()
322
        {
323
7
            if envelope.channel_id != channel_info.channel_id
324
7
                || channel.para_id != channel_info.para_id
325
7
                || channel.agent_id != channel_info.agent_id
326
            {
327
                return false;
328
7
            }
329
        } else {
330
            return false;
331
        }
332

            
333
        // Check it is from the right gateway
334
7
        if envelope.gateway != T::GatewayAddress::get() {
335
            return false;
336
7
        }
337

            
338
7
        if let Some(eth_transfer_data) =
339
7
            Self::decode_message_for_eth_transfer(envelope.payload.as_slice())
340
        {
341
            // Check if the token location is a foreign asset included in ForeignAssetCreator
342
7
            return pallet_foreign_asset_creator::ForeignAssetToAssetId::<T>::get(
343
7
                eth_transfer_data.token_location,
344
7
            )
345
7
            .is_some();
346
        }
347

            
348
        false
349
7
    }
350

            
351
5
    fn process_message(_channel: Channel, envelope: Envelope) -> DispatchResult {
352
5
        let eth_transfer_data = Self::decode_message_for_eth_transfer(envelope.payload.as_slice())
353
5
            .ok_or(DispatchError::Other("unexpected message"))?;
354

            
355
5
        match eth_transfer_data.destination {
356
            Destination::AccountId32 { id: _ } => {
357
5
                Self::process_xcm_local_native_eth_transfer(eth_transfer_data)
358
            }
359
            // TODO: Add support for container transfers here
360
            _ => {
361
                log::error!("EthTokensLocalProcessor: container transfers not supported yet");
362
                return Ok(());
363
            }
364
        }
365
5
    }
366
}
367

            
368
/// Information needed to process an eth transfer message or check its validity.
369
pub struct EthTransferData {
370
    pub token_location: Location,
371
    pub destination: Destination,
372
    pub amount: u128,
373
}
374

            
375
impl<T, XcmProcessor, XcmWeigher, EthereumLocation, EthereumNetwork>
376
    EthTokensLocalProcessor<T, XcmProcessor, XcmWeigher, EthereumLocation, EthereumNetwork>
377
where
378
    T: frame_system::Config,
379
    XcmProcessor: ExecuteXcm<T::RuntimeCall>,
380
    XcmWeigher: WeightBounds<T::RuntimeCall>,
381
    EthereumLocation: Get<Location>,
382
    EthereumNetwork: Get<NetworkId>,
383
{
384
    /// Retrieve the eth transfer data from the message payload.
385
12
    pub fn decode_message_for_eth_transfer(mut payload: &[u8]) -> Option<EthTransferData> {
386
12
        match VersionedXcmMessage::decode_all(&mut payload) {
387
            Ok(VersionedXcmMessage::V1(MessageV1 {
388
                command:
389
                    Command::SendToken {
390
12
                        token: token_address,
391
12
                        destination,
392
12
                        amount,
393
                        fee: _,
394
                    },
395
                ..
396
            })) => {
397
12
                let token_location = if token_address == H160::zero() {
398
5
                    Location {
399
5
                        parents: 1,
400
5
                        interior: X1([GlobalConsensus(EthereumNetwork::get())].into()),
401
5
                    }
402
                } else {
403
7
                    Location {
404
7
                        parents: 1,
405
7
                        interior: X2([
406
7
                            GlobalConsensus(EthereumNetwork::get()),
407
7
                            AccountKey20 {
408
7
                                network: Some(EthereumNetwork::get()),
409
7
                                key: token_address.into(),
410
7
                            },
411
7
                        ]
412
7
                        .into()),
413
7
                    }
414
                };
415

            
416
12
                Some(EthTransferData {
417
12
                    token_location,
418
12
                    destination,
419
12
                    amount,
420
12
                })
421
            }
422
            _ => None,
423
        }
424
12
    }
425

            
426
    /// Process a native ETH transfer message to a local account in Tanssi chain.
427
5
    fn process_xcm_local_native_eth_transfer(eth_transfer_data: EthTransferData) -> DispatchResult {
428
5
        let assets_to_holding: XcmAssets = vec![XcmAsset {
429
5
            id: XcmAssetId::from(eth_transfer_data.token_location),
430
5
            fun: Fungibility::Fungible(eth_transfer_data.amount),
431
5
        }]
432
5
        .into();
433

            
434
5
        let destination_account = match eth_transfer_data.destination {
435
5
            Destination::AccountId32 { id } => id,
436
            _ => {
437
                log::error!("EthTokensLocalProcessor: invalid destination");
438
                return Ok(());
439
            }
440
        };
441

            
442
5
        let mut xcm = Xcm::<T::RuntimeCall>(vec![
443
5
            ReserveAssetDeposited(assets_to_holding),
444
5
            DepositAsset {
445
5
                assets: AllCounted(1).into(),
446
5
                beneficiary: Location::new(
447
5
                    0,
448
5
                    [AccountId32 {
449
5
                        network: None,
450
5
                        id: destination_account,
451
5
                    }],
452
5
                ),
453
5
            },
454
5
        ]);
455
5

            
456
5
        let ethereum_location = EthereumLocation::get();
457

            
458
5
        if let Ok(weight) = XcmWeigher::weight(&mut xcm) {
459
5
            let mut message_id = xcm.using_encoded(sp_io::hashing::blake2_256);
460
5

            
461
5
            let outcome = XcmProcessor::prepare_and_execute(
462
5
                ethereum_location,
463
5
                xcm,
464
5
                &mut message_id,
465
5
                weight,
466
5
                weight,
467
5
            );
468

            
469
5
            if let Err(error) = outcome.ensure_complete() {
470
1
                log::error!(
471
1
                    "EthTokensLocalProcessor: XCM execution failed with error {:?}",
472
                    error
473
                );
474
4
            }
475
        } else {
476
            log::error!("EthTokensLocalProcessor: unweighable message");
477
        }
478

            
479
5
        Ok(())
480
5
    }
481
}