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
        AccountKey20, Assets, Ethereum, GlobalConsensus, Location, SendResult, SendXcm, Xcm,
34
        XcmHash,
35
    },
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, 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, 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, RuntimeDebug, TypeInfo, PartialEq)]
83
pub enum Command {
84
    // TODO: add real commands here
85
19
    Test(Vec<u8>),
86
25
    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
79
    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
4539
    pub fn abi_encode(&self) -> Vec<u8> {
121
4539
        match self {
122
163
            Command::Test(payload) => {
123
163
                ethabi::encode(&[Token::Tuple(vec![Token::Bytes(payload.clone())])])
124
            }
125
            Command::ReportRewards {
126
703
                external_idx,
127
703
                era_index,
128
703
                total_points,
129
703
                tokens_inflated,
130
703
                rewards_merkle_root,
131
703
                token_id,
132
703
            } => {
133
703
                let external_idx_token = Token::Uint(U256::from(*external_idx));
134
703
                let era_index_token = Token::Uint(U256::from(*era_index));
135
703
                let total_points_token = Token::Uint(U256::from(*total_points));
136
703
                let tokens_inflated_token = Token::Uint(U256::from(*tokens_inflated));
137
703
                let rewards_mr_token = Token::FixedBytes(rewards_merkle_root.0.to_vec());
138
703
                let token_id_token = Token::FixedBytes(token_id.0.to_vec());
139
703

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

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

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

            
163
3673
                let slashes_tokens_array = Token::Array(slashes_tokens_vec);
164
3673
                ethabi::encode(&[Token::Tuple(vec![era_index_token, slashes_tokens_array])])
165
            }
166
        }
167
4539
    }
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
        // The inner payload should not be too large
225
84
        let payload = message.command.abi_encode();
226
84
        ensure!(
227
84
            payload.len() < T::MaxMessagePayloadSize::get() as usize,
228
            SendError::MessageTooLarge
229
        );
230

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

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

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

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

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

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

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

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

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

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

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

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

            
322
pub trait DeliverMessage {
323
    type Ticket;
324

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

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

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

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

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

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

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

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

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

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

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

            
423
22
            let channel = Channel { agent_id, para_id };
424
22
            snowbridge_pallet_system::Channels::<Runtime>::insert(channel_id, channel);
425
22

            
426
22
            return ChannelInfo {
427
22
                channel_id,
428
22
                para_id,
429
22
                agent_id,
430
22
            };
431
        }
432
24
    }
433
}
434

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

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

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