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

Chik-Network / chik_rs / 15696863563

17 Jun 2025 02:46AM UTC coverage: 65.248% (-0.06%) from 65.303%
15696863563

push

github

Chik-Network
update 0.24.0

60 of 180 new or added lines in 13 files covered. (33.33%)

3 existing lines in 2 files now uncovered.

11070 of 16966 relevant lines covered (65.25%)

856058.65 hits per line

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

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

28
#[cfg(feature = "py-bindings")]
29
use klvm_utils::CurriedProgram;
30

NEW
31
#[cfg_attr(feature = "py-bindings", pyclass(subclass), derive(PyStreamable))]
×
32
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
33
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
34
pub struct Program(Bytes);
35

36
impl Default for Program {
37
    fn default() -> Self {
×
38
        Self(vec![0x80].into())
×
39
    }
×
40
}
41

42
impl Program {
43
    pub fn new(bytes: Bytes) -> Self {
230✔
44
        Self(bytes)
230✔
45
    }
230✔
46

47
    pub fn len(&self) -> usize {
×
48
        self.0.len()
×
49
    }
×
50

51
    pub fn is_empty(&self) -> bool {
×
52
        self.0.is_empty()
×
53
    }
×
54

55
    pub fn as_slice(&self) -> &[u8] {
68,282✔
56
        self.0.as_slice()
68,282✔
57
    }
68,282✔
58

59
    pub fn to_vec(&self) -> Vec<u8> {
×
60
        self.0.to_vec()
×
61
    }
×
62

63
    pub fn into_inner(self) -> Bytes {
×
64
        self.0
×
65
    }
×
66

67
    pub fn into_bytes(self) -> Vec<u8> {
×
68
        self.0.into_inner()
×
69
    }
×
70

71
    pub fn run<A: ToKlvm<Allocator>>(
12✔
72
        &self,
12✔
73
        a: &mut Allocator,
12✔
74
        flags: u32,
12✔
75
        max_cost: Cost,
12✔
76
        arg: &A,
12✔
77
    ) -> std::result::Result<(Cost, NodePtr), EvalErr> {
12✔
78
        let arg = arg.to_klvm(a).map_err(|_| {
12✔
79
            EvalErr(
×
80
                a.nil(),
×
81
                "failed to convert argument to KLVM objects".to_string(),
×
82
            )
×
83
        })?;
12✔
84
        let program =
12✔
85
            node_from_bytes_backrefs(a, self.0.as_ref()).expect("invalid SerializedProgram");
12✔
86
        let dialect = ChikDialect::new(flags);
12✔
87
        let reduction = run_program(a, &dialect, program, arg, max_cost)?;
12✔
88
        Ok((reduction.0, reduction.1))
12✔
89
    }
12✔
90
}
91

92
impl From<Bytes> for Program {
93
    fn from(value: Bytes) -> Self {
×
94
        Self(value)
×
95
    }
×
96
}
97

98
impl From<Program> for Bytes {
99
    fn from(value: Program) -> Self {
×
100
        value.0
×
101
    }
×
102
}
103

104
impl From<Vec<u8>> for Program {
105
    fn from(value: Vec<u8>) -> Self {
13,512✔
106
        Self(Bytes::new(value))
13,512✔
107
    }
13,512✔
108
}
109

110
impl From<&[u8]> for Program {
111
    fn from(value: &[u8]) -> Self {
770✔
112
        Self(value.into())
770✔
113
    }
770✔
114
}
115

116
impl From<Program> for Vec<u8> {
117
    fn from(value: Program) -> Self {
×
118
        value.0.into()
×
119
    }
×
120
}
121

122
impl AsRef<[u8]> for Program {
123
    fn as_ref(&self) -> &[u8] {
466,190✔
124
        self.0.as_ref()
466,190✔
125
    }
466,190✔
126
}
127

128
impl Deref for Program {
129
    type Target = [u8];
130

131
    fn deref(&self) -> &[u8] {
×
132
        &self.0
×
133
    }
×
134
}
135

