rialo-types 0.18.1

Rialo Types
Documentation
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

//! `DkgEpochId` — the DKG (distributed key generation) epoch for the
//! crypto-service cluster.
//!
//! A `#[repr(transparent)]` newtype over `u64`, serialized identically to the
//! underlying `u64` (serde transparent; borsh of a single-field struct).

use std::{fmt, mem::size_of};

use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};

/// The DKG epoch for the crypto-service cluster.
#[derive(
    BorshSerialize,
    BorshDeserialize,
    Clone,
    Copy,
    Debug,
    Default,
    Deserialize,
    Eq,
    Hash,
    Ord,
    PartialEq,
    PartialOrd,
    Serialize,
)]
#[serde(transparent)]
#[repr(transparent)]
pub struct DkgEpochId(u64);

impl DkgEpochId {
    /// The maximum size used when serializing a [`DkgEpochId`] using [`borsh`].
    pub const BORSH_SERIALIZED_SIZE: usize = size_of::<u64>();

    /// Wraps a raw epoch number.
    pub const fn new(epoch: u64) -> Self {
        Self(epoch)
    }

    /// Returns the epoch as a `u64`.
    pub const fn as_u64(self) -> u64 {
        self.0
    }
}

impl fmt::Display for DkgEpochId {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn new_as_u64_roundtrip() {
        assert_eq!(DkgEpochId::new(42).as_u64(), 42);
        assert_eq!(DkgEpochId::default(), DkgEpochId::new(0));
    }

    // DKG epochs are persisted on-chain (crypto-service committee, DKG ceremony
    // and public-key accounts) with borsh, and off-chain payloads with serde
    // (bcs/bincode). Pin that `DkgEpochId` is byte-identical to the underlying
    // `u64` in every encoding, so splitting it out of the shared REX epoch type
    // leaves existing account and wire bytes unchanged.
    #[test]
    fn borsh_matches_u64() {
        let raw: u64 = 0x00ad_beef_dead_beef;
        let e = DkgEpochId::new(raw);
        assert_eq!(borsh::to_vec(&e).unwrap(), raw.to_le_bytes());
        assert_eq!(
            borsh::from_slice::<DkgEpochId>(&raw.to_le_bytes()).unwrap(),
            e
        );
        assert_eq!(DkgEpochId::BORSH_SERIALIZED_SIZE, raw.to_le_bytes().len());
    }

    #[test]
    fn bcs_matches_u64() {
        let raw: u64 = 0x00ad_beef_dead_beef;
        let e = DkgEpochId::new(raw);
        assert_eq!(bcs::to_bytes(&e).unwrap(), bcs::to_bytes(&raw).unwrap());
        assert_eq!(
            bcs::from_bytes::<DkgEpochId>(&bcs::to_bytes(&raw).unwrap()).unwrap(),
            e
        );
    }

    #[test]
    fn bincode_matches_u64() {
        let raw: u64 = 0x00ad_beef_dead_beef;
        let e = DkgEpochId::new(raw);
        assert_eq!(
            bincode::serialize(&e).unwrap(),
            bincode::serialize(&raw).unwrap()
        );
    }

    #[test]
    fn serde_json_is_transparent() {
        assert_eq!(serde_json::to_string(&DkgEpochId::new(7)).unwrap(), "7");
        assert_eq!(
            serde_json::from_str::<DkgEpochId>("7").unwrap(),
            DkgEpochId::new(7)
        );
    }
}