Skip to main content

forest/message/
chain_message.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::*;
5use crate::message::signed_message::SignedMessage;
6use crate::shim::address::Protocol;
7use crate::shim::crypto::{SECP_SIG_LEN, Signature, SignatureType};
8use crate::shim::message::MethodNum;
9use crate::shim::{address::Address, econ::TokenAmount, message::Message};
10use ambassador::Delegate;
11use fvm_ipld_encoding::RawBytes;
12use get_size2::GetSize;
13use serde::{Deserialize, Serialize};
14use spire_enum::prelude::delegated_enum;
15use std::sync::Arc;
16
17/// `Enum` to encapsulate signed and unsigned messages. Useful when working with
18/// both types
19#[delegated_enum]
20#[derive(
21    Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq, GetSize, derive_more::From, Delegate,
22)]
23#[delegate(MessageRead)]
24#[serde(untagged)]
25pub enum ChainMessage {
26    Unsigned(Arc<Message>),
27    Signed(Arc<SignedMessage>),
28}
29
30impl From<Message> for ChainMessage {
31    fn from(msg: Message) -> Self {
32        Arc::new(msg).into()
33    }
34}
35
36impl From<SignedMessage> for ChainMessage {
37    fn from(msg: SignedMessage) -> Self {
38        Arc::new(msg).into()
39    }
40}
41
42impl ChainMessage {
43    pub fn message(&self) -> &Message {
44        match self {
45            Self::Unsigned(m) => m,
46            Self::Signed(sm) => sm.message(),
47        }
48    }
49
50    pub fn cid(&self) -> cid::Cid {
51        delegate_chain_message!(self.cid())
52    }
53
54    /// Tests if a message is equivalent to another replacing message.
55    /// A replacing message is a message with a different CID,
56    /// any of Gas values, and different signature, but with all
57    /// other parameters matching (source/destination, nonce, parameters, etc.)
58    /// See <https://github.com/filecoin-project/lotus/blob/813d133c24295629ef442fc3aa60e6e6b2101226/chain/types/message.go#L138>
59    pub fn equal_call(&self, other: &Self) -> bool {
60        self.message().equal_call(other.message())
61    }
62
63    /// Wrap `msg` for gas estimation so its on-chain size approximates a
64    /// real submission — the FVM charges per-byte gas based on the wire
65    /// encoding including the signature. Mirrors Lotus's
66    /// [`CallWithGas`](https://github.com/filecoin-project/lotus/blob/4a72a61c704d64228b4d91c497948442a9bfd235/chain/stmgr/call.go#L247-L267):
67    /// SECP256k1 / Delegated get a 65-byte zero-filled signature; BLS and
68    /// all other protocols (ID, Actor) get the unsigned message.
69    pub fn for_gas_estimation(msg: Message, from_protocol: Protocol) -> Self {
70        match from_protocol {
71            Protocol::Secp256k1 => {
72                SignedMessage::new_unchecked(msg, Signature::new_secp256k1(vec![0; SECP_SIG_LEN]))
73                    .into()
74            }
75            // Lotus uses the same signature length as Secp256k1 for Delegated.
76            Protocol::Delegated => SignedMessage::new_unchecked(
77                msg,
78                Signature::new(SignatureType::Delegated, vec![0; SECP_SIG_LEN]),
79            )
80            .into(),
81            _ => msg.into(),
82        }
83    }
84}
85
86impl MessageReadWrite for ChainMessage {
87    fn set_gas_limit(&mut self, amount: u64) {
88        delegate_chain_message!(self => |i| Arc::make_mut(i).set_gas_limit(amount))
89    }
90
91    fn set_sequence(&mut self, sequence: u64) {
92        delegate_chain_message!(self => |i| Arc::make_mut(i).set_sequence(sequence))
93    }
94
95    fn set_gas_fee_cap(&mut self, cap: TokenAmount) {
96        delegate_chain_message!(self => |i| Arc::make_mut(i).set_gas_fee_cap(cap))
97    }
98
99    fn set_gas_premium(&mut self, prem: TokenAmount) {
100        delegate_chain_message!(self => |i| Arc::make_mut(i).set_gas_premium(prem))
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    fn dummy_msg() -> Message {
109        Message {
110            from: Address::new_id(2),
111            to: Address::new_id(1),
112            ..Default::default()
113        }
114    }
115
116    #[track_caller]
117    fn assert_signed_with_zero_sig(
118        chain_msg: ChainMessage,
119        sig_type: SignatureType,
120        msg: &Message,
121    ) {
122        let ChainMessage::Signed(signed) = chain_msg else {
123            panic!("expected Signed variant");
124        };
125        assert_eq!(signed.signature().signature_type(), sig_type);
126        assert_eq!(signed.signature().bytes(), &vec![0; SECP_SIG_LEN]);
127        assert_eq!(signed.message(), msg);
128    }
129
130    #[track_caller]
131    fn assert_unsigned(chain_msg: ChainMessage, msg: &Message) {
132        let ChainMessage::Unsigned(m) = chain_msg else {
133            panic!("expected Unsigned variant");
134        };
135        assert_eq!(&*m, msg);
136    }
137
138    #[test]
139    fn test_for_gas_estimation_secp256k1() {
140        let msg = dummy_msg();
141        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), Protocol::Secp256k1);
142        assert_signed_with_zero_sig(chain_msg, SignatureType::Secp256k1, &msg);
143    }
144
145    #[test]
146    fn test_for_gas_estimation_delegated() {
147        let msg = dummy_msg();
148        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), Protocol::Delegated);
149        // Lotus uses the same signature length as Secp256k1 for delegated; this
150        // lock-in prevents an accidental length change from regressing parity.
151        assert_signed_with_zero_sig(chain_msg, SignatureType::Delegated, &msg);
152    }
153
154    #[test]
155    fn test_for_gas_estimation_bls() {
156        let msg = dummy_msg();
157        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), Protocol::BLS);
158        // Lotus passes BLS senders to ApplyMessage as the raw unsigned message
159        // (see chain/stmgr/call.go); matching that here.
160        assert_unsigned(chain_msg, &msg);
161    }
162
163    #[test]
164    fn test_for_gas_estimation_id() {
165        let msg = dummy_msg();
166        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), Protocol::ID);
167        assert_unsigned(chain_msg, &msg);
168    }
169
170    #[test]
171    fn test_for_gas_estimation_actor() {
172        let msg = dummy_msg();
173        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), Protocol::Actor);
174        assert_unsigned(chain_msg, &msg);
175    }
176}