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

ergoplatform / sigma-rust / 12515632034

27 Dec 2024 11:41AM UTC coverage: 78.537% (+0.09%) from 78.445%
12515632034

Pull #797

github

web-flow
Merge 64e1cbcd6 into 1d4ef472c
Pull Request #797: Global.serialize, tree-based versioning for methods

107 of 118 new or added lines in 19 files covered. (90.68%)

6 existing lines in 1 file now uncovered.

11047 of 14066 relevant lines covered (78.54%)

3.05 hits per line

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

83.51
/ergotree-ir/src/ergo_tree.rs
1
//! ErgoTree
2
use crate::mir::constant::Constant;
3
use crate::mir::constant::TryExtractFromError;
4
use crate::mir::expr::Expr;
5
use crate::serialization::SigmaSerializationError;
6
use crate::serialization::SigmaSerializeResult;
7
use crate::serialization::{
8
    sigma_byte_reader::{SigmaByteRead, SigmaByteReader},
9
    sigma_byte_writer::{SigmaByteWrite, SigmaByteWriter},
10
    SigmaParsingError, SigmaSerializable,
11
};
12
use crate::sigma_protocol::sigma_boolean::ProveDlog;
13
use crate::types::stype::SType;
14

15
use alloc::string::String;
16
use alloc::string::ToString;
17
use alloc::vec;
18
use alloc::vec::Vec;
19
use sigma_ser::vlq_encode::WriteSigmaVlqExt;
20

21
use crate::serialization::constant_store::ConstantStore;
22
use core::convert::TryFrom;
23
use core2::io;
24
use derive_more::From;
25
use io::Cursor;
26
#[cfg(feature = "std")]
27
use std::sync::OnceLock;
28
use thiserror::Error;
29

30
mod tree_header;
31
pub use tree_header::*;
32

33
/// Parsed ErgoTree
34
#[derive(Eq, Debug, Clone)]
35
pub struct ParsedErgoTree {
36
    header: ErgoTreeHeader,
37
    constants: Vec<Constant>,
38
    root: Expr,
39
    #[cfg(feature = "std")]
40
    has_deserialize: OnceLock<bool>,
41
}
42

43
impl ParsedErgoTree {
44
    /// Returns new ParsedTree with a new constant value for a given index in constants list
45
    /// (as stored in serialized ErgoTree), or an error
46
    fn with_constant(self, index: usize, constant: Constant) -> Result<Self, SetConstantError> {
2✔
47
        let mut new_constants = self.constants.clone();
4✔
48
        if let Some(old_constant) = self.constants.get(index) {
4✔
49
            if constant.tpe == old_constant.tpe {
6✔
50
                let _ = core::mem::replace(&mut new_constants[index], constant);
4✔
51
                Ok(Self {
2✔
52
                    constants: new_constants,
2✔
53
                    ..self
54
                })
55
            } else {
56
                Err(SetConstantError::TypeMismatch(format!(
×
57
                    "with_constant: expected constant type to be {:?}, got {:?}",
58
                    old_constant.tpe, constant.tpe
59
                )))
60
            }
61
        } else {
62
            Err(SetConstantError::OutOfBounds(format!(
×
63
                "with_constant: index({0}) out of bounds (lengh = {1})",
64
                index,
65
                self.constants.len()
×
66
            )))
67
        }
68
    }
69

70
    fn template_bytes(&self) -> Result<Vec<u8>, ErgoTreeError> {
1✔
71
        Ok(self.root.sigma_serialize_bytes()?)
1✔
72
    }
73
}
74

75
impl PartialEq for ParsedErgoTree {
76
    fn eq(&self, other: &Self) -> bool {
4✔
77
        self.header == other.header && self.constants == other.constants && self.root == other.root
4✔
78
    }
79
}
80

81
/// Errors on fail to set a new constant value
82
#[derive(Error, PartialEq, Eq, Debug, Clone)]
83
pub enum SetConstantError {
84
    /// Index is out of bounds
85
    #[error("Index is out of bounds: {0}")]
86
    OutOfBounds(String),
87
    /// Existing constant type differs from the provided new constant type
88
    #[error("Existing constant type differs from the provided new constant type: {0}")]
89
    TypeMismatch(String),
90
}
91

92
/// ErgoTree serialization and parsing (deserialization) error
93
#[derive(Error, PartialEq, Eq, Debug, Clone, From)]
94
pub enum ErgoTreeError {
95
    /// ErgoTree header error
96
    #[error("ErgoTree header error: {0:?}")]
97
    HeaderError(ErgoTreeHeaderError),
98
    /// ErgoTree constants error
99
    #[error("ErgoTree constants error: {0:?}")]
100
    ConstantsError(ErgoTreeConstantError),
101
    /// ErgoTree serialization error
102
    #[error("ErgoTree serialization error: {0}")]
103
    RootSerializationError(SigmaSerializationError),
104
    /// Sigma parsing error
105
    #[error("Sigma parsing error: {0:?}")]
106
    SigmaParsingError(SigmaParsingError),
107
    /// IO error
108
    #[error("IO error: {0:?}")]
109
    IoError(String),
110
    /// ErgoTree root error. ErgoTree root TPE should be SigmaProp
111
    #[error("Root Tpe error: expected SigmaProp, got {0}")]
112
    RootTpeError(SType),
113
}
114

