snarkvm_ledger_puzzle/solution_id/
string.rs

1// Copyright (c) 2019-2025 Provable Inc.
2// This file is part of the snarkVM 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
16use super::*;
17
18pub static SOLUTION_ID_PREFIX: &str = "solution";
19
20impl<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
39impl<N: Network> Debug for SolutionID<N> {
40    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
41        Display::fmt(self, f)
42    }
43}
44
45impl<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)]
59mod 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.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.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}