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
//! Crate containing various traits used by moondance crates allowing to connect pallet
18
//! with each other or with mocks.
19

            
20
#![cfg_attr(not(feature = "std"), no_std)]
21

            
22
#[cfg(feature = "runtime-benchmarks")]
23
mod benchmarks;
24
#[cfg(test)]
25
mod tests;
26

            
27
pub mod generic_token_message_processor;
28
pub mod snowbridge_outbound_token_transfer;
29
pub mod symbiotic_message_processor;
30

            
31
use {
32
    core::marker::PhantomData,
33
    cumulus_primitives_core::{
34
        relay_chain::{AccountId, Balance},
35
        AccountKey20, Assets, Ethereum, GlobalConsensus, Location, SendResult, SendXcm, Xcm,
36
        XcmHash,
37
    },
38
    ethabi::{Token, U256},
39
    frame_support::{
40
        ensure,
41
        pallet_prelude::{Decode, Encode, Get},
42
        traits::Contains,
43
    },
44
    frame_system::unique,
45
    parity_scale_codec::{DecodeWithMemTracking, MaxEncodedLen},
46
    scale_info::TypeInfo,
47
    snowbridge_core::{AgentId, Channel, ChannelId, ParaId},
48
    snowbridge_inbound_queue_primitives::v1::{
49
        ConvertMessage, ConvertMessageError, VersionedXcmMessage,
50
    },
51
    snowbridge_outbound_queue_primitives::{v1::Fee, SendError},
52
    snowbridge_pallet_outbound_queue::send_message_impl::Ticket,
53
    sp_core::{blake2_256, hashing, H256},
54
    sp_runtime::{app_crypto::sp_core, traits::Convert, RuntimeDebug},
55
    sp_std::vec::Vec,
56
};
57

            
58
// Separate import as rustfmt wrongly change it to `sp_std::vec::self`, which is the module instead
59
// of the macro.
60
use sp_std::vec;
61

            
62
pub use {
63
    custom_do_process_message::{ConstantGasMeter, CustomProcessSnowbridgeMessage},
64
    custom_send_message::CustomSendMessage,
65
    xcm_executor::traits::ConvertLocation,
66
};
67

            
68
#[cfg(feature = "runtime-benchmarks")]
69
pub use benchmarks::*;
70

            
71
mod custom_do_process_message;
72
mod custom_send_message;
73

            
74
#[derive(Clone, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo, PartialEq)]
75
pub struct SlashData {
76
    pub encoded_validator_id: Vec<u8>,
77
    pub slash_fraction: u32,
78
    pub external_idx: u64,
79
}
80

            
81
/// A command which is executable by the Gateway contract on Ethereum
82
#[derive(Clone, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo, PartialEq)]
83
pub enum Command {
84
    // TODO: add real commands here
85
    Test(Vec<u8>),
86
    ReportRewards {
87
        // external identifier for validators
88
        external_idx: u64,
89
        // index of the era we are sending info of
90
        era_index: u32,
91
        // total_points for the era
92
        total_points: u128,
93
        // new tokens inflated during the era
94
        tokens_inflated: u128,
95
        // merkle root of vec![(validatorId, rewardPoints)]
96
        rewards_merkle_root: H256,
97
        // the token id in which we need to mint
98
        token_id: H256,
99
    },
100
    ReportSlashes {
101
        // index of the era we are sending info of
102
        era_index: u32,
103
        // vec of `SlashData`
104
        slashes: Vec<SlashData>,
105
    },
106
}
107

            
108
impl Command {
109
    /// Compute the enum variant index
110
1344
    pub fn index(&self) -> u8 {
111
1344
        match self {
112
            // Starting from 32 to keep compatibility with Snowbridge Command enum
113
64
            Command::Test { .. } => 32,
114
192
            Command::ReportRewards { .. } => 33,
115
1088
            Command::ReportSlashes { .. } => 34,
116
        }
117
1344
    }
118

            
119
    /// ABI-encode the Command.
120
4035
    pub fn abi_encode(&self) -> Vec<u8> {
121
4035
        match self {
122
145
            Command::Test(payload) => {
123
145
                ethabi::encode(&[Token::Tuple(vec![Token::Bytes(payload.clone())])])
124
            }
125
            Command::ReportRewards {
126
625
                external_idx,
127
625
                era_index,
128
625
                total_points,
129
625
                tokens_inflated,
130
625
                rewards_merkle_root,
131
625
                token_id,
132
625
            } => {
133
625
                let external_idx_token = Token::Uint(U256::from(*external_idx));
134
625
                let era_index_token = Token::Uint(U256::from(*era_index));
135
625
                let total_points_token = Token::Uint(U256::from(*total_points));
136
625
                let tokens_inflated_token = Token::Uint(U256::from(*tokens_inflated));
137
625
                let rewards_mr_token = Token::FixedBytes(rewards_merkle_root.0.to_vec());
138
625
                let token_id_token = Token::FixedBytes(token_id.0.to_vec());
139
625

            
140
625
                ethabi::encode(&[Token::Tuple(vec![
141
625
                    external_idx_token,
142
625
                    era_index_token,
143
625
                    total_points_token,
144
625
                    tokens_inflated_token,
145
625
                    rewards_mr_token,
146
625
                    token_id_token,
147
625
                ])])
148
            }
149
3265
            Command::ReportSlashes { era_index, slashes } => {
150
3265
                let era_index_token = Token::Uint(U256::from(*era_index));
151
3265
                let mut slashes_tokens_vec: Vec<Token> = vec![];
152

            
153
30051
                for slash in slashes.iter() {
154
30051
                    let account_token = Token::FixedBytes(slash.encoded_validator_id.clone());
155
30051
                    let slash_fraction_token = Token::Uint(U256::from(slash.slash_fraction));
156
30051
                    let external_idx = Token::Uint(U256::from(slash.external_idx));
157
30051
                    let tuple_token =
158
30051
                        Token::Tuple(vec![account_token, slash_fraction_token, external_idx]);
159
30051

            
160
30051
                    slashes_tokens_vec.push(tuple_token);
161
30051
                }
162

            
163
3265
                let slashes_tokens_array = Token::Array(slashes_tokens_vec);
164
3265
                ethabi::encode(&[Token::Tuple(vec![era_index_token, slashes_tokens_array])])
165
            }
166
        }
167
4035
    }
168
}
169

            
170
// A message which can be accepted by implementations of `/[`SendMessage`\]`
171
#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)]
172
#[cfg_attr(feature = "std", derive(PartialEq))]
173
pub struct Message {
174
    /// ID for this message. One will be automatically generated if not provided.
175
    ///
176
    /// When this message is created from an XCM message, the ID should be extracted
177
    /// from the `SetTopic` instruction.
178
    ///
179
    /// The ID plays no role in bridge consensus, and is purely meant for message tracing.
180
    pub id: Option<H256>,
181
    /// The message channel ID
182
    pub channel_id: ChannelId,
183
    /// The stable ID for a receiving gateway contract
184
    pub command: Command,
185
}
186

            
187
pub trait TicketInfo {
188
    fn message_id(&self) -> H256;
189
}
190

            
191
impl TicketInfo for () {
192
16
    fn message_id(&self) -> H256 {
193
16
        H256::zero()
194
16
    }
195
}
196

            
197
#[cfg(not(feature = "runtime-benchmarks"))]
198
impl<T: snowbridge_pallet_outbound_queue::Config> TicketInfo for Ticket<T> {
199
88
    fn message_id(&self) -> H256 {
200
88
        self.message_id
201
88
    }
202
}
203

            
204
// Benchmarks check message_id so it must be deterministic.
205
#[cfg(feature = "runtime-benchmarks")]
206
impl<T: snowbridge_pallet_outbound_queue::Config> TicketInfo for Ticket<T> {
207
    fn message_id(&self) -> H256 {
208
        H256::default()
209
    }
210
}
211

            
212
pub struct MessageValidator<T: snowbridge_pallet_outbound_queue::Config>(PhantomData<T>);
213

            
214
pub trait ValidateMessage {
215
    type Ticket: TicketInfo;
216

            
217
    fn validate(message: &Message) -> Result<(Self::Ticket, Fee<u64>), SendError>;
218
}
219

            
220
impl<T: snowbridge_pallet_outbound_queue::Config> ValidateMessage for MessageValidator<T> {
221
    type Ticket = Ticket<T>;
222

            
223
84
    fn validate(message: &Message) -> Result<(Self::Ticket, Fee<u64>), SendError> {
224
84
        log::trace!("MessageValidator: {:?}", message);
225
        // The inner payload should not be too large
226
84
        let payload = message.command.abi_encode();
227
84
        ensure!(
228
84
            payload.len() < T::MaxMessagePayloadSize::get() as usize,
229
            SendError::MessageTooLarge
230
        );
231

            
232
        // Ensure there is a registered channel we can transmit this message on
233
84
        ensure!(
234
84
            T::Channels::contains(&message.channel_id),
235
            SendError::InvalidChannel
236
        );
237

            
238
        // Generate a unique message id unless one is provided
239
84
        let message_id: H256 = message
240
84
            .id
241
84
            .unwrap_or_else(|| unique((message.channel_id, &message.command)).into());
242
84

            
243
84
        // Fee not used
244
84
        /*
245
84
        let gas_used_at_most = T::GasMeter::maximum_gas_used_at_most(&message.command);
246
84
        let fee = Self::calculate_fee(gas_used_at_most, T::PricingParameters::get());
247
84
         */
248
84

            
249
84
        let queued_message: VersionedQueuedMessage = QueuedMessage {
250
84
            id: message_id,
251
84
            channel_id: message.channel_id,
252
84
            command: message.command.clone(),
253
84
        }
254
84
        .into();
255
        // The whole message should not be too large
256
84
        let encoded = queued_message
257
84
            .encode()
258
84
            .try_into()
259
84
            .map_err(|_| SendError::MessageTooLarge)?;
260

            
261
84
        let ticket = Ticket {
262
84
            message_id,
263
84
            channel_id: message.channel_id,
264
84
            message: encoded,
265
84
        };
266
84
        let fee = Fee {
267
84
            local: Default::default(),
268
84
            remote: Default::default(),
269
84
        };
270
84

            
271
84
        Ok((ticket, fee))
272
84
    }
273
}
274

            
275
impl ValidateMessage for () {
276
    type Ticket = ();
277

            
278
16
    fn validate(_message: &Message) -> Result<(Self::Ticket, Fee<u64>), SendError> {
279
16
        Ok((
280
16
            (),
281
16
            Fee {
282
16
                local: 1,
283
16
                remote: 1,
284
16
            },
285
16
        ))
286
16
    }
287
}
288

            
289
/// Message which is awaiting processing in the MessageQueue pallet
290
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
291
#[cfg_attr(feature = "std", derive(PartialEq))]
292
pub struct QueuedMessage {
293
    /// Message ID
294
    pub id: H256,
295
    /// Channel ID
296
    pub channel_id: ChannelId,
297
    /// Command to execute in the Gateway contract
298
    pub command: Command,
299
}
300

            
301
/// Enqueued outbound messages need to be versioned to prevent data corruption
302
/// or loss after forkless runtime upgrades
303
#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)]
304
#[cfg_attr(feature = "std", derive(PartialEq))]
305
pub enum VersionedQueuedMessage {
306
    V1(QueuedMessage),
307
}
308

            
309
impl From<QueuedMessage> for VersionedQueuedMessage {
310
1344
    fn from(x: QueuedMessage) -> Self {
311
1344
        VersionedQueuedMessage::V1(x)
312
1344
    }
313
}
314

            
315
impl From<VersionedQueuedMessage> for QueuedMessage {
316
1312
    fn from(x: VersionedQueuedMessage) -> Self {
317
1312
        match x {
318
1312
            VersionedQueuedMessage::V1(x) => x,
319
1312
        }
320
1312
    }
321
}
322

            
323
pub trait DeliverMessage {
324
    type Ticket;
325

            
326
    fn deliver(ticket: Self::Ticket) -> Result<H256, SendError>;
327
}
328

            
329
/// Dummy router for xcm messages coming from ethereum
330
pub struct DoNothingRouter;
331
impl SendXcm for DoNothingRouter {
332
    type Ticket = Xcm<()>;
333

            
334
    fn validate(
335
        _dest: &mut Option<Location>,
336
        xcm: &mut Option<Xcm<()>>,
337
    ) -> SendResult<Self::Ticket> {
338
        Ok((xcm.clone().unwrap(), Assets::new()))
339
    }
340
    fn deliver(xcm: Xcm<()>) -> Result<XcmHash, cumulus_primitives_core::SendError> {
341
        let hash = xcm.using_encoded(hashing::blake2_256);
342
        Ok(hash)
343
    }
344
}
345

            
346
/// Dummy message converter to convert message to Xcm
347
pub struct DoNothingConvertMessage;
348

            
349
impl ConvertMessage for DoNothingConvertMessage {
350
    type Balance = Balance;
351
    type AccountId = AccountId;
352

            
353
    fn convert(
354
        _: H256,
355
        _message: VersionedXcmMessage,
356
    ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> {
357
        Err(ConvertMessageError::UnsupportedVersion)
358
    }
359
}
360

            
361
// This is a variation of the converter found here:
362
// https://github.com/paritytech/polkadot-sdk/blob/711e6ff33373bc08b026446ce19b73920bfe068c/bridges/snowbridge/primitives/router/src/inbound/mod.rs#L467
363
//
364
// Upstream converter only works for parachains (parents 2) while we to use it in tanssi solo-chain
365
// (parents 1).
366
pub struct EthereumLocationsConverterFor<AccountId>(PhantomData<AccountId>);
367
impl<AccountId> ConvertLocation<AccountId> for EthereumLocationsConverterFor<AccountId>
368
where
369
    AccountId: From<[u8; 32]> + Clone,
370
{
371
166
    fn convert_location(location: &Location) -> Option<AccountId> {
372
166
        match location.unpack() {
373
166
            (1, [GlobalConsensus(Ethereum { chain_id })]) => {
374
166
                Some(Self::from_chain_id(chain_id).into())
375
            }
376
            (1, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => {
377
                Some(Self::from_chain_id_with_key(chain_id, *key).into())
378
            }
379
            _ => None,
380
        }
381
166
    }
382
}
383

            
384
impl<AccountId> EthereumLocationsConverterFor<AccountId> {
385
166
    pub fn from_chain_id(chain_id: &u64) -> [u8; 32] {
386
166
        (b"ethereum-chain", chain_id).using_encoded(blake2_256)
387
166
    }
388
    pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] {
389
        (b"ethereum-chain", chain_id, key).using_encoded(blake2_256)
390
    }
391
}
392

            
393
/// Information of a recently created channel.
394
#[derive(
395
    Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo, Clone, PartialEq, MaxEncodedLen,
396
)]
397
pub struct ChannelInfo {
398
    pub channel_id: ChannelId,
399
    pub para_id: ParaId,
400
    pub agent_id: AgentId,
401
}
402

            
403
/// Trait to manage channel creation inside EthereumSystem pallet.
404
pub trait EthereumSystemChannelManager {
405
    fn create_channel(channel_id: ChannelId, agent_id: AgentId, para_id: ParaId) -> ChannelInfo;
406
}
407

            
408
/// Implementation struct for EthereumSystemChannelManager trait.
409
pub struct EthereumSystemHandler<Runtime>(PhantomData<Runtime>);
410
impl<Runtime> EthereumSystemChannelManager for EthereumSystemHandler<Runtime>
411
where
412
    Runtime: snowbridge_pallet_system::Config,
