Skip to main content

snarkvm_ledger_block/transactions/rejected_reason/
serialize.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18impl<N: Network> Serialize for RejectedReason<N> {
19    /// Serializes the rejected reason into string or bytes.
20    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
21        match serializer.is_human_readable() {
22            true => match self {
23                Self::DuplicateProgramID(program_id) => {
24                    let mut object = serializer.serialize_struct("RejectedReason", 2)?;
25                    object.serialize_field("type", "duplicate_program_id")?;
26                    object.serialize_field("program_id", program_id)?;
27                    object.end()
28                }
29                Self::Finalize { program_id, edition, resource, index, command } => {
30                    let mut object = serializer.serialize_struct("RejectedReason", 6)?;
31                    object.serialize_field("type", "finalize")?;
32                    object.serialize_field("program_id", program_id)?;
33                    object.serialize_field("edition", edition)?;
34                    object.serialize_field("resource", resource)?;
35                    object.serialize_field("index", index)?;
36                    // Serialize the command via its display string to keep the JSON human-readable.
37                    object.serialize_field("command", &command.to_string())?;
38                    object.end()
39                }
40                Self::VM(program_id, resource) => {
41                    // Only include fields that are present.
42                    let mut object = serializer.serialize_struct(
43                        "RejectedReason",
44                        1 + program_id.is_some() as usize * 2 + resource.is_some() as usize,
45                    )?;
46                    object.serialize_field("type", "non_finalize")?;
47                    if let Some((pid, edition)) = program_id {
48                        object.serialize_field("program_id", pid)?;
49                        object.serialize_field("edition", edition)?;
50                    }
51                    if let Some(resource) = resource {
52                        object.serialize_field("resource", resource)?;
53                    }
54                    object.end()
55                }
56            },
57            false => ToBytesSerializer::serialize_with_size_encoding(self, serializer),
58        }
59    }
60}
61
62impl<'de, N: Network> Deserialize<'de> for RejectedReason<N> {
63    /// Deserializes the rejected reason from a string or bytes.
64    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
65        match deserializer.is_human_readable() {
66            true => {
67                // Parse the rejected reason from a string into a value.
68                let mut object = serde_json::Value::deserialize(deserializer)?;
69                // Parse the type.
70                let type_ = object.get("type").and_then(|t| t.as_str());
71                // Recover the rejected reason.
72                match type_ {
73                    Some("duplicate_program_id") => {
74                        let program_id: ProgramID<N> = DeserializeExt::take_from_value::<D>(&mut object, "program_id")?;
75                        Ok(Self::DuplicateProgramID(program_id))
76                    }
77                    Some("finalize") => {
78                        let program_id: ProgramID<N> = DeserializeExt::take_from_value::<D>(&mut object, "program_id")?;
79                        let edition: u16 = DeserializeExt::take_from_value::<D>(&mut object, "edition")?;
80                        let resource: Identifier<N> = DeserializeExt::take_from_value::<D>(&mut object, "resource")?;
81                        let index: usize = DeserializeExt::take_from_value::<D>(&mut object, "index")?;
82                        // The command is stored as a display string; parse it back.
83                        let command_str: String = DeserializeExt::take_from_value::<D>(&mut object, "command")?;
84                        let command = command_str.parse::<Command<N>>().map_err(de::Error::custom)?;
85                        Ok(Self::Finalize { program_id, edition, resource, index, command: Box::new(command) })
86                    }
87                    Some("non_finalize") => {
88                        // Both fields are optional; use `.get()` to check presence before parsing.
89                        let program_id = match object.get("program_id").and_then(|v| v.as_str()) {
90                            Some(s) => {
91                                let pid = ProgramID::<N>::from_str(s).map_err(de::Error::custom)?;
92                                let edition: u16 = DeserializeExt::take_from_value::<D>(&mut object, "edition")?;
93                                Some((pid, edition))
94                            }
95                            None => None,
96                        };
97                        let resource = match object.get("resource").and_then(|v| v.as_str()) {
98                            Some(s) => Some(Identifier::<N>::from_str(s).map_err(de::Error::custom)?),
99                            None => None,
100                        };
101                        Ok(Self::VM(program_id, resource))
102                    }
103                    _ => Err(de::Error::custom("Invalid rejected reason type")),
104                }
105            }
106            false => FromBytesDeserializer::<Self>::deserialize_with_size_encoding(deserializer, "rejected reason"),
107        }
108    }
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    type CurrentNetwork = console::network::MainnetV0;
116
117    fn check_serde_json(expected: RejectedReason<CurrentNetwork>) {
118        // Serialize.
119        let expected_string = expected.to_string();
120        let candidate_string = serde_json::to_string(&expected).unwrap();
121        let candidate = serde_json::from_str::<RejectedReason<CurrentNetwork>>(&candidate_string).unwrap();
122        assert_eq!(expected, candidate);
123        assert_eq!(expected_string, candidate_string);
124        assert_eq!(expected_string, candidate.to_string());
125
126        // Deserialize.
127        assert_eq!(expected, RejectedReason::from_str(&expected_string).unwrap());
128        assert_eq!(expected, serde_json::from_str(&candidate_string).unwrap());
129    }
130
131    fn check_bincode(expected: RejectedReason<CurrentNetwork>) {
132        // Serialize.
133        let expected_bytes = expected.to_bytes_le().unwrap();
134        let expected_bytes_with_size_encoding = bincode::serialize(&expected).unwrap();
135        assert_eq!(&expected_bytes[..], &expected_bytes_with_size_encoding[8..]);
136
137        // Deserialize.
138        assert_eq!(expected, RejectedReason::read_le(&expected_bytes[..]).unwrap());
139        assert_eq!(expected, bincode::deserialize(&expected_bytes_with_size_encoding[..]).unwrap());
140    }
141
142    #[test]
143    fn test_serde_json() {
144        for reason in test_helpers::sample_rejected_reasons::<CurrentNetwork>() {
145            check_serde_json(reason);
146        }
147    }
148
149    #[test]
150    fn test_bincode() {
151        for reason in test_helpers::sample_rejected_reasons::<CurrentNetwork>() {
152            check_bincode(reason);
153        }
154    }
155}