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 mock;
27
#[cfg(test)]
28
mod tests;
29

            
30
use {
31
    alloc::vec::Vec,
32
    core::marker::PhantomData,
33
    cumulus_primitives_core::{
34
        relay_chain::{AccountId, Balance},
35
        AccountKey20, Assets, Ethereum, GlobalConsensus, Location, SendResult, SendXcm, Xcm,
36
        XcmHash,
37
    },
38
    ethabi::{Token, U256},
39
    frame_support::{
40
        pallet_prelude::{Decode, Encode, Get},
41
        traits::{Contains, EnqueueMessage},
42
    },
43
    frame_system::unique,
44
    parity_scale_codec::{DecodeWithMemTracking, MaxEncodedLen},
45
    scale_info::TypeInfo,
46
    snowbridge_core::{
47
        location::{DescribeGlobalPrefix, DescribeHere, DescribeTokenTerminal},
48
        AgentId, Channel, ChannelId, ParaId,
49
    },
50
    snowbridge_inbound_queue_primitives::v1::{
51
        ConvertMessage, ConvertMessageError, VersionedXcmMessage,
52
    },
53
    snowbridge_inbound_queue_primitives::v2::{
54
        ConvertMessage as ConvertMessageV2, ConvertMessageError as ConvertMessageV2Error,
55
        Message as MessageV2,
56
    },
57
    snowbridge_outbound_queue_primitives::{
58
        v1::Fee, v2::Message as SnowbridgeMessageV2, SendError,
59
    },
60
    snowbridge_pallet_outbound_queue::send_message_impl::Ticket,
61
    sp_core::{blake2_256, hashing, H256},
62
    sp_runtime::{app_crypto::sp_core, BoundedVec, RuntimeDebug},
63
    xcm_builder::{
64
        DescribeAccountId32Terminal, DescribeAllTerminal, DescribeFamily, DescribeTerminus,
65
        HashedDescription,
66
    },
67
};
68

            
69
// Separate import as rustfmt wrongly change it to `alloc::vec::self`, which is the module instead
70
// of the macro.
71
use alloc::vec;
72

            
73
pub use xcm_executor::traits::ConvertLocation;
74

            
75
#[cfg(feature = "runtime-benchmarks")]
76
pub use benchmarks::*;
77

            
78
pub mod custom_exporters;
79
pub mod inbound_queue;
80
pub mod outbound_queue;
81

            
82
pub use custom_exporters::*;
83
pub use inbound_queue::*;
84
pub use outbound_queue::*;
85

            
86
/// Means of converting an ML origin into a h256
87
/// We need to add DescribeAccountId32Terminal for cases in which a local user is the one sending the tokens
88
pub type TanssiAgentIdOf = HashedDescription<
89
    AgentId,
90
    (
91
        DescribeHere,
92
        DescribeFamily<DescribeAllTerminal>,
93
        DescribeGlobalPrefix<(
94
            DescribeTerminus,
95
            DescribeFamily<DescribeTokenTerminal>,
96
            DescribeAccountId32Terminal,
97
        )>,
98
    ),
99
>;
100

            
101
pub type TanssiTicketV1<T> = Ticket<T>;
102

            
103
/// The maximal length of an enqueued message, as determined by the MessageQueue pallet in v2
104
pub type MaxEnqueuedMessageSizeOfV2<T> =
105
    <<T as snowbridge_pallet_outbound_queue_v2::Config>::MessageQueue as EnqueueMessage<
106
        <T as snowbridge_pallet_outbound_queue_v2::Config>::AggregateMessageOrigin,
107
    >>::MaxMessageLen;
108

            
109
// We need to crate a new one for this. the ticket type
110
// in snowbridge v2 is tied to their commands (unlike v1, which only asked for a set of bytes)
111
// therefore we create our own structure
112
#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)]
113
#[cfg_attr(feature = "std", derive(PartialEq))]
114
pub struct TanssiTicketV2<T>
115
where
116
    T: snowbridge_pallet_outbound_queue_v2::Config,
117
{
118
    /// Origin
119
    pub origin: H256,
120
    /// ID
121
    pub id: H256,
122
    /// Fee
123
    pub fee: u128,
124
    /// Commands
125
    /// TODO: change to bounded
126
    /// I cannot change it to bounded right now because I need a cherry-pick in polkadot-sdk
127
    /// will leave it as a const for now, it should be  pub const MessageQueueHeapSize: u32 = 32 * 1024  - the size of a u32;
128
    /// since it's for now not going to be used, I will leave it to 32**102
129
    pub message: BoundedVec<u8, MaxEnqueuedMessageSizeOfV2<T>>,
130
}
131

            
132
// A versioned ticket that will allow us to route between v1 and v2 processors
133
#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)]
134
#[cfg_attr(feature = "std", derive(PartialEq))]
135
pub enum VersionedTanssiTicket<T>
136
where
137
    T: snowbridge_pallet_outbound_queue_v2::Config,
