Skip to main content

solana_version/
v4.rs

1use {
2    crate::{self as v3, compute_commit, ClientId},
3    rand::{thread_rng, Rng},
4    serde::{de::Error as _, ser::Error as _, Deserialize, Deserializer, Serialize, Serializer},
5    solana_sanitize::Sanitize,
6    solana_serde_varint as serde_varint,
7    std::{convert::TryInto, fmt, str::FromStr},
8};
9
10#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub enum Prerelease {
13    Stable,
14    ReleaseCandidate(u16),
15    Beta(u16),
16    Alpha(u16),
17}
18
19impl Prerelease {
20    const ENCODE_TAG_STABLE: u16 = 0;
21    const ENCODE_TAG_RELEASE_CANDIDATE: u16 = 1;
22    const ENCODE_TAG_BETA: u16 = 2;
23    const ENCODE_TAG_ALPHA: u16 = 3;
24    const IDENTIFIER_RELEASE_CANDIDATE: &str = "rc";
25    const IDENTIFIER_BETA: &str = "beta";
26    const IDENTIFIER_ALPHA: &str = "alpha";
27
28    pub fn patch_is_valid(&self, patch: u16) -> bool {
29        *self == Self::Stable || patch == 0
30    }
31}
32
33#[derive(Clone, Debug, PartialEq)]
34pub enum ParsePrereleaseError {
35    DotSeparatorMissing,
36    NumericPartNotAU16,
37    UnknownIdentifier,
38}
39
40impl fmt::Display for Prerelease {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        let (identifier, number) = match &self {
43            Self::Stable => return Ok(()),
44            Self::ReleaseCandidate(rc) => (Self::IDENTIFIER_RELEASE_CANDIDATE, rc),
45            Self::Beta(beta) => (Self::IDENTIFIER_BETA, beta),
46            Self::Alpha(alpha) => (Self::IDENTIFIER_ALPHA, alpha),
47        };
48        write!(f, "{identifier}.{number}")
49    }
50}
51
52impl FromStr for Prerelease {
53    type Err = ParsePrereleaseError;
54    fn from_str(s: &str) -> Result<Self, Self::Err> {
55        if s.is_empty() {
56            Ok(Self::Stable)
57        } else {
58            let mut parts = s.rsplitn(2, '.');
59            let num_part = parts
60                .next()
61                .expect("rsplitn returns at least one empty string");
62            let identifier = parts
63                .next()
64                .ok_or(ParsePrereleaseError::DotSeparatorMissing)?;
65            assert_eq!(
66                parts.next(),
67                None,
68                "Safety: rsplitn(2, ...) produces no more than two parts"
69            );
70            let num =
71                u16::from_str(num_part).map_err(|_| ParsePrereleaseError::NumericPartNotAU16)?;
72            match identifier {
73                Self::IDENTIFIER_RELEASE_CANDIDATE => Ok(Self::ReleaseCandidate(num)),
74                Self::IDENTIFIER_BETA => Ok(Self::Beta(num)),
75                Self::IDENTIFIER_ALPHA => Ok(Self::Alpha(num)),
76                _ => Err(ParsePrereleaseError::UnknownIdentifier),
77            }
78        }
79    }
80}
81
82#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
83#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
84#[serde(transparent)]
85struct PackedMinor(#[serde(with = "serde_varint")] u16);
86
87#[derive(Clone, Debug, PartialEq)]
88pub enum PackedMinorPackError {
89    MinorTooLarge,
90    InvalidPatchForPrerelease,
91}
92
93#[derive(Clone, Debug, PartialEq)]
94pub enum PackedMinorUnpackError {
95    ReservedBitsSet,
96}
97
98impl PackedMinor {
99    const PRERELEASE_BITS_OFFSET: u32 = 14;
100    const PRERELEASE_MASK_BITS: u32 = 2;
101    const PRERELEASE_FIRST_UNMASKED_BIT: u16 = 1 << Self::PRERELEASE_MASK_BITS;
102    const PRERELEASE_MASK: u16 = Self::PRERELEASE_FIRST_UNMASKED_BIT - 1;
103    const PRERELEASE_MINOR_MAX: u16 = (1 << Self::PRERELEASE_BITS_OFFSET) - 1;
104
105    fn try_pack(
106        minor: u16,
107        patch: u16,
108        prerelease: &Prerelease,
109    ) -> Result<(Self, u16), PackedMinorPackError> {
110        if minor > Self::PRERELEASE_MINOR_MAX {
111            return Err(PackedMinorPackError::MinorTooLarge);
112        }
113        if !prerelease.patch_is_valid(patch) {
114            return Err(PackedMinorPackError::InvalidPatchForPrerelease);
115        }
116        let (prerelease_encode_tag, patch) = match *prerelease {
117            Prerelease::Stable => (Prerelease::ENCODE_TAG_STABLE, patch),
118            Prerelease::ReleaseCandidate(rc) => (Prerelease::ENCODE_TAG_RELEASE_CANDIDATE, rc),
119            Prerelease::Beta(beta) => (Prerelease::ENCODE_TAG_BETA, beta),
120            Prerelease::Alpha(alpha) => (Prerelease::ENCODE_TAG_ALPHA, alpha),
121        };
122        let packed_minor = minor | prerelease_encode_tag << Self::PRERELEASE_BITS_OFFSET;
123        Ok((Self(packed_minor), patch))
124    }
125
126    fn try_unpack(self, patch: u16) -> Result<(u16, u16, Prerelease), PackedMinorUnpackError> {
127        let Self(packed_minor) = self;
128        let shifted_prerelease_bits = packed_minor >> Self::PRERELEASE_BITS_OFFSET;
129
130        let reserved_bits = shifted_prerelease_bits & !Self::PRERELEASE_MASK;
131        if reserved_bits != 0 {
132            return Err(PackedMinorUnpackError::ReservedBitsSet);
133        }
134
135        let prerelease_variant = shifted_prerelease_bits & Self::PRERELEASE_MASK;
136        let minor = packed_minor & !(Self::PRERELEASE_MASK << Self::PRERELEASE_BITS_OFFSET);
137
138        let (patch, prerelease) = match prerelease_variant {
139            Prerelease::ENCODE_TAG_STABLE => (patch, Prerelease::Stable),
140            Prerelease::ENCODE_TAG_RELEASE_CANDIDATE => (0, Prerelease::ReleaseCandidate(patch)),
141            Prerelease::ENCODE_TAG_BETA => (0, Prerelease::Beta(patch)),
142            Prerelease::ENCODE_TAG_ALPHA => (0, Prerelease::Alpha(patch)),
143            Self::PRERELEASE_FIRST_UNMASKED_BIT..=u16::MAX => unreachable!(),
144        };
145        Ok((minor, patch, prerelease))
146    }
147}
148
149#[derive(Debug, Clone, PartialEq, Eq)]
150pub struct Version {
151    major: u16,
152    minor: u16,
153    patch: u16,
154    commit: u32,
155    feature_set: u32,
156    client: ClientId,
157    prerelease: Prerelease,
158}
159
160impl Version {
161    fn new_from_parts(
162        major: u16,
163        minor: u16,
164        patch: u16,
165        commit: u32,
166        feature_set: u32,
167        client: ClientId,
168        prerelease: Prerelease,
169    ) -> Self {
170        assert!(prerelease.patch_is_valid(patch));
171        Self {
172            major,
173            minor,
174            patch,
175            commit,
176            feature_set,
177            client,
178            prerelease,
179        }
180    }
181
182    pub fn this_build() -> Self {
183        Self::new_from_parts(
184            env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
185            env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
186            env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
187            compute_commit(option_env!("CI_COMMIT"))
188                .or(compute_commit(option_env!("AGAVE_GIT_COMMIT_HASH")))
189                .unwrap_or_else(|| thread_rng().gen::<u32>()),
190            u32::from_le_bytes(agave_feature_set::ID.as_ref()[..4].try_into().unwrap()),
191            ClientId::Agave,
192            Prerelease::from_str(env!("CARGO_PKG_VERSION_PRE")).unwrap(),
193        )
194    }
195
196    pub fn major(&self) -> u16 {
197        self.major
198    }
199
200    pub fn minor(&self) -> u16 {
201        self.minor
202    }
203
204    pub fn patch(&self) -> u16 {
205        self.patch
206    }
207
208    pub fn commit(&self) -> u32 {
209        self.commit
210    }
211
212    pub fn feature_set(&self) -> u32 {
213        self.feature_set
214    }
215
216    pub fn client(&self) -> &ClientId {
217        &self.client
218    }
219
220    pub fn prerelease(&self) -> &Prerelease {
221        &self.prerelease
222    }
223
224    pub fn as_semver_string(&self) -> String {
225        format!("{self}")
226    }
227
228    pub fn as_detailed_string(&self) -> String {
229        format!(
230            "{} (src:{:08x}; feat:{:08x}, client:{:?})",
231            self, self.commit, self.feature_set, self.client,
232        )
233    }
234
235    pub fn as_semver_version(&self) -> semver::Version {
236        let major = u64::from(self.major);
237        let minor = u64::from(self.minor);
238        let patch = u64::from(self.patch);
239        let pre = semver::Prerelease::new(&self.prerelease.to_string())
240            .expect("solana_version::Prerelease is semver::Prerelease-compatible");
241        let build = semver::BuildMetadata::EMPTY;
242        semver::Version {
243            major,
244            minor,
245            patch,
246            pre,
247            build,
248        }
249    }
250}
251
252impl From<v3::Version> for Version {
253    fn from(other: v3::Version) -> Self {
254        let client = other.client();
255        let v3::Version {
256            major,
257            minor,
258            patch,
259            commit,
260            feature_set,
261            ..
262        } = other;
263        let (minor, patch, prerelease) =
264            PackedMinor(minor)
265                .try_unpack(patch)
266                .unwrap_or((minor, patch, Prerelease::Stable));
267        Version::new_from_parts(major, minor, patch, commit, feature_set, client, prerelease)
268    }
269}
270
271impl Default for Version {
272    fn default() -> Self {
273        Self::this_build()
274    }
275}
276
277impl fmt::Display for Version {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        let sep = if self.prerelease == Prerelease::Stable {
280            ""
281        } else {
282            "-"
283        };
284        write!(
285            f,
286            "{}.{}.{}{}{}",
287            self.major, self.minor, self.patch, sep, self.prerelease
288        )
289    }
290}
291
292impl Sanitize for Version {}
293#[cfg_attr(feature = "frozen-abi", derive(AbiExample))]
294#[derive(Deserialize, Serialize)]
295struct SerializedVersion {
296    #[serde(with = "serde_varint")]
297    major: u16,
298    #[serde(rename = "minor")]
299    packed_minor: PackedMinor,
300    #[serde(with = "serde_varint")]
301    patch: u16,
302    commit: u32,
303    feature_set: u32,
304    #[serde(with = "serde_varint")]
305    client: u16,
306}
307
308impl Serialize for Version {
309    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
310    where
311        S: Serializer,
312    {
313        let &Version {
314            major,
315            minor,
316            patch,
317            commit,
318            feature_set,
319            ref client,
320            ref prerelease,
321        } = self;
322
323        let (packed_minor, patch) = PackedMinor::try_pack(minor, patch, prerelease)
324            .map_err(|err| S::Error::custom(format!("{err:?}")))?;
325        let client = u16::try_from(*client).map_err(S::Error::custom)?;
326
327        let serialized_version = SerializedVersion {
328            major,
329            packed_minor,
330            patch,
331            commit,
332            feature_set,
333            client,
334        };
335
336        SerializedVersion::serialize(&serialized_version, serializer)
337    }
338}
339
340impl<'de> Deserialize<'de> for Version {
341    fn deserialize<D>(deserializer: D) -> Result<Version, D::Error>
342    where
343        D: Deserializer<'de>,
344    {
345        let SerializedVersion {
346            major,
347            packed_minor,
348            patch,
349            commit,
350            feature_set,
351            client,
352        } = SerializedVersion::deserialize(deserializer)?;
353
354        let (minor, patch, prerelease) = packed_minor
355            .try_unpack(patch)
356            .map_err(|err| D::Error::custom(format!("{err:?}")))?;
357        let client = ClientId::from(client);
358
359        Ok(Version {
360            major,
361            minor,
362            patch,
363            commit,
364            feature_set,
365            client,
366            prerelease,
367        })
368    }
369}
370
371#[cfg(test)]
372mod tests {
373    use super::*;
374
375    #[test]
376    fn test_prerelease_patch_is_valid() {
377        assert!(Prerelease::Alpha(0).patch_is_valid(0));
378        assert!(Prerelease::Beta(0).patch_is_valid(0));
379        assert!(Prerelease::ReleaseCandidate(0).patch_is_valid(0));
380        assert!(Prerelease::Stable.patch_is_valid(0));
381        assert!(Prerelease::Stable.patch_is_valid(1));
382        assert!(Prerelease::Stable.patch_is_valid(u16::MAX));
383
384        assert!(!Prerelease::ReleaseCandidate(0).patch_is_valid(1));
385        assert!(!Prerelease::Beta(0).patch_is_valid(1));
386        assert!(!Prerelease::Alpha(0).patch_is_valid(1));
387    }
388
389    #[test]
390    fn test_prerelease_display() {
391        assert!(format!("{}", Prerelease::Stable).is_empty());
392        assert_eq!(format!("{}", Prerelease::ReleaseCandidate(0)), "rc.0",);
393        assert_eq!(format!("{}", Prerelease::Beta(1)), "beta.1",);
394        assert_eq!(format!("{}", Prerelease::Alpha(2)), "alpha.2",);
395        assert_eq!(format!("{}", Prerelease::Alpha(u16::MAX)), "alpha.65535",);
396    }
397
398    #[test]
399    fn test_prerelease_from_str() {
400        assert_eq!(Prerelease::from_str(""), Ok(Prerelease::Stable));
401        assert_eq!(
402            Prerelease::from_str("rc.0"),
403            Ok(Prerelease::ReleaseCandidate(0))
404        );
405        assert_eq!(Prerelease::from_str("beta.1"), Ok(Prerelease::Beta(1)));
406        assert_eq!(Prerelease::from_str("alpha.2"), Ok(Prerelease::Alpha(2)));
407        assert_eq!(
408            Prerelease::from_str("alpha.65535"),
409            Ok(Prerelease::Alpha(u16::MAX))
410        );
411        assert_eq!(
412            Prerelease::from_str("rc0"),
413            Err(ParsePrereleaseError::DotSeparatorMissing),
414        );
415        assert_eq!(
416            Prerelease::from_str("rc.a"),
417            Err(ParsePrereleaseError::NumericPartNotAU16),
418        );
419        assert_eq!(
420            Prerelease::from_str("rc.-1"),
421            Err(ParsePrereleaseError::NumericPartNotAU16),
422        );
423        assert_eq!(
424            Prerelease::from_str("rc.65536"),
425            Err(ParsePrereleaseError::NumericPartNotAU16),
426        );
427        assert_eq!(
428            Prerelease::from_str("unknown.0"),
429            Err(ParsePrereleaseError::UnknownIdentifier),
430        );
431    }
432
433    #[test]
434    fn test_prerelease_roundtrips() {
435        let test_cases = [
436            ("", Prerelease::Stable),
437            ("rc.0", Prerelease::ReleaseCandidate(0)),
438            ("beta.1", Prerelease::Beta(1)),
439            ("alpha.2", Prerelease::Alpha(2)),
440        ];
441        for (test_str, test_pr) in test_cases.into_iter() {
442            let gen_pr = Prerelease::from_str(test_str).unwrap();
443            let gen_str = test_pr.to_string();
444            let regen_pr = Prerelease::from_str(&gen_str).unwrap();
445            let regen_str = gen_pr.to_string();
446
447            assert_eq!(test_str, gen_str);
448            assert_eq!(test_str, regen_str);
449            assert_eq!(test_pr, gen_pr);
450            assert_eq!(test_pr, regen_pr);
451        }
452    }
453
454    #[test]
455    fn test_prerelease_compatible_with_semver_prerelease() {
456        let test_cases = [
457            ("", Prerelease::Stable),
458            ("rc.0", Prerelease::ReleaseCandidate(0)),
459            ("beta.1", Prerelease::Beta(1)),
460            ("alpha.2", Prerelease::Alpha(2)),
461        ];
462        for (test_str, test_pr) in test_cases.into_iter() {
463            let svpr = semver::Prerelease::new(test_pr.to_string().as_str()).unwrap();
464            assert_eq!(svpr.as_str(), test_str);
465        }
466    }
467
468    #[test]
469    fn test_packed_minor_try_pack() {
470        assert_eq!(
471            PackedMinor::try_pack(0, 0, &Prerelease::Stable),
472            Ok((PackedMinor(0), 0)),
473        );
474        assert_eq!(
475            PackedMinor::try_pack(
476                PackedMinor::PRERELEASE_MINOR_MAX,
477                u16::MAX,
478                &Prerelease::Stable,
479            ),
480            Ok((PackedMinor(0x3FFF), u16::MAX)),
481        );
482        assert_eq!(
483            PackedMinor::try_pack(0, 0, &Prerelease::ReleaseCandidate(0)),
484            Ok((PackedMinor(0x4000), 0)),
485        );
486        assert_eq!(
487            PackedMinor::try_pack(
488                PackedMinor::PRERELEASE_MINOR_MAX,
489                0,
490                &Prerelease::ReleaseCandidate(u16::MAX),
491            ),
492            Ok((PackedMinor(0x7FFF), u16::MAX)),
493        );
494        assert_eq!(
495            PackedMinor::try_pack(0, 0, &Prerelease::Beta(0)),
496            Ok((PackedMinor(0x8000), 0)),
497        );
498        assert_eq!(
499            PackedMinor::try_pack(
500                PackedMinor::PRERELEASE_MINOR_MAX,
501                0,
502                &Prerelease::Beta(u16::MAX),
503            ),
504            Ok((PackedMinor(0xBFFF), u16::MAX)),
505        );
506        assert_eq!(
507            PackedMinor::try_pack(0, 0, &Prerelease::Alpha(0)),
508            Ok((PackedMinor(0xC000), 0)),
509        );
510        assert_eq!(
511            PackedMinor::try_pack(
512                PackedMinor::PRERELEASE_MINOR_MAX,
513                0,
514                &Prerelease::Alpha(u16::MAX),
515            ),
516            Ok((PackedMinor(0xFFFF), u16::MAX)),
517        );
518
519        assert_eq!(
520            PackedMinor::try_pack(
521                PackedMinor::PRERELEASE_MINOR_MAX + 1,
522                0,
523                &Prerelease::Stable
524            ),
525            Err(PackedMinorPackError::MinorTooLarge),
526        );
527        assert_eq!(
528            PackedMinor::try_pack(0, 1, &Prerelease::Beta(0),),
529            Err(PackedMinorPackError::InvalidPatchForPrerelease),
530        );
531    }
532
533    #[test]
534    fn test_packed_minor_try_unpack() {
535        assert_eq!(
536            PackedMinor(0x0000).try_unpack(0),
537            Ok((0, 0, Prerelease::Stable))
538        );
539        assert_eq!(
540            PackedMinor(0x3FFF).try_unpack(u16::MAX),
541            Ok((
542                PackedMinor::PRERELEASE_MINOR_MAX,
543                u16::MAX,
544                Prerelease::Stable
545            )),
546        );
547        assert_eq!(
548            PackedMinor(0x4000).try_unpack(0),
549            Ok((0, 0, Prerelease::ReleaseCandidate(0)))
550        );
551        assert_eq!(
552            PackedMinor(0x7FFF).try_unpack(u16::MAX),
553            Ok((
554                PackedMinor::PRERELEASE_MINOR_MAX,
555                0,
556                Prerelease::ReleaseCandidate(u16::MAX)
557            )),
558        );
559        assert_eq!(
560            PackedMinor(0x8000).try_unpack(0),
561            Ok((0, 0, Prerelease::Beta(0)))
562        );
563        assert_eq!(
564            PackedMinor(0xBFFF).try_unpack(u16::MAX),
565            Ok((
566                PackedMinor::PRERELEASE_MINOR_MAX,
567                0,
568                Prerelease::Beta(u16::MAX)
569            )),
570        );
571        assert_eq!(
572            PackedMinor(0xC000).try_unpack(0),
573            Ok((0, 0, Prerelease::Alpha(0)))
574        );
575        assert_eq!(
576            PackedMinor(0xFFFF).try_unpack(u16::MAX),
577            Ok((
578                PackedMinor::PRERELEASE_MINOR_MAX,
579                0,
580                Prerelease::Alpha(u16::MAX)
581            )),
582        );
583
584        // minor bits above the the prerelease mask are reserved. test that
585        // the aren't set if they exist
586        if PackedMinor::PRERELEASE_BITS_OFFSET + PackedMinor::PRERELEASE_MASK_BITS < u16::BITS {
587            unimplemented!("current configuration leaves no bits reserved");
588        }
589    }
590
591    fn prerelease_stable(_v: u16, patch: u16) -> (Prerelease, u16) {
592        (Prerelease::Stable, patch)
593    }
594
595    fn prerelease_release_candidate(v: u16, _patch: u16) -> (Prerelease, u16) {
596        (Prerelease::ReleaseCandidate(v), 0)
597    }
598
599    fn prerelease_beta(v: u16, _patch: u16) -> (Prerelease, u16) {
600        (Prerelease::Beta(v), 0)
601    }
602
603    fn prerelease_alpha(v: u16, _patch: u16) -> (Prerelease, u16) {
604        (Prerelease::Alpha(v), 0)
605    }
606
607    #[test]
608    fn test_packed_minor_roundtrips() {
609        let test_cases = [
610            prerelease_stable,
611            prerelease_release_candidate,
612            prerelease_beta,
613            prerelease_alpha,
614        ];
615        for prerelease_ctor in &test_cases {
616            for minor in [0, 1, PackedMinor::PRERELEASE_MINOR_MAX] {
617                for patch in [0, 1, u16::MAX] {
618                    for prerelease in [0, 1, u16::MAX] {
619                        let (prerelease, patch) = prerelease_ctor(prerelease, patch);
620                        let (packed_minor, packed_patch) =
621                            PackedMinor::try_pack(minor, patch, &prerelease).unwrap();
622                        let unpacked = packed_minor.try_unpack(packed_patch).unwrap();
623                        assert_eq!((minor, patch, prerelease), unpacked);
624                    }
625                }
626            }
627        }
628    }
629
630    #[test]
631    fn test_v3_and_v4_same_size() {
632        // smallest
633        let v3_version = v3::Version {
634            major: 0,
635            minor: 0,
636            patch: 0,
637            commit: 0,
638            feature_set: 0,
639            client: 0,
640        };
641        let v4_version =
642            Version::new_from_parts(0, 0, 0, 0, 0, ClientId::Agave, Prerelease::Stable);
643        assert_eq!(
644            bincode::serialized_size(&v3_version).unwrap(),
645            bincode::serialized_size(&v4_version).unwrap(),
646        );
647
648        // largest
649        let v3_version = v3::Version {
650            major: u16::MAX,
651            minor: u16::MAX,
652            patch: u16::MAX,
653            commit: u32::MAX,
654            feature_set: u32::MAX,
655            client: u16::MAX,
656        };
657        let v4_version = Version::new_from_parts(
658            u16::MAX,
659            PackedMinor::PRERELEASE_MINOR_MAX,
660            0,
661            u32::MAX,
662            u32::MAX,
663            ClientId::Unknown(u16::MAX),
664            Prerelease::Alpha(u16::MAX),
665        );
666        assert_eq!(
667            bincode::serialized_size(&v3_version).unwrap(),
668            bincode::serialized_size(&v4_version).unwrap(),
669        );
670    }
671
672    #[test]
673    fn test_v4_from_v3() {
674        let major = 3;
675        let minor = 2;
676        let patch = 1;
677        let alpha = 1;
678        let beta = 2;
679        let rc = 3;
680        let commit = 0;
681        let feature_set = 0;
682        let client = ClientId::Agave;
683
684        let v3_version = v3::Version {
685            major,
686            minor: minor | Prerelease::ENCODE_TAG_ALPHA << PackedMinor::PRERELEASE_BITS_OFFSET,
687            patch: alpha,
688            commit,
689            feature_set,
690            client: u16::try_from(client).expect("agave client id"),
691        };
692        assert_eq!(
693            Version::from(v3_version),
694            Version::new_from_parts(
695                major,
696                minor,
697                0,
698                commit,
699                feature_set,
700                client,
701                Prerelease::Alpha(alpha),
702            ),
703        );
704
705        let v3_version = v3::Version {
706            major,
707            minor: minor | Prerelease::ENCODE_TAG_BETA << PackedMinor::PRERELEASE_BITS_OFFSET,
708            patch: beta,
709            commit,
710            feature_set,
711            client: u16::try_from(client).expect("agave client id"),
712        };
713        assert_eq!(
714            Version::from(v3_version),
715            Version::new_from_parts(
716                major,
717                minor,
718                0,
719                commit,
720                feature_set,
721                client,
722                Prerelease::Beta(beta),
723            ),
724        );
725
726        let v3_version = v3::Version {
727            major,
728            minor: minor
729                | Prerelease::ENCODE_TAG_RELEASE_CANDIDATE << PackedMinor::PRERELEASE_BITS_OFFSET,
730            patch: rc,
731            commit,
732            feature_set,
733            client: u16::try_from(client).expect("agave client id"),
734        };
735        assert_eq!(
736            Version::from(v3_version),
737            Version::new_from_parts(
738                major,
739                minor,
740                0,
741                commit,
742                feature_set,
743                client,
744                Prerelease::ReleaseCandidate(rc),
745            ),
746        );
747
748        let v3_version = v3::Version {
749            major,
750            minor,
751            patch,
752            commit,
753            feature_set,
754            client: u16::try_from(client).expect("agave client id"),
755        };
756        assert_eq!(
757            Version::from(v3_version),
758            Version::new_from_parts(
759                major,
760                minor,
761                patch,
762                commit,
763                feature_set,
764                client,
765                Prerelease::Stable,
766            ),
767        );
768    }
769
770    #[test]
771    fn test_serde() {
772        let version =
773            Version::new_from_parts(0, 0, 0, 0, 0, ClientId::SolanaLabs, Prerelease::Stable);
774
775        let bytes = bincode::serialize(&version).unwrap();
776        assert_eq!(bytes, [0u8; 12]);
777
778        let de_version: Version = bincode::deserialize(&bytes).unwrap();
779        assert_eq!(version, de_version);
780
781        let version = Version::new_from_parts(
782            u16::MAX,
783            PackedMinor::PRERELEASE_MINOR_MAX,
784            0,
785            u32::MAX,
786            u32::MAX,
787            ClientId::Unknown(u16::MAX),
788            Prerelease::Alpha(u16::MAX),
789        );
790
791        let bytes = bincode::serialize(&version).unwrap();
792        assert_eq!(
793            bytes,
794            [
795                0xff, 0xff, 0x03, 0xff, 0xff, 0x03, 0xff, 0xff, 0x03, 0xff, 0xff, 0xff, 0xff, 0xff,
796                0xff, 0xff, 0xff, 0xff, 0xff, 0x03,
797            ]
798        );
799
800        let de_version: Version = bincode::deserialize(&bytes).unwrap();
801        assert_eq!(version, de_version);
802    }
803
804    #[test]
805    fn test_version_as_detailed_string() {
806        let version = Version::new_from_parts(0, 0, 0, 0, 0, ClientId::Agave, Prerelease::Stable);
807        assert_eq!(
808            version.as_detailed_string(),
809            "0.0.0 (src:00000000; feat:00000000, client:Agave)",
810        );
811
812        let version = Version::new_from_parts(
813            0,
814            0,
815            0,
816            0,
817            0,
818            ClientId::Agave,
819            Prerelease::ReleaseCandidate(0),
820        );
821        assert_eq!(
822            version.as_detailed_string(),
823            "0.0.0-rc.0 (src:00000000; feat:00000000, client:Agave)",
824        );
825    }
826}