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
105
    pub(crate) fn do_process_message(
41
105
        _: ProcessMessageOriginOf<T>,
42
105
        mut message: &[u8],
43
105
    ) -> 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
105
        ensure!(
49
105
            MessageLeaves::<T>::decode_len().unwrap_or(0) < T::MaxMessagesPerBlock::get() as usize,
50
6
            Yield
51
        );
52

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

            
57
        // Convert versioned message into latest supported message version
58
99
        let queued_message: QueuedMessage = versioned_queued_message
59
99
            .try_into()
60
99
            .map_err(|_| Unsupported)?;
61

            
62
        // Obtain next nonce
63
99
        let nonce = <Nonce<T>>::try_mutate(
64
99
            queued_message.channel_id,
65
99
            |nonce| -> Result<u64, ProcessMessageError> {
66
99
                *nonce = nonce.checked_add(1).ok_or(Unsupported)?;
67
99
                Ok(*nonce)
68
99
            },
69
99
        )?;
70

            
71
99
        let pricing_params = T::PricingParameters::get();
72
99
        let command = queued_message.command.index();
73
99
        let params = queued_message.command.abi_encode();
74
99
        let max_dispatch_gas =
75
99
            ConstantGasMeter::maximum_dispatch_gas_used_at_most(&queued_message.command);
76
99
        let reward = pricing_params.rewards.remote;
77
99

            
78
99
        // Construct the final committed message
79
99
        let message = CommittedMessage {
80
99
            channel_id: queued_message.channel_id,
81
99
            nonce,
82
99
            command,
83
99
            params,
84
99
            max_dispatch_gas,
85
99
            max_fee_per_gas: pricing_params
86
99
                .fee_per_gas
87
99
                .try_into()
88
99
                .defensive_unwrap_or(u128::MAX),
89
99
            reward: reward.try_into().defensive_unwrap_or(u128::MAX),
90
99
            id: queued_message.id,
91
99
        };
92
99

            
93
99
        // ABI-encode and hash the prepared message
94
99
        let message_abi_encoded = ethabi::encode(&[message.clone().into()]);
95
99
        let message_abi_encoded_hash =
96
99
            <T as snowbridge_pallet_outbound_queue::Config>::Hashing::hash(&message_abi_encoded);
97
99

            
98
99
        Messages::<T>::append(Box::new(message));
99
99
        MessageLeaves::<T>::append(message_abi_encoded_hash);
100
99

            
101
99
        snowbridge_pallet_outbound_queue::Pallet::<T>::deposit_event(
102
99
            snowbridge_pallet_outbound_queue::Event::MessageAccepted {
103
99
                id: queued_message.id,
104
99
                nonce,
105
99
            },
106
99
        );
107
99

            
108
99
        Ok(true)
109
105
    }
110
}
111

            
112
impl<T> ProcessMessage for CustomProcessSnowbridgeMessage<T>
113
where
114
    T: snowbridge_pallet_outbound_queue::Config,
115
{
116
    type Origin = T::AggregateMessageOrigin;
117

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

            
131
105
        Self::do_process_message(origin.clone(), message)
132
105
    }
133
}
134

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

            
145
impl ConstantGasMeter {
146
    // The base transaction cost, which includes:
147
    // 21_000 transaction cost, roughly worst case 64_000 for calldata, and 100_000
148
    // for message verification
149
    pub const MAXIMUM_BASE_GAS: u64 = 185_000;
150

            
151
1312
    fn maximum_dispatch_gas_used_at_most(command: &Command) -> u64 {
152
1312
        match command {
153
32
            Command::Test { .. } => 60_000,
154
            // TODO: revisit gas cost
155
192
            Command::ReportRewards { .. } => 1_000_000,
156
1088
            Command::ReportSlashes { .. } => 1_000_000,
157
        }
158
1312
    }
159
}