115
/// The root of ErgoScript IR. Serialized instances of this class are self sufficient and can be passed around.
116
#[derive(PartialEq, Eq, Debug, Clone, From)]
117
pub enum ErgoTree {
118
    /// Unparsed tree, with original bytes and error
119
    Unparsed {
120
        /// Original tree bytes
121
        tree_bytes: Vec<u8>,
122
        /// Parsing error
123
        error: ErgoTreeError,
124
    },
125
    /// Parsed tree
126
    Parsed(ParsedErgoTree),
127
}
128

129
impl ErgoTree {
130
    fn parsed_tree(&self) -> Result<&ParsedErgoTree, ErgoTreeError> {
7✔
131
        match self {
5✔
132
            ErgoTree::Unparsed {
1✔
133
                tree_bytes: _,
134
                error,
135
            } => Err(error.clone()),
136
            ErgoTree::Parsed(parsed) => Ok(parsed),
7✔
137
        }
138
    }
139

140
    /// Return ErgoTreeHeader. Errors if deserializing ergotree failed
NEW
141
    pub fn header(&self) -> Result<ErgoTreeHeader, ErgoTreeError> {
×
NEW
142
        self.parsed_tree().map(|parsed| parsed.header.clone())
×
143
    }
144

145
    fn sigma_parse_sized<R: SigmaByteRead>(
5✔
146
        r: &mut R,
147
        header: ErgoTreeHeader,
148
    ) -> Result<ParsedErgoTree, ErgoTreeError> {
149
        let constants = if header.is_constant_segregation() {
10✔
150
            ErgoTree::sigma_parse_constants(r)?
11✔
151
        } else {
152
            vec![]
3✔
153
        };
154
        r.set_constant_store(ConstantStore::new(constants.clone()));
10✔
155
        let was_deserialize = r.was_deserialize();
5✔
156
        r.set_deserialize(false);
6✔
157
        let root = Expr::sigma_parse(r)?;
6✔
158
        #[allow(unused)]
159
        let has_deserialize = r.was_deserialize();
14✔
160
        r.set_deserialize(was_deserialize);
7✔
161
        if root.tpe() != SType::SSigmaProp {
7✔
162
            return Err(ErgoTreeError::RootTpeError(root.tpe()));
1✔
163
        }
164
        Ok(ParsedErgoTree {
4✔
165
            header,
×
166
            constants,
7✔
167
            root,
7✔
168
            #[cfg(feature = "std")]
×
169
            has_deserialize: has_deserialize.into(),
13✔
170
        })
171
    }
172

173
    fn sigma_parse_constants<R: SigmaByteRead>(
10✔
174
        r: &mut R,
175
    ) -> Result<Vec<Constant>, SigmaParsingError> {
176
        let constants_len = r.get_u32()?;
10✔
177
        if constants_len as usize > ErgoTree::MAX_CONSTANTS_COUNT {
10✔
178
            return Err(SigmaParsingError::ValueOutOfBounds(
×
179
                "too many constants".to_string(),
×
180
            ));
181
        }
182
        //dbg!(&constants_len);
183
        let mut constants = Vec::with_capacity(constants_len as usize);
10✔
184
        for _ in 0..constants_len {
30✔
185
            let c = Constant::sigma_parse(r)?;
22✔
186
            //dbg!(&c);
187
            constants.push(c);
10✔
188
        }
189
        Ok(constants)
10✔
190
    }
191

192
    /// Creates a tree using provided header and root expression
193
    pub fn new(header: ErgoTreeHeader, expr: &Expr) -> Result<Self, ErgoTreeError> {
8✔
194
        Ok(if header.is_constant_segregation() {
13✔
195
            let mut data = Vec::new();
8✔
196
            let cs = ConstantStore::empty();
9✔
197
            let ww = &mut data;
9✔
198
            let mut w = SigmaByteWriter::new(ww, Some(cs));
9✔
199
            expr.sigma_serialize(&mut w)?;
24✔
200
            #[allow(clippy::unwrap_used)]
201
            // We set constant store earlier
202
            let constants = w.constant_store_mut_ref().unwrap().get_all();
13✔
203
            let cursor = Cursor::new(&mut data[..]);
13✔
204
            let new_cs = ConstantStore::new(constants.clone());
8✔
205
            let mut sr = SigmaByteReader::new(cursor, new_cs);
7✔
206
            let parsed_expr = sr.with_tree_version(header.version(), Expr::sigma_parse)?;
13✔
207
            ErgoTree::Parsed(ParsedErgoTree {
9✔
208
                header,
209
                constants,
9✔
210
                root: parsed_expr,
7✔
211
                #[cfg(feature = "std")]
212
                has_deserialize: OnceLock::new(),
16✔
213
            })
214
        } else {
215
            ErgoTree::Parsed(ParsedErgoTree {
7✔
216
                header,
217
                constants: Vec::new(),
7✔
218
                root: expr.clone(),
7✔
219
                #[cfg(feature = "std")]
220
                has_deserialize: OnceLock::new(),
14✔
221
            })
222
        })
223
    }
224

225
    /// Reasonable limit for the number of constants allowed in the ErgoTree
226
    pub const MAX_CONSTANTS_COUNT: usize = 4096;
227

228
    /// get Expr out of ErgoTree
229
    pub fn proposition(&self) -> Result<Expr, ErgoTreeError> {
7✔
230
        let tree = self.parsed_tree()?.clone();
5✔
231
        let root = tree.root;
5✔
232
        // This tree has ConstantPlaceholder nodes instead of Constant nodes.
233
        // We need to substitute placeholders with constant values.
234
        if tree.header.is_constant_segregation() {
22✔
235
            Ok(root.substitute_constants(&tree.constants)?)
7✔
236
        } else {
237
            Ok(root)
4✔
238
        }
239
    }
240

241
    /// Check if ErgoTree root has [`crate::mir::deserialize_context::DeserializeContext`] or [`crate::mir::deserialize_register::DeserializeRegister`] nodes
242
    pub fn has_deserialize(&self) -> bool {
3✔
243
        match self {
5✔
244
            ErgoTree::Unparsed { .. } => false,
×
245
            #[cfg(feature = "std")]
246
            ErgoTree::Parsed(ParsedErgoTree {
11✔
247
                root,
248
                has_deserialize,
249
                ..
250
            }) => *has_deserialize.get_or_init(|| root.has_deserialize()),
251
            #[cfg(not(feature = "std"))]
252
            ErgoTree::Parsed(ParsedErgoTree { root, .. }) => root.has_deserialize(),
253
        }
254
    }
255

256
    /// Prints with newlines
257
    pub fn debug_tree(&self) -> String {
×
258
        let tree = format!("{:#?}", self);
×
259
        tree
260
    }
261

262
    /// Returns pretty printed tree
263
    pub fn pretty_print(&self) -> Result<(Expr, String), String> {
×
264
        let tree = self.parsed_tree().map_err(|e| e.to_string())?;
×
265
        tree.root.pretty_print().map_err(|e| e.to_string())
×
266
    }
267

268
    /// Returns Base16-encoded serialized bytes
269
    pub fn to_base16_bytes(&self) -> Result<String, SigmaSerializationError> {
×
270
        let bytes = self.sigma_serialize_bytes()?;
×
271
        Ok(base16::encode_lower(&bytes))
×
272
    }
273

274
    /// Returns constants number as stored in serialized ErgoTree or error if the parsing of
275
    /// constants is failed
276
    pub fn constants_len(&self) -> Result<usize, ErgoTreeError> {
2✔
277
        self.parsed_tree().map(|tree| tree.constants.len())
6✔
278
    }
279

280
    /// Returns constant with given index (as stored in serialized ErgoTree)
281
    /// or None if index is out of bounds
282
    /// or error if constants parsing were failed
283
    pub fn get_constant(&self, index: usize) -> Result<Option<Constant>, ErgoTreeError> {
2✔
284
        self.parsed_tree()
2✔
285
            .map(|tree| tree.constants.get(index).cloned())
4✔
286
    }
287

288
    /// Returns all constants (as stored in serialized ErgoTree)
289
    /// or error if constants parsing were failed
290
    pub fn get_constants(&self) -> Result<Vec<Constant>, ErgoTreeError> {
×
291
        self.parsed_tree().map(|tree| tree.constants.clone())
×
292
    }
293

294
    /// Returns new ErgoTree with a new constant value for a given index in constants list (as
295
    /// stored in serialized ErgoTree), or an error. Note that the type of the new constant must
296
    /// coincide with that of the constant being replaced, or an error is returned too.
297
    pub fn with_constant(self, index: usize, constant: Constant) -> Result<Self, ErgoTreeError> {
2✔
298
        let parsed_tree = self.parsed_tree()?.clone();
4✔
299
        Ok(Self::Parsed(
2✔
300
            parsed_tree
6✔
301
                .with_constant(index, constant)
2✔
302
                .map_err(ErgoTreeConstantError::from)?,
×
303
        ))
304
    }
305

306
    /// Serialized proposition expression of SigmaProp type with
307
    /// ConstantPlaceholder nodes instead of Constant nodes
308
    pub fn template_bytes(&self) -> Result<Vec<u8>, ErgoTreeError> {
1✔
309
        self.clone().parsed_tree()?.template_bytes()
2✔
310
    }
311
}
312

