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
110
    pub(crate) fn do_process_message(
57
110
        _: ProcessMessageOriginOf<T>,
58
110
        mut message: &[u8],
59
110
    ) -> 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
110
        ensure!(
65
110
            MessageLeaves::<T>::decode_len().unwrap_or(0) < T::MaxMessagesPerBlock::get() as usize,
66
7
            Yield
67
        );
68

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

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

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

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

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

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

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

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

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

            
127
102
        Ok(true)
128
110
    }
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
111
    fn process_message(
138
111
        message: &[u8],
139
111
        origin: Self::Origin,
140
111
        meter: &mut WeightMeter,
141
111
        _id: &mut [u8; 32],
142
111
    ) -> 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
111
        let weight = T::WeightInfo::do_process_message();
146
111
        if meter.try_consume(weight).is_err() {
147
1
            return Err(ProcessMessageError::Overweight(weight));
148
110
        }
149

            
150
110
        Self::do_process_message(origin.clone(), message)
151
111
    }
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
42
    pub(crate) fn do_process_message(
165
42
        _: ProcessMessageOriginOfV2<T>,
166
42
        mut message: &[u8],
167
42
    ) -> 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
42
        ensure!(
173
42
            MessageLeavesV2::<T>::decode_len().unwrap_or(0)
174
42
                < T::MaxMessagesPerBlock::get() as usize,
175
1
            Yield
176
        );
177

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

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

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

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

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

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

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

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

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

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

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

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

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

            
255
40
        Ok(true)
256
42
    }
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
43
    fn process_message(
267
43
        message: &[u8],
268
43
        origin: Self::Origin,
269
43
        meter: &mut WeightMeter,
270
43
        _id: &mut [u8; 32],
271
43
    ) -> 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
43
        let weight = T::WeightInfo::do_process_message();
275
43
        if meter.try_consume(weight).is_err() {
276
1
            return Err(ProcessMessageError::Overweight(weight));
277
42
        }
278

            
279
42
        Self::do_process_message(origin.clone(), message)
280
43
    }
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
1954
    fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64 {
300
1954
        match command {
301
32
            Command::Test { .. } => 60_000,
302
            // TODO: revisit gas cost
303
306
            Command::ReportRewards { .. } => 1_000_000,
304
1616
            Command::ReportSlashes { .. } => 1_000_000,
305
        }
306
1954
    }
307
}