Repository: ergoplatform/sigma-rust (language: rust)

Issue:
[300 SigmaUSD] Evaluation for `Header` properties
from #418
For each Header property implement evaluation in `eval::sheader` module.

See `sbox`, `scoll` modules for examples.


Discussion / hints:

Fix patch:
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index f4bc6639f..c19513e16 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -79,6 +79,7 @@ pub(crate) mod scoll;
 pub(crate) mod scontext;
 pub(crate) mod select_field;
 pub(crate) mod sgroup_elem;
+pub(crate) mod sheader;
 pub(crate) mod sigma_and;
 pub(crate) mod sigma_or;
 pub(crate) mod sigma_prop_bytes;
@@ -263,6 +264,30 @@ fn smethod_eval_fn(method: &SMethod) -> Result<EvalFn, EvalError> {
                 )))
             }
         },
+        sheader::TYPE_CODE => match method.method_id() {
+            sheader::VERSION_METHOD_ID => self::sheader::VERSION_EVAL_FN,
+            sheader::ID_METHOD_ID => self::sheader::ID_EVAL_FN,
+            sheader::PARENT_ID_METHOD_ID => self::sheader::PARENT_ID_EVAL_FN,
+            sheader::AD_PROOFS_ROOT_METHOD_ID => self::sheader::AD_PROOFS_ROOT_EVAL_FN,
+            sheader::STATE_ROOT_METHOD_ID => self::sheader::STATE_ROOT_EVAL_FN,
+            sheader::TRANSACTIONS_ROOT_METHOD_ID => self::sheader::TRANSACTION_ROOT_EVAL_FN,
+            sheader::EXTENSION_ROOT_METHOD_ID => self::sheader::EXTENSION_ROOT_EVAL_FN,
+            sheader::TIMESTAMP_METHOD_ID => self::sheader::TIMESTAMP_EVAL_FN,
+            sheader::N_BITS_METHOD_ID => self::sheader::N_BITS_EVAL_FN,
+            sheader::HEIGHT_METHOD_ID => self::sheader::HEIGHT_EVAL_FN,
+            sheader::MINER_PK_METHOD_ID => self::sheader::MINER_PK_EVAL_FN,
+            sheader::POW_ONETIME_PK_METHOD_ID => self::sheader::POW_ONETIME_PK_EVAL_FN,
+            sheader::POW_DISTANCE_METHOD_ID => self::sheader::POW_DISTANCE_EVAL_FN,
+            sheader::POW_NONCE_METHOD_ID => self::sheader::POW_NONCE_EVAL_FN,
+            sheader::VOTES_METHOD_ID => self::sheader::VOTES_EVAL_FN,
+            method_id => {
+                return Err(EvalError::NotFound(format!(
+                    "Eval fn: method {:?} with method id {:?} not found in SHeader",
+                    method.name(),
+                    method_id,
+                )))
+            }
+        },
         type_id => {
             return Err(EvalError::NotFound(format!(
                 "Eval fn: unknown type id {:?}",
diff --git a/ergotree-interpreter/src/eval/sheader.rs b/ergotree-interpreter/src/eval/sheader.rs
new file mode 100644
index 000000000..02668e8de
--- /dev/null
+++ b/ergotree-interpreter/src/eval/sheader.rs
@@ -0,0 +1,339 @@
+//! Evaluating predefined `Header` (or SHeader) type properties
+
+use std::convert::TryInto;
+
+use ergotree_ir::{bigint256::BigInt256, chain::header::Header, mir::constant::TryExtractInto};
+
+use super::{EvalError, EvalFn};
+
+pub(crate) static VERSION_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok((header.version as i8).into())
+};
+
+pub(crate) static ID_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok(Into::<Vec<i8>>::into(header.id).into())
+};
+
+pub(crate) static PARENT_ID_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok(Into::<Vec<i8>>::into(header.parent_id).into())
+};
+
+pub(crate) static AD_PROOFS_ROOT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok(Into::<Vec<i8>>::into(header.ad_proofs_root).into())
+};
+
+pub(crate) static STATE_ROOT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok(Into::<Vec<i8>>::into(header.state_root).into())
+};
+
+pub(crate) static TRANSACTION_ROOT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok(Into::<Vec<i8>>::into(header.transaction_root).into())
+};
+
+pub(crate) static EXTENSION_ROOT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok(Into::<Vec<i8>>::into(header.extension_root).into())
+};
+
+pub(crate) static TIMESTAMP_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok((header.timestamp as i64).into())
+};
+
+pub(crate) static N_BITS_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok((header.n_bits as i64).into())
+};
+
+pub(crate) static HEIGHT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok((header.height as i32).into())
+};
+
+pub(crate) static MINER_PK_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok(header.miner_pk.into())
+};
+
+pub(crate) static POW_ONETIME_PK_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok(header.pow_onetime_pk.into())
+};
+
+pub(crate) static POW_NONCE_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok(header.nonce.into())
+};
+
+pub(crate) static POW_DISTANCE_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    let pow_distance: BigInt256 = header.pow_distance.try_into().map_err(EvalError::Misc)?;
+    Ok(pow_distance.into())
+};
+
+pub(crate) static VOTES_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let header = obj.try_extract_into::<Header>()?;
+    Ok(Into::<Vec<u8>>::into(header.votes).into())
+};
+
+#[cfg(test)]
+#[cfg(feature = "arbitrary")]
+#[allow(clippy::expect_used)]
+mod tests {
+    use std::convert::{TryFrom, TryInto};
+    use std::rc::Rc;
+
+    use ergotree_ir::{
+        bigint256::BigInt256,
+        chain::{
+            block_id::BlockId,
+            digest32::{Digest, Digest32},
+            votes::Votes,
+        },
+        mir::{coll_by_index::ByIndex, expr::Expr, property_call::PropertyCall},
+        sigma_protocol::dlog_group::EcPoint,
+        types::{scontext, sheader, smethod::SMethod},
+        util::AsVecU8,
+    };
+    use sigma_test_util::force_any_val;
+
+    use crate::eval::{
+        context::Context,
+        tests::{eval_out, try_eval_out_wo_ctx},
+    };
+
+    // Index in Context.headers array
+    const HEADER_INDEX: usize = 0;
+
+    // Evaluates `Header.minerPk`, `Header.powOnetimePk`
+    fn eval_header_pks(ctx: Rc<Context>) -> [Box<EcPoint>; 2] {
+        let miner_pk = eval_out::<EcPoint>(
+            &create_get_header_property_expr(sheader::MINER_PK_PROPERTY.clone()),
+            ctx.clone(),
+        );
+        let pow_onetime_pk = eval_out::<EcPoint>(
+            &create_get_header_property_expr(sheader::POW_ONETIME_PK_PROPERTY.clone()),
+            ctx.clone(),
+        );
+        [miner_pk, pow_onetime_pk].map(Box::new)
+    }
+
+    // Evaluates `Header.AdProofsRoot`, `Header.transactionRoot`, `Header.extensionRoot`
+    fn eval_header_roots(ctx: Rc<Context>) -> [Digest32; 3] {
+        vec![
+            sheader::AD_PROOFS_ROOT_PROPERTY.clone(),
+            sheader::TRANSACTIONS_ROOT_PROPERTY.clone(),
+            sheader::EXTENSION_ROOT_PROPERTY.clone(),
+        ]
+        .into_iter()
+        .map(|smethod| eval_out::<Vec<i8>>(&create_get_header_property_expr(smethod), ctx.clone()))
+        .map(digest_from_bytes_signed::<32>)
+        .collect::<Vec<_>>()
+        .try_into()
+        .expect("internal error: smethods vector length is not equal to 3")
+    }
+
+    // Evaluates `Header.id` and `Header.parentId`
+    fn eval_header_ids(ctx: Rc<Context>) -> [BlockId; 2] {
+        let id = eval_out::<Vec<i8>>(
+            &create_get_header_property_expr(sheader::ID_PROPERTY.clone()),
+            ctx.clone(),
+        );
+        let parent_id = eval_out::<Vec<i8>>(
+            &create_get_header_property_expr(sheader::PARENT_ID_PROPERTY.clone()),
+            ctx.clone(),
+        );
+        [id, parent_id].map(block_id_from_bytes_signed)
+    }
+
+    fn create_get_header_property_expr(method: SMethod) -> Expr {
+        let get_headers_expr = create_get_header_by_index_expr();
+        create_header_property_call_expr(get_headers_expr.clone(), method)
+    }
+
+    // An `Expr` for such code in ErgoScript `CONTEXT.headers(0)`
+    fn create_get_header_by_index_expr() -> Expr {
+        let prop_call = PropertyCall::new(Expr::Context, scontext::HEADERS_PROPERTY.clone())
+            .expect("internal error: invalid headers property call of Context")
+            .into();
+        ByIndex::new(prop_call, Expr::Const((HEADER_INDEX as i32).into()), None)
+            .expect("internal error: invalid types of ByIndex expression")
+            .into()
+    }
+
+    fn create_header_property_call_expr(headers_expr: Expr, method: SMethod) -> Expr {
+        PropertyCall::new(headers_expr, method)
+            .expect("internal error: invalid header property call")
+            .into()
+    }
+
+    fn block_id_from_bytes_signed(bytes: Vec<i8>) -> BlockId {
+        let arr32 = digest_from_bytes_signed::<32>(bytes);
+        BlockId(arr32)
+    }
+
+    fn digest_from_bytes_signed<const N: usize>(bytes: Vec<i8>) -> Digest<N> {
+        let arr = arr_from_bytes_signed::<N>(bytes);
+        arr.into()
+    }
+
+    fn arr_from_bytes_signed<const N: usize>(bytes: Vec<i8>) -> [u8; N] {
+        bytes.as_vec_u8().try_into().expect(&format!(
+            "internal error: bytes buffer length is not equal to {}",
+            N
+        ))
+    }
+
+    #[test]
+    fn test_eval_version() {
+        let expr = create_get_header_property_expr(sheader::VERSION_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let version = ctx.headers[HEADER_INDEX].version as i8;
+        assert_eq!(version, eval_out::<i8>(&expr, ctx.clone()));
+    }
+
+    #[test]
+    fn test_eval_ids() {
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx
+            .headers
+            .get(HEADER_INDEX)
+            .map(|h| [h.id.clone(), h.parent_id.clone()])
+            .expect("internal error: empty headers array");
+        let actual = eval_header_ids(ctx.clone());
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_roots() {
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx
+            .headers
+            .get(HEADER_INDEX)
+            .map(|h| {
+                [
+                    h.ad_proofs_root.clone(),
+                    h.transaction_root.clone(),
+                    h.extension_root.clone(),
+                ]
+            })
+            .expect("internal error: empty headers array");
+        let actual = eval_header_roots(ctx.clone());
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_state_root() {
+        let expr = create_get_header_property_expr(sheader::STATE_ROOT_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.headers[HEADER_INDEX].state_root.clone();
+        let actual = digest_from_bytes_signed::<33>(eval_out::<Vec<i8>>(&expr, ctx.clone()));
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_timestamp() {
+        let expr = create_get_header_property_expr(sheader::TIMESTAMP_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.headers[HEADER_INDEX].timestamp as i64;
+        let actual = eval_out::<i64>(&expr, ctx.clone());
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_n_bits() {
+        let expr = create_get_header_property_expr(sheader::N_BITS_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.headers[HEADER_INDEX].n_bits as i64;
+        let actual = eval_out::<i64>(&expr, ctx.clone());
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_height() {
+        let expr = create_get_header_property_expr(sheader::HEIGHT_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.headers[HEADER_INDEX].height as i32;
+        let actual = eval_out::<i32>(&expr, ctx.clone());
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_pks() {
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx
+            .headers
+            .get(HEADER_INDEX)
+            .map(|h| [h.miner_pk.clone(), h.pow_onetime_pk.clone()])
+            .expect("internal error: empty headers array");
+        let actual = eval_header_pks(ctx.clone());
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_pow_distance() {
+        let expr = create_get_header_property_expr(sheader::POW_DISTANCE_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.headers[HEADER_INDEX].pow_distance.clone();
+        let actual = {
+            let bi = eval_out::<BigInt256>(&expr, ctx.clone());
+            bi.into()
+        };
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_pow_nonce() {
+        let expr = create_get_header_property_expr(sheader::POW_NONCE_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.headers[HEADER_INDEX].nonce.clone();
+        let actual = eval_out::<Vec<i8>>(&expr, ctx.clone()).as_vec_u8();
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_votes() {
+        let expr = create_get_header_property_expr(sheader::VOTES_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.headers[HEADER_INDEX].votes.clone();
+        let actual = {
+            let votes_bytes = eval_out::<Vec<i8>>(&expr, ctx.clone()).as_vec_u8();
+            Votes::try_from(votes_bytes)
+                .expect("internal error: votes bytes buffer length isn't equal to 3")
+        };
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_failed_invalid_obj() {
+        // calling for Header property on Context obj
+        let expr: Expr = PropertyCall {
+            obj: Box::new(Expr::Context),
+            method: sheader::VERSION_PROPERTY.clone(),
+        }
+        .into();
+        assert!(try_eval_out_wo_ctx::<i8>(&expr).is_err());
+    }
+
+    #[test]
+    fn test_eval_failed_unknown_property() {
+        let unknown_property = {
+            use ergotree_ir::types::{
+                smethod::{MethodId, SMethod, SMethodDesc},
+                stype::SType,
+                stype_companion::STypeCompanion,
+            };
+            let method_desc =
+                SMethodDesc::property(SType::SHeader, "unknown", SType::SByte, MethodId(100));
+            SMethod::new(STypeCompanion::Header, method_desc)
+        };
+        let expr = create_get_header_property_expr(unknown_property);
+        assert!(try_eval_out_wo_ctx::<i8>(&expr).is_err());
+    }
+}
diff --git a/ergotree-ir/src/bigint256.rs b/ergotree-ir/src/bigint256.rs
index 0cff2ce74..15b723ba5 100644
--- a/ergotree-ir/src/bigint256.rs
+++ b/ergotree-ir/src/bigint256.rs
@@ -81,6 +81,13 @@ impl From<i64> for BigInt256 {
     }
 }
 
+impl From<BigInt256> for BigInt {
+    fn from(value: BigInt256) -> Self {
+        let Int256(bi) = value.0;
+        bi
+    }
+}
+
 impl Deref for BigInt256 {
     type Target = Int256;
 
diff --git a/ergotree-ir/src/chain/block_id.rs b/ergotree-ir/src/chain/block_id.rs
index ae8229c0d..9ea826d00 100644
--- a/ergotree-ir/src/chain/block_id.rs
+++ b/ergotree-ir/src/chain/block_id.rs
@@ -6,3 +6,10 @@ use super::digest32::Digest32;
 #[cfg_attr(feature = "json", derive(serde::Serialize, serde::Deserialize))]
 #[derive(PartialEq, Eq, Debug, Clone)]
 pub struct BlockId(pub Digest32);
+
+impl From<BlockId> for Vec<i8> {
+    fn from(value: BlockId) -> Self {
+        let BlockId(digest32) = value;
+        digest32.into()
+    }
+}
diff --git a/ergotree-ir/src/chain/header.rs b/ergotree-ir/src/chain/header.rs
index 87aa98754..05e926f2f 100644
--- a/ergotree-ir/src/chain/header.rs
+++ b/ergotree-ir/src/chain/header.rs
@@ -86,7 +86,7 @@ impl Header {
     }
 }
 
-// todo-sab refactor when implementing this https://github.com/ergoplatform/sigma-rust/issues/373
+// TODO: [sab] refactor when implementing this https://github.com/ergoplatform/sigma-rust/issues/373
 impl From<Header> for PreHeader {
     fn from(bh: Header) -> Self {
         PreHeader {
diff --git a/ergotree-ir/src/types/sheader.rs b/ergotree-ir/src/types/sheader.rs
index f6376b578..adf083a2b 100644
--- a/ergotree-ir/src/types/sheader.rs
+++ b/ergotree-ir/src/types/sheader.rs
@@ -12,6 +12,36 @@ use super::stype_companion::STypeCompanion::Header;
 pub const TYPE_CODE: TypeCode = TypeCode::SHEADER;
 /// SHeader type name
 pub static TYPE_NAME: &str = "Header";
+/// `Header.id`
+pub const ID_METHOD_ID: MethodId = MethodId(1);
+/// `Header.version`
+pub const VERSION_METHOD_ID: MethodId = MethodId(2);
+/// `Header.parentId`
+pub const PARENT_ID_METHOD_ID: MethodId = MethodId(3);
+/// `Header.AdProofsRoot`
+pub const AD_PROOFS_ROOT_METHOD_ID: MethodId = MethodId(4);
+/// `Header.stateRoot`
+pub const STATE_ROOT_METHOD_ID: MethodId = MethodId(5);
+/// `Header.transactionsRoot`
+pub const TRANSACTIONS_ROOT_METHOD_ID: MethodId = MethodId(6);
+/// `Header.timestamp`
+pub const TIMESTAMP_METHOD_ID: MethodId = MethodId(7);
+/// `Header.nBits`
+pub const N_BITS_METHOD_ID: MethodId = MethodId(8);
+/// `Header.height`
+pub const HEIGHT_METHOD_ID: MethodId = MethodId(9);
+/// `Header.extensionRoot`
+pub const EXTENSION_ROOT_METHOD_ID: MethodId = MethodId(10);
+/// `Header.minerPk`
+pub const MINER_PK_METHOD_ID: MethodId = MethodId(11);
+/// `Header.powOnetimePk`
+pub const POW_ONETIME_PK_METHOD_ID: MethodId = MethodId(12);
+/// `Header.powNonce`
+pub const POW_NONCE_METHOD_ID: MethodId = MethodId(13);
+/// `Header.powDistance`
+pub const POW_DISTANCE_METHOD_ID: MethodId = MethodId(14);
+/// `Header.votes`
+pub const VOTES_METHOD_ID: MethodId = MethodId(15);
 
 lazy_static! {
     /// Header method descriptors
@@ -20,7 +50,7 @@ lazy_static! {
             &ID_PROPERTY_METHOD_DESC,
             &VERSION_PROPERTY_METHOD_DESC,
             &PARENT_ID_PROPERTY_METHOD_DESC,
-            &AD_PROOF_ROOT_PROPERTY_METHOD_DESC,
+            &AD_PROOFS_ROOT_PROPERTY_METHOD_DESC,
             &STATE_ROOT_PROPERTY_METHOD_DESC,
             &TRANSACTIONS_ROOT_PROPERTY_METHOD_DESC,
             &TIMESTAMP_PROPERTY_METHOD_DESC,
@@ -36,146 +66,92 @@ lazy_static! {
     ;
 }
 
-// todo all to one lazy_static
-
-pub const ID_PROPERTY_METHOD_ID: MethodId = MethodId(1);
 lazy_static! {
-    pub static ref ID_PROPERTY: SMethod = SMethod::new(Header, ID_PROPERTY_METHOD_DESC.clone(),);
-    static ref ID_PROPERTY_METHOD_DESC: SMethodDesc =
-        property("id", SColl(SByte.into()), ID_PROPERTY_METHOD_ID);
-}
+    // Declaring public `SHeader` properties method signatures type
 
-pub const VERSION_PROPERTY_METHOD_ID: MethodId = MethodId(2);
-lazy_static! {
+    pub static ref ID_PROPERTY: SMethod = SMethod::new(Header, ID_PROPERTY_METHOD_DESC.clone(),);
     pub static ref VERSION_PROPERTY: SMethod =
         SMethod::new(Header, VERSION_PROPERTY_METHOD_DESC.clone(),);
-    static ref VERSION_PROPERTY_METHOD_DESC: SMethodDesc =
-        property("version", SByte, VERSION_PROPERTY_METHOD_ID);
-}
-
-pub const PARENT_ID_PROPERTY_METHOD_ID: MethodId = MethodId(3);
-lazy_static! {
     pub static ref PARENT_ID_PROPERTY: SMethod =
         SMethod::new(Header, PARENT_ID_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref AD_PROOFS_ROOT_PROPERTY: SMethod =
+        SMethod::new(Header, AD_PROOFS_ROOT_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref STATE_ROOT_PROPERTY: SMethod =
+        SMethod::new(Header, STATE_ROOT_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref TRANSACTIONS_ROOT_PROPERTY: SMethod =
+        SMethod::new(Header, TRANSACTIONS_ROOT_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref TIMESTAMP_PROPERTY: SMethod =
+        SMethod::new(Header, TIMESTAMP_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref N_BITS_PROPERTY: SMethod =
+        SMethod::new(Header, N_BITS_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref HEIGHT_PROPERTY: SMethod =
+        SMethod::new(Header, HEIGHT_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref EXTENSION_ROOT_PROPERTY: SMethod =
+        SMethod::new(Header, EXTENSION_ROOT_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref MINER_PK_PROPERTY: SMethod =
+        SMethod::new(Header, MINER_PK_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref POW_ONETIME_PK_PROPERTY: SMethod =
+        SMethod::new(Header, POW_ONETIME_PK_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref POW_NONCE_PROPERTY: SMethod =
+        SMethod::new(Header, POW_NONCE_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref POW_DISTANCE_PROPERTY: SMethod =
+        SMethod::new(Header, POW_DISTANCE_PROPERTY_METHOD_DESC.clone(),);
+    pub static ref VOTES_PROPERTY: SMethod =
+        SMethod::new(Header, VOTES_PROPERTY_METHOD_DESC.clone(),);
+
+    // Declaring private `SHeader` properties methods descriptors
+
+    static ref ID_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("id", SColl(SByte.into()), ID_METHOD_ID);
+    static ref VERSION_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("version", SByte, VERSION_METHOD_ID);
     static ref PARENT_ID_PROPERTY_METHOD_DESC: SMethodDesc = property(
         "parentId",
         SColl(SByte.into()),
-        PARENT_ID_PROPERTY_METHOD_ID
+        PARENT_ID_METHOD_ID
     );
-}
-
-pub const AD_PROOF_ROOT_PROPERTY_METHOD_ID: MethodId = MethodId(4);
-lazy_static! {
-    pub static ref AD_PROOF_ROOT_PROPERTY: SMethod =
-        SMethod::new(Header, AD_PROOF_ROOT_PROPERTY_METHOD_DESC.clone(),);
-    static ref AD_PROOF_ROOT_PROPERTY_METHOD_DESC: SMethodDesc = property(
+    static ref AD_PROOFS_ROOT_PROPERTY_METHOD_DESC: SMethodDesc = property(
         "ADProofsRoot",
         SColl(SByte.into()),
-        AD_PROOF_ROOT_PROPERTY_METHOD_ID
+        AD_PROOFS_ROOT_METHOD_ID
     );
-}
-
-pub const STATE_ROOT_PROPERTY_METHOD_ID: MethodId = MethodId(5);
-lazy_static! {
-    pub static ref STATE_ROOT_PROPERTY: SMethod =
-        SMethod::new(Header, STATE_ROOT_PROPERTY_METHOD_DESC.clone(),);
     static ref STATE_ROOT_PROPERTY_METHOD_DESC: SMethodDesc =
-        property("stateRoot", SType::SAvlTree, STATE_ROOT_PROPERTY_METHOD_ID);
-}
-
-pub const TRANSACTIONS_ROOT_PROPERTY_METHOD_ID: MethodId = MethodId(6);
-lazy_static! {
-    pub static ref TRANSACTIONS_ROOT_PROPERTY: SMethod =
-        SMethod::new(Header, TRANSACTIONS_ROOT_PROPERTY_METHOD_DESC.clone(),);
+        property("stateRoot", SType::SAvlTree, STATE_ROOT_METHOD_ID);
     static ref TRANSACTIONS_ROOT_PROPERTY_METHOD_DESC: SMethodDesc = property(
         "transactionsRoot",
         SColl(SByte.into()),
-        TRANSACTIONS_ROOT_PROPERTY_METHOD_ID
+        TRANSACTIONS_ROOT_METHOD_ID
     );
-}
-
-pub const TIMESTAMP_PROPERTY_METHOD_ID: MethodId = MethodId(7);
-lazy_static! {
-    pub static ref TIMESTAMP_PROPERTY: SMethod =
-        SMethod::new(Header, TIMESTAMP_PROPERTY_METHOD_DESC.clone(),);
     static ref TIMESTAMP_PROPERTY_METHOD_DESC: SMethodDesc =
-        property("timestamp", SType::SLong, TIMESTAMP_PROPERTY_METHOD_ID);
-}
-
-pub const N_BITS_PROPERTY_METHOD_ID: MethodId = MethodId(8);
-lazy_static! {
-    pub static ref N_BITS_PROPERTY: SMethod =
-        SMethod::new(Header, N_BITS_PROPERTY_METHOD_DESC.clone(),);
+        property("timestamp", SType::SLong, TIMESTAMP_METHOD_ID);
     static ref N_BITS_PROPERTY_METHOD_DESC: SMethodDesc =
-        property("nBits", SType::SLong, N_BITS_PROPERTY_METHOD_ID);
-}
-
-pub const HEIGHT_PROPERTY_METHOD_ID: MethodId = MethodId(9);
-lazy_static! {
-    pub static ref HEIGHT_PROPERTY: SMethod =
-        SMethod::new(Header, HEIGHT_PROPERTY_METHOD_DESC.clone(),);
+        property("nBits", SType::SLong, N_BITS_METHOD_ID);
     static ref HEIGHT_PROPERTY_METHOD_DESC: SMethodDesc =
-        property("height", SType::SInt, HEIGHT_PROPERTY_METHOD_ID);
-}
-
-pub const EXTENSION_ROOT_PROPERTY_METHOD_ID: MethodId = MethodId(10);
-lazy_static! {
-    pub static ref EXTENSION_ROOT_PROPERTY: SMethod =
-        SMethod::new(Header, EXTENSION_ROOT_PROPERTY_METHOD_DESC.clone(),);
+        property("height", SType::SInt, HEIGHT_METHOD_ID);
     static ref EXTENSION_ROOT_PROPERTY_METHOD_DESC: SMethodDesc = property(
         "extensionRoot",
         SColl(SByte.into()),
-        EXTENSION_ROOT_PROPERTY_METHOD_ID
+        EXTENSION_ROOT_METHOD_ID
     );
-}
-
-pub const MINER_PK_PROPERTY_METHOD_ID: MethodId = MethodId(11);
-lazy_static! {
-    pub static ref MINER_PK_PROPERTY: SMethod =
-        SMethod::new(Header, MINER_PK_PROPERTY_METHOD_DESC.clone(),);
     static ref MINER_PK_PROPERTY_METHOD_DESC: SMethodDesc =
-        property("minerPk", SType::SGroupElement, MINER_PK_PROPERTY_METHOD_ID);
-}
-
-pub const POW_ONETIME_PK_PROPERTY_METHOD_ID: MethodId = MethodId(12);
-lazy_static! {
-    pub static ref POW_ONETIME_PK_PROPERTY: SMethod =
-        SMethod::new(Header, POW_ONETIME_PK_PROPERTY_METHOD_DESC.clone(),);
+        property("minerPk", SType::SGroupElement, MINER_PK_METHOD_ID);
     static ref POW_ONETIME_PK_PROPERTY_METHOD_DESC: SMethodDesc = property(
         "powOnetimePk",
         SType::SGroupElement,
-        POW_ONETIME_PK_PROPERTY_METHOD_ID
+        POW_ONETIME_PK_METHOD_ID
     );
-}
-
-pub const POW_NONCE_PROPERTY_METHOD_ID: MethodId = MethodId(13);
-lazy_static! {
-    pub static ref POW_NONCE_PROPERTY: SMethod =
-        SMethod::new(Header, POW_NONCE_PROPERTY_METHOD_DESC.clone(),);
     static ref POW_NONCE_PROPERTY_METHOD_DESC: SMethodDesc = property(
         "powNonce",
         SColl(SByte.into()),
-        POW_NONCE_PROPERTY_METHOD_ID
+        POW_NONCE_METHOD_ID
     );
-}
-
-pub const POW_DISTANCE_PROPERTY_METHOD_ID: MethodId = MethodId(14);
-lazy_static! {
-    pub static ref POW_DISTANCE_PROPERTY: SMethod =
-        SMethod::new(Header, POW_DISTANCE_PROPERTY_METHOD_DESC.clone(),);
     static ref POW_DISTANCE_PROPERTY_METHOD_DESC: SMethodDesc = property(
         "powDistance",
         SType::SBigInt,
-        POW_DISTANCE_PROPERTY_METHOD_ID
+        POW_DISTANCE_METHOD_ID
     );
-}
-
-pub const VOTES_PROPERTY_METHOD_ID: MethodId = MethodId(15);
-lazy_static! {
-    pub static ref VOTES_PROPERTY: SMethod =
-        SMethod::new(Header, VOTES_PROPERTY_METHOD_DESC.clone(),);
     static ref VOTES_PROPERTY_METHOD_DESC: SMethodDesc =
-        property("votes", SColl(SByte.into()), VOTES_PROPERTY_METHOD_ID);
+        property("votes", SColl(SByte.into()), VOTES_METHOD_ID);
 }
 
 fn property(name: &'static str, res_tpe: SType, id: MethodId) -> SMethodDesc {
diff --git a/ergotree-ir/src/types/smethod.rs b/ergotree-ir/src/types/smethod.rs
index 8f9aab676..7e050fc1f 100644
--- a/ergotree-ir/src/types/smethod.rs
+++ b/ergotree-ir/src/types/smethod.rs
@@ -37,7 +37,7 @@ pub struct SMethod {
 
 impl SMethod {
     /// Create new SMethod
-    pub(crate) const fn new(obj_type: STypeCompanion, method_raw: SMethodDesc) -> SMethod {
+    pub const fn new(obj_type: STypeCompanion, method_raw: SMethodDesc) -> SMethod {
         SMethod {
             obj_type,
             method_raw,
@@ -94,15 +94,17 @@ impl SMethod {
     }
 }
 
+/// Object method description
 #[derive(PartialEq, Eq, Debug, Clone)]
-pub(crate) struct SMethodDesc {
+pub struct SMethodDesc {
     pub(crate) name: &'static str,
     pub(crate) method_id: MethodId,
     pub(crate) tpe: SFunc,
 }
 
 impl SMethodDesc {
-    pub(crate) fn property(
+    /// Initialize property method description
+    pub fn property(
         obj_tpe: SType,
         name: &'static str,
         res_tpe: SType,


Test patch: