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};
28
use snowbridge_core::Channel;
29
use snowbridge_pallet_inbound_queue::RewardProcessor;
30
use sp_core::Get;
31
use sp_runtime::Vec;
32
use sp_runtime::{
33
    traits::{Hash as _, MaybeEquivalence},
34
    DispatchError, DispatchResult,
35
};
36
use {
37
    snowbridge_inbound_queue_primitives::v1::{
38
        Command, Destination, Envelope, MessageProcessor, MessageV1, VersionedXcmMessage,
39
    },
40
    snowbridge_inbound_queue_primitives::EventProof as Message,
41
};
42

            
43
/// `NativeTokenTransferMessageProcessor` is responsible for receiving and processing the Tanssi
44
/// native token sent from Ethereum. If the message is valid, it performs the token transfer
45
/// from the Ethereum sovereign account to the specified destination account.
46
pub struct NativeTokenTransferMessageProcessor<T>(PhantomData<T>);
47
impl<T> MessageProcessor for NativeTokenTransferMessageProcessor<T>
48
where
49
    T: snowbridge_pallet_inbound_queue::Config
50
        + pallet_ethereum_token_transfers::Config
51
        + snowbridge_pallet_system::Config,
52
    T::AccountId: From<[u8; 32]>,
53
{
54
20
    fn can_process_message(channel: &Channel, envelope: &Envelope) -> bool {
55
        // Ensure that the message is intended for the current channel, para_id and agent_id
56
20
        if let Some(channel_info) = pallet_ethereum_token_transfers::CurrentChannelInfo::<T>::get()
57
        {
58
18
            if envelope.channel_id != channel_info.channel_id
59
16
                || channel.para_id != channel_info.para_id
60
14
                || channel.agent_id != channel_info.agent_id
61
            {
62
6
                log::debug!(
63
                    "Unexpected channel id: {:?} != {:?}",
64
                    (envelope.channel_id, channel.para_id, channel.agent_id),
65
                    (
66
                        channel_info.channel_id,
67
                        channel_info.para_id,
68
                        channel_info.agent_id
69
                    )
70
                );
71
6
                return false;
72
12
            }
73
        } else {
74
2
            log::warn!("CurrentChannelInfo not set in storage");
75
2
            return false;
76
        }
77

            
78
        // Check it is from the right gateway
79
12
        if envelope.gateway != T::GatewayAddress::get() {
80
2
            log::warn!("Wrong gateway address: {:?}", envelope.gateway);
81
2
            return false;
82
10
        }
83
10

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

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

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

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

            
131
            DispatchError::Other("unable to parse the envelope payload")
132
12
        })?;
133

            
134
12
        log::trace!("NativeTokenTransferMessageProcessor: {:?}", message);
135

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

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

            
164
8
                Ok(())
165
            }
166
4
            msg => {
167
4
                log::warn!(
168
                    "NativeTokenTransferMessageProcessor: unexpected message: {:?}",
169
                    msg
170
                );
171
4
                Ok(())
172
            }
173
        }
174
12
    }
175
}
176

            
177
/// Rewards the relayer that processed a native token transfer message
178
/// using the FeesAccount configured in pallet_ethereum_token_transfers
179
pub struct RewardThroughFeesAccount<T>(PhantomData<T>);
180

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

            
192
23
        let fees_account: T::AccountId = T::FeesAccount::get();
193
23

            
194
23
        let amount =
195
23
            T::Token::reducible_balance(&fees_account, Preservation::Preserve, Fortitude::Polite)
196
23
                .min(reward_amount);
197
23

            
198
23
        if amount != reward_amount {
199
6
            log::warn!(
200
                "RewardThroughFeesAccount: fees account running low on funds {:?}: {:?}",
201
                fees_account,
202
                amount
203
            );
204
17
        }
205

            
206
23
        if !amount.is_zero() {
207
17
            T::Token::transfer(&fees_account, &who, amount, Preservation::Preserve)?;
208
6
        }
209

            
210
23
        Ok(())
211
23
    }
212
}
213

            
214
pub struct BabeSlotBeacon<T>(PhantomData<T>);
215
impl<T: pallet_babe::Config> sp_runtime::traits::BlockNumberProvider for BabeSlotBeacon<T> {
216
    type BlockNumber = u32;
217

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

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

            
233
5
    T::Hashing::hash(digest.as_slice())
234
5
}
235

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

            
243
697
    pub fn get_block_randomness_mixed(subject: &[u8]) -> Option<T::Hash> {
244
697
        Self::get_block_randomness().map(|random_hash| mix_randomness::<T>(random_hash, subject))
245
697
    }
246
}
247

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

            
256
        (randomness, block_number)
257
    }
258
}
259

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

            
275
4
                buf
276
            } else {
277
                // If there is no randomness return [0; 32]
278
692
                [0; 32]
279
            }
280
        } else {
281
            // In block 0 (genesis) there is no randomness
282
513
            [0; 32]
283
        };
284

            
285
1209
        random_seed
286
1209
    }
287
}