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

ergoplatform / sigma-rust / 13994202105

21 Mar 2025 02:37PM UTC coverage: 78.402% (+0.07%) from 78.333%
13994202105

Pull #801

github

web-flow
Merge aaf043c2f into b5bdb7089
Pull Request #801: Add Header.checkPow

37 of 43 new or added lines in 7 files covered. (86.05%)

10 existing lines in 1 file now uncovered.

11257 of 14358 relevant lines covered (78.4%)

6.15 hits per line

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

96.0
/ergotree-interpreter/src/eval/sheader.rs
1
//! Evaluating predefined `Header` (or SHeader) type properties
2

3
use alloc::sync::Arc;
4
use core::convert::TryInto;
5

6
use alloc::vec::Vec;
7
use ergo_chain_types::Header;
8
use ergotree_ir::{
9
    bigint256::BigInt256,
10
    mir::{constant::TryExtractInto, value::Value},
11
};
12

13
use super::{EvalError, EvalFn};
14

15
pub(crate) static VERSION_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
16
    let header = obj.try_extract_into::<Header>()?;
6✔
17
    Ok((header.version as i8).into())
4✔
18
};
19

20
pub(crate) static ID_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
21
    let header = obj.try_extract_into::<Header>()?;
4✔
22
    Ok(Into::<Vec<i8>>::into(header.id).into())
4✔
23
};
24

25
pub(crate) static PARENT_ID_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
26
    let header = obj.try_extract_into::<Header>()?;
4✔
27
    Ok(Into::<Vec<i8>>::into(header.parent_id).into())
4✔
28
};
29

30
pub(crate) static AD_PROOFS_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
31
    let header = obj.try_extract_into::<Header>()?;
4✔
32
    Ok(Into::<Vec<i8>>::into(header.ad_proofs_root).into())
4✔
33
};
34

35
pub(crate) static STATE_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
36
    let header = obj.try_extract_into::<Header>()?;
4✔
37
    Ok(Into::<Vec<i8>>::into(header.state_root).into())
4✔
38
};
39

40
pub(crate) static TRANSACTION_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
41
    let header = obj.try_extract_into::<Header>()?;
4✔
42
    Ok(Into::<Vec<i8>>::into(header.transaction_root).into())
4✔
43
};
44

45
pub(crate) static EXTENSION_ROOT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
46
    let header = obj.try_extract_into::<Header>()?;
4✔
47
    Ok(Into::<Vec<i8>>::into(header.extension_root).into())
4✔
48
};
49

50
pub(crate) static TIMESTAMP_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
51
    let header = obj.try_extract_into::<Header>()?;
4✔
52
    Ok((header.timestamp as i64).into())
4✔
53
};
54

55
pub(crate) static N_BITS_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
56
    let header = obj.try_extract_into::<Header>()?;
4✔
57
    Ok((header.n_bits as i64).into())
4✔
58
};
59

60
pub(crate) static HEIGHT_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
61
    let header = obj.try_extract_into::<Header>()?;
4✔
62
    Ok((header.height as i32).into())
4✔
63
};
64

65
pub(crate) static MINER_PK_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
66
    let header = obj.try_extract_into::<Header>()?;
4✔
67
    Ok(Arc::new(*header.autolykos_solution.miner_pk).into())
6✔
68
};
69

70
pub(crate) static POW_ONETIME_PK_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
71
    let header = obj.try_extract_into::<Header>()?;
4✔
72
    Ok((*header.autolykos_solution.pow_onetime_pk.unwrap_or_default()).into())
6✔
73
};
74

75
pub(crate) static POW_NONCE_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
76
    let header = obj.try_extract_into::<Header>()?;
4✔
77
    Ok(header.autolykos_solution.nonce.into())
4✔
78
};
79

80
pub(crate) static POW_DISTANCE_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
81
    let header = obj.try_extract_into::<Header>()?;
4✔
82
    let pow_distance: BigInt256 = header
4✔
83
        .autolykos_solution
84
        .pow_distance
85
        .unwrap_or_default()
86
        .try_into()
87
        .map_err(EvalError::Misc)?;
×
88
    Ok(pow_distance.into())
4✔
89
};
90

91
pub(crate) static VOTES_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
2✔
92
    let header = obj.try_extract_into::<Header>()?;
4✔
93
    Ok(Into::<Vec<u8>>::into(header.votes).into())
4✔
94
};
95

96
pub(crate) static CHECK_POW_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| match obj {
4✔
97
    Value::Header(header) => Ok(header.check_pow()?.into()),
2✔
NEW
98
    _ => Err(EvalError::UnexpectedValue(format!(
×
99
        "SHeader.checkpow expected obj to be Value::Global, got {:?}",
100
        obj
101
    ))),
102
};
103

