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::{DecodeWithMemTracking, MaxEncodedLen},
45
    scale_info::TypeInfo,
46
    snowbridge_core::{AgentId, Channel, ChannelId, ParaId},
47
    snowbridge_inbound_queue_primitives::v1::{
48
        ConvertMessage, ConvertMessageError, VersionedXcmMessage,
49
    },
50
    snowbridge_outbound_queue_primitives::{v1::Fee, SendError},
51
    snowbridge_pallet_outbound_queue::send_message_impl::Ticket,
52
    sp_core::{blake2_256, hashing, H256},
53
    sp_runtime::{app_crypto::sp_core, traits::Convert, RuntimeDebug},
54
    sp_std::vec::Vec,
55
};
56

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

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

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

            
70
mod custom_do_process_message;
71
mod custom_send_message;
72

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            
222
84
    fn validate(message: &Message) -> Result<(Self::Ticket, Fee<u64>), SendError> {
223
84
        log::trace!("MessageValidator: {:?}", message);
224
        // 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
    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
159
    fn convert_location(location: &Location) -> Option<AccountId> {
371
159
        match location.unpack() {
372
159
            (1, [GlobalConsensus(Ethereum { chain_id })]) => {
373
159
                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
159
    }
381
}
382

            
383
impl<AccountId> EthereumLocationsConverterFor<AccountId> {
384
159
    pub fn from_chain_id(chain_id: &u64) -> [u8; 32] {
385
159
        (b"ethereum-chain", chain_id).using_encoded(blake2_256)
386
159
    }
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(
394
    Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo, Clone, PartialEq, MaxEncodedLen,
395
)]
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
}