136
#[cfg(feature = "arbitrary")]
137
impl<'a> arbitrary::Arbitrary<'a> for Program {
138
    fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
×
139
        // generate an arbitrary KLVM structure. Not likely a valid program.
×
140
        let mut items_left = 1;
×
141
        let mut total_items = 0;
×
142
        let mut buf = Vec::<u8>::with_capacity(200);
×
143

144
        while items_left > 0 {
×
145
            if total_items < 100 && u.ratio(1, 4).unwrap() {
×
146
                // make a pair
×
147
                buf.push(0xff);
×
148
                items_left += 2;
×
149
            } else {
×
150
                // make an atom. just single bytes for now
×
151
                buf.push(u.int_in_range(0..=0x80).unwrap());
×
152
            }
×
153
            total_items += 1;
×
154
            items_left -= 1;
×
155
        }
156
        Ok(Self(buf.into()))
×
157
    }
×
158
}
159

160
#[cfg(feature = "py-bindings")]
161
use chik_traits::{FromJsonDict, ToJsonDict};
162

163
#[cfg(feature = "py-bindings")]
164
use chik_py_streamable_macro::PyStreamable;
165

166
#[cfg(feature = "py-bindings")]
167
use pyo3::types::{PyList, PyTuple};
168

169
#[cfg(feature = "py-bindings")]
170
use pyo3::exceptions::*;
171

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

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

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

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

302
#[cfg(feature = "py-bindings")]
303
#[allow(clippy::needless_pass_by_value)]
304
#[pymethods]
×
305
impl Program {
306
    #[pyo3(name = "default")]
307
    #[staticmethod]
308
    fn py_default() -> Self {
×
309
        Self::default()
×
310
    }
×
311

312
    #[staticmethod]
313
    #[pyo3(name = "to")]
314
    fn py_to(args: &Bound<'_, PyAny>) -> PyResult<Program> {
×
315
        let mut a = Allocator::new_limited(500_000_000);
×
316
        let klvm = klvm_convert(&mut a, args)?;
×
317
        Program::from_klvm(&a, klvm)
×
318
            .map_err(|error| PyErr::new::<PyTypeError, _>(error.to_string()))
×
319
    }
×
320

321
    fn get_tree_hash(&self) -> crate::Bytes32 {
×
322
        klvm_utils::tree_hash_from_bytes(self.0.as_ref())
×
323
            .unwrap()
×
324
            .into()
×
325
    }
×
326

327
    #[staticmethod]
328
    fn fromhex(h: String) -> Result<Self> {
×
329
        let s = if let Some(st) = h.strip_prefix("0x") {
×
330
            st
×
331
        } else {
332
            &h[..]
×
333
        };
334
        Self::from_bytes(hex::decode(s).map_err(|_| Error::InvalidString)?.as_slice())
×
335
    }
×
336

NEW
337
    fn run_rust(
×
338
        &self,
×
NEW
339
        py: Python<'_>,
×
340
        max_cost: u64,
×
341
        flags: u32,
×
342
        args: &Bound<'_, PyAny>,
×
NEW
343
    ) -> PyResult<(u64, LazyNode)> {
×
344
        use klvmr::reduction::Response;
345

UNCOV
346
        let mut a = Allocator::new_limited(500_000_000);
×
347
        // The python behavior here is a bit messy, and is best not emulated
348
        // on the rust side. We must be able to pass a Program as an argument,
349
        // and it being treated as the KLVM structure it represents. In python's
350
        // SerializedProgram, we have a hack where we interpret the first
351
        // "layer" of SerializedProgram, or lists of SerializedProgram this way.
352
        // But if we encounter an Optional or tuple, we defer to the klvm
353
        // wheel's conversion function to SExp. This level does not have any
354
        // special treatment for SerializedProgram (as that would cause a
355
        // circular dependency).
356
        let klvm_args = klvm_serialize(&mut a, args)?;
×
357

358
        let r: Response = (|| -> PyResult<Response> {
×
359
            let program = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
×
360
            let dialect = ChikDialect::new(flags);
×
361

×
362
            Ok(py.allow_threads(|| run_program(&mut a, &dialect, program, klvm_args, max_cost)))
×
363
        })()?;
×
364
        match r {
×
365
            Ok(reduction) => {
×
366
                let val = LazyNode::new(Rc::new(a), reduction.1);
×
NEW
367
                Ok((reduction.0, val))
×
368
            }
369
            Err(eval_err) => {
×
370
                let blob = node_to_bytes(&a, eval_err.0).ok().map(hex::encode);
×
371
                Err(PyValueError::new_err((eval_err.1, blob)))
×
372
            }
373
        }
374
    }
×
375

NEW
376
    fn uncurry_rust(&self) -> PyResult<(LazyNode, LazyNode)> {
×
UNCOV
377
        let mut a = Allocator::new_limited(500_000_000);
×
378
        let prg = node_from_bytes_backrefs(&mut a, self.0.as_ref())?;
×
379
        let Ok(uncurried) = CurriedProgram::<NodePtr, NodePtr>::from_klvm(&a, prg) else {
×
380
            let a = Rc::new(a);
×
381
            let prg = LazyNode::new(a.clone(), prg);
×
382
            let ret = a.nil();
×
383
            let ret = LazyNode::new(a, ret);
×
NEW
384
            return Ok((prg, ret));
×
385
        };
386

387
        let mut curried_args = Vec::<NodePtr>::new();
×
388
        let mut args = uncurried.args;
×
389
        loop {
390
            if let SExp::Atom = a.sexp(args) {
×
391
                break;
×
392
            }
×
393
            // the args of curried puzzles are in the form of:
394
            // (c . ((q . <arg1>) . (<rest> . ())))
395
            let (_, ((_, arg), (rest, ()))) =
×
396
                <(
×
397
                    klvm_traits::MatchByte<4>,
×
398
                    (klvm_traits::match_quote!(NodePtr), (NodePtr, ())),
×
399
                ) as FromKlvm<Allocator>>::from_klvm(&a, args)
×
400
                .map_err(|error| PyErr::new::<PyTypeError, _>(error.to_string()))?;
×
401
            curried_args.push(arg);
×
402
            args = rest;
×
403
        }
404
        let mut ret = a.nil();
×
405
        for item in curried_args.into_iter().rev() {
×
406
            ret = a.new_pair(item, ret).map_err(|_e| Error::EndOfBuffer)?;
×
407
        }
408
        let a = Rc::new(a);
×
409
        let prg = LazyNode::new(a.clone(), uncurried.program);
×
410
        let ret = LazyNode::new(a, ret);
×
NEW
411
        Ok((prg, ret))
×
412
    }
×
413
}
414

