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
//! # LayerZero Receiver Example Pallet
18
//!
19
//! This pallet serves as a **reference implementation** to demonstrate how container chains
20
//! can receive LayerZero messages forwarded from the relay chain via XCM.
21
//!
22
//! ## Overview
23
//!
24
//! When a LayerZero message arrives at the relay chain (via the Ethereum bridge), the relay
25
//! chain's `pallet_lz_router` forwards it to the destination container chain using
26
//! XCM Transact. This pallet shows how a container chain can receive and process such messages.
27
//!
28
//! ## Implementation Requirements
29
//!
30
//! To receive LayerZero messages, a container chain pallet needs:
31
//!
32
//! 1. **One extrinsic** that accepts an XCM origin from the parent chain (relay)
33
//! 2. **Accept the message payload** - The relay chain sends the LayerZero message as a
34
//!    `Vec<u8>` containing the raw bytes from LayerZero
35
//!
36
//! The pallet name can be anything - what matters is:
37
//! - The pallet index and call index are registered in the relay chain's forwarding config
38
//! - The extrinsic accepts parent (relay) origin via XCM
39
//! - The extrinsic accepts a `Vec<u8>` payload parameter
40
//!
41
//! ## XCM Configuration
42
//!
43
//! For this pallet to work, the container chain's XCM config must include:
44
//! - `pallet_xcm::XcmPassthrough<RuntimeOrigin>` in `XcmOriginToTransactDispatchOrigin`
45
//! - `AllowExplicitUnpaidExecutionFrom<Equals<ParentLocation>>` in `XcmBarrier`
46
//!
47
//! ## Example Usage
48
//!
49
//! ```ignore
50
//! // In your runtime's lib.rs:
51
//! impl pallet_lz_receiver_example::Config for Runtime {
52
//!     type ParentOrigin = pallet_xcm::EnsureXcm<Equals<xcm_config::ParentLocation>>;
53
//! }
54
//! ```
55
//!
56
//! ## Customization
57
//!
58
//! This is just an example. In production, you would likely:
59
//! - Parse the message payload according to your application's protocol
60
//! - Execute business logic based on the message content
61
//! - Store state or trigger other pallets based on the message
62

            
63
#![cfg_attr(not(feature = "std"), no_std)]
64

            
65
extern crate alloc;
66

            
67
pub use pallet::*;
68

            
69
#[frame_support::pallet]
70
pub mod pallet {
71
    use alloc::vec::Vec;
72
    use frame_support::{pallet_prelude::*, traits::EnsureOrigin};
73
    use frame_system::pallet_prelude::*;
74
    use xcm::latest::Location;
75

            
76
    /// Checks that the origin is the parent location (relay chain)
77
    fn is_parent(location: &Location) -> bool {
78
        matches!(location.unpack(), (1, []))
79
    }
80

            
81
    #[pallet::pallet]
82
    pub struct Pallet<T>(_);
83

            
84
    #[pallet::config]
85
    pub trait Config: frame_system::Config<RuntimeEvent: From<Event<Self>>> {
86
        /// Origin that is allowed to send LayerZero messages.
87
        ///
88
        /// This should be configured to only allow the parent chain (relay).
89
        /// Use `pallet_xcm::EnsureXcm<Equals<ParentLocation>>` for this.
90
        type ParentOrigin: EnsureOrigin<
91
            <Self as frame_system::Config>::RuntimeOrigin,
92
            Success = Location,
93
        >;
94
    }
95

            
96
    #[pallet::event]
97
    #[pallet::generate_deposit(pub(crate) fn deposit_event)]
98
    pub enum Event<T: Config> {
99
        /// A LayerZero message was received from the relay chain.
100
        MessageReceived {
101
            /// The raw message payload from LayerZero.
102
            /// This contains the bytes that were sent from the LayerZero source chain.
103
            payload: Vec<u8>,
104
        },
105
    }
106

            
107
    #[pallet::error]
108
    pub enum Error<T> {
109
        /// The origin is not the parent chain (relay).
110
        NotParentOrigin,
111
    }
112

            
113
    #[pallet::call]
114
    impl<T: Config> Pallet<T> {
115
        /// Receive a LayerZero message forwarded from the relay chain.
116
        ///
117
        /// This extrinsic is called via XCM Transact from the relay chain's
118
        /// `pallet_lz_router`. The relay chain encodes the call as
119
        /// `(pallet_index, call_index, payload)`.
120
        ///
121
        /// # Origin
122
        ///
123
        /// Must be the parent chain (relay) origin via XCM. This is enforced by
124
        /// the `ParentOrigin` type configured in the runtime.
125
        ///
126
        /// # Parameters
127
        ///
128
        /// - `payload`: The raw LayerZero message bytes (`Vec<u8>`). This is the message
129
        ///   content that was sent from the LayerZero source chain. The container chain
130
        ///   application should parse and handle this according to its protocol.
131
        #[pallet::call_index(0)]
132
        #[pallet::weight(Weight::from_parts(10_000, 0) + T::DbWeight::get().writes(1))]
133
        pub fn receive_message(origin: OriginFor<T>, payload: Vec<u8>) -> DispatchResult {
134
            // Ensure the origin is from the parent chain (relay)
135
            let origin_location = T::ParentOrigin::ensure_origin(origin)?;
136
            ensure!(is_parent(&origin_location), Error::<T>::NotParentOrigin);
137

            
138
            // In a real implementation, you would parse the payload and execute
139
            // business logic here. This example just emits an event.
140
            Self::deposit_event(Event::MessageReceived { payload });
141

            
142
            Ok(())
143
        }
144
    }
145
}