Skip to main content

forest/message/
mod.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4pub mod chain_message;
5pub mod signed_message;
6
7use crate::shim::message::MethodNum;
8use crate::shim::{address::Address, econ::TokenAmount, message::Message};
9use crate::shim::{gas::Gas, version::NetworkVersion};
10use ambassador::delegatable_trait;
11pub use chain_message::ChainMessage;
12use fvm_ipld_encoding::RawBytes;
13use num::Zero;
14pub use signed_message::SignedMessage;
15
16/// Message interface to make read-only interactions with Signed and unsigned messages in a generic
17/// context.
18#[auto_impl::auto_impl(&, Arc)]
19#[delegatable_trait]
20pub trait MessageRead {
21    /// Returns the from address of the message.
22    fn from(&self) -> Address;
23    /// Returns the destination address of the message.
24    fn to(&self) -> Address;
25    /// Returns the message sequence or nonce.
26    fn sequence(&self) -> u64;
27    /// Returns the amount sent in message.
28    fn value(&self) -> TokenAmount;
29    /// Returns the method number to be called.
30    fn method_num(&self) -> MethodNum;
31    /// Returns the encoded parameters for the method call.
32    fn params(&self) -> &RawBytes;
33    /// Returns the gas limit for the message.
34    fn gas_limit(&self) -> u64;
35    /// Returns the required funds for the message.
36    fn required_funds(&self) -> TokenAmount;
37    /// gets gas fee cap for the message.
38    fn gas_fee_cap(&self) -> TokenAmount;
39    /// gets gas premium for the message.
40    fn gas_premium(&self) -> TokenAmount;
41    /// This method returns the effective gas premium claimable by the miner
42    /// given the supplied base fee. This method is not used anywhere except the `Eth` API.
43    ///
44    /// Filecoin clamps the gas premium at `gas_fee_cap` - `base_fee`, if lower than the
45    /// specified premium. Returns 0 if `gas_fee_cap` is less than `base_fee`.
46    fn effective_gas_premium(&self, base_fee: &TokenAmount) -> TokenAmount {
47        let available = self.gas_fee_cap() - base_fee;
48        // It's possible that storage providers may include messages with gasFeeCap less than the baseFee
49        // In such cases, their reward should be viewed as zero
50        available.clamp(TokenAmount::zero(), self.gas_premium())
51    }
52}
53
54/// Message interface to interact with Signed and unsigned messages in a generic
55/// context.
56pub trait MessageReadWrite: MessageRead {
57    /// sets the gas limit for the message.
58    fn set_gas_limit(&mut self, amount: u64);
59    /// sets a new sequence to the message.
60    fn set_sequence(&mut self, sequence: u64);
61    /// sets the gas fee cap.
62    fn set_gas_fee_cap(&mut self, cap: TokenAmount);
63    /// sets the gas premium.
64    fn set_gas_premium(&mut self, prem: TokenAmount);
65}
66
67impl MessageRead for Message {
68    fn from(&self) -> Address {
69        self.from
70    }
71    fn to(&self) -> Address {
72        self.to
73    }
74    fn sequence(&self) -> u64 {
75        self.sequence
76    }
77    fn value(&self) -> TokenAmount {
78        self.value.clone()
79    }
80    fn method_num(&self) -> MethodNum {
81        self.method_num
82    }
83    fn params(&self) -> &RawBytes {
84        &self.params
85    }
86    fn gas_limit(&self) -> u64 {
87        self.gas_limit
88    }
89    fn required_funds(&self) -> TokenAmount {
90        &self.gas_fee_cap * self.gas_limit
91    }
92    fn gas_fee_cap(&self) -> TokenAmount {
93        self.gas_fee_cap.clone()
94    }
95    fn gas_premium(&self) -> TokenAmount {
96        self.gas_premium.clone()
97    }
98}
99
100impl MessageReadWrite for Message {
101    fn set_gas_limit(&mut self, token_amount: u64) {
102        self.gas_limit = token_amount;
103    }
104    fn set_sequence(&mut self, new_sequence: u64) {
105        self.sequence = new_sequence;
106    }
107    fn set_gas_fee_cap(&mut self, cap: TokenAmount) {
108        self.gas_fee_cap = cap;
109    }
110    fn set_gas_premium(&mut self, prem: TokenAmount) {
111        self.gas_premium = prem;
112    }
113}
114
115/// Semantic validation and validates the message has enough gas.
116pub fn valid_for_block_inclusion(
117    msg: &Message,
118    min_gas: Gas,
119    version: NetworkVersion,
120) -> anyhow::Result<()> {
121    use crate::shim::address::ZERO_ADDRESS;
122    use crate::shim::econ::{BLOCK_GAS_LIMIT, TOTAL_FILECOIN};
123    if msg.version != 0 {
124        anyhow::bail!("Message version: {} not supported", msg.version);
125    }
126    if msg.to == *ZERO_ADDRESS && version >= NetworkVersion::V7 {
127        anyhow::bail!("invalid 'to' address");
128    }
129    if msg.value.is_negative() {
130        anyhow::bail!("message value cannot be negative");
131    }
132    if msg.value > *TOTAL_FILECOIN {
133        anyhow::bail!("message value cannot be greater than total FIL supply");
134    }
135    if msg.gas_fee_cap.is_negative() {
136        anyhow::bail!("gas_fee_cap cannot be negative");
137    }
138    if msg.gas_premium.is_negative() {
139        anyhow::bail!("gas_premium cannot be negative");
140    }
141    if msg.gas_premium > msg.gas_fee_cap {
142        anyhow::bail!("gas_fee_cap less than gas_premium");
143    }
144    if msg.gas_limit > BLOCK_GAS_LIMIT {
145        anyhow::bail!(
146            "gas_limit {} cannot be greater than block gas limit",
147            msg.gas_limit
148        );
149    }
150
151    if Gas::new(msg.gas_limit) < min_gas {
152        anyhow::bail!(
153            "gas_limit {} cannot be less than cost {} of storing a message on chain",
154            msg.gas_limit,
155            min_gas
156        );
157    }
158
159    Ok(())
160}
161
162#[cfg(test)]
163mod tests {
164    mod builder_test;
165
166    use itertools::Itertools;
167
168    use super::*;
169
170    #[test]
171    fn test_effective_gas_premium() {
172        // Test cases from the FIP-0115
173        // <https://github.com/filecoin-project/FIPs/blob/b84b89a34ccb3d239493392a7867d6b082193b38/FIPS/fip-0115.md#premium>>
174        let test_cases = vec![
175            // (base_fee, gas_fee_cap, gas_premium, expected)
176            (8, 8, 8, 0),
177            (8, 16, 7, 7),
178            (8, 19, 10, 10),
179            (123456, 123455, 123455, 0),
180            (123456, 1234567, 1111112, 1111111),
181        ]
182        .into_iter()
183        .map(|(base_fee, gas_fee_cap, gas_premium, expected)| {
184            (
185                TokenAmount::from_atto(base_fee),
186                TokenAmount::from_atto(gas_fee_cap),
187                TokenAmount::from_atto(gas_premium),
188                TokenAmount::from_atto(expected),
189            )
190        })
191        .collect_vec();
192
193        for (base_fee, gas_fee_cap, gas_premium, expected) in test_cases.into_iter() {
194            let msg = Message {
195                gas_fee_cap: gas_fee_cap.clone(),
196                gas_premium: gas_premium.clone(),
197                ..Default::default()
198            };
199
200            let result = msg.effective_gas_premium(&base_fee);
201            assert_eq!(
202                result, expected,
203                "base_fee={} gas_fee_cap={} gas_premium={} expected={} got={}",
204                base_fee, gas_fee_cap, gas_premium, expected, result
205            );
206        }
207    }
208}