forest/lotus_json/
receipt.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use ::cid::Cid;
5use fvm_ipld_encoding::RawBytes;
6
7use super::*;
8use crate::shim::executor::Receipt;
9
10#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, JsonSchema)]
11#[serde(rename_all = "PascalCase")]
12#[schemars(rename = "Receipt")]
13pub struct ReceiptLotusJson {
14    exit_code: u32,
15    #[schemars(with = "LotusJson<RawBytes>")]
16    #[serde(with = "crate::lotus_json")]
17    r#return: RawBytes,
18    gas_used: u64,
19    #[schemars(with = "LotusJson<Option<Cid>>")]
20    #[serde(with = "crate::lotus_json", default)] // Lotus still does `"EventsRoot": null`
21    events_root: Option<Cid>,
22}
23
24impl HasLotusJson for Receipt {
25    type LotusJson = ReceiptLotusJson;
26
27    #[cfg(test)]
28    fn snapshots() -> Vec<(serde_json::Value, Self)> {
29        vec![
30            (
31                json!({
32                    "ExitCode": 0,
33                    "Return": "aGVsbG8gd29ybGQh",
34                    "GasUsed": 0,
35                    "EventsRoot": null,
36                }),
37                Self::V3(fvm_shared3::receipt::Receipt {
38                    exit_code: fvm_shared3::error::ExitCode::new(0),
39                    return_data: RawBytes::new(Vec::from_iter(*b"hello world!")),
40                    gas_used: 0,
41                    events_root: None,
42                }),
43            ),
44            (
45                json!({
46                    "ExitCode": 0,
47                    "Return": "aGVsbG8gd29ybGQh",
48                    "GasUsed": 0,
49                    "EventsRoot": {
50                        "/": "baeaaaaa"
51                    }
52                }),
53                Self::V3(fvm_shared3::receipt::Receipt {
54                    exit_code: fvm_shared3::error::ExitCode::new(0),
55                    return_data: RawBytes::new(Vec::from_iter(*b"hello world!")),
56                    gas_used: 0,
57                    events_root: Some(Cid::default()),
58                }),
59            ),
60        ]
61    }
62
63    fn into_lotus_json(self) -> Self::LotusJson {
64        Self::LotusJson {
65            exit_code: self.exit_code().value(),
66            r#return: self.return_data(),
67            gas_used: self.gas_used(),
68            events_root: self.events_root(),
69        }
70    }
71
72    fn from_lotus_json(lotus_json: Self::LotusJson) -> Self {
73        let Self::LotusJson {
74            exit_code,
75            r#return,
76            gas_used,
77            events_root,
78        } = lotus_json;
79        Self::V3(fvm_shared3::receipt::Receipt {
80            exit_code: fvm_shared3::error::ExitCode::new(exit_code),
81            return_data: r#return,
82            gas_used,
83            events_root,
84        })
85    }
86}
87
88#[test]
89fn shapshots() {
90    assert_all_snapshots::<Receipt>()
91}
92
93/// [Receipt] knows if it is `V2` or `V3`, but there's no way for
94/// the serialized representation to retain that information,
95/// so [`assert_unchanged_via_json`] tests with arbitrary input will fail.
96///
97/// This can only be fixed by rewriting [Receipt].
98///
99/// See <https://github.com/ChainSafe/forest/issues/3459>.
100#[test]
101fn cannot_call_arbitrary_tests_on_receipt() {
102    use pretty_assertions::assert_eq;
103
104    let v2 = Receipt::V2(fvm_shared2::receipt::Receipt {
105        exit_code: fvm_shared2::error::ExitCode::new(0),
106        return_data: RawBytes::new(Vec::from_iter(*b"hello world!")),
107        gas_used: 0,
108    });
109    let v3 = Receipt::V3(fvm_shared3::receipt::Receipt {
110        exit_code: fvm_shared3::error::ExitCode::new(0),
111        return_data: RawBytes::new(Vec::from_iter(*b"hello world!")),
112        gas_used: 0,
113        events_root: None,
114    });
115    let json = json!({
116        "ExitCode": 0,
117        "Return": "aGVsbG8gd29ybGQh",
118        "GasUsed": 0,
119        "EventsRoot": null,
120    });
121
122    // they serialize to the same thing...
123    assert_eq!(
124        serde_json::to_value(v2.clone().into_lotus_json()).unwrap(),
125        json
126    );
127    assert_eq!(
128        serde_json::to_value(v3.clone().into_lotus_json()).unwrap(),
129        json
130    );
131
132    let deserialized = serde_json::from_value::<LotusJson<Receipt>>(json)
133        .unwrap()
134        .into_inner();
135    assert!(matches!(deserialized, Receipt::V3(_)));
136    assert_eq!(v3, deserialized);
137}