From c590bbb8d7eab9aa13e4642ea1b61e49a768e9a0 Mon Sep 17 00:00:00 2001
From: "hinto.janai" <hinto.janai@protonmail.com>
Date: Thu, 10 Oct 2024 20:39:44 -0400
Subject: [PATCH] apply diffs

---
 Cargo.lock             |  23 +++++++
 Cargo.toml             |   1 +
 types/Cargo.toml       |   1 +
 types/src/hard_fork.rs | 140 ++++++++++++++++++++++++++++++++---------
 4 files changed, 134 insertions(+), 31 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 5fea18d..1b05efa 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -933,6 +933,7 @@ dependencies = [
  "proptest-derive",
  "serde",
  "serde_json",
+ "strum",
  "thiserror",
 ]
 
@@ -2673,6 +2674,28 @@ dependencies = [
  "spin",
 ]
 
+[[package]]
+name = "strum"
+version = "0.26.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
+dependencies = [
+ "strum_macros",
+]
+
+[[package]]
+name = "strum_macros"
+version = "0.26.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
+dependencies = [
+ "heck",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn 2.0.77",
+]
+
 [[package]]
 name = "subtle"
 version = "2.6.1"
diff --git a/Cargo.toml b/Cargo.toml
index fa348cc..6c322fb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -78,6 +78,7 @@ rayon                 = { version = "1.10.0", default-features = false }
 serde_bytes           = { version = "0.11.15", default-features = false }
 serde_json            = { version = "1.0.128", default-features = false }
 serde                 = { version = "1.0.210", default-features = false }
+strum                 = { version = "0.26.3", default-features = false }
 thiserror             = { version = "1.0.63", default-features = false }
 thread_local          = { version = "1.1.8", default-features = false }
 tokio-util            = { version = "0.7.12", default-features = false }
diff --git a/types/Cargo.toml b/types/Cargo.toml
index 1c76290..8ac6b25 100644
--- a/types/Cargo.toml
+++ b/types/Cargo.toml
@@ -27,6 +27,7 @@ curve25519-dalek = { workspace = true }
 monero-serai     = { workspace = true }
 hex              = { workspace = true, features = ["serde", "alloc"], optional = true }
 serde            = { workspace = true, features = ["derive"], optional = true }
+strum            = { workspace = true, features = ["derive"] }
 thiserror        = { workspace = true }
 
 proptest = { workspace = true, optional = true }
diff --git a/types/src/hard_fork.rs b/types/src/hard_fork.rs
index 8b2cd78..ad12953 100644
--- a/types/src/hard_fork.rs
+++ b/types/src/hard_fork.rs
@@ -1,6 +1,10 @@
 //! The [`HardFork`] type.
 use std::time::Duration;
 
+use strum::{
+    AsRefStr, Display, EnumCount, EnumIs, EnumString, FromRepr, IntoStaticStr, VariantArray,
+};
+
 use monero_serai::block::BlockHeader;
 
 /// Target block time for hf 1.
@@ -27,7 +31,25 @@ pub enum HardForkError {
 }
 
 /// An identifier for every hard-fork Monero has had.
-#[derive(Default, Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Hash)]
+#[derive(
+    Default,
+    Debug,
+    PartialEq,
+    Eq,
+    PartialOrd,
+    Ord,
+    Copy,
+    Clone,
+    Hash,
+    EnumCount,
+    Display,
+    AsRefStr,
+    EnumIs,
+    EnumString,
+    FromRepr,
+    IntoStaticStr,
+    VariantArray,
+)]
 #[cfg_attr(any(feature = "proptest"), derive(proptest_derive::Arbitrary))]
 #[repr(u8)]
 pub enum HardFork {
@@ -47,58 +69,87 @@ pub enum HardFork {
     V13,
     V14,
     V15,
-    // remember to update from_vote!
     V16,
 }
 
 impl HardFork {
+    /// The current [`HardFork`].
+    ///
+    /// ```rust
+    /// # use cuprate_types::HardFork;
+    /// assert_eq!(HardFork::CURRENT, HardFork::V16);
+    /// ```
+    pub const CURRENT: Self = Self::VARIANTS[Self::COUNT - 1];
+
     /// Returns the hard-fork for a blocks [`BlockHeader::hardfork_version`] field.
     ///
     /// ref: <https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote>
     ///
     /// # Errors
-    ///
     /// Will return [`Err`] if the version is not a valid [`HardFork`].
+    ///
+    /// ```rust
+    /// # use cuprate_types::{HardFork, HardForkError};
+    /// # use strum::VariantArray;
+    /// assert_eq!(HardFork::from_version(0), Err(HardForkError::HardForkUnknown));
+    /// assert_eq!(HardFork::from_version(17), Err(HardForkError::HardForkUnknown));
+    ///
+    /// for (version, hf) in HardFork::VARIANTS.iter().enumerate() {
+    ///     // +1 because enumerate starts at 0, hf starts at 1.
+    ///     assert_eq!(*hf, HardFork::from_version(version as u8 + 1).unwrap());
+    /// }
+    /// ```
     #[inline]
     pub const fn from_version(version: u8) -> Result<Self, HardForkError> {
-        Ok(match version {
-            1 => Self::V1,
-            2 => Self::V2,
-            3 => Self::V3,
-            4 => Self::V4,
-            5 => Self::V5,
-            6 => Self::V6,
-            7 => Self::V7,
-            8 => Self::V8,
-            9 => Self::V9,
-            10 => Self::V10,
-            11 => Self::V11,
-            12 => Self::V12,
-            13 => Self::V13,
-            14 => Self::V14,
-            15 => Self::V15,
-            16 => Self::V16,
-            _ => return Err(HardForkError::HardForkUnknown),
-        })
+        #[expect(
+            clippy::cast_possible_truncation,
+            reason = "we check that `HardFork::COUNT` fits into a `u8`."
+        )]
+        const COUNT_AS_U8: u8 = {
+            const COUNT: usize = HardFork::COUNT;
+            assert!(COUNT <= u8::MAX as usize);
+            COUNT as u8
+        };
+
+        if version == 0 || version > COUNT_AS_U8 {
+            Err(HardForkError::HardForkUnknown)
+        } else {
+            // INVARIANT: we've proved above that `version` is in a valid range.
+            Ok(Self::VARIANTS[(version - 1) as usize])
+        }
     }
 
     /// Returns the hard-fork for a blocks [`BlockHeader::hardfork_signal`] (vote) field.
     ///
     /// <https://monero-book.cuprate.org/consensus_rules/hardforks.html#blocks-version-and-vote>