415
impl Streamable for Program {
416
    fn update_digest(&self, digest: &mut Sha256) {
×
417
        digest.update(&self.0);
×
418
    }
×
419

420
    fn stream(&self, out: &mut Vec<u8>) -> Result<()> {
×
421
        out.extend_from_slice(self.0.as_ref());
×
422
        Ok(())
×
423
    }
×
424

425
    fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self> {
21,850✔
426
        let pos = input.position();
21,850✔
427
        let buf: &[u8] = &input.get_ref()[pos as usize..];
21,850✔
428
        let len = if TRUSTED {
21,850✔
429
            serialized_length_from_bytes_trusted(buf).map_err(|_e| Error::EndOfBuffer)?
×
430
        } else {
431
            serialized_length_from_bytes(buf).map_err(|_e| Error::EndOfBuffer)?
21,850✔
432
        };
433
        if buf.len() < len as usize {
21,850✔
434
            return Err(Error::EndOfBuffer);
×
435
        }
21,850✔
436
        let program = buf[..len as usize].to_vec();
21,850✔
437
        input.set_position(pos + len);
21,850✔
438
        Ok(Program(program.into()))
21,850✔
439
    }
21,850✔
440
}
441

442
#[cfg(feature = "py-bindings")]
443
impl ToJsonDict for Program {
444
    fn to_json_dict(&self, py: Python<'_>) -> PyResult<PyObject> {
×
445
        self.0.to_json_dict(py)
×
446
    }
×
447
}
448

