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::{v1::Fee, SendError},
58
    snowbridge_pallet_outbound_queue::send_message_impl::Ticket,
59
    sp_core::{blake2_256, hashing, H256},
60
    sp_runtime::{app_crypto::sp_core, BoundedVec, RuntimeDebug},
61
    xcm_builder::{
62
        DescribeAccountId32Terminal, DescribeAllTerminal, DescribeFamily, DescribeTerminus,
63
        HashedDescription,
64
    },
65
};
66

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

            
71
pub use xcm_executor::traits::ConvertLocation;
72

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

            
76
pub mod custom_exporters;
77
pub mod inbound_queue;
78
pub mod outbound_queue;
79

            
80
pub use custom_exporters::*;
81
pub use inbound_queue::*;
82
pub use outbound_queue::*;
83

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

            
99
pub type TanssiTicketV1<T> = Ticket<T>;
100

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

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

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

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

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

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

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

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

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

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

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

            
248
27547
                    slashes_tokens_vec.push(tuple_token);
249
27547
                }
250

            
251
2993
                let slashes_tokens_array = Token::Array(slashes_tokens_vec);
252
2993
                ethabi::encode(&[Token::Tuple(vec![era_index_token, slashes_tokens_array])])
253
            }
254
        }
255
3718
    }
256
}
257

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

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

            
281
impl TicketInfo for () {
282
16
    fn message_id(&self) -> H256 {
283
16
        H256::zero()
284
16
    }
285
}
286

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

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

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

            
313
// Benchmarks check message_id so it must be deterministic.
314
#[cfg(feature = "runtime-benchmarks")]
315
impl<T: snowbridge_pallet_outbound_queue::Config> TicketInfo for Ticket<T> {
316
    fn message_id(&self) -> H256 {
317
        H256::default()
318
    }
319
}
320

            
321
// Benchmarks check message_id so it must be deterministic.
322
#[cfg(feature = "runtime-benchmarks")]
323
impl<T: snowbridge_pallet_outbound_queue_v2::Config> TicketInfo for TanssiTicketV2<T> {
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 + snowbridge_pallet_outbound_queue_v2::Config>
332
    TicketInfo for VersionedTanssiTicket<T>
333
{
334
    fn message_id(&self) -> H256 {
335
        H256::default()
336
    }
337
}
338

            
339
// Our own implementation of validating a tanssiMessage
340
pub trait ValidateMessage {
341
    type Ticket: TicketInfo;
342

            
343
    fn validate(message: &TanssiMessage) -> Result<(Self::Ticket, Fee<u64>), SendError>;
344
}
345

            
346
impl ValidateMessage for () {
347
    type Ticket = ();
348

            
349
16
    fn validate(_message: &TanssiMessage) -> Result<(Self::Ticket, Fee<u64>), SendError> {
350
16
        Ok((
351
16
            (),
352
16
            Fee {
353
16
                local: 1,
354
16
                remote: 1,
355
16
            },
356
16
        ))
357
16
    }
358
}
359

            
360
/// A tanssi message which is awaiting processing in the MessageQueue pallet
361
#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
362
#[cfg_attr(feature = "std", derive(PartialEq))]
363
pub struct QueuedTanssiMessage {
364
    /// Message ID
365
    pub id: H256,
366
    /// Channel ID
367
    pub channel_id: ChannelId,
368
    /// Command to execute in the Gateway contract
369
    pub command: Command,
370
}
371

            
372
/// Enqueued outbound messages need to be versioned to prevent data corruption
373
/// or loss after forkless runtime upgrades
374
#[derive(Encode, Decode, TypeInfo, Clone, RuntimeDebug)]
375
#[cfg_attr(feature = "std", derive(PartialEq))]
376
pub enum VersionedQueuedTanssiMessage {
377
    V1(QueuedTanssiMessage),
378
}
379

            
380
impl From<QueuedTanssiMessage> for VersionedQueuedTanssiMessage {
381
1358
    fn from(x: QueuedTanssiMessage) -> Self {
382
1358
        VersionedQueuedTanssiMessage::V1(x)
383
1358
    }
384
}
385

            
386
impl From<VersionedQueuedTanssiMessage> for QueuedTanssiMessage {
387
1314
    fn from(x: VersionedQueuedTanssiMessage) -> Self {
388
1314
        match x {
389
1314
            VersionedQueuedTanssiMessage::V1(x) => x,
390
        }
391
1314
    }
392
}
393

            
394
// Our own implementation of deliver message.
395
// this one takes a ticket and delivers it
396
// TODO: why did we separate these 2 (validate and deliver)?
397
pub trait DeliverMessage {
398
    type Ticket;
399

            
400
    fn deliver(ticket: Self::Ticket) -> Result<H256, SendError>;
401
}
402

            
403
/// Dummy router for xcm messages coming from ethereum
404
pub struct DoNothingRouter;
405
impl SendXcm for DoNothingRouter {
406
    type Ticket = Xcm<()>;
407

            
408
    fn validate(
409
        _dest: &mut Option<Location>,
410
        xcm: &mut Option<Xcm<()>>,
411
    ) -> SendResult<Self::Ticket> {
412
        Ok((xcm.clone().unwrap(), Assets::new()))
413
    }
414
    fn deliver(xcm: Xcm<()>) -> Result<XcmHash, cumulus_primitives_core::SendError> {
415
        let hash = xcm.using_encoded(hashing::blake2_256);
416
        Ok(hash)
417
    }
418
}
419

            
420
/// Dummy message converter to convert message to Xcm
421
pub struct DoNothingConvertMessage;
422

            
423
impl ConvertMessage for DoNothingConvertMessage {
424
    type Balance = Balance;
425
    type AccountId = AccountId;
426

            
427
    fn convert(
428
        _: H256,
429
        _message: VersionedXcmMessage,
430
    ) -> Result<(Xcm<()>, Self::Balance), ConvertMessageError> {
431
        Err(ConvertMessageError::UnsupportedVersion)
432
    }
433
}
434

            
435
impl ConvertMessageV2 for DoNothingConvertMessage {
436
    fn convert(_: MessageV2) -> Result<Xcm<()>, ConvertMessageV2Error> {
437
        // TODO: figure out what to do here
438
        Err(ConvertMessageV2Error::CannotReanchor)
439
    }
440
}
441

            
442
// This is a variation of the converter found here:
443
// https://github.com/paritytech/polkadot-sdk/blob/711e6ff33373bc08b026446ce19b73920bfe068c/bridges/snowbridge/primitives/router/src/inbound/mod.rs#L467
444
//
445
// Upstream converter only works for parachains (parents 2) while we to use it in tanssi solo-chain
446
// (parents 1).
447
pub struct EthereumLocationsConverterFor<AccountId>(PhantomData<AccountId>);
448
impl<AccountId> ConvertLocation<AccountId> for EthereumLocationsConverterFor<AccountId>
449
where
450
    AccountId: From<[u8; 32]> + Clone,
451
{
452
182
    fn convert_location(location: &Location) -> Option<AccountId> {
453
182
        match location.unpack() {
454
182
            (1, [GlobalConsensus(Ethereum { chain_id })]) => {
455
182
                Some(Self::from_chain_id(chain_id).into())
456
            }
457
            (1, [GlobalConsensus(Ethereum { chain_id }), AccountKey20 { network: _, key }]) => {
458
                Some(Self::from_chain_id_with_key(chain_id, *key).into())
459
            }
460
            _ => None,
461
        }
462
182
    }
463
}
464

            
465
impl<AccountId> EthereumLocationsConverterFor<AccountId> {
466
182
    pub fn from_chain_id(chain_id: &u64) -> [u8; 32] {
467
182
        (b"ethereum-chain", chain_id).using_encoded(blake2_256)
468
182
    }
469
    pub fn from_chain_id_with_key(chain_id: &u64, key: [u8; 20]) -> [u8; 32] {
470
        (b"ethereum-chain", chain_id, key).using_encoded(blake2_256)
471
    }
472
}
473

            
474
/// Information of a recently created channel.
475
#[derive(
476
    Encode, Decode, DecodeWithMemTracking, RuntimeDebug, TypeInfo, Clone, PartialEq, MaxEncodedLen,
477
)]
478
pub struct ChannelInfo {
479
    pub channel_id: ChannelId,
480
    pub para_id: ParaId,
481
    pub agent_id: AgentId,
482
}
483

            
484
/// Trait to manage channel creation inside EthereumSystem pallet.
485
pub trait EthereumSystemChannelManager {
486
    fn create_channel(channel_id: ChannelId, agent_id: AgentId, para_id: ParaId) -> ChannelInfo;
487
}
488

            
489
/// Implementation struct for EthereumSystemChannelManager trait.
490
pub struct EthereumSystemHandler<Runtime>(PhantomData<Runtime>);
491
impl<Runtime> EthereumSystemChannelManager for EthereumSystemHandler<Runtime>
492
where
493
    Runtime: snowbridge_pallet_system::Config,