+    ///
+    /// ```rust
+    /// # use cuprate_types::{HardFork, HardForkError};
+    /// # use strum::VariantArray;
+    /// // 0 is interpreted as 1.
+    /// assert_eq!(HardFork::from_vote(0), HardFork::V1);
+    /// // Unknown defaults to `CURRENT`.
+    /// assert_eq!(HardFork::from_vote(17), HardFork::V16);
+    ///
+    /// for (vote, hf) in HardFork::VARIANTS.iter().enumerate() {
+    ///     // +1 because enumerate starts at 0, hf starts at 1.
+    ///     assert_eq!(*hf, HardFork::from_vote(vote as u8 + 1));
+    /// }
+    /// ```
     #[inline]
     pub fn from_vote(vote: u8) -> Self {
         if vote == 0 {
             // A vote of 0 is interpreted as 1 as that's what Monero used to default to.
-            return Self::V1;
+            Self::V1
+        } else {
+            // This must default to the latest hard-fork!
+            Self::from_version(vote).unwrap_or(Self::CURRENT)
         }
-        // This must default to the latest hard-fork!
-        Self::from_version(vote).unwrap_or(Self::V16)
     }
 
     /// Returns the [`HardFork`] version and vote from this block header.
     ///
     /// # Errors
-    ///
     /// Will return [`Err`] if the [`BlockHeader::hardfork_version`] is not a valid [`HardFork`].
     #[inline]
     pub fn from_block_header(header: &BlockHeader) -> Result<(Self, Self), HardForkError> {
@@ -109,22 +160,49 @@ impl HardFork {
     }
 
     /// Returns the raw hard-fork value, as it would appear in [`BlockHeader::hardfork_version`].
-    pub const fn as_u8(&self) -> u8 {
-        *self as u8
+    ///
+    /// ```rust
+    /// # use cuprate_types::{HardFork, HardForkError};
+    /// # use strum::VariantArray;
+    /// for (i, hf) in HardFork::VARIANTS.iter().enumerate() {
+    ///     // +1 because enumerate starts at 0, hf starts at 1.
+    ///     assert_eq!(hf.as_u8(), i as u8 + 1);
+    /// }
+    /// ```
+    pub const fn as_u8(self) -> u8 {
+        self as u8
     }
 
     /// Returns the next hard-fork.
-    pub fn next_fork(&self) -> Option<Self> {
-        Self::from_version(*self as u8 + 1).ok()
+    pub fn next_fork(self) -> Option<Self> {
+        Self::from_version(self as u8 + 1).ok()
     }
 
     /// Returns the target block time for this hardfork.
     ///
     /// ref: <https://monero-book.cuprate.org/consensus_rules/blocks/difficulty.html#target-seconds>
-    pub const fn block_time(&self) -> Duration {
+    pub const fn block_time(self) -> Duration {
         match self {
             Self::V1 => BLOCK_TIME_V1,
             _ => BLOCK_TIME_V2,
         }
     }
+
+    /// Returns `true` if `self` is [`Self::CURRENT`].
+    ///
+    /// ```rust
+    /// # use cuprate_types::HardFork;
+    /// # use strum::VariantArray;
+    ///
+    /// for hf in HardFork::VARIANTS.iter() {
+    ///     if *hf == HardFork::CURRENT {
+    ///         assert!(hf.is_current());
+    ///     } else {
+    ///         assert!(!hf.is_current());
+    ///     }
+    /// }
+    /// ```
+    pub const fn is_current(self) -> bool {
+        matches!(self, Self::CURRENT)
+    }
 }