313
/// Constants related errors
314
#[derive(Error, PartialEq, Eq, Debug, Clone, From)]
315
pub enum ErgoTreeConstantError {
316
    /// Fail to parse a constant when deserializing an ErgoTree
317
    #[error("Fail to parse a constant when deserializing an ErgoTree: {0}")]
318
    ParsingError(SigmaParsingError),
319
    /// Fail to set a new constant value
320
    #[error("Fail to set a new constant value: {0}")]
321
    SetConstantError(SetConstantError),
322
}
323

324
impl TryFrom<Expr> for ErgoTree {
325
    type Error = ErgoTreeError;
326

327
    fn try_from(expr: Expr) -> Result<Self, Self::Error> {
5✔
328
        match &expr {
5✔
329
            Expr::Const(c) => match c {
4✔
330
                Constant { tpe, .. } if *tpe == SType::SSigmaProp => {
8✔
331
                    ErgoTree::new(ErgoTreeHeader::v0(false), &expr)
8✔
332
                }
333
                _ => ErgoTree::new(ErgoTreeHeader::v0(true), &expr),
2✔
334
            },
335
            _ => ErgoTree::new(ErgoTreeHeader::v0(true), &expr),
10✔
336
        }
337
    }
338
}
339

340
impl SigmaSerializable for ErgoTree {
341
    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> SigmaSerializeResult {
8✔
342
        match self {
8✔
343
            ErgoTree::Unparsed {
1✔
344
                tree_bytes,
×
345
                error: _,
×
346
            } => w.write_all(&tree_bytes[..])?,
×
347
            ErgoTree::Parsed(parsed_tree) => {
8✔
348
                let bytes = {
×
349
                    let mut data = Vec::new();
8✔
350
                    let mut inner_w = SigmaByteWriter::new(&mut data, None);
8✔
351
                    if parsed_tree.header.is_constant_segregation() {
16✔
352
                        inner_w.put_usize_as_u32_unwrapped(parsed_tree.constants.len())?;
7✔
353
                        parsed_tree
16✔
354
                            .constants
×
355
                            .iter()
356
                            .try_for_each(|c| c.sigma_serialize(&mut inner_w))?;
16✔
357
                    };
358
                    parsed_tree.root.sigma_serialize(&mut inner_w)?;
18✔
359
                    data
10✔
360
                };
361

362
                parsed_tree.header.sigma_serialize(w)?;
18✔
363
                if parsed_tree.header.has_size() {
19✔
364
                    w.put_usize_as_u32_unwrapped(bytes.len())?;
10✔
365
                }
366
                w.write_all(&bytes)?;
28✔
367
            }
368
        };
369
        Ok(())
12✔
370
    }
371

372
    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SigmaParsingError> {
13✔
373
        let start_pos = r.position()?;
11✔
374
        let header = ErgoTreeHeader::sigma_parse(r)?;
25✔
375
        r.with_tree_version(header.version(), |r| {
19✔
376
            if header.has_size() {
10✔
377
                let tree_size_bytes = r.get_u32()?;
16✔
378
                let body_pos = r.position()?;
16✔
379
                let mut buf = vec![0u8; tree_size_bytes as usize];
8✔
380
                r.read_exact(buf.as_mut_slice())?;
17✔
NEW
381
                let mut inner_r =
×
382
                    SigmaByteReader::new(Cursor::new(&mut buf[..]), ConstantStore::empty());
16✔
383
                match inner_r.with_tree_version(header.version(), |inner_r| {
24✔
384
                    ErgoTree::sigma_parse_sized(inner_r, header)
8✔
385
                }) {
386
                    Ok(parsed_tree) => Ok(parsed_tree.into()),
14✔
387
                    Err(error) => {
2✔
388
                        let num_bytes = (body_pos - start_pos) + tree_size_bytes as u64;
6✔
389
                        r.seek(io::SeekFrom::Start(start_pos))?;
4✔
390
                        let mut bytes = vec![0; num_bytes as usize];
2✔
391
                        r.read_exact(&mut bytes)?;
13✔
392
                        Ok(ErgoTree::Unparsed {
2✔
393
                            tree_bytes: bytes,
2✔
394
                            error,
2✔
395
                        })
396
                    }
397
                }
398
            } else {
399
                let constants = if header.is_constant_segregation() {
13✔
400
                    ErgoTree::sigma_parse_constants(r)?
15✔
401
                } else {
402
                    vec![]
5✔
403
                };
404
                r.set_constant_store(ConstantStore::new(constants.clone()));
13✔
405
                let root = Expr::sigma_parse(r)?;
8✔
406
                Ok(ErgoTree::Parsed(ParsedErgoTree {
7✔
407
                    header,
6✔
408
                    constants,
6✔
409
                    root,
6✔
NEW
410
                    #[cfg(feature = "std")]
×
411
                    has_deserialize: OnceLock::new(),
13✔
412
                }))
413
            }
414
        })
415
    }
416
}
417