138
    T: snowbridge_pallet_outbound_queue::Config,
139
{
140
    V1(TanssiTicketV1<T>),
141
    V2(TanssiTicketV2<T>),
142
}
143

            
144
impl<T> From<TanssiTicketV1<T>> for VersionedTanssiTicket<T>
145
where
146
    T: snowbridge_pallet_outbound_queue_v2::Config,
147
    T: snowbridge_pallet_outbound_queue::Config,
148
{
149
45
    fn from(ticket: TanssiTicketV1<T>) -> VersionedTanssiTicket<T> {
150
45
        VersionedTanssiTicket::V1(ticket)
151
45
    }
152
}
153

            
154
impl<T> From<TanssiTicketV2<T>> for VersionedTanssiTicket<T>
155
where
156
    T: snowbridge_pallet_outbound_queue_v2::Config,
157
    T: snowbridge_pallet_outbound_queue::Config,
158
{
159
41
    fn from(ticket: TanssiTicketV2<T>) -> VersionedTanssiTicket<T> {
160
41
        VersionedTanssiTicket::V2(ticket)
161
41
    }
162
}
163

            
164
#[derive(Clone, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo, PartialEq)]
165
pub struct SlashData {
166
    pub encoded_validator_id: Vec<u8>,
167
    pub slash_fraction: u32,
168
    pub external_idx: u64,
169
}
170

            
171
/// A command which is executable by the Gateway contract on Ethereum
172
#[derive(Clone, Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo, PartialEq)]
173
pub enum Command {
174
    // TODO: add real commands here
175
    Test(Vec<u8>),
176
    ReportRewards {
177
        // external identifier for validators
178
        external_idx: u64,
179
        // index of the era we are sending info of
180
        era_index: u32,
181
        // total_points for the era
182
        total_points: u128,
183
        // new tokens inflated during the era
184
        tokens_inflated: u128,
185
        // merkle root of vec![(validatorId, rewardPoints)]
186
        rewards_merkle_root: H256,
187
        // the token id in which we need to mint
188
        token_id: H256,
189
    },
190
    ReportSlashes {
191
        // index of the era we are sending info of
192
        era_index: u32,
193
        // vec of `SlashData`
194
        slashes: Vec<SlashData>,
195
    },
196
}
197

            
198
impl Command {
199
    /// Compute the enum variant index
200
1986
    pub fn index(&self) -> u8 {
201
1986
        match self {
202
            // Starting from 32 to keep compatibility with Snowbridge Command enum
203
64
            Command::Test { .. } => 32,
204
306
            Command::ReportRewards { .. } => 33,
205
1616
            Command::ReportSlashes { .. } => 34,
206
        }
207
1986
    }
208

            
209
    /// ABI-encode the Command.
210
5478
    pub fn abi_encode(&self) -> Vec<u8> {
211
5478
        match self {
212
135
            Command::Test(payload) => {
213
135
                ethabi::encode(&[Token::Tuple(vec![Token::Bytes(payload.clone())])])
214
            }
215
            Command::ReportRewards {
216
898
                external_idx,
217
898
                era_index,
218
898
                total_points,
219
898
                tokens_inflated,
220
898
                rewards_merkle_root,
221
898
                token_id,
222
            } => {
223
898
                let external_idx_token = Token::Uint(U256::from(*external_idx));
224
898
                let era_index_token = Token::Uint(U256::from(*era_index));
225
898
                let total_points_token = Token::Uint(U256::from(*total_points));
226
898
                let tokens_inflated_token = Token::Uint(U256::from(*tokens_inflated));
227
898
                let rewards_mr_token = Token::FixedBytes(rewards_merkle_root.0.to_vec());
228
898
                let token_id_token = Token::FixedBytes(token_id.0.to_vec());
229

            
230
898
                ethabi::encode(&[Token::Tuple(vec![
231
898
                    external_idx_token,
232
898
                    era_index_token,
233
898
                    total_points_token,
234
898
                    tokens_inflated_token,
235
898
                    rewards_mr_token,
236
898
                    token_id_token,
237
898
                ])])
238
            }
239
4445
            Command::ReportSlashes { era_index, slashes } => {
240
4445
                let era_index_token = Token::Uint(U256::from(*era_index));
241
4445
                let mut slashes_tokens_vec: Vec<Token> = vec![];
242

            
243
41275
                for slash in slashes.iter() {
244
41275
                    let account_token = Token::FixedBytes(slash.encoded_validator_id.clone());
245
41275
                    let slash_fraction_token = Token::Uint(U256::from(slash.slash_fraction));
246
41275
                    let external_idx = Token::Uint(U256::from(slash.external_idx));
247
41275
                    let tuple_token =
248
41275
                        Token::Tuple(vec![account_token, slash_fraction_token, external_idx]);
249
41275

            
250
41275
                    slashes_tokens_vec.push(tuple_token);
251
41275
                }
252

            
253
4445
                let slashes_tokens_array = Token::Array(slashes_tokens_vec);
254
4445
                ethabi::encode(&[Token::Tuple(vec![era_index_token, slashes_tokens_array])])
255
            }
256
        }
257
5478
    }
258
}
259

            
260
// A message which can be accepted by our custom implementations of `/[`SendMessage`\]`
261
// This message is the COMMON structure to be passed to both V1 and V2 processors
262
// the processors will later process it in whatever format the outbound queues expect
263
#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)]
264
#[cfg_attr(feature = "std", derive(PartialEq))]
265
pub struct TanssiMessage {
266
    /// ID for this message. One will be automatically generated if not provided.
267
    ///
268
    /// When this message is created from an XCM message, the ID should be extracted
269
    /// from the `SetTopic` instruction.
270
    ///
271
    /// The ID plays no role in bridge consensus, and is purely meant for message tracing.
272
    pub id: Option<H256>,
273
    /// The message channel ID
274
    pub channel_id: ChannelId,
275
    /// The stable ID for a receiving gateway contract
276
    pub command: Command,
277
}
278

            
279
pub trait TicketInfo {
280
    fn message_id(&self) -> H256;
281
}
282

            
283
impl TicketInfo for () {
284
104
    fn message_id(&self) -> H256 {
285
104
        H256::zero()
286
104
    }
287
}
288

            
289
#[cfg(not(feature = "runtime-benchmarks"))]
290
impl<T: snowbridge_pallet_outbound_queue::Config> TicketInfo for Ticket<T> {
291
48
    fn message_id(&self) -> H256 {
292
48
        self.message_id
293
48
    }
294
}
295

            
296
#[cfg(not(feature = "runtime-benchmarks"))]
297
impl<T: snowbridge_pallet_outbound_queue_v2::Config> TicketInfo for TanssiTicketV2<T> {
298
1
    fn message_id(&self) -> H256 {
299
1
        self.id
300
1
    }
301
}
302

            
303
#[cfg(not(feature = "runtime-benchmarks"))]
304
impl<T: snowbridge_pallet_outbound_queue::Config + snowbridge_pallet_outbound_queue_v2::Config>
305
    TicketInfo for VersionedTanssiTicket<T>
