• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Chik-Network / chik_rs / 14076764197

26 Mar 2025 06:14AM UTC coverage: 84.616% (+0.1%) from 84.516%
14076764197

push

github

Chik-Network
update 0.21.2

287 of 310 new or added lines in 4 files covered. (92.58%)

77 existing lines in 3 files now uncovered.

14004 of 16550 relevant lines covered (84.62%)

1160098.34 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

52.49
/crates/chik-protocol/src/program.rs
1
use crate::bytes::Bytes;
2
use chik_sha2::Sha256;
3
use chik_traits::chik_error::{Error, Result};
4
use chik_traits::Streamable;
5
use klvm_traits::{FromKlvm, FromKlvmError, ToKlvm, ToKlvmError};
6
use klvmr::allocator::NodePtr;
7
use klvmr::cost::Cost;
8
use klvmr::reduction::EvalErr;
9
use klvmr::run_program;
10
use klvmr::serde::{
11
    node_from_bytes, node_from_bytes_backrefs, node_to_bytes, serialized_length_from_bytes,
12
    serialized_length_from_bytes_trusted,
13
};
14
use klvmr::{Allocator, ChikDialect};
15
#[cfg(feature = "py-bindings")]
16
use pyo3::prelude::*;
17
#[cfg(feature = "py-bindings")]
18
use pyo3::types::PyType;
19
use std::io::Cursor;
20
use std::ops::Deref;
21

22
#[cfg_attr(feature = "py-bindings", pyclass, derive(PyStreamable))]
28,108✔
23
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
24
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
25
pub struct Program(Bytes);
26

27
impl Default for Program {
28
    fn default() -> Self {
×
29
        Self(vec![0x80].into())
×
30
    }
×
31
}
32

33
impl Program {
34
    pub fn new(bytes: Bytes) -> Self {
140✔
35
        Self(bytes)
140✔
36
    }
140✔
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] {
59,928✔
47
        self.0.as_slice()
59,928✔
48
    }
59,928✔
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: ToKlvm<Allocator>>(
96✔
63
        &self,
96✔
64
        a: &mut Allocator,
96✔
65
        flags: u32,
96✔
66
        max_cost: Cost,
96✔
67
        arg: &A,
96✔
68
    ) -> std::result::Result<(Cost, NodePtr), EvalErr> {
96✔
69
        let arg = arg.to_klvm(a).map_err(|_| {
96✔
70
            EvalErr(
×
71
                a.nil(),
×
72
                "failed to convert argument to KLVM objects".to_string(),
×
73
            )
×
74
        })?;
96✔
75
        let program =
96✔
76
            node_from_bytes_backrefs(a, self.0.as_ref()).expect("invalid SerializedProgram");
96✔
77
        let dialect = ChikDialect::new(flags);
96✔
78
        let reduction = run_program(a, &dialect, program, arg, max_cost)?;
96✔
79
        Ok((reduction.0, reduction.1))
96✔
80
    }
96✔
81
}
82

83
impl From<Bytes> for Program {
84
    fn from(value: Bytes) -> Self {
7✔
85
        Self(value)
7✔
86
    }
7✔
87
}
88

89
impl From<Program> for Bytes {
90
    fn from(value: Program) -> Self {
×
91
        value.0
×
92
    }
×
93
}
94

95
impl From<Vec<u8>> for Program {
96
    fn from(value: Vec<u8>) -> Self {
24,844✔
97
        Self(Bytes::new(value))
24,844✔
98
    }
24,844✔
99
}
100

101
impl From<&[u8]> for Program {
102
    fn from(value: &[u8]) -> Self {
336✔
103
        Self(value.into())
336✔
104
    }
336✔
105
}
106

107
impl From<Program> for Vec<u8> {
108
    fn from(value: Program) -> Self {
×
109
        value.0.into()
×
110
    }
×
111
}
112