494
{
495
79
    fn create_channel(channel_id: ChannelId, agent_id: AgentId, para_id: ParaId) -> ChannelInfo {
496
79
        if let Some(channel) = snowbridge_pallet_system::Channels::<Runtime>::get(channel_id) {
497
2
            ChannelInfo {
498
2
                channel_id,
499
2
                para_id: channel.para_id,
500
2
                agent_id: channel.agent_id,
501
2
            }
502
        } else {
503
77
            if !snowbridge_pallet_system::Agents::<Runtime>::contains_key(agent_id) {
504
77
                snowbridge_pallet_system::Agents::<Runtime>::insert(agent_id, ());
505
77
            }
506

            
507
77
            let channel = Channel { agent_id, para_id };
508
77
            snowbridge_pallet_system::Channels::<Runtime>::insert(channel_id, channel);
509

            
510
77
            ChannelInfo {
511
77
                channel_id,
512
77
                para_id,
513
77
                agent_id,
514
77
            }
515
        }
516
79
    }
517
}
518

            
519
/// Helper struct to set up token and channel characteristics needed for EthereumTokenTransfers
520
/// pallet benchmarks.
521
#[cfg(feature = "runtime-benchmarks")]
522
pub struct EthereumTokenTransfersBenchHelper<Runtime>(PhantomData<Runtime>);
523

            
524
#[cfg(feature = "runtime-benchmarks")]
525
impl<Runtime> crate::TokenChannelSetterBenchmarkHelperTrait
526
    for EthereumTokenTransfersBenchHelper<Runtime>
527
where
528
    Runtime: snowbridge_pallet_system::Config,
529
{
530
    fn set_up_token(location: Location, token_id: snowbridge_core::TokenId) {
531
        snowbridge_pallet_system::ForeignToNativeId::<Runtime>::insert(token_id, &location);
532
        snowbridge_pallet_system::NativeToForeignId::<Runtime>::insert(&location, token_id);
533
    }
534

            
535
    fn set_up_channel(channel_id: ChannelId, para_id: ParaId, agent_id: AgentId) {
536
        let channel = Channel { agent_id, para_id };
537
        snowbridge_pallet_system::Agents::<Runtime>::insert(agent_id, ());
538
        snowbridge_pallet_system::Channels::<Runtime>::insert(channel_id, channel);
539
    }
540
}