418
impl TryFrom<ErgoTree> for ProveDlog {
419
    type Error = TryExtractFromError;
420

421
    fn try_from(tree: ErgoTree) -> Result<Self, Self::Error> {
2✔
422
        let expr = tree
4✔
423
            .proposition()
424
            .map_err(|_| TryExtractFromError("cannot read root expr".to_string()))?;
×
425
        match expr {
4✔
426
            Expr::Const(Constant {
3✔
427
                tpe: SType::SSigmaProp,
428
                v,
429
            }) => ProveDlog::try_from(v),
430
            _ => Err(TryExtractFromError(
2✔
431
                "expected ProveDlog in the root".to_string(),
2✔
432
            )),
433
        }
434
    }
435
}
436

437
impl From<core2::io::Error> for ErgoTreeError {
438
    fn from(e: core2::io::Error) -> Self {
×
439
        ErgoTreeError::IoError(e.to_string())
×
440
    }
441
}
442

443
#[cfg(feature = "arbitrary")]
444
#[allow(clippy::unwrap_used)]
445
pub(crate) mod arbitrary {
446

447
    use crate::mir::expr::arbitrary::ArbExprParams;
448

449
    use super::*;
450
    use proptest::prelude::*;
451

452
    impl Arbitrary for ErgoTree {
453
        type Parameters = ();
454
        type Strategy = BoxedStrategy<Self>;
455

456
        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
6✔
457
            // make sure that P2PK tree is included
458
            prop_oneof![
29✔
459
                any::<ProveDlog>().prop_map(|p| ErgoTree::new(
27✔
460
                    ErgoTreeHeader::v0(false),
14✔
461
                    &Expr::Const(p.into())
7✔
462
                )
463
                .unwrap()),
7✔
464
                any::<ProveDlog>().prop_map(|p| ErgoTree::new(
33✔
465
                    ErgoTreeHeader::v1(false),
12✔
466
                    &Expr::Const(p.into())
6✔
467
                )
468
                .unwrap()),
6✔
469
                // SigmaProp with constant segregation using both v0 and v1 versions
470
                any_with::<Expr>(ArbExprParams {
17✔
471
                    tpe: SType::SSigmaProp,
7✔
472
                    depth: 1
473
                })
474
                .prop_map(|e| ErgoTree::new(ErgoTreeHeader::v1(true), &e).unwrap()),
17✔
475
                any_with::<Expr>(ArbExprParams {
13✔
476
                    tpe: SType::SSigmaProp,
7✔
477
                    depth: 1
478
                })
479
                .prop_map(|e| ErgoTree::new(ErgoTreeHeader::v0(true), &e).unwrap()),
15✔
480
            ]
481
            .boxed()
482
        }
483
    }
