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
extern crate alloc;
18

            
19
use crate::processors::v2::fallback_message_processor::{
20
    AssetTrapFallbackProcessor, PrivilegedFallbackProcessor,
21
};
22
use crate::processors::v2::{
23
    CodecError, FallbackMessageProcessor, MessageExtractionError, MessageProcessorWithFallback,
24
};
25
use alloc::boxed::Box;
26
use alloc::format;
27
use alloc::string::ToString;
28
use core::marker::PhantomData;
29
use parity_scale_codec::Decode;
30
use snowbridge_inbound_queue_primitives::v2::{Message, MessageProcessorError, Payload};
31
use sp_core::{Get, H160};
32
use sp_runtime::Weight;
33
use tp_bridge::symbiotic_message_processor::{
34
    InboundCommand, Message as SymbioticMessage, Payload as SymbioticPayload, MAGIC_BYTES,
35
};
36
use v2_processor_proc_macro::MessageProcessor;
37
use xcm::latest::{ExecuteXcm, InteriorLocation, NetworkId};
38
use xcm_executor::traits::WeightBounds;
39

            
40
10
pub fn try_extract_message<T: pallet_external_validators::Config>(
41
10
    message: &Message,
42
10
    gateway_proxy_address: H160,
43
10
) -> Result<SymbioticMessage<T>, MessageExtractionError> {
44
10
    match message.payload {
45
10
        Payload::Raw(ref payload) => {
46
10
            let raw_payload = crate::processors::v2::RawPayload::decode(&mut payload.as_slice())
47
10
                .map_err(|error| MessageExtractionError::InvalidMessage {
48
                    context: "Unable to decode RawMessage".to_string(),
49
                    source: Some(Box::new(CodecError(error))),
50
                })?;
51
10
            match raw_payload {
52
10
                crate::processors::v2::RawPayload::Symbiotic(payload) => {
53
10
                    if message.origin != gateway_proxy_address {
54
3
                        return Err(MessageExtractionError::InvalidMessage {
55
3
                            context: format!(
56
3
                                "Symbiotic message origin is {:?} expected {:?}",
57
3
                                message.origin, gateway_proxy_address
58
3
                            ),
59
3
                            source: None,
60
3
                        });
61
7
                    }
62

            
63
7
                    if message.value > 0 || !message.assets.is_empty() {
64
                        return Err(MessageExtractionError::InvalidMessage {
65
                            context: "Symbiotic message cannot have assets".to_string(),
66
                            source: None,
67
                        });
68
7
                    }
69

            
70
7
                    let symbiotic_payload = SymbioticPayload::decode(&mut payload.as_slice())
71
7
                        .map_err(|error| MessageExtractionError::InvalidMessage {
72
3
                            context: "Unable to decode Symbiotic Payload".to_string(),
73
3
                            source: Some(Box::new(CodecError(error))),
74
3
                        })?;
75
4
                    if symbiotic_payload.magic_bytes != MAGIC_BYTES {
76
2
                        return Err(MessageExtractionError::InvalidMessage {
77
2
                            context: format!(
78
2
                                "Symbiotic magic bytes expected: {:?} got: {:?}",
79
2
                                MAGIC_BYTES, symbiotic_payload.magic_bytes
80
2
                            ),
81
2
                            source: None,
82
2
                        });
83
2
                    }
84

            
85
2
                    return Ok(symbiotic_payload.message);
86
                }
87
                _ => Err(MessageExtractionError::UnsupportedMessage {
88
                    context: "Unsupported Message".to_string(),
89
                    source: None,
90
                }),
91
            }
92
        }
93
        _ => Err(MessageExtractionError::UnsupportedMessage {
94
            context: "Unsupported Message".to_string(),
95
            source: None,
96
        }),
97
    }
98
10
}
99

            
100
1
pub fn process_message<T: pallet_external_validators::Config>(
101
1
    symbiotic_message: SymbioticMessage<T>,
102
1
) -> Result<(), MessageProcessorError> {
103
1
    match symbiotic_message {
104
        tp_bridge::symbiotic_message_processor::Message::V1(
105
            InboundCommand::ReceiveValidators {
106
1
                validators,
107
1
                external_index,
108
            },
109
        ) => {
110
            // It is fine to return an error here as we know that a valid symbiotic message
111
            // does not contain any asset so there is no need to return success here to trap assets.
112
            // Moreover, the failure here might indicate critical issue within runtime, so it is crucial
113
            // that we do not ignore it.
114
1
            pallet_external_validators::Pallet::<T>::set_external_validators_inner(
115
1
                validators,
116
1
                external_index,
117
            )
118
1
            .map_err(|error| MessageProcessorError::ProcessMessage(error))?;
119
1
            Ok(())
120
        }
121
    }
122
1
}
123

            
124
/// Processes Symbiotic protocol messages for external validator set management.
125
///
126
/// This processor handles messages from the Symbiotic middleware that controls validator
127
/// registration and management. Messages must be encoded in `Payload::Raw(RawPayload::Symbiotic)`
128
/// format and originate from the authorized gateway proxy address.
129
///
130
/// # Message Validation
131
///
132
/// Strictly validates incoming messages:
133
/// - **Origin check**: Message must come from the configured gateway proxy address
134
/// - **Asset check**: Symbiotic messages cannot contain assets, ETH value, or execution fees
135
/// - **Magic bytes**: Payload must contain correct set of bytes to identify Symbiotic messages
136
///
137
/// # Supported Commands
138
///
139
/// Currently, processes `InboundCommand::ReceiveValidators` which:
140
/// - Updates the external validator set via `pallet_external_validators`
141
/// - Tracks validator changes by external index
142
/// - Returns errors on validation failures (no asset trapping needed)
143
///
144
/// # Fallback Behavior
145
///
146
/// Uses `PrivilegedFallbackProcessor` which conditionally applies asset trapping:
147
/// - **If origin is not gateway**: Assumes user error or malicious behaviour → trap assets via `AssetTrapFallbackProcessor`
148
/// - **If origin is gateway**: Assumes middleware error → return error to signal the problem
149
///
150
/// Unlike `RawMessageProcessor`, this can safely return errors in case origin is gateway since valid Symbiotic messages
151
/// from middleware never contain assets that could be lost. And error in that case indicate critical runtime issues that
152
/// should not be silently ignored.
153
#[derive(MessageProcessor)]
154
pub struct SymbioticMessageProcessor<
155
    T,