306
{
307
83
    fn message_id(&self) -> H256 {
308
83
        match self {
309
43
            VersionedTanssiTicket::V1(ticket) => ticket.message_id,
310
40
            VersionedTanssiTicket::V2(ticket) => ticket.id,
311
        }
312
83
    }
313
}
314

            
315
#[cfg(not(feature = "runtime-benchmarks"))]
316
impl TicketInfo for SnowbridgeMessageV2 {
317
    fn message_id(&self) -> H256 {
318
        self.id
319
    }
320
}
321

            
322
#[cfg(feature = "runtime-benchmarks")]
323
impl TicketInfo for SnowbridgeMessageV2 {
324
    fn message_id(&self) -> H256 {
325
        H256::default()
326
    }
327
}
328

            
329
// Benchmarks check message_id so it must be deterministic.
330
#[cfg(feature = "runtime-benchmarks")]
331
impl<T: snowbridge_pallet_outbound_queue::Config> TicketInfo for Ticket<T> {
332
    fn message_id(&self) -> H256 {
333
        H256::default()
334
    }
335
}
336

            
337
// Benchmarks check message_id so it must be deterministic.
338
#[cfg(feature = "runtime-benchmarks")]
339
impl<T: snowbridge_pallet_outbound_queue_v2::Config> TicketInfo for TanssiTicketV2<T> {
340
    fn message_id(&self) -> H256 {
341
        H256::default()
342
    }
343
}
344

            
345
// Benchmarks check message_id so it must be deterministic.
346
#[cfg(feature = "runtime-benchmarks")]
347
impl<T: snowbridge_pallet_outbound_queue::Config + snowbridge_pallet_outbound_queue_v2::Config>
348
    TicketInfo for VersionedTanssiTicket<T>
