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
    crate::*,
19
    alloc::boxed::Box,
20
    alloy_core::{
21
        primitives::{Bytes, FixedBytes},
22
        sol_types::SolValue,
23
    },
24
    core::marker::PhantomData,
25
    frame_support::{
26
        ensure,
27
        traits::{Defensive, ProcessMessage, ProcessMessageError},
28
        weights::WeightMeter,
29
    },
30
    snowbridge_outbound_queue_primitives::v2::{
31
        abi::{CommandWrapper, OutboundMessageWrapper},
32
        OutboundCommandWrapper, OutboundMessage as OutboundMessageV2,
33
    },
34
    snowbridge_pallet_outbound_queue::{
35
        CommittedMessage, MessageLeaves, Messages, Nonce, ProcessMessageOriginOf, WeightInfo,
36
    },
37
    snowbridge_pallet_outbound_queue_v2::{
38
        MessageLeaves as MessageLeavesV2, Messages as MessagesV2, Nonce as NonceV2, PendingOrder,
39
        PendingOrders, ProcessMessageOriginOf as ProcessMessageOriginOfV2,
40
        WeightInfo as WeightInfoV2,
41
    },
42
    sp_runtime::traits::{BlockNumberProvider, Get, Hash},
43
    xcm::latest::Location,
44
};
45

            
46
/// Alternative to [snowbridge_pallet_outbound_queue::Pallet::process_message] using a different
47
/// [Command] enum.
48
/// Snowbridge V1 implementation!
49
pub struct TanssiOutboundEthMessageProcessorV1<T>(PhantomData<T>);
50

            
51
impl<T> TanssiOutboundEthMessageProcessorV1<T>
52
where
53
    T: snowbridge_pallet_outbound_queue::Config,
54
{
55
    /// Process a message delivered by the MessageQueue pallet
56
108
    pub(crate) fn do_process_message(
57
108
        _: ProcessMessageOriginOf<T>,
58
108
        mut message: &[u8],
59
108
    ) -> Result<bool, ProcessMessageError> {
60
        use ProcessMessageError::*;
61

            
62
        // Yield if the maximum number of messages has been processed this block.
63
        // This ensures that the weight of `on_finalize` has a known maximum bound.
64
108
        ensure!(
65
108
            MessageLeaves::<T>::decode_len().unwrap_or(0) < T::MaxMessagesPerBlock::get() as usize,
66
7
            Yield
67
        );
68

            
69
        // Decode bytes into versioned message
70
100
        let versioned_queued_message: VersionedQueuedTanssiMessage =
71
101
            VersionedQueuedTanssiMessage::decode(&mut message).map_err(|_| Corrupt)?;
72

            
73
100
        log::trace!(
74
            "CustomProcessSnowbridgeMessage: {:?}",
75
            versioned_queued_message
76
        );
77

            
78
        // Convert versioned message into latest supported message version
79
100
        let queued_message: QueuedTanssiMessage = versioned_queued_message.into();
80

            
81
        // Obtain next nonce
82
100
        let nonce = <Nonce<T>>::try_mutate(
83
100
            queued_message.channel_id,
84
100
            |nonce| -> Result<u64, ProcessMessageError> {
85
100
                *nonce = nonce.checked_add(1).ok_or(Unsupported)?;
86
100
                Ok(*nonce)
87
100
            },
88
        )?;
89

            
90
100
        let pricing_params = T::PricingParameters::get();
91
100
        let command = queued_message.command.index();
92
100
        let params = queued_message.command.abi_encode();
93
100
        let max_dispatch_gas =
94
100
            ConstantGasMeter::maximum_dispatch_gas_used_at_most(&queued_message.command);
95
100
        let reward = pricing_params.rewards.remote;
96

            
97
        // Construct the final committed message
98
100
        let message = CommittedMessage {
99
100
            channel_id: queued_message.channel_id,
100
100
            nonce,
101
100
            command,
102
100
            params,
103
100
            max_dispatch_gas,
104
100
            max_fee_per_gas: pricing_params
105
100
                .fee_per_gas
106
100
                .try_into()
107
100
                .defensive_unwrap_or(u128::MAX),
108
100
            reward: reward.try_into().defensive_unwrap_or(u128::MAX),
109
100
            id: queued_message.id,
110
100
        };
111

            
112
        // ABI-encode and hash the prepared message
113
100
        let message_abi_encoded = ethabi::encode(&[message.clone().into()]);
114
100
        let message_abi_encoded_hash =
115
100
            <T as snowbridge_pallet_outbound_queue::Config>::Hashing::hash(&message_abi_encoded);
116

            
117
100
        Messages::<T>::append(Box::new(message));
118
100
        MessageLeaves::<T>::append(message_abi_encoded_hash);
119

            
120
100
        snowbridge_pallet_outbound_queue::Pallet::<T>::deposit_event(
121
100
            snowbridge_pallet_outbound_queue::Event::MessageAccepted {
122
100
                id: queued_message.id,
123
100
                nonce,
124
100
            },
125
        );
126

            
127
100
        Ok(true)
128
108
    }
129
}
130

            
131
impl<T> ProcessMessage for TanssiOutboundEthMessageProcessorV1<T>
132
where
133
    T: snowbridge_pallet_outbound_queue::Config,
