/ ledger / puzzle / src / solution_id / string.rs
string.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the deltavm library.
  3  
  4  // Licensed under the Apache License, Version 2.0 (the "License");
  5  // you may not use this file except in compliance with the License.
  6  // You may obtain a copy of the License at:
  7  
  8  // http://www.apache.org/licenses/LICENSE-2.0
  9  
 10  // Unless required by applicable law or agreed to in writing, software
 11  // distributed under the License is distributed on an "AS IS" BASIS,
 12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  // See the License for the specific language governing permissions and
 14  // limitations under the License.
 15  
 16  use super::*;
 17  
 18  pub static SOLUTION_ID_PREFIX: &str = "solution";
 19  
 20  impl<N: Network> FromStr for SolutionID<N> {
 21      type Err = Error;
 22  
 23      /// Reads in the solution ID string.
 24      fn from_str(solution_id: &str) -> Result<Self, Self::Err> {
 25          // Decode the solution ID string from bech32m.
 26          let (hrp, data, variant) = bech32::decode(solution_id)?;
 27          if hrp != SOLUTION_ID_PREFIX {
 28              bail!("Failed to decode solution ID: '{hrp}' is an invalid prefix")
 29          } else if data.is_empty() {
 30              bail!("Failed to decode solution ID: data field is empty")
 31          } else if variant != bech32::Variant::Bech32m {
 32              bail!("Found a solution ID that is not bech32m encoded: {solution_id}");
 33          }
 34          // Decode the solution ID data from u5 to u8, and into the solution ID.
 35          Ok(Self::read_le(&Vec::from_base32(&data)?[..])?)
 36      }
 37  }
 38  
 39  impl<N: Network> Debug for SolutionID<N> {
 40      fn fmt(&self, f: &mut Formatter) -> fmt::Result {
 41          Display::fmt(self, f)
 42      }
 43  }
 44  
 45  impl<N: Network> Display for SolutionID<N> {
 46      /// Writes the solution ID as a bech32m string.
 47      fn fmt(&self, f: &mut Formatter) -> fmt::Result {
 48          // Convert the solution ID to bytes.
 49          let bytes = self.to_bytes_le().map_err(|_| fmt::Error)?;
 50          // Encode the bytes into bech32m.
 51          let string =
 52              bech32::encode(SOLUTION_ID_PREFIX, bytes.to_base32(), bech32::Variant::Bech32m).map_err(|_| fmt::Error)?;
 53          // Output the string.
 54          Display::fmt(&string, f)
 55      }
 56  }
 57  
 58  #[cfg(test)]
 59  mod tests {
 60      use super::*;
 61      use console::network::MainnetV0;
 62  
 63      type CurrentNetwork = MainnetV0;
 64  
 65      const ITERATIONS: u64 = 1_000;
 66  
 67      #[test]
 68      fn test_string() -> Result<()> {
 69          // Ensure type and empty value fails.
 70          assert!(SolutionID::<CurrentNetwork>::from_str(&format!("{SOLUTION_ID_PREFIX}1")).is_err());
 71          assert!(SolutionID::<CurrentNetwork>::from_str("").is_err());
 72  
 73          let mut rng = TestRng::default();
 74  
 75          for _ in 0..ITERATIONS {
 76              // Sample a new solution ID.
 77              let expected = SolutionID::<CurrentNetwork>::from(rng.r#gen::<u64>());
 78  
 79              // Check the string representation.
 80              let candidate = format!("{expected}");
 81              assert_eq!(expected, SolutionID::from_str(&candidate)?);
 82              assert_eq!(SOLUTION_ID_PREFIX, candidate.split('1').next().unwrap());
 83          }
 84          Ok(())
 85      }
 86  
 87      #[test]
 88      fn test_display() -> Result<()> {
 89          let mut rng = TestRng::default();
 90  
 91          for _ in 0..ITERATIONS {
 92              // Sample a new solution ID.
 93              let expected = SolutionID::<CurrentNetwork>::from(rng.r#gen::<u64>());
 94  
 95              let candidate = expected.to_string();
 96              assert_eq!(format!("{expected}"), candidate);
 97              assert_eq!(SOLUTION_ID_PREFIX, candidate.split('1').next().unwrap());
 98  
 99              let candidate_recovered = SolutionID::<CurrentNetwork>::from_str(&candidate.to_string())?;
100              assert_eq!(expected, candidate_recovered);
101          }
102          Ok(())
103      }
104  }