1#[cfg(feature = "py-bindings")]
2use crate::LazyNode;
3use crate::bytes::Bytes;
4use chia_sha2::Sha256;
5use chia_traits::Streamable;
6use chia_traits::chia_error::{Error, Result};
7use clvm_traits::{FromClvm, FromClvmError, ToClvm, ToClvmError};
8#[cfg(feature = "py-bindings")]
9use clvmr::SExp;
10use clvmr::cost::Cost;
11use clvmr::error::EvalErr;
12use clvmr::run_program;
13use clvmr::serde::{
14 node_from_bytes, node_from_bytes_backrefs, node_to_bytes, serialized_length_from_bytes,
15 serialized_length_from_bytes_trusted,
16};
17use clvmr::{Allocator, ChiaDialect, ClvmFlags, NodePtr};
18#[cfg(feature = "py-bindings")]
19use pyo3::prelude::*;
20#[cfg(feature = "py-bindings")]
21use pyo3::types::{PyList, PyTuple, PyType};
22use std::io::Cursor;
23use std::ops::Deref;
24#[cfg(feature = "py-bindings")]
25use std::rc::Rc;
26
27#[cfg(feature = "py-bindings")]
28use clvm_utils::CurriedProgram;
29
30#[cfg_attr(
31 feature = "py-bindings",
32 pyclass(subclass, from_py_object),
33 derive(PyStreamable)
34)]
35#[derive(Debug, Clone, PartialEq, Eq, Hash)]
36#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
37pub struct Program(Bytes);
38
39impl Default for Program {
40 fn default() -> Self {
41 Self(vec![0x80].into())
42 }
43}
44
45impl Program {
46 pub fn new(bytes: Bytes) -> Self {
47 Self(bytes)
48 }
49
50 pub fn len(&self) -> usize {
51 self.0.len()
52 }
53
54 pub fn is_empty(&self) -> bool {
55 self.0.is_empty()
56 }
57
58 pub fn as_slice(&self) -> &[u8] {
59 self.0.as_slice()
60 }
61
62 pub fn to_vec(&self) -> Vec<u8> {
63 self.0.to_vec()
64 }
65
66 pub fn into_inner(self) -> Bytes {
67 self.0
68 }
69
70 pub fn into_bytes(self) -> Vec<u8> {
71 self.0.into_inner()
72 }
73
74 pub fn run<A: ToClvm<Allocator>>(
75 &self,
76 a: &mut Allocator,
77 flags: ClvmFlags,
78 max_cost: Cost,
79 arg: &A,
80 ) -> std::result::Result<(Cost, NodePtr), EvalErr> {
81 let arg = arg.to_clvm(a).map_err(|_| {
82 EvalErr::InvalidAllocArg(
83 a.nil(),
84 "failed to convert argument to CLVM objects".to_string(),
85 )
86 })?;
87 let program =
88 node_from_bytes_backrefs(a, self.0.as_ref()).expect("invalid SerializedProgram");
89 let dialect = ChiaDialect::new(flags);
90 let reduction = run_program(a, &dialect, program, arg, max_cost)?;
91 Ok((reduction.0, reduction.1))
92 }
93}
94
95impl From<Bytes> for Program {
96 fn from(value: Bytes) -> Self {
97 Self(value)
98 }
99}
100
101impl From<Program> for Bytes {
102 fn from(value: Program) -> Self {
103 value.0
104 }
105}
106
107impl From<Vec<u8>> for Program {
108 fn from(value: Vec<u8>) -> Self {
109 Self(Bytes::new(value))
110 }
111}
112
113impl From<&[u8]> for Program {
114 fn from(value: &[u8]) -> Self {
115 Self(value.into())
116 }
117}
118
119impl From<Program> for Vec<u8> {
120 fn from(value: Program) -> Self {
121 value.0.into()
122 }
123}
124
125impl AsRef<[u8]> for Program {
126 fn as_ref(&self) -> &[u8] {
127 self.0.as_ref()
128 }
129}
130
131impl Deref for Program {
132 type Target = [u8];
133
134 fn deref(&self) -> &[u8] {
135 &self.0
136 }
137}
138
139#[cfg(feature = "arbitrary")]
140impl<'a> arbitrary::Arbitrary<'a> for Program {
141 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
142 let mut items_left = 1;
144 let mut total_items = 0;
145 let mut buf = Vec::<u8>::with_capacity(200);
146
147 while items_left > 0 {
148 if total_items < 100 && u.ratio(1, 4).unwrap() {
149 buf.push(0xff);
151 items_left += 2;
152 } else {
153 buf.push(u.int_in_range(0..=0x80).unwrap());
155 }
156 total_items += 1;
157 items_left -= 1;
158 }
159 Ok(Self(buf.into()))
160 }
161}
162
163#[cfg(feature = "py-bindings")]
164use chia_traits::{FromJsonDict, ToJsonDict};
165
166#[cfg(feature = "py-bindings")]
167use chia_py_streamable_macro::PyStreamable;
168
169#[cfg(feature = "py-bindings")]
170use pyo3::exceptions::*;
171
172#[cfg(feature = "py-bindings")]
173#[allow(clippy::needless_pass_by_value)]
174fn map_pyerr(err: EvalErr) -> PyErr {
176 PyValueError::new_err(err.to_string())
178}
179
180#[cfg(feature = "py-bindings")]
186fn clvm_convert(a: &mut Allocator, o: &Bound<'_, PyAny>) -> PyResult<NodePtr> {
187 if o.is_none() {
189 Ok(a.nil())
190 } else if let Ok(buffer) = o.extract::<&[u8]>() {
192 a.new_atom(buffer)
193 .map_err(|e| PyMemoryError::new_err(e.to_string()))
194 } else if let Ok(text) = o.extract::<String>() {
196 a.new_atom(text.as_bytes())
197 .map_err(|e| PyMemoryError::new_err(e.to_string()))
198 } else if let Ok(val) = o.extract::<clvmr::number::Number>() {
200 a.new_number(val)
201 .map_err(|e| PyMemoryError::new_err(e.to_string()))
202 } else if let Ok(pair) = o.cast::<PyTuple>() {
204 if pair.len() == 2 {
205 let left = clvm_convert(a, &pair.get_item(0)?)?;
206 let right = clvm_convert(a, &pair.get_item(1)?)?;
207 a.new_pair(left, right)
208 .map_err(|e| PyMemoryError::new_err(e.to_string()))
209 } else {
210 Err(PyValueError::new_err(format!(
211 "can't cast tuple of size {}",
212 pair.len()
213 )))
214 }
215 } else if let Ok(list) = o.cast::<PyList>() {
217 let mut rev = Vec::new();
218 for py_item in list.iter() {
219 rev.push(py_item);
220 }
221 let mut ret = a.nil();
222 for py_item in rev.into_iter().rev() {
223 let item = clvm_convert(a, &py_item)?;
224 ret = a
225 .new_pair(item, ret)
226 .map_err(|e| PyMemoryError::new_err(e.to_string()))?;
227 }
228 Ok(ret)
229 } else if let (Ok(atom), Ok(pair)) = (o.getattr("atom"), o.getattr("pair")) {
231 if atom.is_none() {
232 if pair.is_none() {
233 Err(PyTypeError::new_err(format!("invalid SExp item {o}")))
234 } else {
235 let pair = pair.cast::<PyTuple>()?;
236 let left = clvm_convert(a, &pair.get_item(0)?)?;
237 let right = clvm_convert(a, &pair.get_item(1)?)?;
238 a.new_pair(left, right)
239 .map_err(|e| PyMemoryError::new_err(e.to_string()))
240 }
241 } else {
242 a.new_atom(atom.extract::<&[u8]>()?)
243 .map_err(|e| PyMemoryError::new_err(e.to_string()))
244 }
245 } else if let Ok(prg) = o.extract::<Program>() {
249 a.new_atom(prg.0.as_slice())
250 .map_err(|e| PyMemoryError::new_err(e.to_string()))
251 } else if let Ok(fun) = o.getattr("__bytes__") {
253 let bytes = fun.call0()?;
254 let buffer = bytes.extract::<&[u8]>()?;
255 a.new_atom(buffer)
256 .map_err(|e| PyMemoryError::new_err(e.to_string()))
257 } else {
258 Err(PyTypeError::new_err(format!(
259 "unknown parameter to run_with_cost() {o}"
260 )))
261 }
262}
263
264#[cfg(feature = "py-bindings")]
265fn clvm_serialize(a: &mut Allocator, o: &Bound<'_, PyAny>) -> PyResult<NodePtr> {
266 if let Ok(list) = o.cast::<PyList>() {
290 let mut rev = Vec::new();
291 for py_item in list.iter() {
292 rev.push(py_item);
293 }
294 let mut ret = a.nil();
295 for py_item in rev.into_iter().rev() {
296 let item = clvm_serialize(a, &py_item)?;
297 ret = a
298 .new_pair(item, ret)
299 .map_err(|e| PyMemoryError::new_err(e.to_string()))?;
300 }
301 Ok(ret)
302 } else if let Ok(prg) = o.extract::<Program>() {
304 node_from_bytes_backrefs(a, prg.0.as_slice()).map_err(map_pyerr)
305 } else {
306 clvm_convert(a, o)
307 }
308}
309
310#[cfg(feature = "py-bindings")]
311#[allow(clippy::needless_pass_by_value)]
312#[pymethods]
313impl Program {
314 #[pyo3(name = "default")]
315 #[staticmethod]
316 fn py_default() -> Self {
317 Self::default()
318 }
319
320 #[staticmethod]
321 #[pyo3(name = "to")]
322 fn py_to(args: &Bound<'_, PyAny>) -> PyResult<Program> {
323 let mut a = Allocator::new_limited(500_000_000);
324 let clvm = clvm_convert(&mut a, args)?;
325 Program::from_clvm(&a, clvm)
326 .map_err(|error| PyErr::new::<PyTypeError, _>(error.to_string()))
327 }
328
329 fn get_tree_hash(&self) -> crate::Bytes32 {
330 clvm_utils::tree_hash_from_bytes(self.0.as_ref())
331 .unwrap()
332 .into()
333 }
334
335 #[staticmethod]
336 fn fromhex(h: String) -> Result<Self> {
337 let s = if let Some(st) = h.strip_prefix("0x") {
338 st
339 } else {
340 &h[..]
341 };
342 Self::from_bytes(hex::decode(s).map_err(|_| Error::InvalidString)?.as_slice())
343 }
344
345 fn run_rust(
353 &self,
354 py: Python<'_>,
355 max_cost: u64,
356 flags: u32,
357 args: &Bound<'_, PyAny>,
358 ) -> PyResult<(u64, LazyNode)> {
359 use clvmr::reduction::Response;
360
361 let mut a = Allocator::new_limited(500_000_000);
362 let clvm_args = clvm_serialize(&mut a, args)?;
372
373 let r: Response = (|| -> PyResult<Response> {
374 let program = node_from_bytes_backrefs(&mut a, self.0.as_ref()).map_err(map_pyerr)?;
375 let dialect = ChiaDialect::new(ClvmFlags::from_bits_truncate(flags));
376
377 Ok(py.detach(|| run_program(&mut a, &dialect, program, clvm_args, max_cost)))
378 })()?;
379 match r {
380 Ok(reduction) => {
381 let val = LazyNode::new(Rc::new(a), reduction.1);
382 Ok((reduction.0, val))
383 }
384 Err(eval_err) => {
385 let blob = node_to_bytes(&a, eval_err.node_ptr()).ok().map(hex::encode);
386 Err(PyValueError::new_err((eval_err.to_string(), blob)))
387 }
388 }
389 }
390
391 fn uncurry_rust(&self) -> PyResult<(LazyNode, LazyNode)> {
392 let mut a = Allocator::new_limited(500_000_000);
393 let prg = node_from_bytes_backrefs(&mut a, self.0.as_ref()).map_err(map_pyerr)?;
394 let Ok(uncurried) = CurriedProgram::<NodePtr, NodePtr>::from_clvm(&a, prg) else {
395 let a = Rc::new(a);
396 let prg = LazyNode::new(a.clone(), prg);
397 let ret = a.nil();
398 let ret = LazyNode::new(a, ret);
399 return Ok((prg, ret));
400 };
401
402 let mut curried_args = Vec::<NodePtr>::new();
403 let mut args = uncurried.args;
404 loop {
405 if let SExp::Atom = a.sexp(args) {
406 break;
407 }
408 let (_, ((_, arg), (rest, ()))) =
411 <(
412 clvm_traits::MatchByte<4>,
413 (clvm_traits::match_quote!(NodePtr), (NodePtr, ())),
414 ) as FromClvm<Allocator>>::from_clvm(&a, args)
415 .map_err(|error| PyErr::new::<PyTypeError, _>(error.to_string()))?;
416 curried_args.push(arg);
417 args = rest;
418 }
419 let mut ret = a.nil();
420 for item in curried_args.into_iter().rev() {
421 ret = a.new_pair(item, ret).map_err(|_e| Error::EndOfBuffer)?;
422 }
423 let a = Rc::new(a);
424 let prg = LazyNode::new(a.clone(), uncurried.program);
425 let ret = LazyNode::new(a, ret);
426 Ok((prg, ret))
427 }
428}
429
430impl Streamable for Program {
431 fn update_digest(&self, digest: &mut Sha256) {
432 digest.update(&self.0);
433 }
434
435 fn stream(&self, out: &mut Vec<u8>) -> Result<()> {
436 out.extend_from_slice(self.0.as_ref());
437 Ok(())
438 }
439
440 fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self> {
441 let pos = input.position();
442 let buf: &[u8] = &input.get_ref()[pos as usize..];
443 let len = if TRUSTED {
444 serialized_length_from_bytes_trusted(buf).map_err(|_e| Error::EndOfBuffer)?
445 } else {
446 serialized_length_from_bytes(buf).map_err(|_e| Error::EndOfBuffer)?
447 };
448 if buf.len() < len as usize {
449 return Err(Error::EndOfBuffer);
450 }
451 let program = buf[..len as usize].to_vec();
452 input.set_position(pos + len);
453 Ok(Program(program.into()))
454 }
455}
456
457#[cfg(feature = "py-bindings")]
458impl ToJsonDict for Program {
459 fn to_json_dict(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
460 self.0.to_json_dict(py)
461 }
462}
463
464#[cfg(feature = "py-bindings")]
465#[pymethods]
466impl Program {
467 #[classmethod]
468 #[pyo3(name = "from_parent")]
469 pub fn from_parent(_cls: &Bound<'_, PyType>, _instance: &Self) -> PyResult<Py<PyAny>> {
470 Err(PyNotImplementedError::new_err(
471 "This class does not support from_parent().",
472 ))
473 }
474}
475
476#[cfg(feature = "py-bindings")]
477impl FromJsonDict for Program {
478 fn from_json_dict(o: &Bound<'_, PyAny>) -> PyResult<Self> {
479 let bytes = Bytes::from_json_dict(o)?;
480 let len =
481 serialized_length_from_bytes(bytes.as_slice()).map_err(|_e| Error::EndOfBuffer)?;
482 if len as usize != bytes.len() {
483 Err(Error::InvalidClvm)?;
487 }
488 Ok(Self(bytes))
489 }
490}
491
492impl FromClvm<Allocator> for Program {
493 fn from_clvm(a: &Allocator, node: NodePtr) -> std::result::Result<Self, FromClvmError> {
494 Ok(Self(
495 node_to_bytes(a, node)
496 .map_err(|error| FromClvmError::Custom(error.to_string()))?
497 .into(),
498 ))
499 }
500}
501
502impl ToClvm<Allocator> for Program {
503 fn to_clvm(&self, a: &mut Allocator) -> std::result::Result<NodePtr, ToClvmError> {
504 node_from_bytes(a, self.0.as_ref()).map_err(|error| ToClvmError::Custom(error.to_string()))
505 }
506}
507
508#[cfg(test)]
509mod tests {
510 use super::*;
511
512 #[test]
513 fn program_roundtrip() {
514 let a = &mut Allocator::new();
515 let expected = "ff01ff02ff62ff0480";
516 let expected_bytes = hex::decode(expected).unwrap();
517
518 let ptr = node_from_bytes(a, &expected_bytes).unwrap();
519 let program = Program::from_clvm(a, ptr).unwrap();
520
521 let round_trip = program.to_clvm(a).unwrap();
522 assert_eq!(expected, hex::encode(node_to_bytes(a, round_trip).unwrap()));
523 }
524
525 #[test]
526 fn program_run() {
527 let a = &mut Allocator::new();
528
529 let prg = Program::from_bytes(&hex::decode("ff10ff02ff0580").expect("hex::decode"))
531 .expect("from_bytes");
532 let (cost, result) = prg
533 .run(a, ClvmFlags::empty(), 1000, &[1300, 37])
534 .expect("run");
535 assert_eq!(cost, 869);
536 assert_eq!(a.number(result), 1337.into());
537 }
538}
539
540#[cfg(all(test, feature = "serde"))]
541mod serde_tests {
542 use super::*;
543
544 #[test]
545 fn test_program_is_bytes() -> anyhow::Result<()> {
546 let bytes = Bytes::new(vec![1, 2, 3]);
547 let program = Program::new(bytes.clone());
548
549 let bytes_json = serde_json::to_string(&bytes)?;
550 let program_json = serde_json::to_string(&program)?;
551
552 assert_eq!(program_json, bytes_json);
553
554 Ok(())
555 }
556}