113
impl AsRef<[u8]> for Program {
114
    fn as_ref(&self) -> &[u8] {
490,294✔
115
        self.0.as_ref()
490,294✔
116
    }
490,294✔
117
}
118

119
impl Deref for Program {
120
    type Target = [u8];
121

122
    fn deref(&self) -> &[u8] {
×
123
        &self.0
×
124
    }
×
125
}
126

127
#[cfg(feature = "arbitrary")]
128
impl<'a> arbitrary::Arbitrary<'a> for Program {
129
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
×
130
        // generate an arbitrary KLVM structure. Not likely a valid program.
×
131
        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
                // make a pair
×
138
                buf.push(0xff);
×
139
                items_left += 2;
×
140
            } else {
×
141
                // make an atom. just single bytes for now
×
142
                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")]
152
use crate::lazy_node::LazyNode;
153

154
#[cfg(feature = "py-bindings")]
155
use chik_traits::{FromJsonDict, ToJsonDict};
156

157
#[cfg(feature = "py-bindings")]
158
use chik_py_streamable_macro::PyStreamable;
159

160
#[cfg(feature = "py-bindings")]
161
use pyo3::types::{PyList, PyTuple};
162

163
#[cfg(feature = "py-bindings")]
164
use klvmr::allocator::SExp;
165

166
#[cfg(feature = "py-bindings")]
167
use pyo3::exceptions::*;
168

169
// TODO: this conversion function should probably be converted to a type holding
170
// the PyAny object implementing the ToKlvm trait. That way, the Program::to()
171
// function could turn a python structure directly into bytes, without taking
172
// the detour via Allocator. propagating python errors through ToKlvmError is a
173
// bit tricky though
174
#[cfg(feature = "py-bindings")]
175
fn klvm_convert(a: &mut Allocator, o: &Bound<'_, PyAny>) -> PyResult<NodePtr> {
26✔
176
    // None
26✔
177
    if o.is_none() {
26✔
UNCOV
178
        Ok(a.nil())
×
179
    // bytes
180
    } else if let Ok(buffer) = o.extract::<&[u8]>() {
26✔
UNCOV
181
        a.new_atom(buffer)
×
UNCOV
182
            .map_err(|e| PyMemoryError::new_err(e.to_string()))
×
183
    // str
184
    } else if let Ok(text) = o.extract::<String>() {
26✔
185
        a.new_atom(text.as_bytes())
4✔
186
            .map_err(|e| PyMemoryError::new_err(e.to_string()))
4✔
187
    // int
188
    } else if let Ok(val) = o.extract::<klvmr::number::Number>() {
22✔
189
        a.new_number(val)
14✔
190
            .map_err(|e| PyMemoryError::new_err(e.to_string()))
14✔
191
    // Tuple (SExp-like)
192
    } else if let Ok(pair) = o.downcast::<PyTuple>() {
8✔
193
        if pair.len() == 2 {
4✔
194
            let left = klvm_convert(a, &pair.get_item(0)?)?;
4✔
195
            let right = klvm_convert(a, &pair.get_item(1)?)?;
4✔
196
            a.new_pair(left, right)
4✔
197
                .map_err(|e| PyMemoryError::new_err(e.to_string()))
4✔
198
        } else {
199
            Err(PyValueError::new_err(format!(
×
200
                "can't cast tuple of size {}",
×
201
                pair.len()
×
202
            )))
×
203
        }
204
    // List
205
    } else if let Ok(list) = o.downcast::<PyList>() {
4✔
206
        let mut rev = Vec::new();
4✔
207
        for py_item in list.iter() {
8✔
208
            rev.push(py_item);
8✔
209
        }
8✔
210
        let mut ret = a.nil();
4✔
211
        for py_item in rev.into_iter().rev() {
8✔
212
            let item = klvm_convert(a, &py_item)?;
8✔
213
            ret = a
8✔
214
                .new_pair(item, ret)
8✔
215
                .map_err(|e| PyMemoryError::new_err(e.to_string()))?;
8✔
216
        }
217
        Ok(ret)
4✔
218
    // SExp (such as klvm.SExp)
UNCOV
219
    } else if let (Ok(atom), Ok(pair)) = (o.getattr("atom"), o.getattr("pair")) {
×
UNCOV
220
        if atom.is_none() {
×
UNCOV
221
            if pair.is_none() {
×
222
                Err(PyTypeError::new_err(format!("invalid SExp item {o}")))
×
223
            } else {
UNCOV
224
                let pair = pair.downcast::<PyTuple>()?;
×
UNCOV
225
                let left = klvm_convert(a, &pair.get_item(0)?)?;
×
UNCOV
226
                let right = klvm_convert(a, &pair.get_item(1)?)?;
×
UNCOV
227
                a.new_pair(left, right)
×
UNCOV
228
                    .map_err(|e| PyMemoryError::new_err(e.to_string()))
×
229
            }
230
        } else {
UNCOV
231
            a.new_atom(atom.extract::<&[u8]>()?)
×
UNCOV
232
                .map_err(|e| PyMemoryError::new_err(e.to_string()))
×
233
        }
234
    // Program itself. This is interpreted as a program in serialized form, and
235
    // just a buffer of that serialization. This is an optimization to finding
236
    // __bytes__() and calling it
UNCOV
237
    } else if let Ok(prg) = o.extract::<Program>() {
×
UNCOV
238
        a.new_atom(prg.0.as_slice())
×
UNCOV
239
            .map_err(|e| PyMemoryError::new_err(e.to_string()))
×
240
    // anything convertible to bytes
241
    } 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
}
26✔
252

253
#[cfg(feature = "py-bindings")]
254
fn klvm_serialize(a: &mut Allocator, o: &Bound<'_, PyAny>) -> PyResult<NodePtr> {
2✔
255
    /*
256
    When passing arguments to run(), there's some special treatment, before falling
257
    back on the regular python -> KLVM conversion (implemented by klvm_convert
258
    above). This function mimics the _serialize() function in python:
259

260
       def _serialize(node: object) -> bytes:
261
           if isinstance(node, list):
262
               serialized_list = bytearray()
263
               for a in node:
264
                   serialized_list += b"\xff"
265
                   serialized_list += _serialize(a)
266
               serialized_list += b"\x80"
267
               return bytes(serialized_list)
268
           if type(node) is SerializedProgram:
269
               return bytes(node)
270
           if type(node) is Program:
271
               return bytes(node)
272
           else:
273
               ret: bytes = SExp.to(node).as_bin()
274
               return ret
275
    */
276

277
    // List
278
    if let Ok(list) = o.downcast::<PyList>() {
2✔
279
        let mut rev = Vec::new();
2✔
280
        for py_item in list.iter() {
2✔
UNCOV
281
            rev.push(py_item);
×
UNCOV
282
        }
×
283
        let mut ret = a.nil();
2✔
284
        for py_item in rev.into_iter().rev() {
2✔
UNCOV
285
            let item = klvm_serialize(a, &py_item)?;
×
UNCOV
286
            ret = a
×
UNCOV
287
                .new_pair(item, ret)
×
UNCOV
288
                .map_err(|e| PyMemoryError::new_err(e.to_string()))?;
×
289
        }
290
        Ok(ret)
2✔
291
    // Program itself
UNCOV
292
    } else if let Ok(prg) = o.extract::<Program>() {
×
UNCOV
293
        Ok(node_from_bytes_backrefs(a, prg.0.as_slice())?)
×
294
    } else {
UNCOV
295
        klvm_convert(a, o)
×
296
    }
297
}
2✔
298

299
#[cfg(feature = "py-bindings")]
UNCOV
300
fn to_program(py: Python<'_>, node: LazyNode) -> PyResult<Bound<'_, PyAny>> {
×
UNCOV
301
    let int_module = PyModule::import(py, "chik.types.blockchain_format.program")?;
×
UNCOV
302
    let ty = int_module.getattr("Program")?;
×
UNCOV
303
    ty.call1((node.into_pyobject(py)?,))
×
UNCOV
304
}
×
305

306
#[cfg(feature = "py-bindings")]
307
#[allow(clippy::needless_pass_by_value)]
308
#[pymethods]
22✔
309
impl 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> {
10✔
319
        let mut a = Allocator::new_limited(500_000_000);
10✔
320
        let klvm = klvm_convert(&mut a, args)?;
10✔
321
        Program::from_klvm(&a, klvm)
10✔
322
            .map_err(|error| PyErr::new::<PyTypeError, _>(error.to_string()))
10✔
323
    }
10✔
324

325
    fn get_tree_hash(&self) -> crate::Bytes32 {
8✔
326
        klvm_utils::tree_hash_from_bytes(self.0.as_ref())
8✔
327
            .unwrap()
8✔
328
            .into()
8✔
329
    }
8✔
330

331
    #[staticmethod]
UNCOV
332
    fn from_program(py: Python<'_>, p: PyObject) -> PyResult<Self> {
×
UNCOV
333
        let buf = p.getattr(py, "__bytes__")?.call0(py)?;
×
UNCOV
334
        let buf = buf.extract::<&[u8]>(py)?;
×
UNCOV
335
        Ok(Self(buf.into()))
×
UNCOV
336
    }
×
337

338
    #[staticmethod]
339
    fn fromhex(h: String) -> Result<Self> {
2✔
340
        let s = if let Some(st) = h.strip_prefix("0x") {
2✔
341
            st
×
342
        } else {
343
            &h[..]
2✔
344
        };
345
        Self::from_bytes(hex::decode(s).map_err(|_| Error::InvalidString)?.as_slice())
2✔
346
    }
2✔
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 klvmr::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>(
2✔
360
        &self,
2✔
361
        py: Python<'a>,
2✔
362
        max_cost: u64,
2✔
363
        args: &Bound<'_, PyAny>,
2✔
364
    ) -> PyResult<(u64, Bound<'a, PyAny>)> {
2✔
365
        #[allow(clippy::used_underscore_items)]
2✔
366
        self._run(py, max_cost, 0, args)
2✔
367
    }
2✔
368

369
    // exposed to python so allowing use of the python private indicator leading underscore
370
    fn _run<'a>(
2✔
371
        &self,
2✔
372
        py: Python<'a>,
2✔
373
        max_cost: u64,
2✔
374
        flags: u32,
2✔
375
        args: &Bound<'_, PyAny>,
2✔
376
    ) -> PyResult<(u64, Bound<'a, PyAny>)> {
2✔
377
        use klvmr::reduction::Response;
378
        use std::rc::Rc;
379

380
        let mut a = Allocator::new_limited(500_000_000);
2✔
381
        // The python behavior here is a bit messy, and is best not emulated
382
        // on the rust side. We must be able to pass a Program as an argument,
383
        // and it being treated as the KLVM structure it represents. In python's
384
        // SerializedProgram, we have a hack where we interpret the first
385
        // "layer" of SerializedProgram, or lists of SerializedProgram this way.
386
        // But if we encounter an Optional or tuple, we defer to the klvm
387
        // wheel's conversion function to SExp. This level does not have any
388
        // special treatment for SerializedProgram (as that would cause a
389
        // circular dependency).
390
        let klvm_args = klvm_serialize(&mut a, args)?;
2✔
391

392
        let r: Response = (|| -> PyResult<Response> {
2✔
393
            let program = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
2✔
394
            let dialect = ChikDialect::new(flags);
2✔
395

2✔
396
            Ok(py.allow_threads(|| run_program(&mut a, &dialect, program, klvm_args, max_cost)))
2✔
397
        })()?;
2✔
398
        match r {
2✔
UNCOV
399
            Ok(reduction) => {
×
UNCOV
400
                let val = LazyNode::new(Rc::new(a), reduction.1);
×
UNCOV
401
                Ok((reduction.0, to_program(py, val)?))
×
402
            }
403
            Err(eval_err) => {
2✔
404
                let blob = node_to_bytes(&a, eval_err.0).ok().map(hex::encode);
2✔
405
                Err(PyValueError::new_err((eval_err.1, blob)))
2✔
406
            }
407
        }
408
    }
2✔
409

UNCOV
410
    fn to_program<'a>(&self, py: Python<'a>) -> PyResult<Bound<'a, PyAny>> {