134
{
135
    type Origin = T::AggregateMessageOrigin;
136

            
137
109
    fn process_message(
138
109
        message: &[u8],
139
109
        origin: Self::Origin,
140
109
        meter: &mut WeightMeter,
141
109
        _id: &mut [u8; 32],
142
109
    ) -> Result<bool, ProcessMessageError> {
143
        // TODO: this weight is from the pallet, should be very similar to the weight of
144
        // Self::do_process_message, but ideally we should benchmark this separately
145
109
        let weight = T::WeightInfo::do_process_message();
146
109
        if meter.try_consume(weight).is_err() {
147
1
            return Err(ProcessMessageError::Overweight(weight));
148
108
        }
149

            
150
108
        Self::do_process_message(origin.clone(), message)
151
109
    }
152
}
153

            
154
/// Alternative to [snowbridge_pallet_outbound_queue::Pallet::process_message] using a different
155
/// [Command] enum.
156
pub struct TanssiOutboundEthMessageProcessorV2<T, SelfLocation>(PhantomData<(T, SelfLocation)>);
157

            
158
impl<T, SelfLocation> TanssiOutboundEthMessageProcessorV2<T, SelfLocation>
159
where
160
    T: snowbridge_pallet_outbound_queue_v2::Config + frame_system::Config,
161
    SelfLocation: Get<Location>,