449
#[cfg(feature = "py-bindings")]
450
#[pymethods]
×
451
impl Program {
452
    #[classmethod]
453
    #[pyo3(name = "from_parent")]
454
    pub fn from_parent(_cls: &Bound<'_, PyType>, _instance: &Self) -> PyResult<PyObject> {
×
455
        Err(PyNotImplementedError::new_err(
×
456
            "This class does not support from_parent().",
×
457
        ))
×
458
    }
×
459
}
460

461
#[cfg(feature = "py-bindings")]
462
impl FromJsonDict for Program {
463
    fn from_json_dict(o: &Bound<'_, PyAny>) -> PyResult<Self> {
×
464
        let bytes = Bytes::from_json_dict(o)?;
×
465
        let len =
×
466
            serialized_length_from_bytes(bytes.as_slice()).map_err(|_e| Error::EndOfBuffer)?;
×
467
        if len as usize != bytes.len() {
×
468
            // If the bytes in the JSON string is not a valid KLVM
469
            // serialization, or if it has garbage at the end of the string,
470
            // reject it
471
            return Err(Error::InvalidKlvm)?;
×
472
        }
×
473
        Ok(Self(bytes))
×
474
    }
×
475
}
476

477
impl FromKlvm<Allocator> for Program {
478
    fn from_klvm(a: &Allocator, node: NodePtr) -> std::result::Result<Self, FromKlvmError> {
2✔
479
        Ok(Self(
2✔
480
            node_to_bytes(a, node)
2✔
481
                .map_err(|error| FromKlvmError::Custom(error.to_string()))?
2✔
482
                .into(),
2✔
483
        ))
484
    }
2✔
485
}
486

487
impl ToKlvm<Allocator> for Program {
488
    fn to_klvm(&self, a: &mut Allocator) -> std::result::Result<NodePtr, ToKlvmError> {
2,840✔
489
        node_from_bytes(a, self.0.as_ref()).map_err(|error| ToKlvmError::Custom(error.to_string()))
2,840✔
490
    }
2,840✔
491
}
492

493
#[cfg(test)]
494
mod tests {
495
    use super::*;
496

497
    #[test]
498
    fn program_roundtrip() {
2✔
499
        let a = &mut Allocator::new();
2✔
500
        let expected = "ff01ff02ff62ff0480";
2✔
501
        let expected_bytes = hex::decode(expected).unwrap();
2✔
502

2✔
503
        let ptr = node_from_bytes(a, &expected_bytes).unwrap();
2✔
504
        let program = Program::from_klvm(a, ptr).unwrap();
2✔
505

2✔
506
        let round_trip = program.to_klvm(a).unwrap();
2✔
507
        assert_eq!(expected, hex::encode(node_to_bytes(a, round_trip).unwrap()));
2✔
508
    }
2✔
509

510
    #[test]
511
    fn program_run() {
2✔
512
        let a = &mut Allocator::new();
2✔
513

2✔
514
        // (+ 2 5)
2✔
515
        let prg = Program::from_bytes(&hex::decode("ff10ff02ff0580").expect("hex::decode"))
2✔
516
            .expect("from_bytes");
2✔
517
        let (cost, result) = prg.run(a, 0, 1000, &[1300, 37]).expect("run");
2✔
518
        assert_eq!(cost, 869);
2✔
519
        assert_eq!(a.number(result), 1337.into());
2✔
520
    }
2✔
521
}
522

523
#[cfg(all(test, feature = "serde"))]
524
mod serde_tests {
525
    use super::*;
526

527
    #[test]
528
    fn test_program_is_bytes() -> anyhow::Result<()> {
2✔
529
        let bytes = Bytes::new(vec![1, 2, 3]);
2✔
530
        let program = Program::new(bytes.clone());
2✔
531

532
        let bytes_json = serde_json::to_string(&bytes)?;
2✔
533
        let program_json = serde_json::to_string(&program)?;
2✔
534

535
        assert_eq!(program_json, bytes_json);
2✔
536

537
        Ok(())
2✔
538
    }
2✔
539
}
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