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