×
411
        use std::rc::Rc;
UNCOV
412
        let mut a = Allocator::new_limited(500_000_000);
×
UNCOV
413
        let prg = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
×
UNCOV
414
        let prg = LazyNode::new(Rc::new(a), prg);
×
UNCOV
415
        to_program(py, prg)
×
UNCOV
416
    }
×
417

UNCOV
418
    fn uncurry<'a>(&self, py: Python<'a>) -> PyResult<(Bound<'a, PyAny>, Bound<'a, PyAny>)> {
×
419
        use klvm_utils::CurriedProgram;
420
        use std::rc::Rc;
421

UNCOV
422
        let mut a = Allocator::new_limited(500_000_000);
×
UNCOV
423
        let prg = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
×
UNCOV
424
        let Ok(uncurried) = CurriedProgram::<NodePtr, NodePtr>::from_klvm(&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

UNCOV
432
        let mut curried_args = Vec::<NodePtr>::new();
×
UNCOV
433
        let mut args = uncurried.args;
×
434
        loop {
UNCOV
435
            if let SExp::Atom = a.sexp(args) {
×
UNCOV
436
                break;
×
UNCOV
437
            }
×
438
            // the args of curried puzzles are in the form of:
439
            // (c . ((q . <arg1>) . (<rest> . ())))
UNCOV
440
            let (_, ((_, arg), (rest, ()))) =
×
UNCOV
441
                <(
×
UNCOV
442
                    klvm_traits::MatchByte<4>,
×
UNCOV
443
                    (klvm_traits::match_quote!(NodePtr), (NodePtr, ())),
×
UNCOV
444
                ) as FromKlvm<Allocator>>::from_klvm(&a, args)
×
UNCOV
445
                .map_err(|error| PyErr::new::<PyTypeError, _>(error.to_string()))?;
×
UNCOV
446
            curried_args.push(arg);
×
UNCOV
447
            args = rest;
×
448
        }
UNCOV
449
        let mut ret = a.nil();
×
UNCOV
450
        for item in curried_args.into_iter().rev() {
×
UNCOV
451
            ret = a.new_pair(item, ret).map_err(|_e| Error::EndOfBuffer)?;
×
452
        }
UNCOV
453
        let a = Rc::new(a);
×
UNCOV
454
        let prg = LazyNode::new(a.clone(), uncurried.program);
×
UNCOV
455
        let ret = LazyNode::new(a, ret);
×
UNCOV
456
        Ok((to_program(py, prg)?, to_program(py, ret)?))
×
UNCOV
457
    }
×
458
}
459