349
{
350
    fn message_id(&self) -> H256 {
351
        H256::default()
352
    }
353
}
354

            
355
// Our own implementation of validating a tanssiMessage
356
pub trait ValidateMessage {
357
    type Ticket: TicketInfo;
358

            
359
    fn validate(message: &TanssiMessage) -> Result<(Self::Ticket, Fee<u64>), SendError>;
360
}
361

            
362
impl ValidateMessage for () {
363
    type Ticket = ();
364

            
365
16
    fn validate(_message: &TanssiMessage) -> Result<(Self::Ticket, Fee<u64>), SendError> {
366
16
        Ok((
367
16
            (),
368
16
            Fee {
369
16
                local: 1,
370
16
                remote: 1,
371
16
            },
372
16
        ))
373
16
    }
374
}
375

            
376
/// A tanssi message which is awaiting processing in the MessageQueue pallet
377
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
378
#[cfg_attr(feature = "std", derive(PartialEq))]
379
pub struct QueuedTanssiMessage {
380
    /// Message ID
381
    pub id: H256,
382
    /// Channel ID
383
    pub channel_id: ChannelId,
384
    /// Command to execute in the Gateway contract
385
    pub command: Command,
386
}
387

            
388
/// Enqueued outbound messages need to be versioned to prevent data corruption
389
/// or loss after forkless runtime upgrades
390
#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)]
391
#[cfg_attr(feature = "std", derive(PartialEq))]
392
pub enum VersionedQueuedTanssiMessage {
393
    V1(QueuedTanssiMessage),
394
}
395

            
396
impl From<QueuedTanssiMessage> for VersionedQueuedTanssiMessage {
397
1998
    fn from(x: QueuedTanssiMessage) -> Self {
398
1998
        VersionedQueuedTanssiMessage::V1(x)
399
1998
    }
400
}
401

            
402
impl From<VersionedQueuedTanssiMessage> for QueuedTanssiMessage {
403
1954
    fn from(x: VersionedQueuedTanssiMessage) -> Self {
404
1954
        match x {
405
1954
            VersionedQueuedTanssiMessage::V1(x) => x,
406
        }
407
1954
    }
408
}
409

            
410
// Our own implementation of deliver message.
411
// this one takes a ticket and delivers it
412
// TODO: why did we separate these 2 (validate and deliver)?
413
pub trait DeliverMessage {
414
    type Ticket;
415

            
416
    fn deliver(ticket: Self::Ticket) -> Result<H256, SendError>;
417
}
418

            
419
/// Dummy router for xcm messages coming from ethereum
420
pub struct DoNothingRouter;
421
impl SendXcm for DoNothingRouter {
422
    type Ticket = Xcm<()>;
423

            
424
    fn validate(
425
        _dest: &mut Option<Location>,
426
        xcm: &mut Option<Xcm<()>>,
427
    ) -> SendResult<Self::Ticket> {
428
        Ok((xcm.clone().unwrap(), Assets::new()))
429
    }
430
    fn deliver(xcm: Xcm<()>) -> Result<XcmHash, cumulus_primitives_core::SendError> {
431
        let hash = xcm.using_encoded(hashing::blake2_256);
432
        Ok(hash)
433
    }
434
}
435

            
436
/// Dummy message converter to convert message to Xcm
437
pub struct DoNothingConvertMessage;
438

            
439
impl ConvertMessage for DoNothingConvertMessage {
440
    type Balance = Balance;
441
    type AccountId = AccountId;
442

            
443
    fn convert(
444
        _: H256,
445
        _message: VersionedXcmMessage,
446
    ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> {
447
        Err(ConvertMessageError::UnsupportedVersion)
448
    }
449
}
450

            
451
impl ConvertMessageV2 for DoNothingConvertMessage {
452
    fn convert(_: MessageV2) -> Result<Xcm<()>, ConvertMessageV2Error> {
453
        // TODO: figure out what to do here
454
        Err(ConvertMessageV2Error::CannotReanchor)
455
    }
456
}
457

            
458
// This is a variation of the converter found here:
459
// https://github.com/paritytech/polkadot-sdk/blob/711e6ff33373bc08b026446ce19b73920bfe068c/bridges/snowbridge/primitives/router/src/inbound/mod.rs#L467
460
//
461
// Upstream converter only works for parachains (parents 2) while we to use it in tanssi solo-chain
462
// (parents 1).
463
pub struct EthereumLocationsConverterFor<AccountId>(PhantomData<AccountId>);
464
impl<AccountId> ConvertLocation<AccountId> for EthereumLocationsConverterFor<AccountId>
465
where
466
    AccountId: From<[u8; 32]> + Clone,
467
{
468
198
    fn convert_location(location: &Location) -> Option<AccountId> {
469
198
        match location.unpack() {
470
198
            (1, [GlobalConsensus(Ethereum { chain_id })]) => {
471
198
                Some(Self::from_chain_id(chain_id).into())
472
            }
473
            (1, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => {
474
                Some(Self::from_chain_id_with_key(chain_id, *key).into())
475
            }
476
            _ => None,
477
        }
478
198
    }
479
}
480

            
481
impl<AccountId> EthereumLocationsConverterFor<AccountId> {
482
198
    pub fn from_chain_id(chain_id: &u64) -> [u8; 32] {
483
198
        (b"ethereum-chain", chain_id).using_encoded(blake2_256)
484
198
    }
485
    pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] {
486
        (b"ethereum-chain", chain_id, key).using_encoded(blake2_256)
487
    }
488
}
489

            
490
/// Information of a recently created channel.
491
#[derive(
492
    Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo, Clone, PartialEq, MaxEncodedLen,
493
)]
494
pub struct ChannelInfo {
495
    pub channel_id: ChannelId,
496
    pub para_id: ParaId,
497
    pub agent_id: AgentId,
498
}
499

            
500
/// Trait to manage channel creation inside EthereumSystem pallet.
501
pub trait EthereumSystemChannelManager {
502
    fn create_channel(channel_id: ChannelId, agent_id: AgentId, para_id: ParaId) -> ChannelInfo;
503
}
504

            
505
/// Implementation struct for EthereumSystemChannelManager trait.
506
pub struct EthereumSystemHandler<Runtime>(PhantomData<Runtime>);
507
impl<Runtime> EthereumSystemChannelManager for EthereumSystemHandler<Runtime>
508
where
509
    Runtime: snowbridge_pallet_system::Config,
