arch_program 0.6.4

A Rust library for building programs that run inside the Arch Virtual Machine. Provides core functionality for creating instructions, managing accounts, handling program errors, and interacting with the Arch runtime environment. Includes utilities for logging, transaction handling, and Bitcoin UTXO management.
Documentation
use bitcode::{Decode, Encode};
use borsh::{BorshDeserialize, BorshSerialize};
#[cfg(feature = "fuzzing")]
use libfuzzer_sys::arbitrary;
use serde::{Deserialize, Serialize};
use std::{
    fmt::{Debug, Display},
    str::FromStr,
};
use thiserror::Error;

#[derive(
    Default,
    PartialEq,
    Eq,
    Clone,
    Copy,
    Hash,
    PartialOrd,
    Ord,
    Serialize,
    Deserialize,
    BorshSerialize,
    BorshDeserialize,
    Encode,
    Decode,
)]
#[cfg_attr(feature = "fuzzing", derive(arbitrary::Arbitrary))]
pub struct Hash([u8; 32]);

impl AsRef<[u8; 32]> for Hash {
    fn as_ref(&self) -> &[u8; 32] {
        &self.0
    }
}

#[derive(Error, Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum HashError {
    #[error("Invalid hash length {0}")]
    InvalidLength(usize),

    #[error("Invalid hex string {0}")]
    InvalidHex(String),
}

impl From<hex::FromHexError> for HashError {
    fn from(err: hex::FromHexError) -> Self {
        HashError::InvalidHex(err.to_string())
    }
}

impl Debug for Hash {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", hex::encode(self.0))
    }
}

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

impl From<[u8; 32]> for Hash {
    fn from(value: [u8; 32]) -> Self {
        Hash(value)
    }
}

impl From<Hash> for String {
    fn from(hash: Hash) -> Self {
        hash.to_string()
    }
}

// Enable direct conversion from our Hash to bitcoin::Txid so callers can use `.into()` without hex.
impl From<Hash> for bitcoin::Txid {
    fn from(value: Hash) -> Self {
        use bitcoin::hashes::Hash as _;

        // Convert the 32-byte array into a sha256d::Hash and then into a Txid.
        // This avoids any hex encode/decode round-trips and cannot fail.
        let inner = bitcoin::hashes::sha256d::Hash::from_byte_array(value.to_array());
        bitcoin::Txid::from_raw_hash(inner)
    }
}

impl From<&Hash> for bitcoin::Txid {
    fn from(value: &Hash) -> Self {
        use bitcoin::hashes::Hash as _;
        let inner = bitcoin::hashes::sha256d::Hash::from_byte_array(value.to_array());
        bitcoin::Txid::from_raw_hash(inner)
    }
}

impl TryFrom<String> for Hash {
    type Error = HashError;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        Hash::from_str(&value)
    }
}

impl TryFrom<&str> for Hash {
    type Error = HashError;

    fn try_from(value: &str) -> Result<Self, Self::Error> {
        Hash::from_str(value)
    }
}

impl TryFrom<&[u8]> for Hash {
    type Error = HashError;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        Ok(Hash(
            value
                .try_into()
                .map_err(|_| HashError::InvalidLength(value.len()))?,
        ))
    }
}

impl FromStr for Hash {
    type Err = HashError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let hash = hex::decode(s)?;
        if hash.len() != 32 {
            return Err(HashError::InvalidLength(hash.len()));
        }
        Ok(Hash(hash.try_into().expect("just checked length")))
    }
}

impl Hash {
    pub fn to_array(&self) -> [u8; 32] {
        self.0
    }

    pub fn copy_bytes(&self, out: &mut [u8; 32]) {
        out.copy_from_slice(&self.0)
    }

    pub fn to_string_short(&self) -> String {
        hex::encode(&self.0[..4])
    }
}

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

    #[test]
    fn test_hash_from_valid_string() {
        let string = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string();
        let _hash = Hash::from_str(&string).unwrap();
    }

    #[test]
    fn test_hash_round_trip() {
        let string = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string();
        let hash = Hash::from_str(&string).unwrap();
        assert_eq!(hash.to_string(), string);
    }

    #[test]
    fn test_hash_from_invalid_len_string() {
        let string = "1234567890abce".to_string();
        let result = Hash::from_str(&string);
        assert!(result.is_err());
        println!("Invalid length error : {:?}", result.err());
    }

    #[test]
    fn test_hash_from_invalid_hex_string() {
        let string = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdex".to_string();
        let result = Hash::from_str(&string);
        assert!(result.is_err());
        println!("Invalid hex error : {:?}", result.err());
    }

    #[test]
    fn test_hash_to_array() {
        let string = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef".to_string();
        let hash = Hash::from_str(&string).unwrap();
        let array = hash.to_array();
        assert_eq!(array, hash.0);
    }
}