/ console / program / src / id / mod.rs
mod.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the alphavm 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  mod bytes;
 17  mod parse;
 18  mod serialize;
 19  mod to_address;
 20  mod to_bits;
 21  mod to_fields;
 22  
 23  use crate::Identifier;
 24  use alphavm_console_network::prelude::*;
 25  use alphavm_console_types::{Address, Boolean, Field};
 26  
 27  /// Returns `true` if the string consists of lowercase alphanumeric characters.
 28  fn is_lowercase_alphanumeric(s: &str) -> bool {
 29      s.chars().all(|c| matches!(c, '0'..='9' | 'a'..='z' | '_'))
 30  }
 31  
 32  /// A program ID is of the form `{name}.{network}`.
 33  #[derive(Copy, Clone, PartialEq, Eq, Hash)]
 34  pub struct ProgramID<N: Network> {
 35      /// The program name.
 36      name: Identifier<N>,
 37      /// The network-level domain (NLD).
 38      network: Identifier<N>,
 39  }
 40  
 41  impl<N: Network> From<&ProgramID<N>> for ProgramID<N> {
 42      /// Returns a copy of the program ID.
 43      fn from(program_id: &ProgramID<N>) -> Self {
 44          *program_id
 45      }
 46  }
 47  
 48  impl<N: Network> TryFrom<(Identifier<N>, Identifier<N>)> for ProgramID<N> {
 49      type Error = Error;
 50  
 51      /// Initializes a program ID from a name and network-level domain identifier.
 52      fn try_from((name, network): (Identifier<N>, Identifier<N>)) -> Result<Self> {
 53          // Ensure the name is lowercase alphabets and numbers.
 54          ensure!(is_lowercase_alphanumeric(&name.to_string()), "Program name is invalid: {name}");
 55          // Construct the program ID.
 56          let id = Self { name, network };
 57          // Ensure the program network-level domain is `alpha`.
 58          ensure!(id.is_alpha(), "Program network is invalid: {network}");
 59          // Return the program ID.
 60          Ok(id)
 61      }
 62  }
 63  
 64  impl<N: Network> TryFrom<String> for ProgramID<N> {
 65      type Error = Error;
 66  
 67      /// Initializes a program ID from a name and network-level domain identifier.
 68      fn try_from(program_id: String) -> Result<Self> {
 69          Self::from_str(&program_id)
 70      }
 71  }
 72  
 73  impl<N: Network> TryFrom<&String> for ProgramID<N> {
 74      type Error = Error;
 75  
 76      /// Initializes a program ID from a name and network-level domain identifier.
 77      fn try_from(program_id: &String) -> Result<Self> {
 78          Self::from_str(program_id)
 79      }
 80  }
 81  
 82  impl<N: Network> TryFrom<&str> for ProgramID<N> {
 83      type Error = Error;
 84  
 85      /// Initializes a program ID from a name and network-level domain identifier.
 86      fn try_from(program_id: &str) -> Result<Self> {
 87          // Split the program ID into a name and network-level domain.
 88          let mut split = program_id.split('.');
 89          // Parse the name and network.
 90          if let (Some(name), Some(network), None) = (split.next(), split.next(), split.next()) {
 91              // Ensure the name is lowercase alphabets and numbers.
 92              ensure!(is_lowercase_alphanumeric(name), "Program name is invalid: {name}");
 93              // Construct the program ID.
 94              Self::try_from((Identifier::from_str(name)?, Identifier::from_str(network)?))
 95          } else {
 96              bail!("Invalid program ID '{program_id}'")
 97          }
 98      }
 99  }
100  
101  impl<N: Network> ProgramID<N> {
102      /// Returns the program name.
103      #[inline]
104      pub const fn name(&self) -> &Identifier<N> {
105          &self.name
106      }
107  
108      /// Returns the network-level domain (NLD).
109      #[inline]
110      pub const fn network(&self) -> &Identifier<N> {
111          &self.network
112      }
113  
114      /// Returns `true` if the network-level domain is `alpha`.
115      #[inline]
116      pub fn is_alpha(&self) -> bool {
117          self.network() == &Identifier::from_str("alpha").expect("Failed to parse Alpha domain")
118      }
119  }
120  
121  impl<N: Network> Ord for ProgramID<N> {
122      /// Ordering is determined by the network first, then the program name second.
123      fn cmp(&self, other: &Self) -> Ordering {
124          match self.network == other.network {
125              true => self.name.to_string().cmp(&other.name.to_string()),
126              false => self.network.to_string().cmp(&other.network.to_string()),
127          }
128      }
129  }
130  
131  impl<N: Network> PartialOrd for ProgramID<N> {
132      /// Ordering is determined by the network first, then the program name second.
133      fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
134          Some(self.cmp(other))
135      }
136  }
137  
138  impl<N: Network> Equal<Self> for ProgramID<N> {
139      type Output = Boolean<N>;
140  
141      /// Returns `true` if `self` and `other` are equal.
142      fn is_equal(&self, other: &Self) -> Self::Output {
143          Boolean::new(self == other)
144      }
145  
146      /// Returns `true` if `self` and `other` are **not** equal.
147      fn is_not_equal(&self, other: &Self) -> Self::Output {
148          Boolean::new(self != other)
149      }
150  }
151  
152  #[cfg(test)]
153  mod tests {
154      use super::*;
155      use alphavm_console_network::MainnetV0;
156  
157      type CurrentNetwork = MainnetV0;
158  
159      #[test]
160      fn test_partial_ord() -> Result<()> {
161          let import1 = ProgramID::<CurrentNetwork>::from_str("bar.alpha")?;
162          let import2 = ProgramID::<CurrentNetwork>::from_str("foo.alpha")?;
163  
164          let import3 = ProgramID::<CurrentNetwork>::from_str("bar.alpha")?;
165          let import4 = ProgramID::<CurrentNetwork>::from_str("foo.alpha")?;
166  
167          assert_eq!(import1.partial_cmp(&import1), Some(Ordering::Equal));
168          assert_eq!(import1.partial_cmp(&import2), Some(Ordering::Less));
169          assert_eq!(import1.partial_cmp(&import3), Some(Ordering::Equal));
170          assert_eq!(import1.partial_cmp(&import4), Some(Ordering::Less));
171  
172          assert_eq!(import2.partial_cmp(&import1), Some(Ordering::Greater));
173          assert_eq!(import2.partial_cmp(&import2), Some(Ordering::Equal));
174          assert_eq!(import2.partial_cmp(&import3), Some(Ordering::Greater));
175          assert_eq!(import2.partial_cmp(&import4), Some(Ordering::Equal));
176  
177          assert_eq!(import3.partial_cmp(&import1), Some(Ordering::Equal));
178          assert_eq!(import3.partial_cmp(&import2), Some(Ordering::Less));
179          assert_eq!(import3.partial_cmp(&import3), Some(Ordering::Equal));
180          assert_eq!(import3.partial_cmp(&import4), Some(Ordering::Less));
181  
182          assert_eq!(import4.partial_cmp(&import1), Some(Ordering::Greater));
183          assert_eq!(import4.partial_cmp(&import2), Some(Ordering::Equal));
184          assert_eq!(import4.partial_cmp(&import3), Some(Ordering::Greater));
185          assert_eq!(import4.partial_cmp(&import4), Some(Ordering::Equal));
186  
187          Ok(())
188      }
189  }