413
{
414
43
    fn create_channel(channel_id: ChannelId, agent_id: AgentId, para_id: ParaId) -> ChannelInfo {
415
43
        if let Some(channel) = snowbridge_pallet_system::Channels::<Runtime>::get(channel_id) {
416
2
            ChannelInfo {
417
2
                channel_id,
418
2
                para_id: channel.para_id,
419
2
                agent_id: channel.agent_id,
420
2
            }
421
        } else {
422
41
            if !snowbridge_pallet_system::Agents::<Runtime>::contains_key(agent_id) {
423
41
                snowbridge_pallet_system::Agents::<Runtime>::insert(agent_id, ());
424
41
            }
425

            
426
41
            let channel = Channel { agent_id, para_id };
427
41
            snowbridge_pallet_system::Channels::<Runtime>::insert(channel_id, channel);
428
41

            
429
41
            ChannelInfo {
430
41
                channel_id,
431
41
                para_id,
432
41
                agent_id,
433
41
            }
434
        }
435
43
    }
436
}
437

            
438
/// Helper struct to set up token and channel characteristics needed for EthereumTokenTransfers
439
/// pallet benchmarks.
440
#[cfg(feature = "runtime-benchmarks")]
441
pub struct EthereumTokenTransfersBenchHelper<Runtime>(PhantomData<Runtime>);
442

            
443
#[cfg(feature = "runtime-benchmarks")]
444
impl<Runtime> crate::TokenChannelSetterBenchmarkHelperTrait
445
    for EthereumTokenTransfersBenchHelper<Runtime>
446
where
447
    Runtime: snowbridge_pallet_system::Config,
448
{
449
    fn set_up_token(location: Location, token_id: snowbridge_core::TokenId) {
450
        snowbridge_pallet_system::ForeignToNativeId::<Runtime>::insert(token_id, &location);
451
        snowbridge_pallet_system::NativeToForeignId::<Runtime>::insert(&location, token_id);
452
    }
453

            
454
    fn set_up_channel(channel_id: ChannelId, para_id: ParaId, agent_id: AgentId) {
455
        let channel = Channel { agent_id, para_id };
456
        snowbridge_pallet_system::Agents::<Runtime>::insert(agent_id, ());
457
        snowbridge_pallet_system::Channels::<Runtime>::insert(channel_id, channel);
458
    }
459
}