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

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

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

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

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

            
72
mod custom_do_process_message;
73
mod custom_send_message;
74

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
224
84
    fn validate(message: &Message) -> Result<(Self::Ticket, Fee<u64>), SendError> {
225
84
        log::trace!("MessageValidator: {:?}", message);
226
        // The inner payload should not be too large
227
84
        let payload = message.command.abi_encode();
228
84
        ensure!(
229
84
            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
84
        ensure!(
235
84
            T::Channels::contains(&message.channel_id),
236
            SendError::InvalidChannel
237
        );
238

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

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

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

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

            
272
84
        Ok((ticket, fee))
273
84
    }
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
100
    V1(QueuedMessage),
308
}
309

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

            
316
impl From<VersionedQueuedMessage> for QueuedMessage {
317
1312
    fn from(x: VersionedQueuedMessage) -> Self {
318
1312
        match x {
319
1312
            VersionedQueuedMessage::V1(x) => x,
320
1312
        }
321
1312
    }
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
159
    fn convert_location(location: &Location) -> Option<AccountId> {
373
159
        match location.unpack() {
374
159
            (1, [GlobalConsensus(Ethereum { chain_id })]) => {
375
159
                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
159
    }
383
}
384

            
385
impl<AccountId> EthereumLocationsConverterFor<AccountId> {
386
159
    pub fn from_chain_id(chain_id: &u64) -> [u8; 32] {
387
159
        (b"ethereum-chain", chain_id).using_encoded(blake2_256)
388
159
    }
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
37
    fn create_channel(channel_id: ChannelId, agent_id: AgentId, para_id: ParaId) -> ChannelInfo {
414
37
        if let Some(channel) = snowbridge_pallet_system::Channels::<Runtime>::get(channel_id) {
415
2
            return ChannelInfo {
416
2
                channel_id,
417
2
                para_id: channel.para_id,
418
2
                agent_id: channel.agent_id,
419
2
            };
420
        } else {
421
35
            if !snowbridge_pallet_system::Agents::<Runtime>::contains_key(agent_id) {
422
35
                snowbridge_pallet_system::Agents::<Runtime>::insert(agent_id, ());
423
35
            }
424

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

            
428
35
            return ChannelInfo {
429
35
                channel_id,
430
35
                para_id,
431
35
                agent_id,
432
35
            };
433
        }
434
37
    }
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
}