stacks-rs 0.1.0

A rust toolkit to interact with the stacks blockchain.
Documentation
use crate::clarity::ClarityValue;
use crate::clarity::Error;
use crate::clarity::LengthPrefixedString;
use crate::clarity::CLARITY_TYPE_TUPLE;
use crate::crypto::Deserialize;
use crate::crypto::Serialize;

#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct TupleCV(u8, Vec<(String, ClarityValue)>);

impl TupleCV {
    pub fn new(values: &[(impl Into<String> + std::clone::Clone, ClarityValue)]) -> ClarityValue {
        let values = values
            .iter()
            .cloned()
            .map(|(key, value)| (key.into(), value))
            .collect();

        ClarityValue::Tuple(TupleCV(CLARITY_TYPE_TUPLE, values))
    }
}

impl std::fmt::Display for TupleCV {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "(tuple ")?;
        for (i, (key, value)) in self.1.iter().enumerate() {
            if i > 0 {
                write!(f, " ")?;
            }
            write!(f, "({key} {value})")?;
        }
        write!(f, ")")
    }
}

impl std::fmt::Debug for TupleCV {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "TupleCV(")?;
        for (i, (key, value)) in self.1.iter().enumerate() {
            if i > 0 {
                write!(f, ", ")?;
            }
            write!(f, "{key}: {value:#?}")?;
        }
        write!(f, ")")
    }
}

impl Serialize for TupleCV {
    type Err = Error;

    fn serialize(&self) -> Result<Vec<u8>, Self::Err> {
        let mut buff = vec![CLARITY_TYPE_TUPLE];
        buff.extend_from_slice(&(u32::try_from(self.1.len())?).to_be_bytes());

        for (key, value) in &self.1 {
            buff.extend_from_slice(&LengthPrefixedString::new(key).serialize()?);
            buff.extend_from_slice(&value.serialize()?);
        }

        Ok(buff)
    }
}

impl Deserialize for TupleCV {
    type Output = ClarityValue;
    type Err = Error;

    fn deserialize(bytes: &[u8]) -> Result<Self::Output, Self::Err> {
        let len = u32::from_be_bytes([bytes[1], bytes[2], bytes[3], bytes[4]]);

        let mut values = vec![];
        let mut offset = 5;

        for _ in 0..len {
            let key_len = bytes[offset] as usize;
            let key = std::str::from_utf8(&bytes[offset + 1..offset + 1 + key_len])
                .map_err(|_| Error::InvalidClarityName)?
                .to_string();

            offset += 1 + key_len;

            let cv = ClarityValue::deserialize(&bytes[offset..])?;
            offset += cv.serialize()?.len();

            values.push((key, cv));
        }

        Ok(TupleCV::new(&values))
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::clarity::BufferCV;
    use crate::clarity::ContractPrincipalCV;
    use crate::clarity::ErrCV;
    use crate::clarity::FalseCV;
    use crate::clarity::IntCV;
    use crate::clarity::ListCV;
    use crate::clarity::NoneCV;
    use crate::clarity::OkCV;
    use crate::clarity::SomeCV;
    use crate::clarity::StandardPrincipalCV;
    use crate::clarity::TrueCV;
    use crate::clarity::UIntCV;
    use crate::crypto::hex::bytes_to_hex;

    #[test]
    fn test_tuple_cv() {
        let cv = TupleCV::new(&[("baz", NoneCV::new()), ("foobar", TrueCV::new())]);

        let serialized = cv.serialize().unwrap();

        let hex = bytes_to_hex(&serialized);
        assert_eq!(hex, "0c000000020362617a0906666f6f62617203");

        let deserialized = TupleCV::deserialize(&serialized).unwrap();
        assert_eq!(deserialized, cv);
    }

    #[test]
    fn test_tuple_cv_string() {
        let address = "ST3J2GVMMM2R07ZFBJDWTYEYAR8FZH5WKDTFJ9AHA";

        let cv = TupleCV::new(&[
            ("a", IntCV::new(-1)),
            ("b", UIntCV::new(1)),
            ("c", BufferCV::new(b"test")),
            ("d", TrueCV::new()),
            ("e", SomeCV::new(TrueCV::new())),
            ("f", NoneCV::new()),
            ("g", StandardPrincipalCV::new(address)),
            ("h", ContractPrincipalCV::new(address, "test")),
            ("i", OkCV::new(TrueCV::new())),
            ("j", ErrCV::new(FalseCV::new())),
            ("k", ListCV::new([TrueCV::new(), FalseCV::new()])),
            (
                "l",
                TupleCV::new(&[("a", TrueCV::new()), ("b", FalseCV::new())]),
            ),
        ]);

        let expected = "(tuple (a -1) (b u1) (c 0x74657374) (d true) (e (some true)) (f none) (g ST3J2GVMMM2R07ZFBJDWTYEYAR8FZH5WKDTFJ9AHA) (h ST3J2GVMMM2R07ZFBJDWTYEYAR8FZH5WKDTFJ9AHA.test) (i (ok true)) (j (err false)) (k (list true false)) (l (tuple (a true) (b false))))";

        assert_eq!(cv.to_string(), expected);
    }
}