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
use {
18
    alloc::vec,
19
    core::marker::PhantomData,
20
    cumulus_primitives_core::ParaId,
21
    frame_support::traits::Get,
22
    xcm::{
23
        latest::{Location, NetworkId},
24
        prelude::*,
25
    },
26
    xcm_builder::InspectMessageQueues,
27
    SendError::*,
28
};
29

            
30
// We check if the destination is ETH
31
78
fn is_ethereum_location(loc: &Location, ethereum_network: NetworkId) -> bool {
32
78
    matches!(
33
78
        loc,
34
        Location {
35
            parents: 2,
36
78
            interior: Junctions::X1(juncs)
37
78
        } if juncs[0] == GlobalConsensus(ethereum_network)
38
    )
39
78
}
40

            
41
pub struct SovereignPaidRemoteExporter<
42
    Router,
43
    UniversalLocation,
44
    EthereumNetwork,
45
    ExecutionFee,
46
    SelfParaId,
47
>(
48
    PhantomData<(
49
        Router,
50
        UniversalLocation,
51
        EthereumNetwork,
52
        ExecutionFee,
53
        SelfParaId,
54
    )>,
55
);
56
impl<
57
        Router: SendXcm,
58
        UniversalLocation: Get<InteriorLocation>,
59
        EthereumNetwork,
60
        ExecutionFee,
61
        SelfParaId,
62
    > SendXcm
63
    for SovereignPaidRemoteExporter<
64
        Router,
65
        UniversalLocation,
66
        EthereumNetwork,
67
        ExecutionFee,
68
        SelfParaId,
69
    >
70
where
71
    EthereumNetwork: Get<NetworkId>,
72
    ExecutionFee: Get<u128>,
73
    SelfParaId: Get<ParaId>,
74
{
75
    type Ticket = Router::Ticket;
76

            
77
6
    fn validate(
78
6
        dest: &mut Option<Location>,
79
6
        msg: &mut Option<Xcm<()>>,
80
6
    ) -> SendResult<Router::Ticket> {
81
6
        log::trace!(target: "xcm::sovereign_paid_remote_exporter", "validate params: dest={dest:?}, msg={msg:?}");
82

            
83
6
        let d = dest.as_ref().ok_or(MissingArgument)?;
84
6
        let xcm = msg.take().ok_or(MissingArgument)?;
85
        // Check if the destination is an Ethereum location
86
6
        if !is_ethereum_location(&d.clone(), EthereumNetwork::get()) {
87
            return Err(NotApplicable);
88
6
        }
89

            
90
        // `xcm` should already end with `SetTopic` - if it does, then extract and derive into
91
        // an onward topic ID.
92
6
        let maybe_forward_id = match xcm.last() {
93
6
            Some(SetTopic(t)) => Some(*t),
94
            _ => None,
95
        };
96

            
97
6
        let export_instruction = ExportMessage {
98
6
            network: EthereumNetwork::get(),
99
6
            destination: Here,
100
6
            xcm: xcm.clone(),
101
6
        };
102

            
103
        // Check if xcm contains AliasOrigin instruction.
104
        // We use the presence of an AliasOrigin instruction to distinguish
105
        // between Snowbridge V2 and Snowbridge V1 messages.
106
6
        let has_alias_origin = xcm
107
6
            .0
108
6
            .iter()
109
28
            .any(|instruction| matches!(instruction, AliasOrigin(_)));
110

            
111
        // For now hardcoding fees, but later it should be converted from fixed Tanssi relay amount
112
6
        let fees = Asset {
113
6
            id: AssetId(Location::here()),
114
6
            fun: Fungible(ExecutionFee::get()),
115
6
        };
116
6
        let container_location = Location::new(0, Parachain(SelfParaId::get().into()));
117

            
118
        // Prepare the message to send
119
6
        let mut message_instructions = vec![
120
6
            WithdrawAsset(fees.clone().into()),
121
6
            BuyExecution {
122
6
                fees,
123
6
                weight_limit: Unlimited,
124
6
            },
125
        ];
126

            
127
        // Add SetFeesMode if AliasOrigin is present in the exported xcm
128
6
        if has_alias_origin {
129
2
            message_instructions.push(SetFeesMode { jit_withdraw: true });
130
4
        }
131

            
132
6
        message_instructions.push(SetAppendix(Xcm(vec![DepositAsset {
133
6
            assets: AllCounted(1).into(),
134
6
            beneficiary: container_location,
135
6
        }])));
136
6
        message_instructions.push(export_instruction);
137

            
138
6
        let mut message = Xcm(message_instructions);
139

            
140
6
        let tanssi_location = Location {
141
6
            parents: 1,
142
6
            interior: Here,
143
6
        };
144

            
145
6
        if let Some(forward_id) = maybe_forward_id {
146
6
            message.0.push(SetTopic(forward_id));
147
6
        }
148

            
149
6
        let (v, cost) = validate_send::<Router>(tanssi_location, message).inspect_err(|err| {
150
            if let NotApplicable = err {
151
                *msg = Some(xcm);
152
            }
153
        })?;
154

            
155
6
        Ok((v, cost))
156
6
    }
157

            
158
6
    fn deliver(ticket: Router::Ticket) -> Result<XcmHash, SendError> {
159
6
        Router::deliver(ticket)
160
6
    }
161
}
162

            
163
impl<
164
        Router: SendXcm,
165
        UniversalLocation: Get<InteriorLocation>,
166
        EthereumNetwork,
167
        ExecutionFee,
168
        SelfParaId,
169
    > InspectMessageQueues
170
    for SovereignPaidRemoteExporter<
171
        Router,
172
        UniversalLocation,
173
        EthereumNetwork,
174
        ExecutionFee,
175
        SelfParaId,
176
    >
177
{
178
    fn clear_messages() {}
179

            
180
    fn get_messages() -> vec::Vec<(VersionedLocation, vec::Vec<VersionedXcm<()>>)> {
181
        vec::Vec::new()
182
    }
183
}