1use crate::bytes::Bytes;
2use chia_sha2::Sha256;
3use chia_traits::chia_error::{Error, Result};
4use chia_traits::Streamable;
5use clvm_traits::{FromClvm, FromClvmError, ToClvm, ToClvmError};
6use clvmr::allocator::NodePtr;
7use clvmr::cost::Cost;
8use clvmr::reduction::EvalErr;
9use clvmr::run_program;
10use clvmr::serde::{
11 node_from_bytes, node_from_bytes_backrefs, node_to_bytes, serialized_length_from_bytes,
12 serialized_length_from_bytes_trusted,
13};
14use clvmr::{Allocator, ChiaDialect};
15#[cfg(feature = "py-bindings")]
16use pyo3::prelude::*;
17#[cfg(feature = "py-bindings")]
18use pyo3::types::PyType;
19use std::io::Cursor;
20use std::ops::Deref;
21
22#[cfg_attr(feature = "py-bindings", pyclass, derive(PyStreamable))]
23#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25pub struct Program(Bytes);
26
27impl Default for Program {
28 fn default() -> Self {
29 Self(vec![0x80].into())
30 }
31}
32
33impl Program {
34 pub fn new(bytes: Bytes) -> Self {
35 Self(bytes)
36 }
37
38 pub fn len(&self) -> usize {
39 self.0.len()
40 }
41
42 pub fn is_empty(&self) -> bool {
43 self.0.is_empty()
44 }
45
46 pub fn as_slice(&self) -> &[u8] {
47 self.0.as_slice()
48 }
49
50 pub fn to_vec(&self) -> Vec<u8> {
51 self.0.to_vec()
52 }
53
54 pub fn into_inner(self) -> Bytes {
55 self.0
56 }
57
58 pub fn into_bytes(self) -> Vec<u8> {
59 self.0.into_inner()
60 }
61
62 pub fn run<A: ToClvm<Allocator>>(
63 &self,
64 a: &mut Allocator,
65 flags: u32,
66 max_cost: Cost,
67 arg: &A,
68 ) -> std::result::Result<(Cost, NodePtr), EvalErr> {
69 let arg = arg.to_clvm(a).map_err(|_| {
70 EvalErr(
71 a.nil(),
72 "failed to convert argument to CLVM objects".to_string(),
73 )
74 })?;
75 let program =
76 node_from_bytes_backrefs(a, self.0.as_ref()).expect("invalid SerializedProgram");
77 let dialect = ChiaDialect::new(flags);
78 let reduction = run_program(a, &dialect, program, arg, max_cost)?;
79 Ok((reduction.0, reduction.1))
80 }
81}
82
83impl From<Bytes> for Program {
84 fn from(value: Bytes) -> Self {
85 Self(value)
86 }
87}
88
89impl From<Program> for Bytes {
90 fn from(value: Program) -> Self {
91 value.0
92 }
93}
94
95impl From<Vec<u8>> for Program {
96 fn from(value: Vec<u8>) -> Self {
97 Self(Bytes::new(value))
98 }
99}
100
101impl From<&[u8]> for Program {
102 fn from(value: &[u8]) -> Self {
103 Self(value.into())
104 }
105}
106
107impl From<Program> for Vec<u8> {
108 fn from(value: Program) -> Self {
109 value.0.into()
110 }
111}
112
113impl AsRef<[u8]> for Program {
114 fn as_ref(&self) -> &[u8] {
115 self.0.as_ref()
116 }
117}
118
119impl Deref for Program {
120 type Target = [u8];
121
122 fn deref(&self) -> &[u8] {
123 &self.0
124 }
125}
126
127#[cfg(feature = "arbitrary")]
128impl<'a> arbitrary::Arbitrary<'a> for Program {
129 fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
130 let mut items_left = 1;
132 let mut total_items = 0;
133 let mut buf = Vec::<u8>::with_capacity(200);
134
135 while items_left > 0 {
136 if total_items < 100 && u.ratio(1, 4).unwrap() {
137 buf.push(0xff);
139 items_left += 2;
140 } else {
141 buf.push(u.int_in_range(0..=0x80).unwrap());
143 }
144 total_items += 1;
145 items_left -= 1;
146 }
147 Ok(Self(buf.into()))
148 }
149}
150
151#[cfg(feature = "py-bindings")]
152use crate::lazy_node::LazyNode;
153
154#[cfg(feature = "py-bindings")]
155use chia_traits::{FromJsonDict, ToJsonDict};
156
157#[cfg(feature = "py-bindings")]
158use chia_py_streamable_macro::PyStreamable;
159
160#[cfg(feature = "py-bindings")]
161use pyo3::types::{PyList, PyTuple};
162
163#[cfg(feature = "py-bindings")]
164use clvmr::allocator::SExp;
165
166#[cfg(feature = "py-bindings")]
167use pyo3::exceptions::*;
168
169#[cfg(feature = "py-bindings")]
175fn clvm_convert(a: &mut Allocator, o: &Bound<'_, PyAny>) -> PyResult<NodePtr> {
176 if o.is_none() {
178 Ok(a.nil())
179 } else if let Ok(buffer) = o.extract::<&[u8]>() {
181 a.new_atom(buffer)
182 .map_err(|e| PyMemoryError::new_err(e.to_string()))
183 } else if let Ok(text) = o.extract::<String>() {
185 a.new_atom(text.as_bytes())
186 .map_err(|e| PyMemoryError::new_err(e.to_string()))
187 } else if let Ok(val) = o.extract::<clvmr::number::Number>() {
189 a.new_number(val)
190 .map_err(|e| PyMemoryError::new_err(e.to_string()))
191 } else if let Ok(pair) = o.downcast::<PyTuple>() {
193 if pair.len() == 2 {
194 let left = clvm_convert(a, &pair.get_item(0)?)?;
195 let right = clvm_convert(a, &pair.get_item(1)?)?;
196 a.new_pair(left, right)
197 .map_err(|e| PyMemoryError::new_err(e.to_string()))
198 } else {
199 Err(PyValueError::new_err(format!(
200 "can't cast tuple of size {}",
201 pair.len()
202 )))
203 }
204 } else if let Ok(list) = o.downcast::<PyList>() {
206 let mut rev = Vec::new();
207 for py_item in list.iter() {
208 rev.push(py_item);
209 }
210 let mut ret = a.nil();
211 for py_item in rev.into_iter().rev() {
212 let item = clvm_convert(a, &py_item)?;
213 ret = a
214 .new_pair(item, ret)
215 .map_err(|e| PyMemoryError::new_err(e.to_string()))?;
216 }
217 Ok(ret)
218 } else if let (Ok(atom), Ok(pair)) = (o.getattr("atom"), o.getattr("pair")) {
220 if atom.is_none() {
221 if pair.is_none() {
222 Err(PyTypeError::new_err(format!("invalid SExp item {o}")))
223 } else {
224 let pair = pair.downcast::<PyTuple>()?;
225 let left = clvm_convert(a, &pair.get_item(0)?)?;
226 let right = clvm_convert(a, &pair.get_item(1)?)?;
227 a.new_pair(left, right)
228 .map_err(|e| PyMemoryError::new_err(e.to_string()))
229 }
230 } else {
231 a.new_atom(atom.extract::<&[u8]>()?)
232 .map_err(|e| PyMemoryError::new_err(e.to_string()))
233 }
234 } else if let Ok(prg) = o.extract::<Program>() {
238 a.new_atom(prg.0.as_slice())
239 .map_err(|e| PyMemoryError::new_err(e.to_string()))
240 } else if let Ok(fun) = o.getattr("__bytes__") {
242 let bytes = fun.call0()?;
243 let buffer = bytes.extract::<&[u8]>()?;
244 a.new_atom(buffer)
245 .map_err(|e| PyMemoryError::new_err(e.to_string()))
246 } else {
247 Err(PyTypeError::new_err(format!(
248 "unknown parameter to run_with_cost() {o}"
249 )))
250 }
251}
252
253#[cfg(feature = "py-bindings")]
254fn clvm_serialize(a: &mut Allocator, o: &Bound<'_, PyAny>) -> PyResult<NodePtr> {
255 if let Ok(list) = o.downcast::<PyList>() {
279 let mut rev = Vec::new();
280 for py_item in list.iter() {
281 rev.push(py_item);
282 }
283 let mut ret = a.nil();
284 for py_item in rev.into_iter().rev() {
285 let item = clvm_serialize(a, &py_item)?;
286 ret = a
287 .new_pair(item, ret)
288 .map_err(|e| PyMemoryError::new_err(e.to_string()))?;
289 }
290 Ok(ret)
291 } else if let Ok(prg) = o.extract::<Program>() {
293 Ok(node_from_bytes_backrefs(a, prg.0.as_slice())?)
294 } else {
295 clvm_convert(a, o)
296 }
297}
298
299#[cfg(feature = "py-bindings")]
300fn to_program(py: Python<'_>, node: LazyNode) -> PyResult<Bound<'_, PyAny>> {
301 let int_module = PyModule::import(py, "chia.types.blockchain_format.program")?;
302 let ty = int_module.getattr("Program")?;
303 ty.call1((node.into_pyobject(py)?,))
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 from_program(py: Python<'_>, p: PyObject) -> PyResult<Self> {
333 let buf = p.getattr(py, "__bytes__")?.call0(py)?;
334 let buf = buf.extract::<&[u8]>(py)?;
335 Ok(Self(buf.into()))
336 }
337
338 #[staticmethod]
339 fn fromhex(h: String) -> Result<Self> {
340 let s = if let Some(st) = h.strip_prefix("0x") {
341 st
342 } else {
343 &h[..]
344 };
345 Self::from_bytes(hex::decode(s).map_err(|_| Error::InvalidString)?.as_slice())
346 }
347
348 fn run_mempool_with_cost<'a>(
349 &self,
350 py: Python<'a>,
351 max_cost: u64,
352 args: &Bound<'_, PyAny>,
353 ) -> PyResult<(u64, Bound<'a, PyAny>)> {
354 use clvmr::MEMPOOL_MODE;
355 #[allow(clippy::used_underscore_items)]
356 self._run(py, max_cost, MEMPOOL_MODE, args)
357 }
358
359 fn run_with_cost<'a>(
360 &self,
361 py: Python<'a>,
362 max_cost: u64,
363 args: &Bound<'_, PyAny>,
364 ) -> PyResult<(u64, Bound<'a, PyAny>)> {
365 #[allow(clippy::used_underscore_items)]
366 self._run(py, max_cost, 0, args)
367 }
368
369 fn _run<'a>(
371 &self,
372 py: Python<'a>,
373 max_cost: u64,
374 flags: u32,
375 args: &Bound<'_, PyAny>,
376 ) -> PyResult<(u64, Bound<'a, PyAny>)> {
377 use clvmr::reduction::Response;
378 use std::rc::Rc;
379
380 let mut a = Allocator::new_limited(500_000_000);
381 let clvm_args = clvm_serialize(&mut a, args)?;
391
392 let r: Response = (|| -> PyResult<Response> {
393 let program = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
394 let dialect = ChiaDialect::new(flags);
395
396 Ok(py.allow_threads(|| run_program(&mut a, &dialect, program, clvm_args, max_cost)))
397 })()?;
398 match r {
399 Ok(reduction) => {
400 let val = LazyNode::new(Rc::new(a), reduction.1);
401 Ok((reduction.0, to_program(py, val)?))
402 }
403 Err(eval_err) => {
404 let blob = node_to_bytes(&a, eval_err.0).ok().map(hex::encode);
405 Err(PyValueError::new_err((eval_err.1, blob)))
406 }
407 }
408 }
409
410 fn to_program<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyAny>> {
411 use std::rc::Rc;
412 let mut a = Allocator::new_limited(500_000_000);
413 let prg = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
414 let prg = LazyNode::new(Rc::new(a), prg);
415 to_program(py, prg)
416 }
417
418 fn uncurry<'a>(&self, py: Python<'a>) -> PyResult<(Bound<'a, PyAny>, Bound<'a, PyAny>)> {
419 use clvm_utils::CurriedProgram;
420 use std::rc::Rc;
421
422 let mut a = Allocator::new_limited(500_000_000);
423 let prg = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
424 let Ok(uncurried) = CurriedProgram::<NodePtr, NodePtr>::from_clvm(&a, prg) else {
425 let a = Rc::new(a);
426 let prg = LazyNode::new(a.clone(), prg);
427 let ret = a.nil();
428 let ret = LazyNode::new(a, ret);
429 return Ok((to_program(py, prg)?, to_program(py, ret)?));
430 };
431
432 let mut curried_args = Vec::<NodePtr>::new();
433 let mut args = uncurried.args;
434 loop {
435 if let SExp::Atom = a.sexp(args) {
436 break;
437 }
438 let (_, ((_, arg), (rest, ()))) =
441 <(
442 clvm_traits::MatchByte<4>,
443 (clvm_traits::match_quote!(NodePtr), (NodePtr, ())),
444 ) as FromClvm<Allocator>>::from_clvm(&a, args)
445 .map_err(|error| PyErr::new::<PyTypeError, _>(error.to_string()))?;
446 curried_args.push(arg);
447 args = rest;
448 }
449 let mut ret = a.nil();
450 for item in curried_args.into_iter().rev() {
451 ret = a.new_pair(item, ret).map_err(|_e| Error::EndOfBuffer)?;
452 }
453 let a = Rc::new(a);
454 let prg = LazyNode::new(a.clone(), uncurried.program);
455 let ret = LazyNode::new(a, ret);
456 Ok((to_program(py, prg)?, to_program(py, ret)?))
457 }
458}
459
460impl Streamable for Program {
461 fn update_digest(&self, digest: &mut Sha256) {
462 digest.update(&self.0);
463 }
464
465 fn stream(&self, out: &mut Vec<u8>) -> Result<()> {
466 out.extend_from_slice(self.0.as_ref());
467 Ok(())
468 }
469
470 fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self> {
471 let pos = input.position();
472 let buf: &[u8] = &input.get_ref()[pos as usize..];
473 let len = if TRUSTED {
474 serialized_length_from_bytes_trusted(buf).map_err(|_e| Error::EndOfBuffer)?
475 } else {
476 serialized_length_from_bytes(buf).map_err(|_e| Error::EndOfBuffer)?
477 };
478 if buf.len() < len as usize {
479 return Err(Error::EndOfBuffer);
480 }
481 let program = buf[..len as usize].to_vec();
482 input.set_position(pos + len);
483 Ok(Program(program.into()))
484 }
485}
486
487#[cfg(feature = "py-bindings")]
488impl ToJsonDict for Program {
489 fn to_json_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
490 self.0.to_json_dict(py)
491 }
492}
493
494#[cfg(feature = "py-bindings")]
495#[pymethods]
496impl Program {
497 #[classmethod]
498 #[pyo3(name = "from_parent")]
499 pub fn from_parent(_cls: &Bound<'_, PyType>, _instance: &Self) -> PyResult<PyObject> {
500 Err(PyNotImplementedError::new_err(
501 "This class does not support from_parent().",
502 ))
503 }
504}
505
506#[cfg(feature = "py-bindings")]
507impl FromJsonDict for Program {
508 fn from_json_dict(o: &Bound<'_, PyAny>) -> PyResult<Self> {
509 let bytes = Bytes::from_json_dict(o)?;
510 let len =
511 serialized_length_from_bytes(bytes.as_slice()).map_err(|_e| Error::EndOfBuffer)?;
512 if len as usize != bytes.len() {
513 return Err(Error::InvalidClvm)?;
517 }
518 Ok(Self(bytes))
519 }
520}
521
522impl FromClvm<Allocator> for Program {
523 fn from_clvm(a: &Allocator, node: NodePtr) -> std::result::Result<Self, FromClvmError> {
524 Ok(Self(
525 node_to_bytes(a, node)
526 .map_err(|error| FromClvmError::Custom(error.to_string()))?
527 .into(),
528 ))
529 }
530}
531
532impl ToClvm<Allocator> for Program {
533 fn to_clvm(&self, a: &mut Allocator) -> std::result::Result<NodePtr, ToClvmError> {
534 node_from_bytes(a, self.0.as_ref()).map_err(|error| ToClvmError::Custom(error.to_string()))
535 }
536}
537
538#[cfg(test)]
539mod tests {
540 use super::*;
541
542 #[test]
543 fn program_roundtrip() {
544 let a = &mut Allocator::new();
545 let expected = "ff01ff02ff62ff0480";
546 let expected_bytes = hex::decode(expected).unwrap();
547
548 let ptr = node_from_bytes(a, &expected_bytes).unwrap();
549 let program = Program::from_clvm(a, ptr).unwrap();
550
551 let round_trip = program.to_clvm(a).unwrap();
552 assert_eq!(expected, hex::encode(node_to_bytes(a, round_trip).unwrap()));
553 }
554
555 #[test]
556 fn program_run() {
557 let a = &mut Allocator::new();
558
559 let prg = Program::from_bytes(&hex::decode("ff10ff02ff0580").expect("hex::decode"))
561 .expect("from_bytes");
562 let (cost, result) = prg.run(a, 0, 1000, &[1300, 37]).expect("run");
563 assert_eq!(cost, 869);
564 assert_eq!(a.number(result), 1337.into());
565 }
566}
567
568#[cfg(all(test, feature = "serde"))]
569mod serde_tests {
570 use super::*;
571
572 #[test]
573 fn test_program_is_bytes() -> anyhow::Result<()> {
574 let bytes = Bytes::new(vec![1, 2, 3]);
575 let program = Program::new(bytes.clone());
576
577 let bytes_json = serde_json::to_string(&bytes)?;
578 let program_json = serde_json::to_string(&program)?;
579
580 assert_eq!(program_json, bytes_json);
581
582 Ok(())
583 }
584}