1use 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.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 {
53 delegate_apply_ret!(self.msg_receipt.clone().into())
54 }
55
56 pub fn return_data(&self) -> RawBytes {
58 delegate_apply_ret!(self => |r| r.msg_receipt.return_data.clone())
59 }
60
61 pub fn exit_code(&self) -> ExitCode {
62 ExitCode::new(delegate_apply_ret!(self => |r| r.msg_receipt.exit_code.value()))
63 }
64
65 pub fn gas_used(&self) -> u64 {
66 match self {
67 ApplyRet::V2(v2) => v2.msg_receipt.gas_used as u64,
68 ApplyRet::V3(v3) => v3.msg_receipt.gas_used,
69 ApplyRet::V4(v4) => v4.msg_receipt.gas_used,
70 }
71 }
72
73 pub fn refund(&self) -> TokenAmount {
74 delegate_apply_ret!(self.refund.borrow().into())
75 }
76
77 pub fn base_fee_burn(&self) -> TokenAmount {
78 delegate_apply_ret!(self.base_fee_burn.borrow().into())
79 }
80
81 pub fn over_estimation_burn(&self) -> TokenAmount {
82 delegate_apply_ret!(self.over_estimation_burn.borrow().into())
83 }
84
85 pub fn trace_has_call_return_exit_code(&self, code: ExitCode) -> bool {
89 let want = code.value();
90 match self {
91 ApplyRet::V2(_) => false,
92 ApplyRet::V3(r) => r.exec_trace.iter().any(
93 |e| matches!(e, fvm3::trace::ExecutionEvent::CallReturn(c, _) if c.value() == want),
94 ),
95 ApplyRet::V4(r) => r.exec_trace.iter().any(
96 |e| matches!(e, fvm4::trace::ExecutionEvent::CallReturn(c, _) if c.value() == want),
97 ),
98 }
99 }
100
101 pub fn into_exec_trace(self) -> Vec<ExecutionEvent> {
103 delegate_apply_ret!(self => |r| r.exec_trace.into_iter().map(Into::into).collect())
104 }
105
106 pub fn exec_trace(&self) -> Vec<ExecutionEvent> {
107 delegate_apply_ret!(self => |r| r.exec_trace.iter().cloned().map(Into::into).collect())
108 }
109
110 pub fn into_receipt_and_events(self) -> (Receipt, Option<Vec<StampedEvent>>) {
111 match self {
112 ApplyRet::V2(v2) => (v2.msg_receipt.into(), None),
113 ApplyRet::V3(v3) => {
114 let events = v3
115 .msg_receipt
116 .events_root
117 .is_some()
118 .then(|| v3.events.into_iter().map(Into::into).collect());
119 (v3.msg_receipt.into(), events)
120 }
121 ApplyRet::V4(v4) => {
122 let events = v4
123 .msg_receipt
124 .events_root
125 .is_some()
126 .then(|| v4.events.into_iter().map(Into::into).collect());
127 (v4.msg_receipt.into(), events)
128 }
129 }
130 }
131}
132
133#[delegated_enum(impl_conversions)]
136#[derive(Clone, Debug, Serialize)]
137#[serde(untagged)]
138pub enum Receipt {
139 V2(Receipt_v2),
140 V3(Receipt_v3),
141 V4(Receipt_v4),
142}
143
144impl GetSize for Receipt {
145 fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(&self, tracker: T) -> (usize, T) {
146 delegate_receipt!(self.return_data.bytes().get_heap_size_with_tracker(tracker))
147 }
148}
149
150impl PartialEq for Receipt {
151 fn eq(&self, other: &Self) -> bool {
152 self.exit_code() == other.exit_code()
153 && self.return_data() == other.return_data()
154 && self.gas_used() == other.gas_used()
155 && self.events_root() == other.events_root()
156 }
157}
158
159impl Receipt {
160 pub fn exit_code(&self) -> ExitCode {
161 match self {
162 Receipt::V2(v2) => ExitCode::new(v2.exit_code.value()),
163 Receipt::V3(v3) => ExitCode::new(v3.exit_code.value()),
164 Receipt::V4(v4) => v4.exit_code,
165 }
166 }
167
168 pub fn return_data(&self) -> RawBytes {
169 delegate_receipt!(self.return_data.clone())
170 }
171
172 pub fn gas_used(&self) -> u64 {
173 match self {
174 Receipt::V2(v2) => v2.gas_used as u64,
175 Receipt::V3(v3) => v3.gas_used,
176 Receipt::V4(v4) => v4.gas_used,
177 }
178 }
179 pub fn events_root(&self) -> Option<Cid> {
180 match self {
181 Receipt::V2(_) => None,
182 Receipt::V3(v3) => v3.events_root,
183 Receipt::V4(v4) => v4.events_root,
184 }
185 }
186
187 pub fn get_receipt(
188 db: &impl Blockstore,
189 receipts: &Cid,
190 i: u64,
191 ) -> anyhow::Result<Option<Self>> {
192 if let Ok(amt) = Amtv0::load(receipts, db)
194 && let Ok(receipts) = amt.get(i)
195 {
196 return Ok(receipts.cloned().map(Receipt::V4));
197 }
198
199 let amt = Amtv0::load(receipts, db)?;
201 let receipts = amt.get(i)?;
202 Ok(receipts.cloned().map(Receipt::V2))
203 }
204
205 pub fn get_receipts(db: &impl Blockstore, receipts_cid: Cid) -> anyhow::Result<Vec<Receipt>> {
206 let mut receipts = Vec::new();
207
208 if let Ok(amt) = Amtv0::<fvm_shared4::receipt::Receipt, _>::load(&receipts_cid, db) {
210 amt.for_each_cacheless(|_, receipt| {
211 receipts.push(Receipt::V4(receipt.clone()));
212 Ok(())
213 })?;
214 } else {
215 let amt = Amtv0::<fvm_shared2::receipt::Receipt, _>::load(&receipts_cid, db)?;
217 amt.for_each_cacheless(|_, receipt| {
218 receipts.push(Receipt::V2(receipt.clone()));
219 Ok(())
220 })?;
221 }
222
223 Ok(receipts)
224 }
225}
226
227#[delegated_enum(impl_conversions)]
228#[derive(Clone, Debug)]
229pub enum Entry {
230 V3(Entry_v3),
231 V4(Entry_v4),
232}
233
234impl Entry {
235 pub fn into_parts(self) -> (u64, String, u64, Vec<u8>) {
236 delegate_entry!(self => |e| (e.flags.bits(), e.key, e.codec, e.value))
237 }
238
239 pub fn value(&self) -> &Vec<u8> {
240 delegate_entry!(self.value.borrow())
241 }
242
243 pub fn codec(&self) -> u64 {
244 delegate_entry!(self.codec)
245 }
246
247 pub fn key(&self) -> &String {
248 delegate_entry!(self.key.borrow())
249 }
250}
251
252#[delegated_enum(impl_conversions)]
253#[derive(Clone, Debug)]
254pub enum ActorEvent {
255 V3(ActorEvent_v3),
256 V4(ActorEvent_v4),
257}
258
259#[delegated_enum(impl_conversions)]
261#[derive(Clone, Debug, Serialize)]
262#[serde(untagged)]
263pub enum StampedEvent {
264 V3(StampedEvent_v3),
265 V4(StampedEvent_v4),
266}
267
268impl GetSize for StampedEvent {
269 fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(&self, tracker: T) -> (usize, T) {
270 delegate_stamped_event!(self => |e| vec_heap_size_with_fn_helper(&e.event.entries, tracker, |e, mut tr| {
271 (e.key.get_heap_size_with_tracker(&mut tr).0 + e.value.get_heap_size_with_tracker(&mut tr).0, tr)
272 }))
273 }
274}
275
276impl StampedEvent {
277 pub fn emitter(&self) -> ActorID {
279 delegate_stamped_event!(self.emitter)
280 }
281
282 pub fn entries(&self) -> Vec<Entry> {
283 delegate_stamped_event!(self => |e| e.event.entries.iter().cloned().map(Into::into).collect())
284 }
285
286 pub fn into_entries(self) -> Vec<Entry> {
287 match self {
288 StampedEvent::V3(e) => e.event.entries.into_iter().map(Into::into).collect(),
289 StampedEvent::V4(e) => e.event.entries.into_iter().map(Into::into).collect(),
290 }
291 }
292
293 pub fn get_events<DB: Blockstore>(
296 db: &DB,
297 events_root: &Cid,
298 ) -> anyhow::Result<Vec<StampedEvent>> {
299 let mut events = Vec::new();
300
301 if let Ok(amt) = Amt::<StampedEvent_v4, _>::load(events_root, db) {
303 amt.for_each_cacheless(|_, event| {
304 events.push(StampedEvent::V4(event.clone()));
305 Ok(())
306 })?;
307 } else {
308 let amt = Amt::<StampedEvent_v3, _>::load(events_root, db)?;
310 amt.for_each_cacheless(|_, event| {
311 events.push(StampedEvent::V3(event.clone()));
312 Ok(())
313 })?;
314 }
315
316 Ok(events)
317 }
318}
319
320#[cfg(test)]
321mod test_utils;
322#[cfg(test)]
323pub(crate) use test_utils::{create_raw_event_v3, create_raw_event_v4};
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328 use fvm4::trace::ExecutionEvent as E4;
329 use quickcheck_macros::quickcheck;
330
331 #[quickcheck]
332 fn receipt_cbor_serde_serialize(receipt: Receipt) {
333 let encoded = fvm_ipld_encoding::to_vec(&receipt).unwrap();
334 let encoded2 = match &receipt {
335 Receipt::V2(v) => fvm_ipld_encoding::to_vec(v),
336 Receipt::V3(v) => fvm_ipld_encoding::to_vec(v),
337 Receipt::V4(v) => fvm_ipld_encoding::to_vec(v),
338 }
339 .unwrap();
340 assert_eq!(encoded, encoded2);
341 }
342
343 macro_rules! apply_ret {
347 ($variant:ident, $inner:ident, $receipt:expr, $exec_trace:expr $(, $ef:ident: $ev:expr)*) => {
348 ApplyRet::$variant($inner {
349 msg_receipt: $receipt,
350 penalty: Default::default(),
351 miner_tip: Default::default(),
352 base_fee_burn: Default::default(),
353 over_estimation_burn: Default::default(),
354 refund: Default::default(),
355 gas_refund: 0,
356 gas_burned: 0,
357 failure_info: None,
358 exec_trace: $exec_trace,
359 $($ef: $ev,)*
360 })
361 };
362 }
363
364 #[test]
365 fn trace_has_call_return_exit_code_across_versions() {
366 let want = ExitCode::SYS_OUT_OF_GAS;
367 let miss = ExitCode::new(33);
368
369 let v4 = |t| apply_ret!(V4, ApplyRet_v4, Receipt_v4 { exit_code: ExitCode::OK, return_data: RawBytes::default(), gas_used: 0, events_root: None }, t, events: vec![], return_codec: None);
371 assert!(v4(vec![E4::CallReturn(want, None)]).trace_has_call_return_exit_code(want));
372 assert!(!v4(vec![E4::CallReturn(want, None)]).trace_has_call_return_exit_code(miss));
373 assert!(
374 !v4(vec![E4::CallReturn(ExitCode::OK, None)]).trace_has_call_return_exit_code(want)
375 );
376 assert!(!v4(vec![]).trace_has_call_return_exit_code(want));
377
378 use fvm3::trace::ExecutionEvent as E3;
380 let v3 = |t| apply_ret!(V3, ApplyRet_v3, Receipt_v3 { exit_code: fvm_shared3::error::ExitCode::OK, return_data: RawBytes::default(), gas_used: 0, events_root: None }, t, events: vec![]);
381 let oog3 = fvm_shared3::error::ExitCode::SYS_OUT_OF_GAS;
382 assert!(v3(vec![E3::CallReturn(oog3, None)]).trace_has_call_return_exit_code(want));
383 assert!(!v3(vec![E3::CallReturn(oog3, None)]).trace_has_call_return_exit_code(miss));
384
385 use fvm2::trace::ExecutionEvent as E2;
387 let v2 = apply_ret!(
388 V2,
389 ApplyRet_v2,
390 Receipt_v2 {
391 exit_code: fvm_shared2::error::ExitCode::OK,
392 return_data: RawBytes::default(),
393 gas_used: 0
394 },
395 vec![E2::CallReturn(RawBytes::default())]
396 );
397 assert!(!v2.trace_has_call_return_exit_code(want));
398 }
399}