1use alloc::{
4 format,
5 string::{String, ToString},
6};
7use core::array::TryFromSliceError;
8
9use alloy_primitives::{B256, B64};
10use derive_more::derive::{Display, From};
11
12#[derive(Copy, Clone, Debug, PartialEq, Eq)]
22#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
23#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
24pub struct SuperchainSignal {
25 pub recommended: ProtocolVersion,
27 pub required: ProtocolVersion,
29}
30
31#[derive(Copy, Clone, Debug, PartialEq, Eq)]
46#[non_exhaustive]
47pub enum ProtocolVersion {
48 V0(ProtocolVersionFormatV0),
50}
51
52impl core::fmt::Display for ProtocolVersion {
53 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54 match self {
55 Self::V0(value) => write!(f, "{}", value),
56 }
57 }
58}
59
60#[derive(Copy, Clone, Debug, Display, From)]
62pub enum ProtocolVersionError {
63 #[display("Unsupported version: {_0}")]
65 UnsupportedVersion(u8),
66 #[display("Invalid length: got {}, expected {}", got, expected)]
68 InvalidLength {
69 got: usize,
71 expected: usize,
73 },
74 #[display("Failed to convert slice to array")]
76 #[from(TryFromSliceError)]
77 TryFromSlice,
78}
79
80impl ProtocolVersion {
81 pub fn encode(&self) -> B256 {
89 let mut bytes = [0u8; 32];
90
91 match self {
92 Self::V0(value) => {
93 bytes[0] = 0x00; bytes[1..].copy_from_slice(&value.encode());
95 B256::from_slice(&bytes)
96 }
97 }
98 }
99
100 pub fn decode(value: B256) -> Result<Self, ProtocolVersionError> {
108 let version_type = value[0];
109 let typed_payload = &value[1..];
110
111 match version_type {
112 0 => Ok(Self::V0(ProtocolVersionFormatV0::decode(typed_payload)?)),
113 other => Err(ProtocolVersionError::UnsupportedVersion(other)),
114 }
115 }
116
117 pub const fn inner(&self) -> ProtocolVersionFormatV0 {
119 match self {
120 Self::V0(value) => *value,
121 }
122 }
123
124 pub const fn as_v0(&self) -> Option<ProtocolVersionFormatV0> {
126 match self {
127 Self::V0(value) => Some(*value),
128 }
129 }
130
131 pub const fn build(&self) -> B64 {
133 match self {
134 Self::V0(value) => value.build,
135 }
136 }
137
138 pub const fn major(&self) -> u32 {
140 match self {
141 Self::V0(value) => value.major,
142 }
143 }
144
145 pub const fn minor(&self) -> u32 {
147 match self {
148 Self::V0(value) => value.minor,
149 }
150 }
151
152 pub const fn patch(&self) -> u32 {
154 match self {
155 Self::V0(value) => value.patch,
156 }
157 }
158
159 pub const fn pre_release(&self) -> u32 {
161 match self {
162 Self::V0(value) => value.pre_release,
163 }
164 }
165
166 pub fn display(&self) -> String {
168 match self {
169 Self::V0(value) => format!("{}", value),
170 }
171 }
172}
173
174#[cfg(feature = "serde")]
175impl serde::Serialize for ProtocolVersion {
176 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
177 where
178 S: serde::Serializer,
179 {
180 self.encode().serialize(serializer)
181 }
182}
183
184#[cfg(feature = "serde")]
185impl<'de> serde::Deserialize<'de> for ProtocolVersion {
186 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
187 where
188 D: serde::Deserializer<'de>,
189 {
190 let value = alloy_primitives::B256::deserialize(deserializer)?;
191 Self::decode(value).map_err(serde::de::Error::custom)
192 }
193}
194
195#[derive(Copy, Clone, Debug, PartialEq, Eq)]
208pub struct ProtocolVersionFormatV0 {
209 pub build: B64,
211 pub major: u32,
213 pub minor: u32,
215 pub patch: u32,
217 pub pre_release: u32,
219}
220
221impl core::fmt::Display for ProtocolVersionFormatV0 {
222 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
223 let build_tag = if self.build.0.iter().any(|&byte| byte != 0) {
224 if is_human_readable_build_tag(self.build) {
225 let full = format!("+{}", String::from_utf8_lossy(&self.build.0));
226 full.trim_end_matches('\0').to_string()
227 } else {
228 format!("+{}", self.build)
229 }
230 } else {
231 String::new()
232 };
233
234 let pre_release_tag =
235 if self.pre_release != 0 { format!("-{}", self.pre_release) } else { String::new() };
236
237 write!(f, "v{}.{}.{}{}{}", self.major, self.minor, self.patch, pre_release_tag, build_tag)
238 }
239}
240
241impl ProtocolVersionFormatV0 {
242 pub fn encode(&self) -> [u8; 31] {
254 let mut bytes = [0u8; 31];
255 bytes[0..7].copy_from_slice(&[0u8; 7]);
256 bytes[7..15].copy_from_slice(&self.build.0);
257 bytes[15..19].copy_from_slice(&self.major.to_be_bytes());
258 bytes[19..23].copy_from_slice(&self.minor.to_be_bytes());
259 bytes[23..27].copy_from_slice(&self.patch.to_be_bytes());
260 bytes[27..31].copy_from_slice(&self.pre_release.to_be_bytes());
261 bytes
262 }
263
264 fn decode(value: &[u8]) -> Result<Self, ProtocolVersionError> {
276 if value.len() != 31 {
277 return Err(ProtocolVersionError::InvalidLength { got: value.len(), expected: 31 });
278 }
279
280 Ok(Self {
281 build: B64::from_slice(&value[7..15]),
282 major: u32::from_be_bytes(value[15..19].try_into()?),
283 minor: u32::from_be_bytes(value[19..23].try_into()?),
284 patch: u32::from_be_bytes(value[23..27].try_into()?),
285 pre_release: u32::from_be_bytes(value[27..31].try_into()?),
286 })
287 }
288}
289
290fn is_human_readable_build_tag(build: B64) -> bool {
292 for (i, &c) in build.iter().enumerate() {
293 if c == 0 {
294 if build[i..].iter().any(|&d| d != 0) {
296 return false;
297 }
298 return true;
299 }
300
301 if !(c.is_ascii_alphanumeric() || c == b'-' || (c == b'.' && i > 0)) {
303 return false;
304 }
305 }
306 true
307}
308
309#[cfg(test)]
310mod tests {
311 use alloy_primitives::b256;
312
313 use super::*;
314
315 #[test]
316 fn test_protocol_version_encode_decode() {
317 let test_cases = vec![
318 (
319 ProtocolVersion::V0(ProtocolVersionFormatV0 {
320 build: B64::from_slice(&[0x61, 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]),
321 major: 42,
322 minor: 0,
323 patch: 2,
324 pre_release: 0,
325 }),
326 "v42.0.2+0x6162010000000000",
327 b256!("000000000000000061620100000000000000002a000000000000000200000000"),
328 ),
329 (
330 ProtocolVersion::V0(ProtocolVersionFormatV0 {
331 build: B64::from_slice(&[0x61, 0x62, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]),
332 major: 42,
333 minor: 0,
334 patch: 2,
335 pre_release: 1,
336 }),
337 "v42.0.2-1+0x6162010000000000",
338 b256!("000000000000000061620100000000000000002a000000000000000200000001"),
339 ),
340 (
341 ProtocolVersion::V0(ProtocolVersionFormatV0 {
342 build: B64::from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]),
343 major: 42,
344 minor: 0,
345 patch: 2,
346 pre_release: 0,
347 }),
348 "v42.0.2+0x0102030405060708",
349 b256!("000000000000000001020304050607080000002a000000000000000200000000"),
350 ),
351 (
352 ProtocolVersion::V0(ProtocolVersionFormatV0 {
353 build: B64::from_slice(&[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]),
354 major: 0,
355 minor: 100,
356 patch: 2,
357 pre_release: 0,
358 }),
359 "v0.100.2",
360 b256!("0000000000000000000000000000000000000000000000640000000200000000"),
361 ),
362 (
363 ProtocolVersion::V0(ProtocolVersionFormatV0 {
364 build: B64::from_slice(&[b'O', b'P', b'-', b'm', b'o', b'd', 0x00, 0x00]),
365 major: 42,
366 minor: 0,
367 patch: 2,
368 pre_release: 1,
369 }),
370 "v42.0.2-1+OP-mod",
371 b256!("00000000000000004f502d6d6f6400000000002a000000000000000200000001"),
372 ),
373 (
374 ProtocolVersion::V0(ProtocolVersionFormatV0 {
375 build: B64::from_slice(&[b'a', b'b', 0x01, 0x00, 0x00, 0x00, 0x00, 0x00]),
376 major: 42,
377 minor: 0,
378 patch: 2,
379 pre_release: 0,
380 }),
381 "v42.0.2+0x6162010000000000", b256!("000000000000000061620100000000000000002a000000000000000200000000"),
383 ),
384 (
385 ProtocolVersion::V0(ProtocolVersionFormatV0 {
386 build: B64::from_slice(b"beta.123"),
387 major: 1,
388 minor: 0,
389 patch: 0,
390 pre_release: 0,
391 }),
392 "v1.0.0+beta.123",
393 b256!("0000000000000000626574612e31323300000001000000000000000000000000"),
394 ),
395 ];
396
397 for (decoded_exp, formatted_exp, encoded_exp) in test_cases {
398 encode_decode_v0(encoded_exp, formatted_exp, decoded_exp);
399 }
400 }
401
402 fn encode_decode_v0(encoded_exp: B256, formatted_exp: &str, decoded_exp: ProtocolVersion) {
403 let decoded = ProtocolVersion::decode(encoded_exp).unwrap();
404 assert_eq!(decoded, decoded_exp);
405
406 let encoded = decoded.encode();
407 assert_eq!(encoded, encoded_exp);
408
409 let formatted = decoded.display();
410 assert_eq!(formatted, formatted_exp);
411 }
412}