diff --git a/Cargo.lock b/Cargo.lock index dd4b2bb1..d2281a5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,6 +207,16 @@ dependencies = [ "sha1", ] +[[package]] +name = "clvm-traits" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffa5c29efcbfc09a80251da9974959fc603f87197452e822b3b80f8622639b78" +dependencies = [ + "num-bigint", + "thiserror", +] + [[package]] name = "clvm_rs" version = "0.3.0" @@ -238,6 +248,7 @@ name = "clvmr" version = "0.3.0" dependencies = [ "bls12_381", + "clvm-traits", "criterion", "getrandom", "group", @@ -1522,6 +1533,26 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.29", +] + [[package]] name = "tinytemplate" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index b4fbb7fa..8e1d7957 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,6 +34,7 @@ lazy_static = "=1.4.0" num-bigint = "=0.4.3" num-traits = "=0.2.15" num-integer = "=0.1.45" +clvm-traits = "0.3.0" # the experimental feature enables hashing to curves bls12_381 = { version = "=0.8.0", features = ["experimental"] } # the newer sha2 crate doesn't implement the digest traits required by HKDF diff --git a/src/allocator.rs b/src/allocator.rs index 2e1bc930..c2df666b 100644 --- a/src/allocator.rs +++ b/src/allocator.rs @@ -2,6 +2,7 @@ use crate::err_utils::err; use crate::number::{node_from_number, number_from_u8, Number}; use crate::reduction::EvalErr; use bls12_381::{G1Affine, G1Projective, G2Affine, G2Projective}; +use clvm_traits::{ClvmDecoder, ClvmEncoder, FromClvmError, ToClvmError}; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct NodePtr(pub i32); @@ -334,6 +335,42 @@ impl Allocator { } } +impl ClvmEncoder for Allocator { + type Node = NodePtr; + + fn encode_atom(&mut self, bytes: &[u8]) -> Result { + self.new_atom(bytes).or(Err(ToClvmError::OutOfMemory)) + } + + fn encode_pair( + &mut self, + first: Self::Node, + rest: Self::Node, + ) -> Result { + self.new_pair(first, rest).or(Err(ToClvmError::OutOfMemory)) + } +} + +impl ClvmDecoder for Allocator { + type Node = NodePtr; + + fn decode_atom(&self, node: &Self::Node) -> Result<&[u8], FromClvmError> { + if let SExp::Atom = self.sexp(*node) { + Ok(self.atom(*node)) + } else { + Err(FromClvmError::ExpectedAtom) + } + } + + fn decode_pair(&self, node: &Self::Node) -> Result<(Self::Node, Self::Node), FromClvmError> { + if let SExp::Pair(first, rest) = self.sexp(*node) { + Ok((first, rest)) + } else { + Err(FromClvmError::ExpectedPair) + } + } +} + #[test] fn test_atom_eq() { let mut a = Allocator::new(); diff --git a/src/from_node_ptr.rs b/src/from_node_ptr.rs new file mode 100644 index 00000000..58daf71b --- /dev/null +++ b/src/from_node_ptr.rs @@ -0,0 +1,115 @@ +use crate::{allocator::NodePtr, Allocator}; + +use clvm_traits::{ClvmDecoder, FromClvm, FromClvmError}; + +pub trait FromNodePtr { + fn from_node_ptr(a: &Allocator, node: NodePtr) -> Result + where + Self: Sized; +} + +impl FromNodePtr for T +where + T: FromClvm, +{ + fn from_node_ptr(a: &Allocator, node: NodePtr) -> Result + where + Self: Sized, + { + T::from_clvm(a, node) + } +} + +impl FromClvm for NodePtr { + fn from_clvm( + _decoder: &impl ClvmDecoder, + node: NodePtr, + ) -> Result { + Ok(node) + } +} + +#[cfg(test)] +mod tests { + use crate::{allocator::NodePtr, serde::node_from_bytes, Allocator}; + + use super::*; + + fn decode(a: &mut Allocator, hex: &str) -> Result + where + T: FromClvm, + { + let bytes = hex::decode(hex).unwrap(); + let actual = node_from_bytes(a, &bytes).unwrap(); + T::from_clvm(a, actual) + } + + #[test] + fn test_nodeptr() { + let a = &mut Allocator::new(); + let ptr = a.one(); + assert_eq!(NodePtr::from_clvm(a, ptr).unwrap(), ptr); + } + + #[test] + fn test_primitives() { + let a = &mut Allocator::new(); + assert_eq!(decode(a, "80"), Ok(0u8)); + assert_eq!(decode(a, "80"), Ok(0i8)); + assert_eq!(decode(a, "05"), Ok(5u8)); + assert_eq!(decode(a, "05"), Ok(5u32)); + assert_eq!(decode(a, "05"), Ok(5i32)); + assert_eq!(decode(a, "81e5"), Ok(-27i32)); + assert_eq!(decode(a, "80"), Ok(-0)); + assert_eq!(decode(a, "8180"), Ok(-128i8)); + } + + #[test] + fn test_pair() { + let a = &mut Allocator::new(); + assert_eq!(decode(a, "ff0502"), Ok((5, 2))); + assert_eq!(decode(a, "ff81b8ff8301600980"), Ok((-72, (90121, ())))); + assert_eq!( + decode(a, "ffff80ff80ff80ffff80ff80ff80808080"), + Ok((((), ((), ((), (((), ((), ((), ()))), ())))), ())) + ); + } + + #[test] + fn test_nil() { + let a = &mut Allocator::new(); + assert_eq!(decode(a, "80"), Ok(())); + } + + #[test] + fn test_array() { + let a = &mut Allocator::new(); + assert_eq!(decode(a, "ff01ff02ff03ff0480"), Ok([1, 2, 3, 4])); + assert_eq!(decode(a, "80"), Ok([] as [i32; 0])); + } + + #[test] + fn test_vec() { + let a = &mut Allocator::new(); + assert_eq!(decode(a, "ff01ff02ff03ff0480"), Ok(vec![1, 2, 3, 4])); + assert_eq!(decode(a, "80"), Ok(Vec::::new())); + } + + #[test] + fn test_option() { + let a = &mut Allocator::new(); + assert_eq!(decode(a, "8568656c6c6f"), Ok(Some("hello".to_string()))); + assert_eq!(decode(a, "80"), Ok(None::)); + + // Empty strings get decoded as None instead, since both values are represented by nil bytes. + // This could be considered either intended behavior or not, depending on the way it's used. + assert_ne!(decode(a, "80"), Ok(Some("".to_string()))); + } + + #[test] + fn test_string() { + let a = &mut Allocator::new(); + assert_eq!(decode(a, "8568656c6c6f"), Ok("hello".to_string())); + assert_eq!(decode(a, "80"), Ok("".to_string())); + } +} diff --git a/src/lib.rs b/src/lib.rs index c4fa8487..5008b64b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,13 @@ pub mod serde; pub mod sha2; pub mod traverse_path; -pub use allocator::Allocator; +pub mod from_node_ptr; +pub mod to_node_ptr; + +pub use from_node_ptr::FromNodePtr; +pub use to_node_ptr::ToNodePtr; + +pub use allocator::{Allocator, NodePtr}; pub use chia_dialect::ChiaDialect; pub use run_program::run_program; diff --git a/src/to_node_ptr.rs b/src/to_node_ptr.rs new file mode 100644 index 00000000..17d49c1c --- /dev/null +++ b/src/to_node_ptr.rs @@ -0,0 +1,143 @@ +use crate::{allocator::NodePtr, Allocator}; + +use clvm_traits::{ClvmEncoder, ToClvm, ToClvmError}; + +pub trait ToNodePtr { + fn to_node_ptr(&self, a: &mut Allocator) -> Result; +} + +impl ToNodePtr for T +where + T: ToClvm, +{ + fn to_node_ptr(&self, a: &mut Allocator) -> Result { + self.to_clvm(a) + } +} + +impl ToClvm for NodePtr { + fn to_clvm( + &self, + _encoder: &mut impl ClvmEncoder, + ) -> Result { + Ok(*self) + } +} + +#[cfg(test)] +mod tests { + use crate::{serde::node_to_bytes, Allocator}; + use hex::ToHex; + + use super::*; + + fn encode(a: &mut Allocator, value: T) -> Result + where + T: ToClvm, + { + let actual = value.to_clvm(a).unwrap(); + let actual_bytes = node_to_bytes(a, actual).unwrap(); + Ok(actual_bytes.encode_hex()) + } + + #[test] + fn test_nodeptr() { + let a = &mut Allocator::new(); + let ptr = a.one(); + assert_eq!(ptr.to_clvm(a).unwrap(), ptr); + } + + #[test] + fn test_primitives() { + let a = &mut Allocator::new(); + assert_eq!(encode(a, 0u8), Ok("80".to_owned())); + assert_eq!(encode(a, 0i8), Ok("80".to_owned())); + assert_eq!(encode(a, 5u8), Ok("05".to_owned())); + assert_eq!(encode(a, 5u32), Ok("05".to_owned())); + assert_eq!(encode(a, 5i32), Ok("05".to_owned())); + assert_eq!(encode(a, -27i32), Ok("81e5".to_owned())); + assert_eq!(encode(a, -0), Ok("80".to_owned())); + assert_eq!(encode(a, -128i8), Ok("8180".to_owned())); + } + + #[test] + fn test_reference() { + let a = &mut Allocator::new(); + assert_eq!(encode(a, [1, 2, 3]), encode(a, [1, 2, 3])); + assert_eq!(encode(a, Some(42)), encode(a, Some(42))); + assert_eq!(encode(a, Some(&42)), encode(a, Some(42))); + assert_eq!(encode(a, Some(&42)), encode(a, Some(42))); + } + + #[test] + fn test_pair() { + let a = &mut Allocator::new(); + assert_eq!(encode(a, (5, 2)), Ok("ff0502".to_owned())); + assert_eq!( + encode(a, (-72, (90121, ()))), + Ok("ff81b8ff8301600980".to_owned()) + ); + assert_eq!( + encode(a, (((), ((), ((), (((), ((), ((), ()))), ())))), ())), + Ok("ffff80ff80ff80ffff80ff80ff80808080".to_owned()) + ); + } + + #[test] + fn test_nil() { + let a = &mut Allocator::new(); + assert_eq!(encode(a, ()), Ok("80".to_owned())); + } + + #[test] + fn test_slice() { + let a = &mut Allocator::new(); + assert_eq!( + encode(a, [1, 2, 3, 4].as_slice()), + Ok("ff01ff02ff03ff0480".to_owned()) + ); + assert_eq!(encode(a, [0; 0].as_slice()), Ok("80".to_owned())); + } + + #[test] + fn test_array() { + let a = &mut Allocator::new(); + assert_eq!(encode(a, [1, 2, 3, 4]), Ok("ff01ff02ff03ff0480".to_owned())); + assert_eq!(encode(a, [0; 0]), Ok("80".to_owned())); + } + + #[test] + fn test_vec() { + let a = &mut Allocator::new(); + assert_eq!( + encode(a, vec![1, 2, 3, 4]), + Ok("ff01ff02ff03ff0480".to_owned()) + ); + assert_eq!(encode(a, vec![0; 0]), Ok("80".to_owned())); + } + + #[test] + fn test_option() { + let a = &mut Allocator::new(); + assert_eq!(encode(a, Some("hello")), Ok("8568656c6c6f".to_owned())); + assert_eq!(encode(a, None::<&str>), Ok("80".to_owned())); + assert_eq!(encode(a, Some("")), Ok("80".to_owned())); + } + + #[test] + fn test_str() { + let a = &mut Allocator::new(); + assert_eq!(encode(a, "hello"), Ok("8568656c6c6f".to_owned())); + assert_eq!(encode(a, ""), Ok("80".to_owned())); + } + + #[test] + fn test_string() { + let a = &mut Allocator::new(); + assert_eq!( + encode(a, "hello".to_string()), + Ok("8568656c6c6f".to_owned()) + ); + assert_eq!(encode(a, "".to_string()), Ok("80".to_owned())); + } +}