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 #[cfg(test)]
163 pub fn empty_success() -> Self {
164 Receipt::V4(Receipt_v4 {
165 exit_code: fvm_shared4::error::ExitCode::OK,
166 return_data: Default::default(),
167 gas_used: 0,
168 events_root: None,
169 })
170 }
171
172 pub fn exit_code(&self) -> ExitCode {
173 match self {
174 Receipt::V2(v2) => ExitCode::new(v2.exit_code.value()),
175 Receipt::V3(v3) => ExitCode::new(v3.exit_code.value()),
176 Receipt::V4(v4) => v4.exit_code,
177 }
178 }
179
180 pub fn return_data(&self) -> RawBytes {
181 delegate_receipt!(self.return_data.clone())
182 }
183
184 pub fn gas_used(&self) -> u64 {
185 match self {
186 Receipt::V2(v2) => v2.gas_used as u64,
187 Receipt::V3(v3) => v3.gas_used,
188 Receipt::V4(v4) => v4.gas_used,
189 }
190 }
191 pub fn events_root(&self) -> Option<Cid> {
192 match self {
193 Receipt::V2(_) => None,
194 Receipt::V3(v3) => v3.events_root,
195 Receipt::V4(v4) => v4.events_root,
196 }
197 }
198
199 pub fn get_receipt(
200 db: &impl Blockstore,
201 receipts: &Cid,
202 i: u64,
203 ) -> anyhow::Result<Option<Self>> {
204 if let Ok(amt) = Amtv0::load(receipts, db)
206 && let Ok(receipts) = amt.get(i)
207 {
208 return Ok(receipts.cloned().map(Receipt::V4));
209 }
210
211 let amt = Amtv0::load(receipts, db)?;
213 let receipts = amt.get(i)?;
214 Ok(receipts.cloned().map(Receipt::V2))
215 }
216
217 pub fn get_receipts(db: &impl Blockstore, receipts_cid: Cid) -> anyhow::Result<Vec<Receipt>> {
218 let mut receipts = Vec::new();
219
220 if let Ok(amt) = Amtv0::<fvm_shared4::receipt::Receipt, _>::load(&receipts_cid, db) {
222 amt.for_each_cacheless(|_, receipt| {
223 receipts.push(Receipt::V4(receipt.clone()));
224 Ok(())
225 })?;
226 } else {
227 let amt = Amtv0::<fvm_shared2::receipt::Receipt, _>::load(&receipts_cid, db)?;
229 amt.for_each_cacheless(|_, receipt| {
230 receipts.push(Receipt::V2(receipt.clone()));
231 Ok(())
232 })?;
233 }
234
235 Ok(receipts)
236 }
237}
238
239#[delegated_enum(impl_conversions)]
240#[derive(Clone, Debug)]
241pub enum Entry {
242 V3(Entry_v3),
243 V4(Entry_v4),
244}
245
246impl Entry {
247 #[cfg(test)]
248 pub fn new(
249 flags: crate::shim::fvm_shared_latest::event::Flags,
250 key: String,
251 codec: u64,
252 value: Vec<u8>,
253 ) -> Self {
254 Entry::V4(Entry_v4 {
255 flags,
256 key,
257 codec,
258 value,
259 })
260 }
261
262 pub fn into_parts(self) -> (u64, String, u64, Vec<u8>) {
263 delegate_entry!(self => |e| (e.flags.bits(), e.key, e.codec, e.value))
264 }
265
266 pub fn value(&self) -> &Vec<u8> {
267 delegate_entry!(self.value.borrow())
268 }
269
270 pub fn codec(&self) -> u64 {
271 delegate_entry!(self.codec)
272 }
273
274 pub fn key(&self) -> &String {
275 delegate_entry!(self.key.borrow())
276 }
277}
278
279#[delegated_enum(impl_conversions)]
280#[derive(Clone, Debug)]
281pub enum ActorEvent {
282 V3(ActorEvent_v3),
283 V4(ActorEvent_v4),
284}
285
286#[delegated_enum(impl_conversions)]
288#[derive(Clone, Debug, Serialize)]
289#[serde(untagged)]
290pub enum StampedEvent {
291 V3(StampedEvent_v3),
292 V4(StampedEvent_v4),
293}
294
295impl GetSize for StampedEvent {
296 fn get_heap_size_with_tracker<T: get_size2::GetSizeTracker>(&self, tracker: T) -> (usize, T) {
297 delegate_stamped_event!(self => |e| vec_heap_size_with_fn_helper(&e.event.entries, tracker, |e, mut tr| {
298 (e.key.get_heap_size_with_tracker(&mut tr).0 + e.value.get_heap_size_with_tracker(&mut tr).0, tr)
299 }))
300 }
301}
302
303impl StampedEvent {
304 #[cfg(test)]
308 pub fn new_indexed(emitter: ActorID, key: &str) -> Self {
309 Self::V4(create_raw_event_v4(emitter, key))
310 }
311
312 pub fn emitter(&self) -> ActorID {
314 delegate_stamped_event!(self.emitter)
315 }
316
317 pub fn entries(&self) -> Vec<Entry> {
318 delegate_stamped_event!(self => |e| e.event.entries.iter().cloned().map(Into::into).collect())
319 }
320
321 pub fn into_entries(self) -> Vec<Entry> {
322 match self {
323 StampedEvent::V3(e) => e.event.entries.into_iter().map(Into::into).collect(),
324 StampedEvent::V4(e) => e.event.entries.into_iter().map(Into::into).collect(),
325 }
326 }
327
328 pub fn get_events<DB: Blockstore>(
331 db: &DB,
332 events_root: &Cid,
333 ) -> anyhow::Result<Vec<StampedEvent>> {
334 let mut events = Vec::new();
335
336 if let Ok(amt) = Amt::<StampedEvent_v4, _>::load(events_root, db) {
338 amt.for_each_cacheless(|_, event| {
339 events.push(StampedEvent::V4(event.clone()));
340 Ok(())
341 })?;
342 } else {
343 let amt = Amt::<StampedEvent_v3, _>::load(events_root, db)?;
345 amt.for_each_cacheless(|_, event| {
346 events.push(StampedEvent::V3(event.clone()));
347 Ok(())
348 })?;
349 }
350
351 Ok(events)
352 }
353}
354
355#[cfg(test)]
358pub(crate) fn create_raw_event_v4(emitter: u64, key: &str) -> fvm_shared4::event::StampedEvent {
359 fvm_shared4::event::StampedEvent {
360 emitter,
361 event: fvm_shared4::event::ActorEvent {
362 entries: vec![fvm_shared4::event::Entry {
363 flags: fvm_shared4::event::Flags::FLAG_INDEXED_ALL,
364 key: key.to_string(),
365 codec: fvm_ipld_encoding::IPLD_RAW,
366 value: key.as_bytes().to_vec(),
367 }],
368 },
369 }
370}
371
372#[cfg(test)]
375pub(crate) fn create_raw_event_v3(emitter: u64, key: &str) -> fvm_shared3::event::StampedEvent {
376 fvm_shared3::event::StampedEvent {
377 emitter,
378 event: fvm_shared3::event::ActorEvent {
379 entries: vec![fvm_shared3::event::Entry {
380 flags: fvm_shared3::event::Flags::FLAG_INDEXED_ALL,
381 key: key.to_string(),
382 codec: fvm_ipld_encoding::IPLD_RAW,
383 value: key.as_bytes().to_vec(),
384 }],
385 },
386 }
387}
388
389#[cfg(test)]
390impl quickcheck::Arbitrary for Receipt {
391 fn arbitrary(g: &mut quickcheck::Gen) -> Self {
392 #[derive(derive_quickcheck_arbitrary::Arbitrary, Clone)]
393 enum Helper {
394 V2 {
395 exit_code: u32,
396 return_data: Vec<u8>,
397 gas_used: i64,
398 },
399 V3 {
400 exit_code: u32,
401 return_data: Vec<u8>,
402 gas_used: u64,
403 events_root: Option<::cid::Cid>,
404 },
405 V4 {
406 exit_code: u32,
407 return_data: Vec<u8>,
408 gas_used: u64,
409 events_root: Option<::cid::Cid>,
410 },
411 }
412 match Helper::arbitrary(g) {
413 Helper::V2 {
414 exit_code,
415 return_data,
416 gas_used,
417 } => Self::V2(Receipt_v2 {
418 exit_code: exit_code.into(),
419 return_data: return_data.into(),
420 gas_used,
421 }),
422 Helper::V3 {
423 exit_code,
424 return_data,
425 gas_used,
426 events_root,
427 } => Self::V3(Receipt_v3 {
428 exit_code: exit_code.into(),
429 return_data: return_data.into(),
430 gas_used,
431 events_root,
432 }),
433 Helper::V4 {
434 exit_code,
435 return_data,
436 gas_used,
437 events_root,
438 } => Self::V4(Receipt_v4 {
439 exit_code: exit_code.into(),
440 return_data: return_data.into(),
441 gas_used,
442 events_root,
443 }),
444 }
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451 use fvm4::trace::ExecutionEvent as E4;
452 use quickcheck_macros::quickcheck;
453
454 #[quickcheck]
455 fn receipt_cbor_serde_serialize(receipt: Receipt) {
456 let encoded = fvm_ipld_encoding::to_vec(&receipt).unwrap();
457 let encoded2 = match &receipt {
458 Receipt::V2(v) => fvm_ipld_encoding::to_vec(v),
459 Receipt::V3(v) => fvm_ipld_encoding::to_vec(v),
460 Receipt::V4(v) => fvm_ipld_encoding::to_vec(v),
461 }
462 .unwrap();
463 assert_eq!(encoded, encoded2);
464 }
465
466 macro_rules! apply_ret {
470 ($variant:ident, $inner:ident, $receipt:expr, $exec_trace:expr $(, $ef:ident: $ev:expr)*) => {
471 ApplyRet::$variant($inner {
472 msg_receipt: $receipt,
473 penalty: Default::default(),
474 miner_tip: Default::default(),
475 base_fee_burn: Default::default(),
476 over_estimation_burn: Default::default(),
477 refund: Default::default(),
478 gas_refund: 0,
479 gas_burned: 0,
480 failure_info: None,
481 exec_trace: $exec_trace,
482 $($ef: $ev,)*
483 })
484 };
485 }
486
487 #[test]
488 fn trace_has_call_return_exit_code_across_versions() {
489 let want = ExitCode::SYS_OUT_OF_GAS;
490 let miss = ExitCode::new(33);
491
492 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);
494 assert!(v4(vec![E4::CallReturn(want, None)]).trace_has_call_return_exit_code(want));
495 assert!(!v4(vec![E4::CallReturn(want, None)]).trace_has_call_return_exit_code(miss));
496 assert!(
497 !v4(vec![E4::CallReturn(ExitCode::OK, None)]).trace_has_call_return_exit_code(want)
498 );
499 assert!(!v4(vec![]).trace_has_call_return_exit_code(want));
500
501 use fvm3::trace::ExecutionEvent as E3;
503 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![]);
504 let oog3 = fvm_shared3::error::ExitCode::SYS_OUT_OF_GAS;
505 assert!(v3(vec![E3::CallReturn(oog3, None)]).trace_has_call_return_exit_code(want));
506 assert!(!v3(vec![E3::CallReturn(oog3, None)]).trace_has_call_return_exit_code(miss));
507
508 use fvm2::trace::ExecutionEvent as E2;
510 let v2 = apply_ret!(
511 V2,
512 ApplyRet_v2,
513 Receipt_v2 {
514 exit_code: fvm_shared2::error::ExitCode::OK,
515 return_data: RawBytes::default(),
516 gas_used: 0
517 },
518 vec![E2::CallReturn(RawBytes::default())]
519 );
520 assert!(!v2.trace_has_call_return_exit_code(want));
521 }
522}