460
impl 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<()> {
11,226✔
466
        out.extend_from_slice(self.0.as_ref());
11,226✔
467
        Ok(())
11,226✔
468
    }
11,226✔
469

470
    fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self> {
29,248✔
471
        let pos = input.position();
29,248✔
472
        let buf: &[u8] = &input.get_ref()[pos as usize..];
29,248✔
473
        let len = if TRUSTED {
29,248✔
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)?
29,248✔
477
        };
478
        if buf.len() < len as usize {
29,244✔
479
            return Err(Error::EndOfBuffer);
×
480
        }
29,244✔
481
        let program = buf[..len as usize].to_vec();
29,244✔
482
        input.set_position(pos + len);
29,244✔
483
        Ok(Program(program.into()))
29,244✔
484
    }
29,248✔
485
}
486

487
#[cfg(feature = "py-bindings")]
488
impl 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]
×
496
impl 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")]
507
impl FromJsonDict for Program {
508
    fn from_json_dict(o: &Bound<'_, PyAny>) -> PyResult<Self> {
6✔
509
        let bytes = Bytes::from_json_dict(o)?;
6✔
510
        let len =
4✔
511
            serialized_length_from_bytes(bytes.as_slice()).map_err(|_e| Error::EndOfBuffer)?;
6✔
512
        if len as usize != bytes.len() {
4✔
513
            // If the bytes in the JSON string is not a valid KLVM
514
            // serialization, or if it has garbage at the end of the string,
515
            // reject it
516
            return Err(Error::InvalidKlvm)?;
2✔
517
        }
2✔
518
        Ok(Self(bytes))
2✔
519
    }
6✔
520
}
521