510
{
511
98
    fn create_channel(channel_id: ChannelId, agent_id: AgentId, para_id: ParaId) -> ChannelInfo {
512
98
        if let Some(channel) = snowbridge_pallet_system::Channels::<Runtime>::get(channel_id) {
513
2
            ChannelInfo {
514
2
                channel_id,
515
2
                para_id: channel.para_id,
516
2
                agent_id: channel.agent_id,
517
2
            }
518
        } else {
519
96
            if !snowbridge_pallet_system::Agents::<Runtime>::contains_key(agent_id) {
520
96
                snowbridge_pallet_system::Agents::<Runtime>::insert(agent_id, ());
521
96
            }
522

            
523
96
            let channel = Channel { agent_id, para_id };
524
96
            snowbridge_pallet_system::Channels::<Runtime>::insert(channel_id, channel);
525

            
526
96
            ChannelInfo {
527
96
                channel_id,
528
96
                para_id,
529
96
                agent_id,
530
96
            }
531
        }
532
98
    }
533
}
534

            
535
/// Helper struct to set up token and channel characteristics needed for EthereumTokenTransfers
536
/// pallet benchmarks.
537
#[cfg(feature = "runtime-benchmarks")]
538
pub struct EthereumTokenTransfersBenchHelper<Runtime>(PhantomData<Runtime>);
539

            
540
#[cfg(feature = "runtime-benchmarks")]
541
impl<Runtime> crate::TokenChannelSetterBenchmarkHelperTrait
542
    for EthereumTokenTransfersBenchHelper<Runtime>
543
where
544
    Runtime: snowbridge_pallet_system::Config,
545
{
546
    fn set_up_token(location: Location, token_id: snowbridge_core::TokenId) {
547
        snowbridge_pallet_system::ForeignToNativeId::<Runtime>::insert(token_id, &location);
548
        snowbridge_pallet_system::NativeToForeignId::<Runtime>::insert(&location, token_id);
549
    }
550

            
551
    fn set_up_channel(channel_id: ChannelId, para_id: ParaId, agent_id: AgentId) {
552
        let channel = Channel { agent_id, para_id };
553
        snowbridge_pallet_system::Agents::<Runtime>::insert(agent_id, ());
554
        snowbridge_pallet_system::Channels::<Runtime>::insert(channel_id, channel);
555
    }
556
}