484
}
485

486
#[cfg(test)]
487
#[cfg(feature = "arbitrary")]
488
#[allow(clippy::unreachable)]
489
#[allow(clippy::unwrap_used)]
490
#[allow(clippy::panic)]
491
#[allow(clippy::expect_used)]
492
mod tests {
493
    use super::*;
494
    use crate::chain::address::AddressEncoder;
495
    use crate::chain::address::NetworkPrefix;
496
    use crate::mir::bool_to_sigma::BoolToSigmaProp;
497
    use crate::mir::constant::Literal;
498
    use crate::mir::deserialize_context::DeserializeContext;
499
    use crate::sigma_protocol::sigma_boolean::SigmaProp;
500
    use proptest::prelude::*;
501

502
    proptest! {
503
        #[test]
504
        fn ser_roundtrip(v in any::<ErgoTree>()) {
505
          //dbg!(&v);
506
            let mut data = Vec::new();
507
            let mut w = SigmaByteWriter::new(&mut data, None);
508
            v.sigma_serialize(&mut w).expect("serialization failed");
509
            // sigma_parse
510
            let cursor = Cursor::new(&mut data[..]);
511
            let mut sr = SigmaByteReader::new(cursor, ConstantStore::empty());
512
            let res = ErgoTree::sigma_parse(&mut sr).expect("parse failed");
513
            // prop_assert_eq!(&res.template_bytes().unwrap(), &v.template_bytes().unwrap());
514
            prop_assert_eq![&res, &v];
515
            // sigma_parse_bytes
516
            let res = ErgoTree::sigma_parse_bytes(&data).expect("parse failed");
517
            prop_assert_eq!(&res.template_bytes().unwrap(), &v.template_bytes().unwrap());
518
            prop_assert_eq![res, v];
519
        }
520
    }
521

522
    #[test]
523
    fn deserialization_non_parseable_tree_v0() {
524
        // constants length is set, invalid constant
525
        let bytes = [
526
            ErgoTreeHeader::v0(true).serialized(),
527
            1, // constants quantity
528
            0, // invalid constant type
529
            99,
530
            99,
531
        ];
532
        assert_eq!(
533
            ErgoTree::sigma_parse_bytes(&bytes),
534
            Err(SigmaParsingError::InvalidTypeCode(0))
535
        );
536
    }
537

538
    #[test]
539
    fn deserialization_non_parseable_tree_v1() {
540
        // v1(size is set), constants length is set, invalid constant
541
        let bytes = [
542
            ErgoTreeHeader::v1(true).serialized(),
543
            4, // tree size
544
            1, // constants quantity
545
            0, // invalid constant type
546
            99,
547
            99,
548
        ];
549
        let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
550
        assert!(tree.parsed_tree().is_err(), "parsing constants should fail");
551
        assert_eq!(
552
            tree.sigma_serialize_bytes().unwrap(),
553
            bytes,
554
            "serialization should return original bytes"
555
        );
556
        assert!(
557
            tree.template_bytes().is_err(),
558
            "template bytes should not be parsed"
559
        );
560
    }
561

562
    #[test]
563
    fn deserialization_non_parseable_root_v0() {
564
        // no constant segregation, Expr is invalid
565
        let bytes = [ErgoTreeHeader::v0(false).serialized(), 0, 1];
566
        assert!(ErgoTree::sigma_parse_bytes(&bytes).is_err());
567
    }
568

569
    #[test]
570
    fn deserialization_non_parseable_root_v1() {
571
        // no constant segregation, Expr is invalid
572
        let bytes = [
573
            ErgoTreeHeader::v1(false).serialized(),
574
            2, // tree size
575
            0,
576
            1,
577
        ];
578
        let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
579
        assert!(tree.parsed_tree().is_err(), "parsing root should fail");
580
        assert_eq!(
581
            tree.sigma_serialize_bytes().unwrap(),
582
            bytes,
583
            "serialization should return original bytes"
584
        );
585
        assert!(
586
            tree.template_bytes().is_err(),
587
            "template bytes should not be parsed"
588
        );
589
        // parsing via sigma_parse should fail as well
590
        let mut reader = SigmaByteReader::new(Cursor::new(&bytes), ConstantStore::empty());
591
        let tree = ErgoTree::sigma_parse(&mut reader).unwrap();
592
        assert!(tree.parsed_tree().is_err(), "parsing root should fail");
593
        assert_eq!(
594
            tree.sigma_serialize_bytes().unwrap(),
595
            bytes,
596
            "serialization should return original bytes"
597
        );
598
        assert!(
599
            tree.template_bytes().is_err(),
600
            "template bytes should not be parsed"
601
        );
602
    }
603

604
    #[test]
605
    fn test_constant_segregation_header_flag_support() {
606
        let encoder = AddressEncoder::new(NetworkPrefix::Mainnet);
607
        let address = encoder
608
            .parse_address_from_str("9hzP24a2q8KLPVCUk7gdMDXYc7vinmGuxmLp5KU7k9UwptgYBYV")
609
            .unwrap();
610
        let bytes = address.script().unwrap().sigma_serialize_bytes().unwrap();
611
        assert_eq!(&bytes[..2], vec![0u8, 8u8].as_slice());
612
    }
613

614
    #[test]
615
    fn test_constant_segregation() {
616
        let expr = Expr::Const(Constant {
617
            tpe: SType::SSigmaProp,
618
            v: Literal::SigmaProp(SigmaProp::new(true.into()).into()),
619
        });
620
        let ergo_tree = ErgoTree::new(ErgoTreeHeader::v0(false), &expr).unwrap();
621
        let bytes = ergo_tree.sigma_serialize_bytes().unwrap();
622
        let parsed_expr = ErgoTree::sigma_parse_bytes(&bytes)
623
            .unwrap()
624
            .proposition()
625
            .unwrap();
626
        assert_eq!(parsed_expr, expr)
627
    }
628

629
    #[test]
630
    fn test_constant_len() {
631
        let expr = Expr::Const(Constant {
632
            tpe: SType::SBoolean,
633
            v: Literal::Boolean(false),
634
        });
635
        let ergo_tree = ErgoTree::new(ErgoTreeHeader::v0(true), &expr).unwrap();
636
        assert_eq!(ergo_tree.constants_len().unwrap(), 1);
637
    }
638

639
    #[test]
640
    fn test_get_constant() {
641
        let expr = Expr::Const(Constant {
642
            tpe: SType::SBoolean,
643
            v: Literal::Boolean(false),
644
        });
645
        let ergo_tree = ErgoTree::new(ErgoTreeHeader::v0(true), &expr).unwrap();
646
        assert_eq!(ergo_tree.constants_len().unwrap(), 1);
647
        assert_eq!(ergo_tree.get_constant(0).unwrap().unwrap(), false.into());
648
    }
649

650
    #[test]
651
    fn test_set_constant() {
652
        let expr = Expr::Const(Constant {
653
            tpe: SType::SBoolean,
654
            v: Literal::Boolean(false),
655
        });
656
        let ergo_tree = ErgoTree::new(ErgoTreeHeader::v0(true), &expr).unwrap();
657
        let new_ergo_tree = ergo_tree.with_constant(0, true.into()).unwrap();
658
        assert_eq!(new_ergo_tree.get_constant(0).unwrap().unwrap(), true.into());
659
    }
660

661
    #[test]
662
    fn dex_t2tpool_parse() {
663
        let base16_str = "19a3030f0400040204020404040404060406058080a0f6f4acdbe01b058080a0f6f4acdbe01b050004d00f0400040005000500d81ad601b2a5730000d602e4c6a70405d603db63087201d604db6308a7d605b27203730100d606b27204730200d607b27203730300d608b27204730400d609b27203730500d60ab27204730600d60b9973078c720602d60c999973088c720502720bd60d8c720802d60e998c720702720dd60f91720e7309d6108c720a02d6117e721006d6127e720e06d613998c7209027210d6147e720d06d615730ad6167e721306d6177e720c06d6187e720b06d6199c72127218d61a9c72167218d1edededededed93c27201c2a793e4c672010405720292c17201c1a793b27203730b00b27204730c00938c7205018c720601ed938c7207018c720801938c7209018c720a019593720c730d95720f929c9c721172127e7202069c7ef07213069a9c72147e7215067e9c720e720206929c9c721472167e7202069c7ef0720e069a9c72117e7215067e9c721372020695ed720f917213730e907217a19d721972149d721a7211ed9272199c7217721492721a9c72177211";
664
        let tree_bytes = base16::decode(base16_str.as_bytes()).unwrap();
665
        let tree = ErgoTree::sigma_parse_bytes(&tree_bytes).unwrap();
666
        //dbg!(&tree);
667
        let header = tree.parsed_tree().unwrap().header.clone();
668
        assert!(header.has_size());
669
        assert!(header.is_constant_segregation());
670
        assert_eq!(header.version(), ErgoTreeVersion::V1);
671
        let new_tree = tree
672
            .with_constant(7, 1i64.into())
673
            .unwrap()
674
            .with_constant(8, 2i64.into())
675
            .unwrap();
676
        assert_eq!(new_tree.get_constant(7).unwrap().unwrap(), 1i64.into());
677
        assert_eq!(new_tree.get_constant(8).unwrap().unwrap(), 2i64.into());
678
        assert!(new_tree.sigma_serialize_bytes().unwrap().len() > 1);
679
    }
680

681
    #[test]
682
    fn parse_invalid_677() {
683
        // also see https://github.com/ergoplatform/sigma-rust/issues/587
684
        let base16_str = "cd07021a8e6f59fd4a";
685
        let tree_bytes = base16::decode(base16_str.as_bytes()).unwrap();
686
        let tree = ErgoTree::sigma_parse_bytes(&tree_bytes).unwrap();
687
        //dbg!(&tree);
688
        assert_eq!(tree.sigma_serialize_bytes().unwrap(), tree_bytes);
689
        assert_eq!(
690
            tree,
691
            ErgoTree::Unparsed {
692
                tree_bytes,
693
                error: ErgoTreeError::RootTpeError(SType::SByte)
694
            }
695
        );
696
    }
697

698
    #[test]
699
    fn parse_tree_extra_bytes() {
700
        let valid_ergo_tree_hex =
701
            "0008cd02a706374307f3038cb2f16e7ae9d3e29ca03ea5333681ca06a9bd87baab1164bc";
702
        let valid_ergo_tree_bytes = base16::decode(valid_ergo_tree_hex).unwrap();
703
        // extra bytes at the end will be left unparsed
704
        let invalid_ergo_tree_with_extra_bytes = format!("{}aaaa", valid_ergo_tree_hex);
705
        let bytes = base16::decode(invalid_ergo_tree_with_extra_bytes.as_bytes()).unwrap();
706
        let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
707
        assert_eq!(tree.sigma_serialize_bytes().unwrap(), valid_ergo_tree_bytes);
708
    }
709

710
    #[test]
711
    fn parse_p2pk_672() {
712
        // see https://github.com/ergoplatform/sigma-rust/issues/672
713
        let valid_p2pk = "0e2103e02fa2bbd85e9298aa37fe2634602a0fba746234fe2a67f04d14deda55fac491";
714
        let bytes = base16::decode(valid_p2pk).unwrap();
715
        let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
716
        //dbg!(&tree);
717
        assert_eq!(tree.sigma_serialize_bytes().unwrap(), bytes);
718
        assert_eq!(
719
            tree,
720
            ErgoTree::Unparsed {
721
                tree_bytes: bytes,
722
                error: ErgoTreeError::RootTpeError(SType::SShort)
723
            }
724
        );
725
    }
726

727
    #[test]
728
    fn parse_tree_707() {
729
        // see https://github.com/ergoplatform/sigma-rust/issues/707
730
        let ergo_tree_hex =
731
            "100208cd03553448c194fdd843c87d080f5e8ed983f5bb2807b13b45a9683bba8c7bfb5ae808cd0354c06b1af711e51986d787ff1df2883fcaf8d34865fea720f549e382063a08ebd1eb0273007301";
732
        let bytes = base16::decode(ergo_tree_hex.as_bytes()).unwrap();
733
        let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
734
        //dbg!(&tree);
735
        assert!(tree.parsed_tree().is_ok());
736
    }
737

738
    // Test Ergotree.proposition() for contract with some constants segregated already and some not. See: https://github.com/ergoplatform/sigma-rust/issues/757
739
    #[test]
740
    fn test_contract_template() {
741
        let ergo_tree_hex =
742
            "10010e20007a24c677a4dc0fdbeaa1c6db1052fc1839b7675851358aaf96823b2245408bd80ed60183200202de02ae02cf025b026402ba02d602f50257020b02ad020a0261020c024e02480249025702cf0247028202300284020002bc02900240024c021d0214021002dad602d9010263e4c672020464d603d901033c0c630eb58c720301d9010563aedb63087205d901074d0e938c7207018c720302d604b2da7203018602db6501fe7300040000d605e3010ed606d9010632b4e4720604020442d607d9010763b2db63087207040000d608da720701a7d609b2da7203018602a58c720801040000d60ad9010a63b2db6308720a040200d60bd9010b0ed801d60ddc640bda72020172040283020e7201720be472059683020193b1dad9010e3c0c630eb58c720e01d901106393cbc272108c720e02018602a4da720601b2720d0402000402dad9010e0e9683030193cbc27209720e93da72070172097208938cda720a017209018cda720a01a70101da720601b2720d040000d60ce4e30002d60ddc0c1aa402a70400d60ed9010e05958f720e0580020402958f720e058080020404958f720e05808080020406958f720e0580808080020408958f720e05808080808002040a958f720e0580808080808002040c958f720e058080808080808002040e958f720e0580808080808080800204100412d197830801dad9010f029593720f0200da720b0183200202030292020802bc024e02ef029a020302e802d7028b0286026302a3020102bb025f02ad02dc02a7028b02e1029d027f02e5023502b302c6024c02be02fe0242010001720cdad9010f029593720f0202da720b01832002028b02c7028f021c026a02ae02c9021e0262028e021502cf0266028c021602cc021e029b02d802e402b902b702e1026d0263021802b502f5022302a502e902bd010001720cdad9010f029593720f0201da720b01832002028802300261022c02520235025f026f0228020d0212029702f1029f026702b0027802c902da02a702d702b0024b0245029c029102cc02640249025702c20280010001720cdad9010f029593720f0203da720b01832002024f02d802b002d602d9028202420272026f025702b302df02a6028602120267029202b802e50205026e021d025102b602e9020d0268028002cf022d02cd02c5010001720cdad9010f029593720f0204da720b018320020289022e026f024702a1020d025c029002b8027a02d402860233025502ce02ad020002c302e202980232021702ee021502530232025302cd029a0260022502c2010001720cdad9010f029593720f0205da720b01832002023a02110295025c0247021902e5028802bc02e602a70261021d022702bd021f02df02db02570238025c02ae02e2026602d80204020c0289024f021c022e021d010001720cdad9010f029593720f0206da720b0183200202090282020f02cb0288027102fb0245020c023e020602b702cb025e022702b002450250028702a302660262021a029d02de0275028202a002190211021e023e010001720cdad9010f029593720f0207dad901113c0e639592720db1a50100d809d613b2a5720d00d614c17213d615c1a7d616c27213d617c4a7d618c2a7d6198cc7a701d61ac47213d61b8cc772130196830401927214997215058092f40193cb7216da720601b2dc640bda7202018c7211020283010e8c721101e5720583000204000093b472179a9ada720e017215b17218da720e017e721905b17217b4721a9a9ada720e017214b17216da720e017e721b05b1721a978302019299721b72190480c33d947218721601860272017204010001720c";
743
        let bytes = base16::decode(ergo_tree_hex.as_bytes()).unwrap();
744
        let tree = ErgoTree::sigma_parse_bytes(&bytes).unwrap();
745
        tree.proposition().unwrap();
746
    }
747

748
    fn has_deserialize(tree: ErgoTree) -> bool {
749
        let ErgoTree::Parsed(ParsedErgoTree {
750
            has_deserialize, ..
751
        }) = ErgoTree::sigma_parse_bytes(&tree.sigma_serialize_bytes().unwrap()).unwrap()
752
        else {
753
            unreachable!();
754
        };
755
        *has_deserialize.get().unwrap()
756
    }
757

758
    #[test]
759
    fn test_lazy_has_deserialize() {
760
        let has_deserialize_expr: Expr = BoolToSigmaProp {
761
            input: Box::new(
762
                DeserializeContext {
763
                    tpe: SType::SBoolean,
764
                    id: 0,
765
                }
766
                .into(),
767
            ),
768
        }
769
        .into();
770
        let tree = ErgoTree::new(ErgoTreeHeader::v1(false), &has_deserialize_expr).unwrap();
771
        assert!(has_deserialize(tree));
772
        let no_deserialize_expr: Expr = BoolToSigmaProp {
773
            input: Box::new(true.into()),
774
        }
775
        .into();
776
        let tree = ErgoTree::new(ErgoTreeHeader::v1(false), &no_deserialize_expr).unwrap();
777
        assert!(!has_deserialize(tree));
778
    }
779
}
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