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

Issue:
[150 SigmaUSD] Evaluation for `PreHeader` properties
from #418
Should be done after #420.

For each `PreHeader` property implement evaluation in `eval::spreheader` module.
See `sbox`, `scoll` modules for examples.

Change `PreHeader::parent_id` type to `BlockId`.
Change `PreHeader::votes` type to `Votes`.


Discussion / hints:
Due to similarity will do with #421 

Fix patch:
diff --git a/bindings/ergo-lib-wasm/src/header.rs b/bindings/ergo-lib-wasm/src/header.rs
index c81e6799f..82d118881 100644
--- a/bindings/ergo-lib-wasm/src/header.rs
+++ b/bindings/ergo-lib-wasm/src/header.rs
@@ -10,14 +10,14 @@ use crate::block_header::BlockHeader;
 /// by a miner before it's formation
 #[wasm_bindgen]
 #[derive(PartialEq, Eq, Debug, Clone, From, Into)]
-pub struct PreHeader(ergo_lib::ergotree_ir::mir::header::PreHeader);
+pub struct PreHeader(ergo_lib::ergotree_ir::chain::preheader::PreHeader);
 
 #[wasm_bindgen]
 impl PreHeader {
     /// Create using data from block header
     pub fn from_block_header(block_header: BlockHeader) -> Self {
         let bh: ergo_lib::ergotree_ir::chain::header::Header = block_header.into();
-        let ph: ergo_lib::ergotree_ir::mir::header::PreHeader = bh.into();
+        let ph: ergo_lib::ergotree_ir::chain::preheader::PreHeader = bh.into();
         ph.into()
     }
 }
diff --git a/ergo-lib/src/chain/ergo_state_context.rs b/ergo-lib/src/chain/ergo_state_context.rs
index aea7ab0a7..ec4474889 100644
--- a/ergo-lib/src/chain/ergo_state_context.rs
+++ b/ergo-lib/src/chain/ergo_state_context.rs
@@ -2,7 +2,7 @@
 use std::convert::TryInto;
 
 use ergotree_ir::chain::header::Header;
-use ergotree_ir::mir::header::PreHeader;
+use ergotree_ir::chain::preheader::PreHeader;
 
 /// Blockchain state (last headers, etc.)
 #[derive(PartialEq, Eq, Debug, Clone)]
diff --git a/ergotree-interpreter/src/eval.rs b/ergotree-interpreter/src/eval.rs
index 2130a6f17..a6a032e53 100644
--- a/ergotree-interpreter/src/eval.rs
+++ b/ergotree-interpreter/src/eval.rs
@@ -84,6 +84,7 @@ pub(crate) mod sigma_and;
 pub(crate) mod sigma_or;
 pub(crate) mod sigma_prop_bytes;
 pub(crate) mod soption;
+pub(crate) mod spreheader;
 pub(crate) mod subst_const;
 pub(crate) mod tuple;
 pub(crate) mod upcast;
