Skip to main content

forest/shim/
executor.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::trace::ExecutionEvent;
5use crate::shim::{
6    econ::TokenAmount, fvm_shared_latest::ActorID, fvm_shared_latest::error::ExitCode,
7};
8use crate::utils::get_size::{GetSize, vec_heap_size_with_fn_helper};
9use cid::Cid;
10use fil_actors_shared::fvm_ipld_amt::{Amt, Amtv0};
11use fvm_ipld_blockstore::Blockstore;
12use fvm_ipld_encoding::RawBytes;
13use fvm_shared2::receipt::Receipt as Receipt_v2;
14use fvm_shared3::event::ActorEvent as ActorEvent_v3;
15use fvm_shared3::event::Entry as Entry_v3;
16use fvm_shared3::event::StampedEvent as StampedEvent_v3;
17pub use fvm_shared3::receipt::Receipt as Receipt_v3;
18use fvm_shared4::event::ActorEvent as ActorEvent_v4;
19use fvm_shared4::event::Entry as Entry_v4;
20use fvm_shared4::event::StampedEvent as StampedEvent_v4;
21use fvm_shared4::receipt::Receipt as Receipt_v4;
22use fvm2::executor::ApplyRet as ApplyRet_v2;
23use fvm3::executor::ApplyRet as ApplyRet_v3;
24use fvm4::executor::ApplyRet as ApplyRet_v4;
25use serde::Serialize;
26use spire_enum::prelude::delegated_enum;
27use std::borrow::Borrow as _;
28
29#[delegated_enum(impl_conversions)]
30#[derive(Clone, Debug)]
31pub enum ApplyRet {
32    V2(ApplyRet_v2),
33    V3(ApplyRet_v3),
34    V4(ApplyRet_v4),
35}
36
37impl ApplyRet {
38    pub fn failure_info(&self) -> Option<String> {
39        delegate_apply_ret!(self => |r| r.failure_info.as_ref().map(|failure| format!("{failure} (RetCode={})", self.msg_receipt().exit_code())))
40    }
41
42    pub fn miner_tip(&self) -> TokenAmount {
43        delegate_apply_ret!(self.miner_tip.borrow().into())
44    }
45
46    pub fn penalty(&self) -> TokenAmount {
47        delegate_apply_ret!(self.penalty.borrow().into())
48    }
49
50    pub fn msg_receipt(&self) -> Receipt {
51        delegate_apply_ret!(self.msg_receipt.clone().into())
52    }
53
54    pub fn refund(&self) -> TokenAmount {
55        delegate_apply_ret!(self.refund.borrow().into())
56    }
57
58    pub fn base_fee_burn(&self) -> TokenAmount {
59        delegate_apply_ret!(self.base_fee_burn.borrow().into())
60    }
61
62    pub fn over_estimation_burn(&self) -> TokenAmount {
63        delegate_apply_ret!(self.over_estimation_burn.borrow().into())
64    }
65
66    pub fn exec_trace(&self) -> Vec<ExecutionEvent> {
67        delegate_apply_ret!(self => |r| r.exec_trace.iter().cloned().map(Into::into).collect())
68    }
69
70    pub fn events(&self) -> Vec<StampedEvent> {
71        match self {
72            ApplyRet::V2(_) => Vec::<StampedEvent>::default(),
73            ApplyRet::V3(v3) => v3.events.iter().cloned().map(Into::into).collect(),
74            ApplyRet::V4(v4) => v4.events.iter().cloned().map(Into::into).collect(),
75        }
76    }
77}
78
79// Note: it's impossible to properly derive Deserialize.
80// To deserialize into `Receipt`, refer to `fn get_parent_receipt`
81#[delegated_enum(impl_conversions)]
82#[derive(Clone, Debug, Serialize)]
83#[serde(untagged)]
84pub enum Receipt {
85    V2(Receipt_v2),
86    V3(Receipt_v3),
87    V4(Receipt_v4),
88}
89
90impl GetSize for Receipt {
91    fn get_heap_size(&self) -> usize {
92        delegate_receipt!(self.return_data.bytes().get_heap_size())
93    }
94}
95
96impl PartialEq for Receipt {
97    fn eq(&self, other: &Self) -> bool {
98        self.exit_code() == other.exit_code()
99            && self.return_data() == other.return_data()
100            && self.gas_used() == other.gas_used()
101            && self.events_root() == other.events_root()
102    }
103}
104
105impl Receipt {
106    pub fn exit_code(&self) -> ExitCode {
107        match self {
108            Receipt::V2(v2) => ExitCode::new(v2.exit_code.value()),
109            Receipt::V3(v3) => ExitCode::new(v3.exit_code.value()),
110            Receipt::V4(v4) => v4.exit_code,
111        }
112    }
113
114    pub fn return_data(&self) -> RawBytes {
115        delegate_receipt!(self.return_data.clone())
116    }
117
118    pub fn gas_used(&self) -> u64 {
119        match self {
120            Receipt::V2(v2) => v2.gas_used as u64,
121            Receipt::V3(v3) => v3.gas_used,
122            Receipt::V4(v4) => v4.gas_used,
123        }
124    }
125    pub fn events_root(&self) -> Option<Cid> {
126        match self {
127            Receipt::V2(_) => None,
128            Receipt::V3(v3) => v3.events_root,
129            Receipt::V4(v4) => v4.events_root,
130        }
131    }
132
133    pub fn get_receipt(
134        db: &impl Blockstore,
135        receipts: &Cid,
136        i: u64,
137    ) -> anyhow::Result<Option<Self>> {
138        // Try Receipt_v4 first. (Receipt_v4 and Receipt_v3 are identical, use v4 here)
139        if let Ok(amt) = Amtv0::load(receipts, db)
140            && let Ok(receipts) = amt.get(i)
141        {
142            return Ok(receipts.cloned().map(Receipt::V4));
143        }
144
145        // Fallback to Receipt_v2.
146        let amt = Amtv0::load(receipts, db)?;
147        let receipts = amt.get(i)?;
148        Ok(receipts.cloned().map(Receipt::V2))
149    }
150
151    pub fn get_receipts(db: &impl Blockstore, receipts_cid: Cid) -> anyhow::Result<Vec<Receipt>> {
152        let mut receipts = Vec::new();
153
154        // Try Receipt_v4 first. (Receipt_v4 and Receipt_v3 are identical, use v4 here)
155        if let Ok(amt) = Amtv0::<fvm_shared4::receipt::Receipt, _>::load(&receipts_cid, db) {
156            amt.for_each(|_, receipt| {
157                receipts.push(Receipt::V4(receipt.clone()));
158                Ok(())
159            })?;
160        } else {
161            // Fallback to Receipt_v2.
162            let amt = Amtv0::<fvm_shared2::receipt::Receipt, _>::load(&receipts_cid, db)?;
163            amt.for_each(|_, receipt| {
164                receipts.push(Receipt::V2(receipt.clone()));
165                Ok(())
166            })?;
167        }
168
169        Ok(receipts)
170    }
171}
172
173#[delegated_enum(impl_conversions)]
174#[derive(Clone, Debug)]
175pub enum Entry {
176    V3(Entry_v3),
177    V4(Entry_v4),
178}
179
180impl Entry {
181    #[cfg(test)]
182    pub fn new(
183        flags: crate::shim::fvm_shared_latest::event::Flags,
184        key: String,
185        codec: u64,
186        value: Vec<u8>,
187    ) -> Self {
188        Entry::V4(Entry_v4 {
189            flags,
190            key,
191            codec,
192            value,
193        })
194    }
195
196    pub fn into_parts(self) -> (u64, String, u64, Vec<u8>) {
197        delegate_entry!(self => |e| (e.flags.bits(), e.key, e.codec, e.value))
198    }
199
200    pub fn value(&self) -> &Vec<u8> {
201        delegate_entry!(self.value.borrow())
202    }
203
204    pub fn codec(&self) -> u64 {
205        delegate_entry!(self.codec)
206    }
207
208    pub fn key(&self) -> &String {
209        delegate_entry!(self.key.borrow())
210    }
211}
212
213#[delegated_enum(impl_conversions)]
214#[derive(Clone, Debug)]
215pub enum ActorEvent {
216    V3(ActorEvent_v3),
217    V4(ActorEvent_v4),
218}
219
220impl ActorEvent {
221    pub fn entries(&self) -> Vec<Entry> {
222        delegate_actor_event!(self => |e| e.entries.clone().into_iter().map(Into::into).collect())
223    }
224}
225
226/// Event with extra information stamped by the FVM.
227#[delegated_enum(impl_conversions)]
228#[derive(Clone, Debug, Serialize)]
229#[serde(untagged)]
230pub enum StampedEvent {
231    V3(StampedEvent_v3),
232    V4(StampedEvent_v4),
233}
234
235impl GetSize for StampedEvent {
236    fn get_heap_size(&self) -> usize {
237        delegate_stamped_event!(self => |e| vec_heap_size_with_fn_helper(&e.event.entries, |e| {
238            e.key.get_heap_size() + e.value.get_heap_size()
239        }))
240    }
241}
242
243impl StampedEvent {
244    /// Returns the ID of the actor that emitted this event.
245    pub fn emitter(&self) -> ActorID {
246        delegate_stamped_event!(self.emitter)
247    }
248
249    /// Returns the event as emitted by the actor.
250    pub fn event(&self) -> ActorEvent {
251        delegate_stamped_event!(self.event.clone().into())
252    }
253
254    /// Loads events directly from the events AMT root CID.
255    /// Returns events in the exact order they are stored in the AMT.
256    pub fn get_events<DB: Blockstore>(
257        db: &DB,
258        events_root: &Cid,
259    ) -> anyhow::Result<Vec<StampedEvent>> {
260        let mut events = Vec::new();
261
262        // Try StampedEvent_v4 first (StampedEvent_v4 and StampedEvent_v3 are identical, use v4 here)
263        if let Ok(amt) = Amt::<StampedEvent_v4, _>::load(events_root, db) {
264            amt.for_each_cacheless(|_, event| {
265                events.push(StampedEvent::V4(event.clone()));
266                Ok(())
267            })?;
268        } else {
269            // Fallback to StampedEvent_v3
270            let amt = Amt::<StampedEvent_v3, _>::load(events_root, db)?;
271            amt.for_each_cacheless(|_, event| {
272                events.push(StampedEvent::V3(event.clone()));
273                Ok(())
274            })?;
275        }
276
277        Ok(events)
278    }
279}
280
281#[cfg(test)]
282impl quickcheck::Arbitrary for Receipt {
283    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
284        #[derive(derive_quickcheck_arbitrary::Arbitrary, Clone)]
285        enum Helper {
286            V2 {
287                exit_code: u32,
288                return_data: Vec<u8>,
289                gas_used: i64,
290            },
291            V3 {
292                exit_code: u32,
293                return_data: Vec<u8>,
294                gas_used: u64,
295                events_root: Option<::cid::Cid>,
296            },
297            V4 {
298                exit_code: u32,
299                return_data: Vec<u8>,
300                gas_used: u64,
301                events_root: Option<::cid::Cid>,
302            },
303        }
304        match Helper::arbitrary(g) {
305            Helper::V2 {
306                exit_code,
307                return_data,
308                gas_used,
309            } => Self::V2(Receipt_v2 {
310                exit_code: exit_code.into(),
311                return_data: return_data.into(),
312                gas_used,
313            }),
314            Helper::V3 {
315                exit_code,
316                return_data,
317                gas_used,
318                events_root,
319            } => Self::V3(Receipt_v3 {
320                exit_code: exit_code.into(),
321                return_data: return_data.into(),
322                gas_used,
323                events_root,
324            }),
325            Helper::V4 {
326                exit_code,
327                return_data,
328                gas_used,
329                events_root,
330            } => Self::V4(Receipt_v4 {
331                exit_code: exit_code.into(),
332                return_data: return_data.into(),
333                gas_used,
334                events_root,
335            }),
336        }
337    }
338}
339
340#[cfg(test)]
341mod tests {
342    use super::*;
343    use quickcheck_macros::quickcheck;
344
345    #[quickcheck]
346    fn receipt_cbor_serde_serialize(receipt: Receipt) {
347        let encoded = fvm_ipld_encoding::to_vec(&receipt).unwrap();
348        let encoded2 = match &receipt {
349            Receipt::V2(v) => fvm_ipld_encoding::to_vec(v),
350            Receipt::V3(v) => fvm_ipld_encoding::to_vec(v),
351            Receipt::V4(v) => fvm_ipld_encoding::to_vec(v),
352        }
353        .unwrap();
354        assert_eq!(encoded, encoded2);
355    }
356}