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 symbiotic_message_processor;
28

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

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

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

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

            
73
mod custom_do_process_message;
74
mod custom_send_message;
75

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

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

            
110
impl Command {
111
    /// Compute the enum variant index
112
294
    pub fn index(&self) -> u8 {
113
294
        match self {
114
            // Starting from 32 to keep compatibility with Snowbridge Command enum
115
14
            Command::Test { .. } => 32,
116
42
            Command::ReportRewards { .. } => 33,
117
238
            Command::ReportSlashes { .. } => 34,
118
        }
119
294
    }
120

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

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

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

            
162
6889
                    slashes_tokens_vec.push(tuple_token);
163
6889
                }
164

            
165
749
                let slashes_tokens_array = Token::Array(slashes_tokens_vec);
166
749
                ethabi::encode(&[Token::Tuple(vec![era_index_token, slashes_tokens_array])])
167
            }
168
        }
169
927
    }
170
}
171

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

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

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

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

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

            
214
pub struct MessageValidator<T: snowbridge_pallet_outbound_queue::Config>(PhantomData<T>);
215

            
216
pub trait ValidateMessage {
217
    type Ticket: TicketInfo;
218

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

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

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

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

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

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

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

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

            
272
42
        Ok((ticket, fee))
273
42
    }
274
}
275

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

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

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

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

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

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

            
324
pub trait DeliverMessage {
325
    type Ticket;
326

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

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

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

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

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

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

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

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

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

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

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

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

            
428
10
            return ChannelInfo {
429
10
                channel_id,
430
10
                para_id,
431
10
                agent_id,
432
10
            };
433
        }
434
11
    }
435
}
436

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

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

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