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
extern crate alloc;
22

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

            
28
pub mod container_token_to_ethereum_message_exporter;
29
pub mod generic_token_message_processor;
30
pub mod snowbridge_outbound_token_transfer;
31
pub mod symbiotic_message_processor;
32

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

            
60
// Separate import as rustfmt wrongly change it to `alloc::vec::self`, which is the module instead
61
// of the macro.
62
use alloc::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, DecodeWithMemTracking, 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, DecodeWithMemTracking, RuntimeDebug, TypeInfo, PartialEq)]
85
pub enum Command {
86
    // TODO: add real commands here
87
    Test(Vec<u8>),
88
    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
    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
1344
    pub fn index(&self) -> u8 {
113
1344
        match self {
114
            // Starting from 32 to keep compatibility with Snowbridge Command enum
115
64
            Command::Test { .. } => 32,
116
192
            Command::ReportRewards { .. } => 33,
117
1088
            Command::ReportSlashes { .. } => 34,
118
        }
119
1344
    }
120

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

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

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

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

            
165
2993
                let slashes_tokens_array = Token::Array(slashes_tokens_vec);
166
2993
                ethabi::encode(&[Token::Tuple(vec![era_index_token, slashes_tokens_array])])
167
            }
168
        }
169
3699
    }
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
88
    fn message_id(&self) -> H256 {
202
88
        self.message_id
203
88
    }
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
84
    fn validate(message: &Message) -> Result<(Self::Ticket, Fee<u64>), SendError> {
226
84
        log::trace!("MessageValidator: {:?}", message);
227
        // The inner payload should not be too large
228
84
        let payload = message.command.abi_encode();
229
84
        ensure!(
230
84
            payload.len() < T::MaxMessagePayloadSize::get() as usize,
231
            SendError::MessageTooLarge
232
        );
233

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

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

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

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

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

            
273
84
        Ok((ticket, fee))
274
84
    }
275
}
276

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

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

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

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

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

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

            
325
pub trait DeliverMessage {
326
    type Ticket;
327

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

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

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

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

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

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

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

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

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

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

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

            
428
51
            let channel = Channel { agent_id, para_id };
429
51
            snowbridge_pallet_system::Channels::<Runtime>::insert(channel_id, channel);
430
51

            
431
51
            ChannelInfo {
432
51
                channel_id,
433
51
                para_id,
434
51
                agent_id,
435
51
            }
436
        }
437
53
    }
438
}
439

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

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

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