104
#[cfg(test)]
105
#[cfg(feature = "arbitrary")]
106
#[allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]
107
mod tests {
108
    use core::convert::{TryFrom, TryInto};
109

110
    use alloc::{boxed::Box, vec::Vec};
111
    use ergo_chain_types::{BlockId, Digest, Digest32, EcPoint, Votes};
112
    use ergotree_ir::{
113
        bigint256::BigInt256,
114
        chain::context::Context,
115
        mir::{
116
            coll_by_index::ByIndex, expr::Expr, method_call::MethodCall,
117
            property_call::PropertyCall,
118
        },
119
        types::{
120
            scontext::{self, HEADERS_PROPERTY},
121
            sheader,
122
            smethod::SMethod,
123
        },
124
    };
125
    use sigma_test_util::force_any_val;
126
    use sigma_util::AsVecU8;
127

128
    use crate::eval::tests::{eval_out, try_eval_out_wo_ctx};
129

130
    // Index in Context.headers array
131
    const HEADER_INDEX: usize = 0;
132

133
    // Evaluates `Header.minerPk`, `Header.powOnetimePk`
134
    fn eval_header_pks(ctx: &Context<'static>) -> [Box<EcPoint>; 2] {
135
        let miner_pk = eval_out::<EcPoint>(
136
            &create_get_header_property_expr(sheader::MINER_PK_PROPERTY.clone()),
137
            ctx,
138
        );
139
        let pow_onetime_pk = eval_out::<EcPoint>(
140
            &create_get_header_property_expr(sheader::POW_ONETIME_PK_PROPERTY.clone()),
141
            ctx,
142
        );
143
        [miner_pk, pow_onetime_pk].map(Box::new)
144
    }
145

146
    // Evaluates `Header.AdProofsRoot`, `Header.transactionRoot`, `Header.extensionRoot`
147
    fn eval_header_roots(ctx: &Context<'static>) -> [Digest32; 3] {
148
        vec![
149
            sheader::AD_PROOFS_ROOT_PROPERTY.clone(),
150
            sheader::TRANSACTIONS_ROOT_PROPERTY.clone(),
151
            sheader::EXTENSION_ROOT_PROPERTY.clone(),
152
        ]
153
        .into_iter()
154
        .map(|smethod| eval_out::<Vec<i8>>(&create_get_header_property_expr(smethod), ctx))
155
        .map(digest_from_bytes_signed::<32>)
156
        .collect::<Vec<_>>()
157
        .try_into()
158
        .expect("internal error: smethods vector length is not equal to 3")
159
    }
160

161
    // Evaluates `Header.id` and `Header.parentId`
162
    fn eval_header_ids(ctx: &Context<'static>) -> [BlockId; 2] {
163
        let id = eval_out::<Vec<i8>>(
164
            &create_get_header_property_expr(sheader::ID_PROPERTY.clone()),
165
            ctx,
166
        );
167
        let parent_id = eval_out::<Vec<i8>>(
168
            &create_get_header_property_expr(sheader::PARENT_ID_PROPERTY.clone()),
169
            ctx,
170
        );
171
        [id, parent_id].map(block_id_from_bytes_signed)
172
    }
173

174
    fn create_get_header_property_expr(method: SMethod) -> Expr {
175
        let get_headers_expr = create_get_header_by_index_expr();
176
        create_header_property_call_expr(get_headers_expr, method)
177
    }
178

179
    // An `Expr` for such code in ErgoScript `CONTEXT.headers(0)`
180
    fn create_get_header_by_index_expr() -> Expr {
181
        let prop_call = PropertyCall::new(Expr::Context, scontext::HEADERS_PROPERTY.clone())
182
            .expect("internal error: invalid headers property call of Context")
183
            .into();
184
        ByIndex::new(prop_call, Expr::Const((HEADER_INDEX as i32).into()), None)
185
            .expect("internal error: invalid types of ByIndex expression")
186
            .into()
187
    }
188

189
    fn create_header_property_call_expr(headers_expr: Expr, method: SMethod) -> Expr {
190
        PropertyCall::new(headers_expr, method)
191
            .expect("internal error: invalid header property call")
192
            .into()
193
    }
194

195
    fn block_id_from_bytes_signed(bytes: Vec<i8>) -> BlockId {
196
        let arr32 = digest_from_bytes_signed::<32>(bytes);
197
        BlockId(arr32)
198
    }
199

200
    fn digest_from_bytes_signed<const N: usize>(bytes: Vec<i8>) -> Digest<N> {
201
        let arr = arr_from_bytes_signed::<N>(bytes);
202
        arr.into()
203
    }
204

205
    fn arr_from_bytes_signed<const N: usize>(bytes: Vec<i8>) -> [u8; N] {
206
        bytes
207
            .as_vec_u8()
208
            .try_into()
209
            .unwrap_or_else(|_| panic!("internal error: bytes buffer length is not equal to {}", N))
210
    }
211

212
    #[test]
213
    fn test_eval_version() {
214
        let expr = create_get_header_property_expr(sheader::VERSION_PROPERTY.clone());
215
        let ctx = force_any_val::<Context>();
216
        let version = ctx.headers[HEADER_INDEX].version as i8;
217
        assert_eq!(version, eval_out::<i8>(&expr, &ctx));
218
    }
219

220
    #[test]
221
    fn test_eval_ids() {
222
        let ctx = force_any_val::<Context>();
223
        let expected = ctx
224
            .headers
225
            .get(HEADER_INDEX)
226
            .map(|h| [h.id, h.parent_id])
227
            .expect("internal error: empty headers array");
228
        let actual = eval_header_ids(&ctx);
229
        assert_eq!(expected, actual);
230
    }
231

232
    #[test]
233
    fn test_eval_roots() {
234
        let ctx = force_any_val::<Context>();
235
        let expected = ctx
236
            .headers
237
            .get(HEADER_INDEX)
238
            .map(|h| [h.ad_proofs_root, h.transaction_root, h.extension_root])
239
            .expect("internal error: empty headers array");
240
        let actual = eval_header_roots(&ctx);
241
        assert_eq!(expected, actual);
242
    }
243

244
    #[test]
245
    fn test_eval_state_root() {
246
        let expr = create_get_header_property_expr(sheader::STATE_ROOT_PROPERTY.clone());
247
        let ctx = force_any_val::<Context>();
248
        let expected = ctx.headers[HEADER_INDEX].state_root;
249
        let actual = digest_from_bytes_signed::<33>(eval_out::<Vec<i8>>(&expr, &ctx));
250
        assert_eq!(expected, actual);
251
    }
252

253
    #[test]
254
    fn test_eval_timestamp() {
255
        let expr = create_get_header_property_expr(sheader::TIMESTAMP_PROPERTY.clone());
256
        let ctx = force_any_val::<Context>();
257
        let expected = ctx.headers[HEADER_INDEX].timestamp as i64;
258
        let actual = eval_out::<i64>(&expr, &ctx);
259
        assert_eq!(expected, actual);
260
    }
261

262
    #[test]
263
    fn test_eval_n_bits() {
264
        let expr = create_get_header_property_expr(sheader::N_BITS_PROPERTY.clone());
265
        let ctx = force_any_val::<Context>();
266
        let expected = ctx.headers[HEADER_INDEX].n_bits as i64;
267
        let actual = eval_out::<i64>(&expr, &ctx);
268
        assert_eq!(expected, actual);
269
    }
270

271
    #[test]
272
    fn test_eval_height() {
273
        let expr = create_get_header_property_expr(sheader::HEIGHT_PROPERTY.clone());
274
        let ctx = force_any_val::<Context>();
275
        let expected = ctx.headers[HEADER_INDEX].height as i32;
276
        let actual = eval_out::<i32>(&expr, &ctx);
277
        assert_eq!(expected, actual);
278
    }
279

280
    #[test]
281
    fn test_eval_pks() {
282
        let ctx = force_any_val::<Context>();
283
        let expected = ctx
284
            .headers
285
            .get(HEADER_INDEX)
286
            .map(|h| {
287
                [
288
                    h.autolykos_solution.miner_pk.clone(),
289
                    h.autolykos_solution
290
                        .pow_onetime_pk
291
                        .clone()
292
                        .unwrap_or_default(),
293
                ]
294
            })
295
            .expect("internal error: empty headers array");
296
        let actual = eval_header_pks(&ctx);
297
        assert_eq!(expected, actual);
298
    }
299

300
    #[test]
301
    fn test_eval_pow_distance() {
302
        let expr = create_get_header_property_expr(sheader::POW_DISTANCE_PROPERTY.clone());
303
        let ctx = force_any_val::<Context>();
304
        let expected = ctx.headers[HEADER_INDEX]
305
            .autolykos_solution
306
            .pow_distance
307
            .clone()
308
            .unwrap_or_default();
309
        let actual = {
310
            let bi = eval_out::<BigInt256>(&expr, &ctx);
311
            bi.into()
312
        };
313
        assert_eq!(expected, actual);
314
    }
315

316
    #[test]
317
    fn test_eval_pow_nonce() {
318
        let expr = create_get_header_property_expr(sheader::POW_NONCE_PROPERTY.clone());
319
        let ctx = force_any_val::<Context>();
320
        let expected = ctx.headers[HEADER_INDEX].autolykos_solution.nonce.clone();
321
        let actual = eval_out::<Vec<i8>>(&expr, &ctx).as_vec_u8();
322
        assert_eq!(expected, actual);
323
    }
324

325
    #[test]
326
    fn test_eval_votes() {
327
        let expr = create_get_header_property_expr(sheader::VOTES_PROPERTY.clone());
328
        let ctx = force_any_val::<Context>();
329
        let expected = ctx.headers[HEADER_INDEX].votes.clone();
330
        let actual = {
331
            let votes_bytes = eval_out::<Vec<i8>>(&expr, &ctx).as_vec_u8();
332
            Votes::try_from(votes_bytes)
333
                .expect("internal error: votes bytes buffer length isn't equal to 3")
334
        };
335
        assert_eq!(expected, actual);
336
    }
337

338
    #[test]
339
    fn test_eval_failed_invalid_obj() {
340
        // calling for Header property on Context obj
341
        let expr: Expr = PropertyCall {
342
            obj: Box::new(Expr::Context),
343
            method: sheader::VERSION_PROPERTY.clone(),
344
        }
345
        .into();
346
        assert!(try_eval_out_wo_ctx::<i8>(&expr).is_err());
347
    }
348

349
    #[test]
350
    fn test_eval_failed_unknown_property() {
351
        let unknown_property = {
352
            use ergotree_ir::types::{
353
                smethod::{MethodId, SMethod, SMethodDesc},
354
                stype::SType,
355
                stype_companion::STypeCompanion,
356
            };
357
            let method_desc =
358
                SMethodDesc::property(SType::SHeader, "unknown", SType::SByte, MethodId(100));
359
            SMethod::new(STypeCompanion::Header, method_desc)
360
        };
361
        let expr = create_get_header_property_expr(unknown_property);
362
        assert!(try_eval_out_wo_ctx::<i8>(&expr).is_err());
363
    }
364
    #[test]
365
    fn test_eval_check_pow() {
366
        let mut ctx = force_any_val::<Context>();
367
        ctx.headers[0] = serde_json::from_str(
368
            r#"{
369
            "extensionId": "d51a477cc12b187d9bc7f464b22d00e3aa7c92463874e863bf3acf2f427bb48b",
370
            "difficulty": "1595361307131904",
371
            "votes": "000000",
372
            "timestamp": 1736177881102,
373
            "size": 220,
374
            "unparsedBytes": "",
375
            "stateRoot": "4dfafb43842680fd5870d8204a218f873479e1f5da1b34b059ca8da526abcc8719",
376
            "height": 1433531,
377
            "nBits": 117811961,
378
            "version": 3,
379
            "id": "3473e7b5aaf623e4260d5798253d26f3cdc912c12594b7e3a979e3db8ed883f6",
380
            "adProofsRoot": "73160faa9f0e47bf7da598d4e9d3de58e8a24b8564458ad8a4d926514f435dc1",
381
            "transactionsRoot": "c88d5f50ece85c2b918b5bd41d2bc06159e6db1b3aad95091d994c836a172950",
382
            "extensionHash": "d5a43bf63c1d8c7f10b15b6d2446abe565b93a4fd3f5ca785b00e6bda831644f",
383
            "powSolutions": {
384
              "pk": "0274e729bb6615cbda94d9d176a2f1525068f12b330e38bbbf387232797dfd891f",
385
              "w": "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
386
              "n": "a6905b8c65f5864a",
387
              "d": 0
388
            },
389
            "adProofsId": "80a5ff0c6cd98440163bd27f2d7c775ea516af09024a98d9d83f16029bfbd034",
390
            "transactionsId": "c7315c49df258522d3e92ce2653d9f4d8a35309a7a7dd470ebf8db53dd3fb792",
391
            "parentId": "93172f3152a6a25dc89dc45ede1130c5eb86636a50bfb93a999556d16016ceb7"
392
          }"#,
393
        )
394
        .unwrap();
395
        // Add a mainnet block header with valid PoW to context. TODO: this can be simplified once Header serialization is added to sigma-rust (v6.0), right now we need to access CONTEXT.headers(0)
396
        let headers = PropertyCall::new(Expr::Context, HEADERS_PROPERTY.clone()).unwrap();
397
        let header = ByIndex::new(headers.into(), 0i32.into(), None).unwrap();
398
        let check_pow: Expr =
399
            MethodCall::new(header.into(), sheader::CHECK_POW_METHOD.clone(), vec![])
400
                .unwrap()
401
                .into();
402
        assert!(eval_out::<bool>(&check_pow, &ctx));
403
        // Mutate header to invalidate proof-of-work
404
        ctx.headers[0].timestamp -= 1;
405
        assert!(!eval_out::<bool>(&check_pow, &ctx));
406
    }
407
}
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