156
    GatewayAddress,
157
    DefaultClaimer,
158
    EthereumNetwork,
159
    EthereumUniversalLocation,
160
    TanssiUniversalLocation,
161
    XcmProcessor,
162
    XcmWeigher,
163
>(
164
    PhantomData<(
165
        T,
166
        GatewayAddress,
167
        DefaultClaimer,
168
        EthereumNetwork,
169
        EthereumUniversalLocation,
170
        TanssiUniversalLocation,
171
        XcmProcessor,
172
        XcmWeigher,
173
    )>,
174
);
175

            
176
impl<
177
        T,
178
        AccountId,
179
        GatewayAddress,
180
        DefaultClaimer,
181
        EthereumNetwork,
182
        EthereumUniversalLocation,
183
        TanssiUniversalLocation,
184
        XcmProcessor,
185
        XcmWeigher,
186
    > MessageProcessorWithFallback<AccountId>
187
    for SymbioticMessageProcessor<
188
        T,
189
        GatewayAddress,
190
        DefaultClaimer,
191
        EthereumNetwork,
192
        EthereumUniversalLocation,
193
        TanssiUniversalLocation,
194
        XcmProcessor,
195
        XcmWeigher,
196
    >
197
where
198
    T: snowbridge_pallet_inbound_queue::Config
199
        + pallet_xcm::Config
200
        + snowbridge_pallet_system::Config
201
        + pallet_external_validators::Config,
202
    [u8; 32]: From<<T as frame_system::Config>::AccountId>,
203
    GatewayAddress: Get<H160>,
204
    DefaultClaimer: Get<<T as frame_system::Config>::AccountId>,
205
    EthereumNetwork: Get<NetworkId>,
206
    EthereumUniversalLocation: Get<InteriorLocation>,
207
    TanssiUniversalLocation: Get<InteriorLocation>,
208
    XcmProcessor: ExecuteXcm<<T as pallet_xcm::Config>::RuntimeCall>,
209
    XcmWeigher: WeightBounds<<T as pallet_xcm::Config>::RuntimeCall>,
210
{
211
    type Fallback = PrivilegedFallbackProcessor<
212
        T,
213
        AssetTrapFallbackProcessor<
214
            T,
215
            GatewayAddress,
216
            DefaultClaimer,
217
            EthereumNetwork,
218
            EthereumUniversalLocation,
219
            TanssiUniversalLocation,
220
            XcmProcessor,
221
            XcmWeigher,
222
        >,
223
        GatewayAddress,
224
        DefaultClaimer,
225
        EthereumNetwork,
226
        EthereumUniversalLocation,
227
        TanssiUniversalLocation,
228
        XcmProcessor,
229
        XcmWeigher,
230
    >;
231
    type ExtractedMessage = SymbioticMessage<T>;
232

            
233
10
    fn try_extract_message(
234
10
        _sender: &AccountId,
235
10
        message: &Message,
236
10
    ) -> Result<Self::ExtractedMessage, MessageExtractionError> {
237
10
        let gateway_proxy_address = T::GatewayAddress::get();
238
10
        try_extract_message(message, gateway_proxy_address)
239
10
    }
240

            
241
1
    fn process_extracted_message(
242
1
        _sender: AccountId,
243
1
        extracted_message: Self::ExtractedMessage,
244
1
    ) -> Result<Option<Weight>, MessageProcessorError> {
245
        // TODO: Add proper consumed weight
246
1
        process_message(extracted_message).map(|_| None)
247
1
    }
248
}