522
impl FromKlvm<Allocator> for Program {
523
    fn from_klvm(a: &Allocator, node: NodePtr) -> std::result::Result<Self, FromKlvmError> {
12✔
524
        Ok(Self(
12✔
525
            node_to_bytes(a, node)
12✔
526
                .map_err(|error| FromKlvmError::Custom(error.to_string()))?
12✔
527
                .into(),
12✔
528
        ))
529
    }
12✔
530
}
531

532
impl ToKlvm<Allocator> for Program {
533
    fn to_klvm(&self, a: &mut Allocator) -> std::result::Result<NodePtr, ToKlvmError> {
2,520✔
534
        node_from_bytes(a, self.0.as_ref()).map_err(|error| ToKlvmError::Custom(error.to_string()))
2,520✔
535
    }
2,520✔
536
}
537

538
#[cfg(test)]
539
mod tests {
540
    use super::*;
541

542
    #[test]
543
    fn program_roundtrip() {
2✔
544
        let a = &mut Allocator::new();
2✔
545
        let expected = "ff01ff02ff62ff0480";
2✔
546
        let expected_bytes = hex::decode(expected).unwrap();
2✔
547

2✔
548
        let ptr = node_from_bytes(a, &expected_bytes).unwrap();
2✔
549
        let program = Program::from_klvm(a, ptr).unwrap();
2✔
550

2✔
551
        let round_trip = program.to_klvm(a).unwrap();
2✔
552
        assert_eq!(expected, hex::encode(node_to_bytes(a, round_trip).unwrap()));
2✔
553
    }
2✔
554

555
    #[test]
556
    fn program_run() {
2✔
557
        let a = &mut Allocator::new();
2✔
558

2✔
559
        // (+ 2 5)
2✔
560
        let prg = Program::from_bytes(&hex::decode("ff10ff02ff0580").expect("hex::decode"))
2✔
561
            .expect("from_bytes");
2✔
562
        let (cost, result) = prg.run(a, 0, 1000, &[1300, 37]).expect("run");
2✔
563
        assert_eq!(cost, 869);
2✔
564
        assert_eq!(a.number(result), 1337.into());
2✔
565
    }
2✔
566
}
567

568
#[cfg(all(test, feature = "serde"))]
569
mod serde_tests {
570
    use super::*;
571

572
    #[test]
573
    fn test_program_is_bytes() -> anyhow::Result<()> {
2✔
574
        let bytes = Bytes::new(vec![1, 2, 3]);
2✔
575
        let program = Program::new(bytes.clone());
2✔
576

577
        let bytes_json = serde_json::to_string(&bytes)?;
2✔
578
        let program_json = serde_json::to_string(&program)?;
2✔
579

580
        assert_eq!(program_json, bytes_json);
2✔
581

582
        Ok(())
2✔
583
    }
2✔
584
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc