1use minicbor::{data::Type, Decode, Decoder};
2use semver::Version;
3use std::str::FromStr;
4use thiserror::Error;
5
6#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
11pub struct MetadataHash {
12 pub solc: Option<Version>,
13}
14
15impl MetadataHash {
16 pub fn from_cbor(encoded: &[u8]) -> Result<(Self, usize), minicbor::decode::Error> {
17 let mut context = DecodeContext::default();
18 let result = minicbor::decode_with(encoded, &mut context)?;
19
20 Ok((result, context.used_size))
21 }
22}
23
24#[derive(Clone, Debug, Error, PartialEq, Eq, Hash)]
25enum ParseMetadataHashError {
26 #[error("invalid solc type. Expected \"string\" or \"bytes\", found \"{0}\"")]
27 InvalidSolcType(Type),
28 #[error("solc is not a valid version: {0}")]
29 InvalidSolcVersion(String),
30 #[error("\"solc\" key met more than once")]
31 DuplicateKeys,
32}
33
34#[derive(Default, Debug, Clone, PartialEq, Eq)]
35struct DecodeContext {
36 used_size: usize,
37}
38
39impl<'b> Decode<'b, DecodeContext> for MetadataHash {
40 fn decode(
41 d: &mut Decoder<'b>,
42 ctx: &mut DecodeContext,
43 ) -> Result<Self, minicbor::decode::Error> {
44 use minicbor::decode::Error;
45
46 let number_of_elements = d.map()?.unwrap_or(u64::MAX);
47
48 let mut solc = None;
49 for _ in 0..number_of_elements {
50 match d.str() {
52 Ok("solc") => {
53 if solc.is_some() {
54 return Err(Error::custom(ParseMetadataHashError::DuplicateKeys));
56 }
57 solc = match d.datatype()? {
58 Type::Bytes => {
61 let bytes = d.bytes()?;
64 if bytes.len() != 3 {
65 return Err(Error::custom(
67 ParseMetadataHashError::InvalidSolcVersion(
68 "release build should be encoded as exactly 3 bytes".into(),
69 ),
70 ));
71 }
72 let (major, minor, patch) = (bytes[0], bytes[1], bytes[2]);
73 Some(Version::new(major as u64, minor as u64, patch as u64))
74 }
75 Type::String => {
76 let s = d.str()?;
78 let version = Version::from_str(s).map_err(|err| {
79 Error::custom(ParseMetadataHashError::InvalidSolcVersion(
80 err.to_string(),
81 ))
82 })?;
83 Some(version)
84 }
85 type_ => {
86 return Err(Error::custom(ParseMetadataHashError::InvalidSolcType(
88 type_,
89 )));
90 }
91 }
92 }
93 Ok(_) => {
94 d.skip()?;
96 }
97 Err(err) => return Err(err),
98 }
99 }
100
101 ctx.used_size = d.position();
105
106 Ok(MetadataHash { solc })
107 }
108
109 fn nil() -> Option<Self> {
110 Some(Self { solc: None })
111 }
112}
113
114#[cfg(test)]
115mod metadata_hash_deserialization_tests {
116 use super::*;
117 use blockscout_display_bytes::decode_hex;
118 use std::str::FromStr;
119
120 fn is_valid_custom_error(
121 error: minicbor::decode::Error,
122 expected: ParseMetadataHashError,
123 ) -> bool {
124 if !error.is_custom() {
125 return false;
126 }
127
128 let parse_metadata_hash_error_to_string = |err: ParseMetadataHashError| match err {
132 ParseMetadataHashError::InvalidSolcType(_) => "InvalidSolcType",
133 ParseMetadataHashError::InvalidSolcVersion(_) => "InvalidSolcVersion",
134 ParseMetadataHashError::DuplicateKeys => "DuplicateKeys",
135 };
136 format!("{error:?}").contains(parse_metadata_hash_error_to_string(expected))
137 }
138
139 #[test]
140 fn deserialization_metadata_hash_without_solc_tag() {
141 let hex =
144 "a165627a7a72305820d4fba422541feba2d648f6657d9354ec14ea9f5919b520abe0feb60981d7b17c";
145 let encoded = decode_hex(hex).unwrap();
146 let expected = MetadataHash { solc: None };
147 let expected_size = encoded.len();
148
149 let (decoded, decoded_size) = MetadataHash::from_cbor(encoded.as_ref())
151 .expect("Error when decoding valid metadata hash");
152
153 assert_eq!(expected, decoded, "Incorrectly decoded");
155 assert_eq!(expected_size, decoded_size, "Incorrect decoded size")
156 }
157
158 #[test]
159 fn deserialization_metadata_hash_with_solc_as_version() {
160 let hex = "a2646970667358221220bcc988b1311237f2c00ccd0bfbd8b01d24dc18f720603b0de93fe6327df5362564736f6c634300080e";
163 let encoded = decode_hex(hex).unwrap();
164 let expected = MetadataHash {
165 solc: Some(Version::new(0, 8, 14)),
166 };
167 let expected_size = encoded.len();
168
169 let (decoded, decoded_size) = MetadataHash::from_cbor(encoded.as_ref())
171 .expect("Error when decoding valid metadata hash");
172
173 assert_eq!(expected, decoded, "Incorrectly decoded");
175 assert_eq!(expected_size, decoded_size, "Incorrect decoded size")
176 }
177
178 #[test]
179 fn deserialization_metadata_hash_with_solc_as_string() {
180 let hex = "a2646970667358221220ba5af27fe13bc83e671bd6981216d35df49ab3ac923741b8948b277f93fbf73264736f6c637823302e382e31352d63692e323032322e352e32332b636f6d6d69742e3231353931353331";
183 let encoded = decode_hex(hex).unwrap();
184 let expected = MetadataHash {
185 solc: Some(
186 Version::from_str("0.8.15-ci.2022.5.23+commit.21591531")
187 .expect("solc version parsing"),
188 ),
189 };
190 let expected_size = encoded.len();
191
192 let (decoded, decoded_size) = MetadataHash::from_cbor(encoded.as_ref())
194 .expect("Error when decoding valid metadata hash");
195
196 assert_eq!(expected, decoded, "Incorrectly decoded");
198 assert_eq!(expected_size, decoded_size, "Incorrect decoded size")
199 }
200
201 #[test]
202 fn deserialization_of_non_exhausted_string() {
203 let first = "a2646970667358221220bcc988b1311237f2c00ccd0bfbd8b01d24dc18f720603b0de93fe6327df5362564736f6c634300080e";
207 let second =
208 "a165627a7a72305820d4fba422541feba2d648f6657d9354ec14ea9f5919b520abe0feb60981d7b17c";
209 let hex = format!("{first}{second}");
210 let encoded = decode_hex(&hex).unwrap();
211 let expected = MetadataHash {
212 solc: Some(Version::new(0, 8, 14)),
213 };
214 let expected_size = decode_hex(first).unwrap().len();
215
216 let (decoded, decoded_size) = MetadataHash::from_cbor(encoded.as_ref())
218 .expect("Error when decoding valid metadata hash");
219
220 assert_eq!(expected, decoded, "Incorrectly decoded");
222 assert_eq!(expected_size, decoded_size, "Incorrect decoded size")
223 }
224
225 #[test]
226 fn deserialization_of_non_cbor_hex_should_fail() {
227 let hex = "1234567890";
229 let encoded = decode_hex(hex).unwrap();
230
231 let decoded = MetadataHash::from_cbor(encoded.as_ref());
233
234 assert!(decoded.is_err(), "Deserialization should fail");
236 assert!(
237 decoded.unwrap_err().is_type_mismatch(),
238 "Should fail with type mismatch"
239 )
240 }
241
242 #[test]
243 fn deserialization_of_non_map_should_fail() {
244 let hex = "64736f6c63";
247 let encoded = decode_hex(hex).unwrap();
248
249 let decoded = MetadataHash::from_cbor(encoded.as_ref());
251
252 assert!(decoded.is_err(), "Deserialization should fail");
254 assert!(
255 decoded.unwrap_err().is_type_mismatch(),
256 "Should fail with type mismatch"
257 )
258 }
259
260 #[test]
261 fn deserialization_with_duplicated_solc_should_fail() {
262 let hex = "a364736f6c6343000400646970667358221220bcc988b1311237f2c00ccd0bfbd8b01d24dc18f720603b0de93fe6327df5362564736f6c634300080e";
265 let encoded = decode_hex(hex).unwrap();
266
267 let decoded = MetadataHash::from_cbor(encoded.as_ref());
269
270 assert!(decoded.is_err(), "Deserialization should fail");
272 assert!(
273 is_valid_custom_error(decoded.unwrap_err(), ParseMetadataHashError::DuplicateKeys),
274 "Should fail with custom (DuplicateKey) error"
275 );
276 }
277
278 #[test]
279 fn deserialization_with_not_enough_elements_should_fail() {
280 let hex = "a3646970667358221220bcc988b1311237f2c00ccd0bfbd8b01d24dc18f720603b0de93fe6327df5362564736f6c634300080e";
284 let encoded = decode_hex(hex).unwrap();
285
286 let decoded = MetadataHash::from_cbor(encoded.as_ref());
288
289 assert!(decoded.is_err(), "Deserialization should fail");
291 assert!(
292 decoded.unwrap_err().is_end_of_input(),
293 "Should fail with end of input error"
294 );
295 }
296
297 #[test]
298 fn deserialization_with_solc_neither_bytes_nor_string_should_fail() {
299 let hex= "a2646970667358221220bcc988b1311237f2c00ccd0bfbd8b01d24dc18f720603b0de93fe6327df5362564736f6c63187B";
302 let encoded = decode_hex(hex).unwrap();
303
304 let decoded = MetadataHash::from_cbor(encoded.as_ref());
306
307 assert!(decoded.is_err(), "Deserialization should fail");
309 assert!(
310 is_valid_custom_error(
311 decoded.unwrap_err(),
312 ParseMetadataHashError::InvalidSolcType(minicbor::data::Type::Int)
313 ),
314 "Should fail with custom (InvalidSolcType) error"
315 );
316 }
317}