@@ -220,6 +221,7 @@ fn smethod_eval_fn(method: &SMethod) -> Result<EvalFn, EvalError> {
             scontext::DATA_INPUTS_PROPERTY_METHOD_ID => self::scontext::DATA_INPUTS_EVAL_FN,
             scontext::SELF_BOX_INDEX_PROPERTY_METHOD_ID => self::scontext::SELF_BOX_INDEX_EVAL_FN,
             scontext::HEADERS_PROPERTY_METHOD_ID => self::scontext::HEADERS_EVAL_FN,
+            scontext::PRE_HEADER_PROPERTY_METHOD_ID => self::scontext::PRE_HEADER_EVAL_FN,
             method_id => {
                 return Err(EvalError::NotFound(format!(
                     "Eval fn: unknown method id in SContext: {:?}",
@@ -297,6 +299,22 @@ fn smethod_eval_fn(method: &SMethod) -> Result<EvalFn, EvalError> {
                 )))
             }
         },
+        spreheader::TYPE_CODE => match method.method_id() {
+            spreheader::VERSION_METHOD_ID => self::spreheader::VERSION_EVAL_FN,
+            spreheader::PARENT_ID_METHOD_ID => self::spreheader::PARENT_ID_EVAL_FN,
+            spreheader::TIMESTAMP_METHOD_ID => self::spreheader::TIMESTAMP_EVAL_FN,
+            spreheader::N_BITS_METHOD_ID => self::spreheader::N_BITS_EVAL_FN,
+            spreheader::HEIGHT_METHOD_ID => self::spreheader::HEIGHT_EVAL_FN,
+            spreheader::MINER_PK_METHOD_ID => self::spreheader::MINER_PK_EVAL_FN,
+            spreheader::VOTES_METHOD_ID => self::spreheader::VOTES_EVAL_FN,
+            method_id => {
+                return Err(EvalError::NotFound(format!(
+                    "Eval fn: method {:?} with method id {:?} not found in SPreHeader",
+                    method.name(),
+                    method_id,
+                )))
+            }
+        },
         type_id => {
             return Err(EvalError::NotFound(format!(
                 "Eval fn: unknown type id {:?}",
diff --git a/ergotree-interpreter/src/eval/context.rs b/ergotree-interpreter/src/eval/context.rs
index e7821d0a3..196cde0fe 100644
--- a/ergotree-interpreter/src/eval/context.rs
+++ b/ergotree-interpreter/src/eval/context.rs
@@ -3,7 +3,7 @@ use std::rc::Rc;
 use crate::sigma_protocol::prover::ContextExtension;
 use ergotree_ir::chain::ergo_box::ErgoBox;
 use ergotree_ir::chain::header::Header;
-use ergotree_ir::mir::header::PreHeader;
+use ergotree_ir::chain::preheader::PreHeader;
 
 /// Interpreter's context (blockchain state)
 #[derive(Debug)]
diff --git a/ergotree-interpreter/src/eval/scontext.rs b/ergotree-interpreter/src/eval/scontext.rs
index 85e0d6007..ccfaf5609 100644
--- a/ergotree-interpreter/src/eval/scontext.rs
+++ b/ergotree-interpreter/src/eval/scontext.rs
@@ -60,6 +60,16 @@ pub(crate) static HEADERS_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
     }))
 };
 
+pub(crate) static PRE_HEADER_EVAL_FN: EvalFn = |_env, ctx, obj, _args| {
+    if obj != Value::Context {
+        return Err(EvalError::UnexpectedValue(format!(
+            "Context.preHeader: expected object of Value::Context, got {:?}",
+            obj
+        )));
+    }
+    Ok(Box::from(ctx.ctx.pre_header.clone()).into())
+};
+
 #[cfg(test)]
 #[cfg(feature = "arbitrary")]
 #[allow(clippy::unwrap_used, clippy::expect_used)]
@@ -68,6 +78,7 @@ mod tests {
     use crate::eval::tests::eval_out;
     use ergotree_ir::chain::ergo_box::ErgoBox;
     use ergotree_ir::chain::header::Header;
+    use ergotree_ir::chain::preheader::PreHeader;
     use ergotree_ir::mir::expr::Expr;
     use ergotree_ir::mir::property_call::PropertyCall;
     use ergotree_ir::types::scontext;
@@ -104,4 +115,13 @@ mod tests {
         let ctx = Rc::new(force_any_val::<Context>());
         assert_eq!(eval_out::<[Header; 10]>(&expr, ctx.clone()), ctx.headers);
     }
+
+    #[test]
+    fn eval_preheader() {
+        let expr: Expr = PropertyCall::new(Expr::Context, scontext::PRE_HEADER_PROPERTY.clone())
+            .unwrap()
+            .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(eval_out::<PreHeader>(&expr, ctx.clone()), ctx.pre_header);
+    }
 }
diff --git a/ergotree-interpreter/src/eval/spreheader.rs b/ergotree-interpreter/src/eval/spreheader.rs
new file mode 100644
index 000000000..23679eccf
--- /dev/null
+++ b/ergotree-interpreter/src/eval/spreheader.rs
@@ -0,0 +1,185 @@
+use ergotree_ir::{chain::preheader::PreHeader, mir::constant::TryExtractInto};
+
+use super::EvalFn;
+
+pub(crate) static VERSION_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let preheader = obj.try_extract_into::<PreHeader>()?;
+    Ok((preheader.version as i8).into())
+};
+
+pub(crate) static PARENT_ID_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let preheader = obj.try_extract_into::<PreHeader>()?;
+    Ok(Into::<Vec<i8>>::into(preheader.parent_id).into())
+};
+
+pub(crate) static TIMESTAMP_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let preheader = obj.try_extract_into::<PreHeader>()?;
+    Ok((preheader.timestamp as i64).into())
+};
+
+pub(crate) static N_BITS_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let preheader = obj.try_extract_into::<PreHeader>()?;
+    Ok((preheader.n_bits as i64).into())
+};
+
+pub(crate) static HEIGHT_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let preheader = obj.try_extract_into::<PreHeader>()?;
+    Ok((preheader.height as i32).into())
+};
+
+pub(crate) static MINER_PK_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let preheader = obj.try_extract_into::<PreHeader>()?;
+    Ok(preheader.miner_pk.into())
+};
+
+pub(crate) static VOTES_EVAL_FN: EvalFn = |_env, _ctx, obj, _args| {
+    let preheader = obj.try_extract_into::<PreHeader>()?;
+    Ok(Into::<Vec<u8>>::into(preheader.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::{
+        chain::{block_id::BlockId, votes::Votes},
+        mir::{expr::Expr, property_call::PropertyCall},
+        sigma_protocol::dlog_group::EcPoint,
+        types::{scontext, smethod::SMethod, spreheader},
+        util::AsVecU8,
+    };
+    use sigma_test_util::force_any_val;
+
+    use crate::eval::{
+        context::Context,
+        tests::{eval_out, try_eval_out_wo_ctx},
+    };
+
+    fn create_get_preheader_property_expr(method: SMethod) -> Expr {
+        let get_preheader_expr = create_get_preheader_expr();
+        create_get_preheader_property_expr_impl(get_preheader_expr.clone(), method)
+    }
+
+    // An `Expr` for such code in ErgoScript: `CONTEXT.preHeader`
+    fn create_get_preheader_expr() -> Expr {
+        PropertyCall::new(Expr::Context, scontext::PRE_HEADER_PROPERTY.clone())
+            .expect("internal error: invalid preheader property call of Context")
+            .into()
+    }
+
+    fn create_get_preheader_property_expr_impl(headers_expr: Expr, method: SMethod) -> Expr {
+        PropertyCall::new(headers_expr, method)
+            .expect("internal error: invalid property call of PreHeader")
+            .into()
+    }
+
+    fn block_id_from_bytes_signed(bytes: Vec<i8>) -> BlockId {
+        let arr32 = bytes
+            .as_vec_u8()
+            .try_into()
+            .expect("internal error: bytes buffer length is not equal to 32");
+        BlockId(arr32)
+    }
+
+    #[test]
+    fn test_eval_version() {
+        let expr = create_get_preheader_property_expr(spreheader::VERSION_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.pre_header.version as i8;
+        assert_eq!(expected, eval_out::<i8>(&expr, ctx.clone()));
+    }
+
+    #[test]
+    fn test_eval_parent_id() {
+        let expr = create_get_preheader_property_expr(spreheader::PARENT_ID_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.pre_header.parent_id.clone();
+        let actual = {
+            let bs = eval_out::<Vec<i8>>(&expr, ctx.clone());
+            block_id_from_bytes_signed(bs)
+        };
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_timestamp() {
+        let expr = create_get_preheader_property_expr(spreheader::TIMESTAMP_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.pre_header.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_preheader_property_expr(spreheader::N_BITS_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.pre_header.n_bits as i64;
+        let actual = eval_out::<i64>(&expr, ctx.clone());
+        assert_eq!(expected as i64, actual);
+    }
+
+    #[test]
+    fn test_eval_height() {
+        let expr = create_get_preheader_property_expr(spreheader::HEIGHT_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.pre_header.height as i32;
+        let actual = eval_out::<i32>(&expr, ctx.clone());
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_miner_pk() {
+        let expr = create_get_preheader_property_expr(spreheader::MINER_PK_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.pre_header.miner_pk.clone();
+        let actual = {
+            let pk = eval_out::<EcPoint>(&expr, ctx.clone());
+            Box::new(pk)
+        };
+        assert_eq!(expected, actual);
+    }
+
+    #[test]
+    fn test_eval_votes() {
+        let expr = create_get_preheader_property_expr(spreheader::VOTES_PROPERTY.clone());
+        let ctx = Rc::new(force_any_val::<Context>());
+        let expected = ctx.pre_header.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 PreHeader property on Context obj
+        let expr: Expr = PropertyCall {
+            obj: Box::new(Expr::Context),
+            method: spreheader::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, SMethodDesc},
+                stype::SType,
+                stype_companion::STypeCompanion,
+            };
+            let method_desc =
+                SMethodDesc::property(SType::SPreHeader, "unknown", SType::SByte, MethodId(100));
+            SMethod::new(STypeCompanion::PreHeader, method_desc)
+        };
+        let expr = create_get_preheader_property_expr(unknown_property);
+        assert!(try_eval_out_wo_ctx::<i8>(&expr).is_err());
+    }
+}
diff --git a/ergotree-ir/src/chain.rs b/ergotree-ir/src/chain.rs
index 5ec12c8cf..94fb17e39 100644
--- a/ergotree-ir/src/chain.rs
+++ b/ergotree-ir/src/chain.rs
@@ -8,6 +8,7 @@ pub mod ergo_box;
 pub mod header;
 #[cfg(feature = "json")]
 pub mod json;
+pub mod preheader;
 pub mod token;
 pub mod tx_id;
 pub mod votes;
diff --git a/ergotree-ir/src/chain/header.rs b/ergotree-ir/src/chain/header.rs
index 05e926f2f..773c28eda 100644
--- a/ergotree-ir/src/chain/header.rs
+++ b/ergotree-ir/src/chain/header.rs
@@ -1,12 +1,11 @@
 //! Block header
 use num_bigint::BigInt;
 
-use crate::mir::header::PreHeader;
 use crate::sigma_protocol::dlog_group;
 
 use super::block_id::BlockId;
-use super::digest32::ADDigest;
-use super::digest32::Digest32;
+use super::digest32::{ADDigest, Digest32};
+use super::preheader::PreHeader;
 use super::votes::Votes;
 
 /// Represents data of the block header available in Sigma propositions.
@@ -86,17 +85,16 @@ impl Header {
     }
 }
 
-// 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 {
             version: bh.version,
-            parent_id: bh.parent_id.0.into(),
+            parent_id: bh.parent_id,
             timestamp: bh.timestamp,
             n_bits: bh.n_bits,
             height: bh.height,
-            miner_pk: dlog_group::identity().into(), // TODO: get from bh.powSolution when its implemented
-            votes: bh.votes.into(),
+            miner_pk: bh.miner_pk,
+            votes: bh.votes,
         }
     }
 }
diff --git a/ergotree-ir/src/mir/header.rs b/ergotree-ir/src/chain/preheader.rs
similarity index 66%
rename from ergotree-ir/src/mir/header.rs
rename to ergotree-ir/src/chain/preheader.rs
index b865bdee9..7df0ac58e 100644
--- a/ergotree-ir/src/mir/header.rs
+++ b/ergotree-ir/src/chain/preheader.rs
@@ -1,5 +1,9 @@
+//! Block header with fields that can be predicted by miner
+
 use crate::sigma_protocol::dlog_group;
 
+use super::{block_id::BlockId, digest32::Digest32, votes::Votes};
+
 /// Block header with the current `spendingTransaction`, that can be predicted
 /// by a miner before it's formation
 #[derive(PartialEq, Eq, Debug, Clone)]
@@ -7,7 +11,7 @@ pub struct PreHeader {
     /// Block version, to be increased on every soft and hardfork
     pub version: u8,
     /// Hash of parent block
-    pub parent_id: Vec<u8>,
+    pub parent_id: BlockId,
     /// Timestamp of a block in ms from UNIX epoch
     pub timestamp: u64,
     /// Current difficulty in a compressed view.
@@ -17,7 +21,7 @@ pub struct PreHeader {
     /// Public key of miner
     pub miner_pk: Box<dlog_group::EcPoint>,
     /// Votes
-    pub votes: Vec<u8>,
+    pub votes: Votes,
 }
 
 impl PreHeader {
@@ -25,46 +29,53 @@ impl PreHeader {
     pub fn dummy() -> Self {
         PreHeader {
             version: 1,
-            parent_id: vec![0; 32],
+            parent_id: BlockId(Digest32::zero()),
             timestamp: 0,
             n_bits: 0,
             height: 0,
             miner_pk: dlog_group::generator().into(),
-            votes: Vec::new(),
+            votes: Votes([0u8; 3]),
         }
     }
 }
 
 #[cfg(feature = "arbitrary")]
 mod arbitrary {
-    use crate::mir::header::PreHeader;
-    use crate::sigma_protocol::dlog_group::EcPoint;
-    use proptest::collection::vec;
+    use proptest::array::{uniform3, uniform32};
     use proptest::prelude::*;
 
+    use crate::sigma_protocol::dlog_group::EcPoint;
+
+    use super::*;
+
     impl Arbitrary for PreHeader {
         type Parameters = ();
-        type Strategy = BoxedStrategy<PreHeader>;
-
         fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
             (
-                vec(any::<u8>(), 32),
+                uniform32(1u8..),
                 // Timestamps between 2000-2050
                 946_674_000_000..2_500_400_300_000u64,
                 any::<u64>(),
                 0..1_000_000u32,
                 any::<Box<EcPoint>>(),
+                uniform3(1u8..),
             )
-                .prop_map(|(parent_id, timestamp, n_bits, height, miner_pk)| Self {
-                    version: 1,
-                    parent_id,
-                    timestamp,
-                    n_bits,
-                    height,
-                    miner_pk,
-                    votes: Vec::new(),
+                .prop_map(|(parent_id, timestamp, n_bits, height, miner_pk, votes)| {
+                    let parent_id = BlockId(parent_id.into());
+                    let votes = Votes(votes);
+                    Self {
+                        version: 1,
+                        parent_id,
+                        timestamp,
+                        n_bits,
+                        height,
+                        miner_pk,
+                        votes,
+                    }
                 })
                 .boxed()
         }
+
+        type Strategy = BoxedStrategy<PreHeader>;
     }
 }
diff --git a/ergotree-ir/src/mir.rs b/ergotree-ir/src/mir.rs
index f5c3794a6..caa7abe81 100644
--- a/ergotree-ir/src/mir.rs
+++ b/ergotree-ir/src/mir.rs
@@ -67,8 +67,6 @@ pub mod extract_script_bytes;
 pub mod func_value;
 pub mod get_var;
 pub mod global_vars;
-/// Headers data types
-pub mod header;
 /// If-else conditional op
 pub mod if_op;
 /// Logical NOT op
diff --git a/ergotree-ir/src/mir/constant.rs b/ergotree-ir/src/mir/constant.rs
index 622bea51a..12c0ba070 100644
--- a/ergotree-ir/src/mir/constant.rs
+++ b/ergotree-ir/src/mir/constant.rs
@@ -226,6 +226,7 @@ impl TryFrom<Value> for Constant {
             Value::AvlTree(a) => Ok(Constant::from(*a)),
             Value::Context => Err("Cannot convert Value::Context into Constant".into()),
             Value::Header(_) => Err("Cannot convert Value::Header(_) into Constant".into()),
+            Value::PreHeader(_) => Err("Cannot convert Value::PreHeader(_) into Constant".into()),
             Value::Global => Err("Cannot convert Value::Global into Constant".into()),
             Value::Lambda(_) => Err("Cannot convert Value::Lambda(_) into Constant".into()),
         }
diff --git a/ergotree-ir/src/mir/value.rs b/ergotree-ir/src/mir/value.rs
index d72760da9..3223bac2f 100644
--- a/ergotree-ir/src/mir/value.rs
+++ b/ergotree-ir/src/mir/value.rs
@@ -8,6 +8,7 @@ use impl_trait_for_tuples::impl_for_tuples;
 use crate::bigint256::BigInt256;
 use crate::chain::ergo_box::ErgoBox;
 use crate::chain::header::Header;
+use crate::chain::preheader::PreHeader;
 use crate::sigma_protocol::dlog_group::EcPoint;
 use crate::sigma_protocol::sigma_boolean::SigmaProp;
 use crate::types::stuple::TupleItems;
@@ -162,6 +163,8 @@ pub enum Value {
     Context,
     /// Block header
     Header(Box<Header>),
+    /// Header with predictable data
+    PreHeader(Box<PreHeader>),
     /// Global which is used to define global methods
     Global,
     /// Optional value
@@ -379,6 +382,18 @@ impl TryExtractFrom<Value> for Header {
     }
 }
 
+impl TryExtractFrom<Value> for PreHeader {
+    fn try_extract_from(c: Value) -> Result<Self, TryExtractFromError> {
+        match c {
+            Value::PreHeader(ph) => Ok(*ph),
+            _ => Err(TryExtractFromError(format!(
+                "expected PreHeader, found {:?}",
+                c
+            ))),
+        }
+    }
+}
+
 impl<T: TryExtractFrom<Value> + StoreWrapped> TryExtractFrom<Value> for Vec<T> {
     fn try_extract_from(c: Value) -> Result<Self, TryExtractFromError> {
         match c {
diff --git a/ergotree-ir/src/types/scontext.rs b/ergotree-ir/src/types/scontext.rs
index 5ca9fdace..1f398dcf2 100644
--- a/ergotree-ir/src/types/scontext.rs
+++ b/ergotree-ir/src/types/scontext.rs
@@ -20,7 +20,7 @@ lazy_static! {
     pub(crate) static ref METHOD_DESC: Vec<&'static SMethodDesc> = vec![
         &DATA_INPUTS_PROPERTY_METHOD_DESC,
         &HEADERS_PROPERTY_METHOD_DESC,
-        &PRE_HEADERS_PROPERTY_METHOD_DESC,
+        &PRE_HEADER_PROPERTY_METHOD_DESC,
         &INPUTS_PROPERTY_METHOD_DESC,
         &OUTPUTS_PROPERTY_METHOD_DESC,
         &HEIGHT_PROPERTY_METHOD_DESC,
@@ -60,15 +60,15 @@ lazy_static! {
     );
 }
 
-pub const PRE_HEADERS_PROPERTY_METHOD_ID: MethodId = MethodId(3);
+pub const PRE_HEADER_PROPERTY_METHOD_ID: MethodId = MethodId(3);
 lazy_static! {
-    static ref PRE_HEADERS_PROPERTY_METHOD_DESC: SMethodDesc =
-        property("preHeader", SPreHeader, PRE_HEADERS_PROPERTY_METHOD_ID);
+    static ref PRE_HEADER_PROPERTY_METHOD_DESC: SMethodDesc =
+        property("preHeader", SPreHeader, PRE_HEADER_PROPERTY_METHOD_ID);
 }
 lazy_static! {
-    pub static ref PRE_HEADERS_PROPERTY: SMethod = SMethod::new(
+    pub static ref PRE_HEADER_PROPERTY: SMethod = SMethod::new(
         STypeCompanion::Context,
-        PRE_HEADERS_PROPERTY_METHOD_DESC.clone()
+        PRE_HEADER_PROPERTY_METHOD_DESC.clone()
     );
 }
 
diff --git a/ergotree-ir/src/types/spreheader.rs b/ergotree-ir/src/types/spreheader.rs
index 71d605822..23ebed87e 100644
--- a/ergotree-ir/src/types/spreheader.rs
+++ b/ergotree-ir/src/types/spreheader.rs
@@ -12,6 +12,20 @@ use lazy_static::lazy_static;
 pub const TYPE_CODE: TypeCode = TypeCode::SPRE_HEADER;
 /// SPreHeader type name
 pub static TYPE_NAME: &str = "PreHeader";
+/// `PreHeader.version`
+pub const VERSION_METHOD_ID: MethodId = MethodId(1);
+/// `PreHeader.parentId`
+pub const PARENT_ID_METHOD_ID: MethodId = MethodId(2);
+/// `PreHeader.timestamp`
+pub const TIMESTAMP_METHOD_ID: MethodId = MethodId(3);
+/// `PreHeader.nBits`
+pub const N_BITS_METHOD_ID: MethodId = MethodId(4);
+/// `PreHeader.height`
+pub const HEIGHT_METHOD_ID: MethodId = MethodId(5);
+/// `PreHeader.minerPk`
+pub const MINER_PK_METHOD_ID: MethodId = MethodId(6);
+/// `PreHeader.votes`
+pub const VOTES_METHOD_ID: MethodId = MethodId(7);
 
 lazy_static! {
     /// Pre-header method descriptors
@@ -31,45 +45,46 @@ lazy_static! {
 lazy_static! {
     pub static ref VERSION_PROPERTY: SMethod =
         SMethod::new(STypeCompanion::PreHeader, VERSION_METHOD.clone());
-    static ref VERSION_METHOD: SMethodDesc = property("version", SByte, MethodId(1));
+    static ref VERSION_METHOD: SMethodDesc = property("version", SByte, VERSION_METHOD_ID);
 }
 
 lazy_static! {
     pub static ref PARENT_ID_PROPERTY: SMethod =
         SMethod::new(STypeCompanion::PreHeader, PARENT_ID_METHOD.clone());
     static ref PARENT_ID_METHOD: SMethodDesc =
-        property("parentId", SColl(SByte.into()), MethodId(2));
+        property("parentId", SColl(SByte.into()), PARENT_ID_METHOD_ID);
 }
 
 lazy_static! {
     pub static ref TIMESTAMP_PROPERTY: SMethod =
         SMethod::new(STypeCompanion::PreHeader, TIMESTAMP_METHOD.clone());
-    static ref TIMESTAMP_METHOD: SMethodDesc = property("timestamp", SType::SLong, MethodId(3));
+    static ref TIMESTAMP_METHOD: SMethodDesc =
+        property("timestamp", SType::SLong, TIMESTAMP_METHOD_ID);
 }
 
 lazy_static! {
     pub static ref N_BITS_PROPERTY: SMethod =
         SMethod::new(STypeCompanion::PreHeader, N_BITS_METHOD.clone());
-    static ref N_BITS_METHOD: SMethodDesc = property("nBits", SType::SLong, MethodId(4));
+    static ref N_BITS_METHOD: SMethodDesc = property("nBits", SType::SLong, N_BITS_METHOD_ID);
 }
 
 lazy_static! {
     pub static ref HEIGHT_PROPERTY: SMethod =
         SMethod::new(STypeCompanion::PreHeader, HEIGHT_METHOD.clone());
-    static ref HEIGHT_METHOD: SMethodDesc = property("height", SType::SInt, MethodId(5));
+    static ref HEIGHT_METHOD: SMethodDesc = property("height", SType::SInt, HEIGHT_METHOD_ID);
 }
 
 lazy_static! {
     pub static ref MINER_PK_PROPERTY: SMethod =
         SMethod::new(STypeCompanion::PreHeader, MINER_PK_METHOD.clone());
     static ref MINER_PK_METHOD: SMethodDesc =
-        property("minerPk", SType::SGroupElement, MethodId(6));
+        property("minerPk", SType::SGroupElement, MINER_PK_METHOD_ID);
 }
 
 lazy_static! {
     pub static ref VOTES_PROPERTY: SMethod =
         SMethod::new(STypeCompanion::PreHeader, VOTES_METHOD.clone());
-    static ref VOTES_METHOD: SMethodDesc = property("votes", SColl(SByte.into()), MethodId(7));
+    static ref VOTES_METHOD: SMethodDesc = property("votes", SColl(SByte.into()), VOTES_METHOD_ID);
 }
 
 fn property(name: &'static str, res_tpe: SType, id: MethodId) -> SMethodDesc {


Test patch: