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
    super::*,
19
    frame_support::{
20
        ensure,
21
        traits::{Defensive, ProcessMessage, ProcessMessageError},
22
        weights::WeightMeter,
23
    },
24
    snowbridge_pallet_outbound_queue::{
25
        CommittedMessage, MessageLeaves, Messages, Nonce, ProcessMessageOriginOf, WeightInfo,
26
    },
27
    sp_runtime::traits::Hash,
28
    sp_std::boxed::Box,
29
};
30

            
31
/// Alternative to [snowbridge_pallet_outbound_queue::Pallet::process_message] using a different
32
/// [Command] enum.
33
pub struct CustomProcessSnowbridgeMessage<T>(PhantomData<T>);
34

            
35
impl<T> CustomProcessSnowbridgeMessage<T>
36
where
37
    T: snowbridge_pallet_outbound_queue::Config,
38
{
39
    /// Process a message delivered by the MessageQueue pallet
40
106
    pub(crate) fn do_process_message(
41
106
        _: ProcessMessageOriginOf<T>,
42
106
        mut message: &[u8],
43
106
    ) -> Result<bool, ProcessMessageError> {
44
        use ProcessMessageError::*;
45

            
46
        // Yield if the maximum number of messages has been processed this block.
47
        // This ensures that the weight of `on_finalize` has a known maximum bound.
48
106
        ensure!(
49
106
            MessageLeaves::<T>::decode_len().unwrap_or(0) < T::MaxMessagesPerBlock::get() as usize,
50
6
            Yield
51
        );
52

            
53
        // Decode bytes into versioned message
54
100
        let versioned_queued_message: VersionedQueuedMessage =
55
100
            VersionedQueuedMessage::decode(&mut message).map_err(|_| Corrupt)?;
56

            
57
100
        log::trace!(
58
            "CustomProcessSnowbridgeMessage: {:?}",
59
            versioned_queued_message
60
        );
61

            
62
        // Convert versioned message into latest supported message version
63
100
        let queued_message: QueuedMessage = versioned_queued_message
64
100
            .try_into()
65
100
            .map_err(|_| Unsupported)?;
66

            
67
        // Obtain next nonce
68
100
        let nonce = <Nonce<T>>::try_mutate(
69
100
            queued_message.channel_id,
70
100
            |nonce| -> Result<u64, ProcessMessageError> {
71
100
                *nonce = nonce.checked_add(1).ok_or(Unsupported)?;
72
100
                Ok(*nonce)
73
100
            },
74
100
        )?;
75

            
76
100
        let pricing_params = T::PricingParameters::get();
77
100
        let command = queued_message.command.index();
78
100
        let params = queued_message.command.abi_encode();
79
100
        let max_dispatch_gas =
80
100
            ConstantGasMeter::maximum_dispatch_gas_used_at_most(&queued_message.command);
81
100
        let reward = pricing_params.rewards.remote;
82
100

            
83
100
        // Construct the final committed message
84
100
        let message = CommittedMessage {
85
100
            channel_id: queued_message.channel_id,
86
100
            nonce,
87
100
            command,
88
100
            params,
89
100
            max_dispatch_gas,
90
100
            max_fee_per_gas: pricing_params
91
100
                .fee_per_gas
92
100
                .try_into()
93
100
                .defensive_unwrap_or(u128::MAX),
94
100
            reward: reward.try_into().defensive_unwrap_or(u128::MAX),
95
100
            id: queued_message.id,
96
100
        };
97
100

            
98
100
        // ABI-encode and hash the prepared message
99
100
        let message_abi_encoded = ethabi::encode(&[message.clone().into()]);
100
100
        let message_abi_encoded_hash =
101
100
            <T as snowbridge_pallet_outbound_queue::Config>::Hashing::hash(&message_abi_encoded);
102
100

            
103
100
        Messages::<T>::append(Box::new(message));
104
100
        MessageLeaves::<T>::append(message_abi_encoded_hash);
105
100

            
106
100
        snowbridge_pallet_outbound_queue::Pallet::<T>::deposit_event(
107
100
            snowbridge_pallet_outbound_queue::Event::MessageAccepted {
108
100
                id: queued_message.id,
109
100
                nonce,
110
100
            },
111
100
        );
112
100

            
113
100
        Ok(true)
114
106
    }
115
}
116

            
117
impl<T> ProcessMessage for CustomProcessSnowbridgeMessage<T>
118
where
119
    T: snowbridge_pallet_outbound_queue::Config,
120
{
121
    type Origin = T::AggregateMessageOrigin;
122

            
123
106
    fn process_message(
124
106
        message: &[u8],
125
106
        origin: Self::Origin,
126
106
        meter: &mut WeightMeter,
127
106
        _id: &mut [u8; 32],
128
106
    ) -> Result<bool, ProcessMessageError> {
129
106
        // TODO: this weight is from the pallet, should be very similar to the weight of
130
106
        // Self::do_process_message, but ideally we should benchmark this separately
131
106
        let weight = T::WeightInfo::do_process_message();
132
106
        if meter.try_consume(weight).is_err() {
133
            return Err(ProcessMessageError::Overweight(weight));
134
106
        }
135
106

            
136
106
        Self::do_process_message(origin.clone(), message)
137
106
    }
138
}
139

            
140
/// A meter that assigns a constant amount of gas for the execution of a command
141
///
142
/// The gas figures are extracted from this report:
143
/// > forge test --match-path test/Gateway.t.sol --gas-report
144
///
145
/// A healthy buffer is added on top of these figures to account for:
146
/// * The EIP-150 63/64 rule
147
/// * Future EVM upgrades that may increase gas cost
148
pub struct ConstantGasMeter;
149

            
150
impl ConstantGasMeter {
151
    // The base transaction cost, which includes:
152
    // 21_000 transaction cost, roughly worst case 64_000 for calldata, and 100_000
153
    // for message verification
154
    pub const MAXIMUM_BASE_GAS: u64 = 185_000;
155

            
156
1312
    fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64 {
157
1312
        match command {
158
32
            Command::Test { .. } => 60_000,
159
            // TODO: revisit gas cost
160
192
            Command::ReportRewards { .. } => 1_000_000,
161
1088
            Command::ReportSlashes { .. } => 1_000_000,
162
        }
163
1312
    }
164
}