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

Chik-Network / chik_rs / 15659902311

15 Jun 2025 05:25AM UTC coverage: 65.303% (-1.8%) from 67.072%
15659902311

push

github

Chik-Network
update 0.23.0

257 of 694 new or added lines in 6 files covered. (37.03%)

3 existing lines in 1 file now uncovered.

11074 of 16958 relevant lines covered (65.3%)

856462.93 hits per line

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

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

83
impl From<Bytes> for Program {
UNCOV
84
    fn from(value: Bytes) -> Self {
×
UNCOV
85
        Self(value)
×
UNCOV
86
    }
×
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 {
13,512✔
97
        Self(Bytes::new(value))
13,512✔
98
    }
13,512✔
99
}
100

101
impl From<&[u8]> for Program {
102
    fn from(value: &[u8]) -> Self {
770✔
103
        Self(value.into())
770✔
104
    }
770✔
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] {
466,190✔
115
        self.0.as_ref()
466,190✔
116
    }
466,190✔
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> {
×
176
    // None
×
177
    if o.is_none() {
×
178
        Ok(a.nil())
×
179
    // bytes
180
    } else if let Ok(buffer) = o.extract::<&[u8]>() {
×
181
        a.new_atom(buffer)
×
182
            .map_err(|e| PyMemoryError::new_err(e.to_string()))
×
183
    // str
184
    } 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
    // int
188
    } else if let Ok(val) = o.extract::<klvmr::number::Number>() {
×
189
        a.new_number(val)
×
190
            .map_err(|e| PyMemoryError::new_err(e.to_string()))
×
191
    // Tuple (SExp-like)
192
    } else if let Ok(pair) = o.downcast::<PyTuple>() {
×
193
        if pair.len() == 2 {
×
194
            let left = klvm_convert(a, &pair.get_item(0)?)?;
×
195
            let right = klvm_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
    // List
205
    } 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 = klvm_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
    // SExp (such as klvm.SExp)
219
    } 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 = klvm_convert(a, &pair.get_item(0)?)?;
×
226
                let right = klvm_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
    // 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
237
    } 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
    // 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
}
×
252

253
#[cfg(feature = "py-bindings")]
254
fn klvm_serialize(a: &mut Allocator, o: &Bound<'_, PyAny>) -> PyResult<NodePtr> {
×
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>() {
×
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 = klvm_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
    // Program itself
292
    } else if let Ok(prg) = o.extract::<Program>() {
×
293
        Ok(node_from_bytes_backrefs(a, prg.0.as_slice())?)
×
294
    } else {
295
        klvm_convert(a, o)
×
296
    }
297
}
×
298

299
#[cfg(feature = "py-bindings")]
300
fn to_program(py: Python<'_>, node: LazyNode) -> PyResult<Bound<'_, PyAny>> {
×
301
    let int_module = PyModule::import(py, "chik.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]
×
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> {
×
319
        let mut a = Allocator::new_limited(500_000_000);
×
320
        let klvm = klvm_convert(&mut a, args)?;
×
321
        Program::from_klvm(&a, klvm)
×
322
            .map_err(|error| PyErr::new::<PyTypeError, _>(error.to_string()))
×
323
    }
×
324

325
    fn get_tree_hash(&self) -> crate::Bytes32 {
×
326
        klvm_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 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>(
×
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
    // exposed to python so allowing use of the python private indicator leading underscore
370
    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 klvmr::reduction::Response;
378
        use std::rc::Rc;
379

380
        let mut a = Allocator::new_limited(500_000_000);
×
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)?;
×
391

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

×
396
            Ok(py.allow_threads(|| run_program(&mut a, &dialect, program, klvm_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 klvm_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_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

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
            // the args of curried puzzles are in the form of:
439
            // (c . ((q . <arg1>) . (<rest> . ())))
440
            let (_, ((_, arg), (rest, ()))) =
×
441
                <(
×
442
                    klvm_traits::MatchByte<4>,
×
443
                    (klvm_traits::match_quote!(NodePtr), (NodePtr, ())),
×
444
                ) as FromKlvm<Allocator>>::from_klvm(&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

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<()> {
×
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> {
21,850✔
471
        let pos = input.position();
21,850✔
472
        let buf: &[u8] = &input.get_ref()[pos as usize..];
21,850✔
473
        let len = if TRUSTED {
21,850✔
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)?
21,850✔
477
        };
478
        if buf.len() < len as usize {
21,850✔
479
            return Err(Error::EndOfBuffer);
×
480
        }
21,850✔
481
        let program = buf[..len as usize].to_vec();
21,850✔
482
        input.set_position(pos + len);
21,850✔
483
        Ok(Program(program.into()))
21,850✔
484
    }
21,850✔
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> {
×
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
            // 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)?;
×
517
        }
×
518
        Ok(Self(bytes))
×
519
    }
×
520
}
521

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

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

© 2025 Coveralls, Inc