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::{
20
    execute_xcm, fallback_message_processor::AssetTrapFallbackProcessor,
21
    prepare_raw_message_xcm_instructions, CodecError, ExtractedXcmConstructionInfo,
22
    FallbackMessageProcessor, MessageExtractionError, MessageProcessorWithFallback, RawPayload,
23
};
24
use crate::processors::v2::{reanchor_location_to_tanssi, RAW_MESSAGE_PROCESSOR_TOPIC_PREFIX};
25
use alloc::boxed::Box;
26
use alloc::string::ToString;
27
use core::marker::PhantomData;
28
use pallet_xcm::Event;
29
use parity_scale_codec::{Decode, DecodeLimit};
30
use snowbridge_inbound_queue_primitives::v2::{message::Message, MessageProcessorError, Payload};
31
use sp_core::{Get, H160};
32
use sp_runtime::DispatchError;
33
use sp_runtime::Weight;
34
use thiserror::Error;
35
use v2_processor_proc_macro::MessageProcessor;
36
use xcm::latest::{ExecuteXcm, Outcome};
37
use xcm::prelude::{InteriorLocation, NetworkId, VersionedXcm, Xcm};
38
use xcm::{IdentifyVersion, Version, MAX_XCM_DECODE_DEPTH};
39
use xcm_executor::traits::WeightBounds;
40

            
41
#[derive(Error, Debug)]
42
pub enum XcmDecodeError {
43
    #[error("Failed to decode versioned xcm message: {0}")]
44
    VersionedXcmDecodeError(#[from] CodecError),
45
    #[error("Xcm version {version} is not supported")]
46
    UnsupportedXcmVersion { version: Version },
47
}
48

            
49
/// Parse and strictly decode `raw` XCM bytes into a `Xcm<()>`.
50
35
fn decode_raw_xcm<T>(
51
35
    mut data: &[u8],
52
35
) -> Result<Xcm<<T as pallet_xcm::Config>::RuntimeCall>, XcmDecodeError>
53
35
where
54
35
    T: pallet_xcm::Config,
55
{
56
35
    VersionedXcm::<<T as pallet_xcm::Config>::RuntimeCall>::decode_with_depth_limit(
57
        MAX_XCM_DECODE_DEPTH,
58
35
        &mut data,
59
    )
60
35
    .map_err(|e| XcmDecodeError::VersionedXcmDecodeError(e.into()))
61
35
    .and_then(|xcm| {
62
33
        let version = xcm.identify_version();
63
33
        xcm.try_into()
64
33
            .map_err(|_| XcmDecodeError::UnsupportedXcmVersion { version })
65
33
    })
66
35
}
67

            
68
41
pub fn try_extract_message<T>(
69
41
    message: &Message,
70
41
) -> Result<
71
41
    ExtractedXcmConstructionInfo<<T as pallet_xcm::Config>::RuntimeCall>,
72
41
    MessageExtractionError,
73
41
>
74
41
where
75
41
    T: pallet_xcm::Config,
76
{
77
41
    match message.payload {
78
41
        Payload::Raw(ref payload) => {
79
41
            let raw_payload =
80
41
                RawPayload::decode(&mut payload.as_slice()).map_err(|decode_error| {
81
                    MessageExtractionError::InvalidMessage {
82
                        context: "Unable to decode RawPayload".to_string(),
83
                        source: Some(Box::new(CodecError(decode_error))),
84
                    }
85
                })?;
86
41
            match raw_payload {
87
35
                RawPayload::Xcm(payload) => Ok(decode_raw_xcm::<T>(&payload)
88
35
                    .map(|xcm| ExtractedXcmConstructionInfo {
89
33
                        origin: message.origin,
90
33
                        maybe_claimer: message.claimer.clone(),
91
33
                        assets: message.assets.clone(),
92
33
                        eth_value: message.value,
93
33
                        execution_fee_in_eth: message.execution_fee,
94
33
                        nonce: message.nonce,
95
33
                        user_xcm: xcm,
96
33
                    })
97
35
                    .map_err(|error| MessageExtractionError::InvalidMessage {
98
2
                        context: "Unable to decode Xcm".to_string(),
99
2
                        source: Some(Box::new(error)),
100
2
                    })?),
101
6
                RawPayload::Symbiotic(_) => Err(MessageExtractionError::UnsupportedMessage {
102
6
                    context: "Message is unsupported".to_string(),
103
6
                    source: None,
104
6
                }),
105
            }
106
        }
107
        _ => Err(MessageExtractionError::UnsupportedMessage {
108
            context: "Message is unsupported".to_string(),
109
            source: None,
110
        }),
111
    }
112
41
}
113

            
114
/// Processes raw XCM messages from Snowbridge V2 with automatic asset trapping fallback.
115
///
116
/// This processor handles messages containing user-provided XCM instructions encoded in the
117
/// `Payload::Raw(RawPayload::Xcm)` format. The workflow is:
118
///
119
/// 1. **Extract**: Decode the `VersionedXcm` from the message payload
120
/// 2. **Prepare**: Wrap user XCM
121
/// 3. **Execute**: Run the prepared XCM on Tanssi relay chain
122
///
123
/// # Fallback Behavior
124
///
125
/// Uses `AssetTrapFallbackProcessor` as fallback for unsupported message types (e.g., Symbiotic).
126
/// This ensures assets are never lost:
127
/// - If message extraction fails → fallback traps assets for claimer recovery
128
/// - If XCM execution fails → still returns success to prevent Ethereum revert, assets become
129
///   trapped and claimable
130
///
131
/// # Error Handling
132
///
133
/// Always returns success even on XCM execution errors to avoid leaving assets in limbo on
134
/// Ethereum. Execution errors are logged but don't cause transaction reversion. This is
135
/// necessary because user XCM may contain non-revertible operations (e.g., cross-chain messages).
136
#[derive(MessageProcessor)]
137
pub struct RawMessageProcessor<
138
    T,
139
    GatewayAddress,
140
    DefaultClaimer,
141
    EthereumNetwork,
142
    EthereumUniversalLocation,
143
    TanssiUniversalLocation,
144
    XcmProcessor,
145
    XcmWeigher,
146
    MaxXcmWeight,
147
>(
148
    PhantomData<(
149
        T,
150
        GatewayAddress,
151
        DefaultClaimer,
152
        EthereumNetwork,
153
        EthereumUniversalLocation,
154
        TanssiUniversalLocation,
155
        XcmProcessor,
156
        XcmWeigher,
157
        MaxXcmWeight,
158
    )>,
159
);
160

            
161
impl<
162
        T,
163
        AccountId,
164
        GatewayAddress,
165
        DefaultClaimer,
166
        EthereumNetwork,
167
        EthereumUniversalLocation,
168
        TanssiUniversalLocation,
169
        XcmProcessor,
170
        XcmWeigher,
171
        MaxXcmWeight,
172
    > MessageProcessorWithFallback<AccountId>
173
    for RawMessageProcessor<
174
        T,
175
        GatewayAddress,
176
        DefaultClaimer,
177
        EthereumNetwork,
178
        EthereumUniversalLocation,
179
        TanssiUniversalLocation,
180
        XcmProcessor,
181
        XcmWeigher,
182
        MaxXcmWeight,
183
    >
184
where
185
    T: snowbridge_pallet_inbound_queue::Config
186
        + pallet_xcm::Config
187
        + snowbridge_pallet_system::Config,
188
    [u8; 32]: From<<T as frame_system::Config>::AccountId>,
189
    GatewayAddress: Get<H160>,
190
    DefaultClaimer: Get<<T as frame_system::Config>::AccountId>,
191
    EthereumNetwork: Get<NetworkId>,
192
    EthereumUniversalLocation: Get<InteriorLocation>,
193
    TanssiUniversalLocation: Get<InteriorLocation>,
194
    XcmProcessor: ExecuteXcm<<T as pallet_xcm::Config>::RuntimeCall>,
195
    XcmWeigher: WeightBounds<<T as pallet_xcm::Config>::RuntimeCall>,
196
    MaxXcmWeight: Get<Weight>,
197
{
198
    type Fallback = AssetTrapFallbackProcessor<
199
        T,
200
        GatewayAddress,
201
        DefaultClaimer,
202
        EthereumNetwork,
203
        EthereumUniversalLocation,
204
        TanssiUniversalLocation,
205
        XcmProcessor,
206
        XcmWeigher,
207
        MaxXcmWeight,
208
    >;
209
    type ExtractedMessage = ExtractedXcmConstructionInfo<<T as pallet_xcm::Config>::RuntimeCall>;
210

            
211
41
    fn try_extract_message(
212
41
        _sender: &AccountId,
213
41
        message: &Message,
214
41
    ) -> Result<Self::ExtractedMessage, MessageExtractionError> {
215
41
        try_extract_message::<T>(message)
216
41
    }
217

            
218
16
    fn process_extracted_message(
219
16
        _sender: AccountId,
220
16
        extracted_message: Self::ExtractedMessage,
221
16
    ) -> Result<Option<Weight>, MessageProcessorError> {
222
16
        let prepared_xcm = prepare_raw_message_xcm_instructions::<T>(
223
16
            EthereumNetwork::get(),
224
16
            &EthereumUniversalLocation::get(),
225
16
            &TanssiUniversalLocation::get(),
226
16
            GatewayAddress::get(),
227
16
            DefaultClaimer::get(),
228
16
            RAW_MESSAGE_PROCESSOR_TOPIC_PREFIX,
229
16
            extracted_message,
230
        )
231
16
        .map_err(|location_conversion_error| {
232
            log::error!(
233
                "Error while preparing xcm instructions: {:?}",
234
                location_conversion_error
235
            );
236
            MessageProcessorError::ProcessMessage(DispatchError::Other(
237
                "Error while preparing xcm instructions",
238
            ))
239
        })?
240
16
        .into();
241

            
242
16
        let eth_location_reanchored_to_tanssi = reanchor_location_to_tanssi(
243
16
            &EthereumUniversalLocation::get(),
244
16
            &TanssiUniversalLocation::get(),
245
16
            ().into(),
246
        )
247
16
        .map_err(|location_conversion_error| {
248
            log::error!(
249
                "Unable to reanchor eth location to tanssi: {:?}",
250
                location_conversion_error
251
            );
252
            MessageProcessorError::ProcessMessage(DispatchError::Other(
253
                "Unable to reanchor eth location to tanssi",
254
            ))
255
        })?;
256

            
257
        // Depending upon the content of raw xcm, it might be the case that it is not fully revertible
258
        // (i.e xcm that sends a message in another container chain and then return an error).
259
        // Another reason we are not returning error here as otherwise the tx will be reverted and assets will be in limbo in ethereum.
260
        // By returning success here, the assets will be trapped here and claimable by the claimer.
261
16
        let execution_result = execute_xcm::<T, XcmProcessor, XcmWeigher>(
262
16
            eth_location_reanchored_to_tanssi,
263
16
            MaxXcmWeight::get(),
264
16
            prepared_xcm,
265
        );
266

            
267
16
        match execution_result {
268
16
            Ok(outcome) => match outcome {
269
16
                Outcome::Complete { used } => Ok(Some(used)),
270
                Outcome::Incomplete {
271
                    used,
272
                    error: instruction_error,
273
                } => {
274
                    log::error!(
275
                        "Error while executing xcm in raw message processor: {:?}",
276
                        instruction_error
277
                    );
278
                    Ok(Some(used))
279
                }
280
                // This branch will never be executed since current xcm executor implementation only returns
281
                // this Outcome when the weight limit is less than required which is already checked earlier
282
                // in execute_xcm.
283
                Outcome::Error(instruction_error) => {
284
                    log::error!(
285
                        "Error while starting xcm execution in raw message processor: {:?}",
286
                        instruction_error
287
                    );
288
                    frame_system::Pallet::<T>::deposit_event(Event::Attempted {
289
                        outcome: Outcome::Error(instruction_error),
290
                    });
291
                    Ok(Some(Weight::zero()))
292
                }
293
            },
294
            Err(instruction_error) => {
295
                log::error!(
296
                    "Error while estimating weight for xcm execution in raw message processor: {:?}",
297
                    instruction_error
298
                );
299
                frame_system::Pallet::<T>::deposit_event(Event::Attempted {
300
                    outcome: Outcome::Error(instruction_error),
301
                });
302
                Ok(Some(Weight::zero()))
303
            }
304
        }
305
16
    }
306

            
307
    fn worst_case_message_processor_weight() -> Weight {
308
        MaxXcmWeight::get()
309
    }
310
}