162
{
163
    /// Process a message delivered by the MessageQueue pallet
164
3
    pub(crate) fn do_process_message(
165
3
        _: ProcessMessageOriginOfV2<T>,
166
3
        mut message: &[u8],
167
3
    ) -> Result<bool, ProcessMessageError> {
168
        use ProcessMessageError::*;
169

            
170
        // Yield if the maximum number of messages has been processed this block.
171
        // This ensures that the weight of `on_finalize` has a known maximum bound.
172
3
        ensure!(
173
3
            MessageLeavesV2::<T>::decode_len().unwrap_or(0)
174
3
                < T::MaxMessagesPerBlock::get() as usize,
175
1
            Yield
176
        );
177

            
178
        // Decode bytes into versioned message
179
1
        let versioned_queued_message: VersionedQueuedTanssiMessage =
180
2
            VersionedQueuedTanssiMessage::decode(&mut message).map_err(|_| Corrupt)?;
181

            
182
1
        log::trace!(
183
            "CustomProcessSnowbridgeMessage: {:?}",
184
            versioned_queued_message
185
        );
186

            
187
        // Convert versioned message into latest supported message version
188
1
        let queued_message: QueuedTanssiMessage = versioned_queued_message.into();
189

            
190
        // Obtain next nonce
191
1
        let nonce = <NonceV2<T>>::try_mutate(|nonce| -> Result<u64, ProcessMessageError> {
192
1
            *nonce = nonce.checked_add(1).ok_or(Unsupported)?;
193
1
            Ok(*nonce)
194
1
        })?;
195

            
196
1
        let command = queued_message.command.index();
197
1
        let params = queued_message.command.abi_encode();
198
1
        let max_dispatch_gas =
199
1
            ConstantGasMeter::maximum_dispatch_gas_used_at_most(&queued_message.command);
200

            
201
1
        let commands: Vec<OutboundCommandWrapper> = vec![OutboundCommandWrapper {
202
1
            kind: command,
203
1
            gas: max_dispatch_gas,
204
1
            payload: params,
205
1
        }];
206

            
207
1
        let origin = TanssiAgentIdOf::convert_location(&SelfLocation::get()).ok_or(Unsupported)?;
208
1
        let topic = queued_message.id;
209
        // Construct the final committed message
210
1
        let message = OutboundMessageV2 {
211
1
            origin,
212
1
            nonce,
213
1
            commands: commands.clone().try_into().unwrap(),
214
1
            topic,
215
1
        };
216

            
217
1
        let abi_commands: Vec<CommandWrapper> = commands
218
1
            .into_iter()
219
1
            .map(|command| CommandWrapper {
220
1
                kind: command.kind,
221
1
                gas: command.gas,
222
1
                payload: Bytes::from(command.payload),
223
1
            })
224
1
            .collect();
225

            
226
1
        let committed_message = OutboundMessageWrapper {
227
1
            origin: FixedBytes::from(origin.as_fixed_bytes()),
228
1
            nonce,
229
1
            topic: FixedBytes::from(topic.as_fixed_bytes()),
230
1
            commands: abi_commands,
231
1
        };
232

            
233
        // ABI-encode and hash the prepared message
234
1
        let message_abi_encoded = committed_message.abi_encode();
235
1
        let message_abi_encoded_hash =
236
1
            <T as snowbridge_pallet_outbound_queue_v2::Config>::Hashing::hash(&message_abi_encoded);
237

            
238
1
        MessagesV2::<T>::append(Box::new(message));
239
1
        MessageLeavesV2::<T>::append(message_abi_encoded_hash);
240

            
241
1
        let order = PendingOrder {
242
1
            nonce,
243
1
            fee: 0,
244
1
            block_number: frame_system::Pallet::<T>::current_block_number(),
245
1
        };
246
1
        <PendingOrders<T>>::insert(nonce, order);
247

            
248
1
        snowbridge_pallet_outbound_queue_v2::Pallet::<T>::deposit_event(
249
1
            snowbridge_pallet_outbound_queue_v2::Event::MessageAccepted {
250
1
                id: queued_message.id,
251
1
                nonce,
252
1
            },
253
        );
254

            
255
1
        Ok(true)
256
3
    }
257
}
258

            
259
impl<T, SelfLocation> ProcessMessage for TanssiOutboundEthMessageProcessorV2<T, SelfLocation>
260
where
261
    T: snowbridge_pallet_outbound_queue_v2::Config + frame_system::Config,
262
    SelfLocation: Get<Location>,
263
{
264
    type Origin = T::AggregateMessageOrigin;
265

            
266
4
    fn process_message(
267
4
        message: &[u8],
268
4
        origin: Self::Origin,
269
4
        meter: &mut WeightMeter,
270
4
        _id: &mut [u8; 32],
271
4
    ) -> Result<bool, ProcessMessageError> {
272
        // TODO: this weight is from the pallet, should be very similar to the weight of
273
        // Self::do_process_message, but ideally we should benchmark this separately
274
4
        let weight = T::WeightInfo::do_process_message();
275
4
        if meter.try_consume(weight).is_err() {
276
1
            return Err(ProcessMessageError::Overweight(weight));
277
3
        }
278

            
279
3
        Self::do_process_message(origin.clone(), message)
280
4
    }
281
}
282

            
283
/// A meter that assigns a constant amount of gas for the execution of a command
284
///
285
/// The gas figures are extracted from this report:
286
/// > forge test --match-path test/Gateway.t.sol --gas-report
287
///
288
/// A healthy buffer is added on top of these figures to account for:
289
/// * The EIP-150 63/64 rule
290
/// * Future EVM upgrades that may increase gas cost
291
pub struct ConstantGasMeter;
292

            
293
impl ConstantGasMeter {
294
    // The base transaction cost, which includes:
295
    // 21_000 transaction cost, roughly worst case 64_000 for calldata, and 100_000
296
    // for message verification
297
    pub const MAXIMUM_BASE_GAS: u64 = 185_000;
298

            
299
1314
    fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64 {
300
1314
        match command {
301
32
            Command::Test { .. } => 60_000,
302
            // TODO: revisit gas cost
303
194
            Command::ReportRewards { .. } => 1_000_000,
304
1088
            Command::ReportSlashes { .. } => 1_000_000,
305
        }
306
1314
    }
307
}