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

Issue:
Collection.fold eval and serialization



Discussion / hints:

Fix patch:
diff --git a/ergo-lib/CHANGELOG.md b/ergo-lib/CHANGELOG.md
index 8db23010b..9611c3974 100644
--- a/ergo-lib/CHANGELOG.md
+++ b/ergo-lib/CHANGELOG.md
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
 ### Added 
 - `BlockValue`, `ValDef`, `ValUse`, `FuncValue`, `Apply` IR nodes evaluation and serialization [#171](https://github.com/ergoplatform/sigma-rust/pull/171);
 - `SimpleBoxSelector`: sort inputs by target tokens and skip inputs that does not have target tokens [#175](https://github.com/ergoplatform/sigma-rust/pull/175);
+- `Fold`(collection), `ExtractAmount`(`Box.value`), `SelectField`(tuple field access) IR nodes evaluation and serialization [#173]()
 
 ## [0.4.3] - 2021-01-15
 
diff --git a/ergo-lib/README.md b/ergo-lib/README.md
index afe43ea1d..b4c2f66bf 100644
--- a/ergo-lib/README.md
+++ b/ergo-lib/README.md
@@ -16,6 +16,7 @@
 - Value types with serialization for most of them(types and values);
 - `EQ`(`==`), `NEQ`(`!=`);
 - `BlockValue`, `ValDef`, `ValUse`, `FuncValue`, `Apply`;
+- `Fold`(collection), `ExtractAmount`(`Box.value`), `SelectField`(tuple field access); 
 
 ## Usage 
 ## Crate features
diff --git a/ergo-lib/src/ast.rs b/ergo-lib/src/ast.rs
index 8d85f22dd..477cc4633 100644
--- a/ergo-lib/src/ast.rs
+++ b/ergo-lib/src/ast.rs
@@ -5,6 +5,7 @@ pub(crate) mod bin_op;
 pub(crate) mod block;
 pub(crate) mod coll_fold;
 pub(crate) mod expr;
+pub(crate) mod extract_amount;
 pub(crate) mod extract_reg_as;
 pub(crate) mod func_value;
 pub(crate) mod global_vars;
@@ -12,6 +13,7 @@ pub(crate) mod method_call;
 pub(crate) mod option_get;
 pub(crate) mod predef_func;
 pub(crate) mod property_call;
+pub(crate) mod select_field;
 pub(crate) mod val_def;
 pub(crate) mod val_use;
 
diff --git a/ergo-lib/src/ast/bin_op.rs b/ergo-lib/src/ast/bin_op.rs
index 2d3568bd5..ad32b7a61 100644
--- a/ergo-lib/src/ast/bin_op.rs
+++ b/ergo-lib/src/ast/bin_op.rs
@@ -10,6 +10,7 @@ use crate::eval::Evaluable;
 use crate::serialization::op_code::OpCode;
 use crate::types::stype::SType;
 
+use super::constant::TryExtractInto;
 use super::expr::Expr;
 use super::value::Value;
 
@@ -56,14 +57,14 @@ impl From<LogicOp> for OpCode {
 /// Binary operations
 pub enum BinOpKind {
     /// Binary operations for numerical types
-    // Num(NumOp),
+    Num(NumOp),
     Logic(LogicOp),
 }
 
 impl From<BinOpKind> for OpCode {
     fn from(op: BinOpKind) -> Self {
         match op {
-            // BinOpKind::Num(o) => o.into(),
+            BinOpKind::Num(o) => o.into(),
             BinOpKind::Logic(o) => o.into(),
         }
     }
@@ -84,6 +85,7 @@ impl BinOp {
     pub fn tpe(&self) -> SType {
         match self.kind {
             BinOpKind::Logic(_) => SType::SBoolean,
+            BinOpKind::Num(_) => self.left.tpe(),
         }
     }
 }
@@ -98,6 +100,19 @@ impl Evaluable for BinOp {
                 LogicOp::Eq => Ok(Value::Boolean(lv == rv)),
                 LogicOp::NEq => Ok(Value::Boolean(lv != rv)),
             },
+            BinOpKind::Num(op) => match op {
+                NumOp::Add => match lv {
+                    Value::Byte(_) => todo!(),
+                    Value::Short(_) => todo!(),
+                    Value::Int(_) => todo!(),
+                    Value::Long(lvv) => Ok(Value::Long(lvv + rv.try_extract_into::<i64>()?)),
+                    Value::BigInt => todo!(),
+                    _ => Err(EvalError::UnexpectedValue(format!(
+                        "expected BinOp::left to be numeric value, got {0:?}",
+                        lv
+                    ))),
+                },
+            },
         }
     }
 }
diff --git a/ergo-lib/src/ast/coll_fold.rs b/ergo-lib/src/ast/coll_fold.rs
index 699c0fa25..3b8b8f69f 100644
--- a/ergo-lib/src/ast/coll_fold.rs
+++ b/ergo-lib/src/ast/coll_fold.rs
@@ -6,10 +6,12 @@ use crate::serialization::sigma_byte_reader::SigmaByteRead;
 use crate::serialization::sigma_byte_writer::SigmaByteWrite;
 use crate::serialization::SerializationError;
 use crate::serialization::SigmaSerializable;
+use crate::types::stuple::STuple;
+use crate::types::stuple::TupleItems;
 use crate::types::stype::SType;
-use crate::types::stype::TupleItems;
 
 use super::expr::Expr;
+use super::expr::InvalidArgumentError;
 use super::value::Coll::NonPrimitive;
 use super::value::Coll::Primitive;
 use super::value::CollPrim;
@@ -26,6 +28,38 @@ pub struct Fold {
 }
 
 impl Fold {
+    pub fn new(input: Expr, zero: Expr, fold_op: Expr) -> Result<Self, InvalidArgumentError> {
+        let input_elem_type: SType = *match input.tpe() {
+            SType::SColl(elem_type) => Ok(elem_type),
+            SType::SFunc(sfunc) => match sfunc.t_range {
+                SType::SColl(elem_type) => Ok(elem_type),
+                _ => Err(InvalidArgumentError(format!(
+                    "Expected Fold input to be SColl, got {0:?}",
+                    sfunc.t_range
+                ))),
+            },
+            _ => Err(InvalidArgumentError(format!(
+                "Expected Fold input to be SColl, got {0:?}",
+                input.tpe()
+            ))),
+        }?;
+        match fold_op.tpe() {
+            SType::SFunc(sfunc)
+                if sfunc.t_dom == vec![STuple::pair(zero.tpe(), input_elem_type).into()] =>
+            {
+                Ok(Fold {
+                    input,
+                    zero,
+                    fold_op,
+                })
+            }
+            _ => Err(InvalidArgumentError(format!(
+                "Invalid fold_op tpe: {0:?}",
+                fold_op.tpe()
+            ))),
+        }
+    }
+
     pub fn tpe(&self) -> SType {
         self.zero.tpe()
     }
@@ -90,3 +124,105 @@ impl Evaluable for Fold {
         }
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use std::convert::TryInto;
+    use std::rc::Rc;
+
+    use crate::ast::bin_op::BinOp;
+    use crate::ast::bin_op::NumOp;
+    use crate::ast::expr::Expr;
+    use crate::ast::extract_amount::ExtractAmount;
+    use crate::ast::func_value::FuncArg;
+    use crate::ast::func_value::FuncValue;
+    use crate::ast::property_call::PropertyCall;
+    use crate::ast::select_field::SelectField;
+    use crate::ast::val_use::ValUse;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::serialization::sigma_serialize_roundtrip;
+    use crate::test_util::force_any_val;
+    use crate::types::scontext;
+    use crate::types::stuple::STuple;
+
+    use super::*;
+
+    use proptest::prelude::*;
+
+    #[test]
+    fn eval_box_value() {
+        let data_inputs: Expr = Box::new(PropertyCall {
+            obj: Expr::Context,
+            method: scontext::DATA_INPUTS_PROPERTY.clone(),
+        })
+        .into();
+        let tuple: Expr = Box::new(ValUse {
+            val_id: 1.into(),
+            tpe: SType::STuple(STuple {
+                items: TupleItems::pair(SType::SLong, SType::SBox),
+            }),
+        })
+        .into();
+        let fold_op_body: Expr = Box::new(BinOp {
+            kind: NumOp::Add.into(),
+            left: Expr::SelectField(
+                SelectField::new(tuple.clone(), 1.try_into().unwrap()).unwrap(),
+            ),
+            right: Expr::ExtractAmount(
+                ExtractAmount::new(Expr::SelectField(
+                    SelectField::new(tuple, 2.try_into().unwrap()).unwrap(),
+                ))
+                .unwrap(),
+            ),
+        })
+        .into();
+        let expr: Expr = Box::new(
+            Fold::new(
+                data_inputs,
+                Expr::Const(Box::new(0i64.into())),
+                Expr::FuncValue(Box::new(FuncValue::new(
+                    vec![FuncArg {
+                        idx: 1.into(),
+                        tpe: SType::STuple(STuple {
+                            items: TupleItems::pair(SType::SLong, SType::SBox),
+                        }),
+                    }],
+                    fold_op_body,
+                ))),
+            )
+            .unwrap(),
+        )
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(
+            eval_out::<i64>(&expr, ctx.clone()),
+            ctx.data_inputs
+                .iter()
+                .fold(0i64, |acc, b| acc + b.value.as_i64())
+        );
+    }
+
+    impl Arbitrary for Fold {
+        type Strategy = BoxedStrategy<Self>;
+        type Parameters = ();
+
+        fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
+            (any::<Expr>(), any::<Expr>(), any::<Expr>())
+                .prop_map(|(input, zero, fold_op)| Self {
+                    input,
+                    zero,
+                    fold_op,
+                })
+                .boxed()
+        }
+    }
+
+    proptest! {
+
+        #[test]
+        fn ser_roundtrip(v in any::<Fold>()) {
+            prop_assert_eq![sigma_serialize_roundtrip(&v), v];
+        }
+    }
+}
diff --git a/ergo-lib/src/ast/constant.rs b/ergo-lib/src/ast/constant.rs
index 4573701f5..2c243a7b6 100644
--- a/ergo-lib/src/ast/constant.rs
+++ b/ergo-lib/src/ast/constant.rs
@@ -224,7 +224,7 @@ impl<T: TryExtractFrom<Value>> TryExtractFrom<Constant> for T {
 mod tests {
     use core::fmt;
 
-    use crate::types::stype::TupleItems;
+    use crate::types::stuple::STuple;
 
     use super::*;
     use proptest::collection::vec;
@@ -323,7 +323,7 @@ mod tests {
                             // Tuple
                             vec(elem, 2..=4).prop_map(|constants| Constant {
                                 tpe: SType::STuple(
-                                    TupleItems::try_from(
+                                    STuple::try_from(
                                         constants
                                             .clone()
                                             .into_iter()
diff --git a/ergo-lib/src/ast/expr.rs b/ergo-lib/src/ast/expr.rs
index 6fe7e5479..85d7d60f7 100644
--- a/ergo-lib/src/ast/expr.rs
+++ b/ergo-lib/src/ast/expr.rs
@@ -8,6 +8,7 @@ use super::block::BlockValue;
 use super::coll_fold::Fold;
 use super::constant::Constant;
 use super::constant::ConstantPlaceholder;
+use super::extract_amount::ExtractAmount;
 use super::extract_reg_as::ExtractRegisterAs;
 use super::func_value::FuncValue;
 use super::global_vars::GlobalVars;
@@ -15,6 +16,7 @@ use super::method_call::MethodCall;
 use super::option_get::OptionGet;
 use super::predef_func::PredefFunc;
 use super::property_call::PropertyCall;
+use super::select_field::SelectField;
 use super::val_def::ValDef;
 use super::val_use::ValUse;
 
@@ -56,6 +58,10 @@ pub enum Expr {
     ExtractRegisterAs(Box<ExtractRegisterAs>),
     /// Collection fold op
     Fold(Box<Fold>),
+    /// Tuple field access
+    SelectField(SelectField),
+    /// Box monetary value
+    ExtractAmount(ExtractAmount),
 }
 
 impl Expr {
@@ -75,6 +81,8 @@ impl Expr {
             Expr::ValUse(op) => op.op_code(),
             Expr::FuncValue(op) => op.op_code(),
             Expr::ValDef(op) => op.op_code(),
+            Expr::ExtractAmount(op) => op.op_code(),
+            Expr::SelectField(op) => op.op_code(),
             _ => todo!("not yet implemented opcode for {0:?}", self),
         }
     }
@@ -98,6 +106,8 @@ impl Expr {
             Expr::OptionGet(v) => v.tpe(),
             Expr::ExtractRegisterAs(v) => v.tpe.clone(),
             Expr::Fold(v) => v.tpe(),
+            Expr::SelectField(v) => v.tpe(),
+            Expr::ExtractAmount(v) => v.tpe(),
         }
     }
 }
diff --git a/ergo-lib/src/ast/extract_amount.rs b/ergo-lib/src/ast/extract_amount.rs
new file mode 100644
index 000000000..8d6ad673c
--- /dev/null
+++ b/ergo-lib/src/ast/extract_amount.rs
@@ -0,0 +1,84 @@
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+use crate::serialization::op_code::OpCode;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SerializationError;
+use crate::serialization::SigmaSerializable;
+use crate::types::stype::SType;
+
+use super::expr::Expr;
+use super::expr::InvalidArgumentError;
+use super::value::Value;
+
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct ExtractAmount {
+    input: Box<Expr>,
+}
+
+impl ExtractAmount {
+    pub fn new(input: Expr) -> Result<Self, InvalidArgumentError> {
+        if input.tpe() == SType::SBox {
+            Ok(ExtractAmount {
+                input: input.into(),
+            })
+        } else {
+            Err(InvalidArgumentError(format!(
+                "ExtractAmount type expected to be SBox, got {0:?}",
+                input.tpe()
+            )))
+        }
+    }
+
+    pub fn tpe(&self) -> SType {
+        SType::SBox
+    }
+
+    pub fn op_code(&self) -> OpCode {
+        OpCode::EXTRACT_AMOUNT
+    }
+}
+
+impl Evaluable for ExtractAmount {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        match input_v {
+            Value::CBox(b) => Ok(Value::Long(b.value.as_i64())),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "Expected ExtractAmount input to be Value::CBox, got {0:?}",
+                input_v
+            ))),
+        }
+    }
+}
+
+impl SigmaSerializable for ExtractAmount {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        self.input.sigma_serialize(w)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        Ok(ExtractAmount {
+            input: Expr::sigma_parse(r)?.into(),
+        })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::ast::global_vars::GlobalVars;
+    use crate::serialization::sigma_serialize_roundtrip;
+
+    use super::*;
+
+    #[test]
+    fn ser_roundtrip() {
+        let e: Expr = ExtractAmount {
+            input: Box::new(Box::new(GlobalVars::SelfBox).into()),
+        }
+        .into();
+        assert_eq![sigma_serialize_roundtrip(&e), e];
+    }
+}
diff --git a/ergo-lib/src/ast/select_field.rs b/ergo-lib/src/ast/select_field.rs
new file mode 100644
index 000000000..ad7033639
--- /dev/null
+++ b/ergo-lib/src/ast/select_field.rs
@@ -0,0 +1,151 @@
+use std::convert::TryFrom;
+
+use crate::eval::env::Env;
+use crate::eval::EvalContext;
+use crate::eval::EvalError;
+use crate::eval::Evaluable;
+use crate::serialization::op_code::OpCode;
+use crate::serialization::sigma_byte_reader::SigmaByteRead;
+use crate::serialization::sigma_byte_writer::SigmaByteWrite;
+use crate::serialization::SerializationError;
+use crate::serialization::SigmaSerializable;
+use crate::types::stuple::STuple;
+use crate::types::stuple::STupleItemsOutOfBoundsError;
+use crate::types::stype::SType;
+
+use super::expr::Expr;
+use super::expr::InvalidArgumentError;
+use super::value::Value;
+
+/// Tuple field access index (1..255)
+#[derive(PartialEq, Eq, Debug, Copy, Clone)]
+pub struct TupleFieldIndex(u8);
+
+impl TryFrom<u8> for TupleFieldIndex {
+    type Error = STupleItemsOutOfBoundsError;
+
+    fn try_from(value: u8) -> Result<Self, Self::Error> {
+        if value >= 1 {
+            Ok(TupleFieldIndex(value))
+        } else {
+            Err(STupleItemsOutOfBoundsError())
+        }
+    }
+}
+
+impl From<TupleFieldIndex> for usize {
+    fn from(v: TupleFieldIndex) -> Self {
+        v.0 as usize
+    }
+}
+
+impl SigmaSerializable for TupleFieldIndex {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        w.put_u8(self.0)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        let field_index = r.get_u8()?;
+        TupleFieldIndex::try_from(field_index).map_err(|_| {
+            SerializationError::ValueOutOfBounds(format!(
+                "invalid tuple field index: {0}",
+                field_index
+            ))
+        })
+    }
+}
+
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct SelectField {
+    input: Box<Expr>,
+    field_index: TupleFieldIndex,
+}
+
+impl SelectField {
+    pub fn new(input: Expr, field_index: TupleFieldIndex) -> Result<Self, InvalidArgumentError> {
+        match input.tpe() {
+            SType::STuple(STuple { items }) => {
+                if items.len() >= field_index.into() {
+                    Ok(SelectField {
+                        input: Box::new(input),
+                        field_index,
+                    })
+                } else {
+                    Err(InvalidArgumentError(format!(
+                        "SelectField field index is out of bounds {0:?}, tuple type: {1:?}",
+                        field_index,
+                        input.tpe()
+                    )))
+                }
+            }
+            tpe => Err(InvalidArgumentError(format!(
+                "expected SelectField input type to be STuple, got {0:?}",
+                tpe
+            ))),
+        }
+    }
+
+    pub fn op_code(&self) -> OpCode {
+        OpCode::SELECT_FIELD
+    }
+}
+
+impl SelectField {
+    pub fn tpe(&self) -> SType {
+        match self.input.tpe() {
+            SType::STuple(STuple { items }) => items.get(self.field_index).unwrap().clone(),
+            tpe => panic!("expected input type to be STuple, got {0:?}", tpe),
+        }
+    }
+}
+
+impl Evaluable for SelectField {
+    fn eval(&self, env: &Env, ctx: &mut EvalContext) -> Result<Value, EvalError> {
+        let input_v = self.input.eval(env, ctx)?;
+        match input_v {
+            Value::Tup(items) => items.get(self.field_index).cloned().ok_or_else(|| {
+                EvalError::NotFound(format!(
+                    "SelectField field index is out of bounds. Index: {0:?}, tuple: {1:?}",
+                    self.field_index, items
+                ))
+            }),
+            _ => Err(EvalError::UnexpectedValue(format!(
+                "expected SelectField input to be Value::Tup, got: {0:?}",
+                input_v
+            ))),
+        }
+    }
+}
+
+impl SigmaSerializable for SelectField {
+    fn sigma_serialize<W: SigmaByteWrite>(&self, w: &mut W) -> Result<(), std::io::Error> {
+        self.input.sigma_serialize(w)?;
+        self.field_index.sigma_serialize(w)
+    }
+
+    fn sigma_parse<R: SigmaByteRead>(r: &mut R) -> Result<Self, SerializationError> {
+        let input = Expr::sigma_parse(r)?.into();
+        let field_index = TupleFieldIndex::sigma_parse(r)?;
+        Ok(SelectField { input, field_index })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::convert::TryInto;
+
+    use crate::serialization::sigma_serialize_roundtrip;
+
+    use super::*;
+
+    #[test]
+    fn ser_roundtrip() {
+        let e: Expr = SelectField::new(
+            Expr::Const(Box::new((1i64, true).into())),
+            1u8.try_into().unwrap(),
+        )
+        .unwrap()
+        .into();
+        assert_eq![sigma_serialize_roundtrip(&e), e];
+    }
+}
diff --git a/ergo-lib/src/ast/value.rs b/ergo-lib/src/ast/value.rs
index abc4ecc13..76606c23c 100644
--- a/ergo-lib/src/ast/value.rs
+++ b/ergo-lib/src/ast/value.rs
@@ -13,9 +13,9 @@ use crate::sigma_protocol::sigma_boolean::ProveDlog;
 use crate::sigma_protocol::sigma_boolean::SigmaBoolean;
 use crate::sigma_protocol::sigma_boolean::SigmaProofOfKnowledgeTree;
 use crate::sigma_protocol::sigma_boolean::SigmaProp;
+use crate::types::stuple::TupleItems;
 use crate::types::stype::LiftIntoSType;
 use crate::types::stype::SType;
-use crate::types::stype::TupleItems;
 
 use super::constant::TryExtractFrom;
 use super::constant::TryExtractFromError;
diff --git a/ergo-lib/src/chain/ergo_box/box_value.rs b/ergo-lib/src/chain/ergo_box/box_value.rs
index 3e9e27782..849365368 100644
--- a/ergo-lib/src/chain/ergo_box/box_value.rs
+++ b/ergo-lib/src/chain/ergo_box/box_value.rs
@@ -201,7 +201,7 @@ pub mod tests {
 
     impl Default for ArbBoxValueRange {
         fn default() -> Self {
-            ArbBoxValueRange(BoxValue::MIN_RAW..BoxValue::MAX_RAW)
+            ArbBoxValueRange(BoxValue::MIN_RAW..(BoxValue::MAX_RAW / 10))
         }
     }
 
diff --git a/ergo-lib/src/eval/context.rs b/ergo-lib/src/eval/context.rs
index ecc143695..0343aaf4b 100644
--- a/ergo-lib/src/eval/context.rs
+++ b/ergo-lib/src/eval/context.rs
@@ -75,7 +75,7 @@ mod tests {
             (
                 0..i32::MAX,
                 any::<ErgoBox>(),
-                vec(any::<ErgoBox>(), 0..3),
+                vec(any::<ErgoBox>(), 1..3),
                 vec(any::<ErgoBox>(), 0..3),
             )
                 .prop_map(|(height, self_box, outputs, data_inputs)| Self {
diff --git a/ergo-lib/src/eval/expr.rs b/ergo-lib/src/eval/expr.rs
index a610842c0..8045914b6 100644
--- a/ergo-lib/src/eval/expr.rs
+++ b/ergo-lib/src/eval/expr.rs
@@ -12,7 +12,7 @@ impl Evaluable for Expr {
         match self {
             Expr::Const(c) => Ok(c.v.clone()),
             Expr::PredefFunc(_) => todo!(),
-            Expr::Fold(_) => todo!(),
+            Expr::Fold(op) => op.eval(env, ctx),
             Expr::ExtractRegisterAs(op) => op.eval(env, ctx),
             Expr::GlobalVars(op) => op.eval(env, ctx),
             Expr::MethodCall(op) => op.eval(env, ctx),
@@ -24,6 +24,8 @@ impl Evaluable for Expr {
             Expr::FuncValue(op) => op.eval(env, ctx),
             Expr::ValUse(op) => op.eval(env, ctx),
             Expr::BlockValue(op) => op.eval(env, ctx),
+            Expr::SelectField(op) => op.eval(env, ctx),
+            Expr::ExtractAmount(op) => op.eval(env, ctx),
             _ => Err(EvalError::UnexpectedExpr(format!(
                 "Don't know how to eval Expr: {0:?}",
                 self
diff --git a/ergo-lib/src/serialization/data.rs b/ergo-lib/src/serialization/data.rs
index e0b5bc6fa..81dc88035 100644
--- a/ergo-lib/src/serialization/data.rs
+++ b/ergo-lib/src/serialization/data.rs
@@ -7,6 +7,7 @@ use crate::serialization::{
 use crate::sigma_protocol::{
     dlog_group::EcPoint, sigma_boolean::SigmaBoolean, sigma_boolean::SigmaProp,
 };
+use crate::types::stuple;
 use crate::types::stype::SType;
 use crate::util::AsVecU8;
 
@@ -82,7 +83,7 @@ impl DataSerializer {
                     v: elems,
                 }))
             }
-            STuple(types) => {
+            STuple(stuple::STuple { items: types }) => {
                 let mut items = Vec::new();
                 types.iter().try_for_each(|tpe| {
                     DataSerializer::sigma_parse(tpe, r).map(|v| items.push(v))
diff --git a/ergo-lib/src/serialization/expr.rs b/ergo-lib/src/serialization/expr.rs
index 7198d315a..122759a8a 100644
--- a/ergo-lib/src/serialization/expr.rs
+++ b/ergo-lib/src/serialization/expr.rs
@@ -7,12 +7,14 @@ use crate::ast::coll_fold::Fold;
 use crate::ast::constant::Constant;
 use crate::ast::constant::ConstantPlaceholder;
 use crate::ast::expr::Expr;
+use crate::ast::extract_amount::ExtractAmount;
 use crate::ast::extract_reg_as::ExtractRegisterAs;
 use crate::ast::func_value::FuncValue;
 use crate::ast::global_vars::GlobalVars;
 use crate::ast::method_call::MethodCall;
 use crate::ast::option_get::OptionGet;
 use crate::ast::property_call::PropertyCall;
+use crate::ast::select_field::SelectField;
 use crate::ast::val_def::ValDef;
 use crate::ast::val_use::ValUse;
 use crate::serialization::{
@@ -49,6 +51,8 @@ impl SigmaSerializable for Expr {
                     Expr::ValUse(op) => op.sigma_serialize(w),
                     Expr::ValDef(op) => op.sigma_serialize(w),
                     Expr::FuncValue(op) => op.sigma_serialize(w),
+                    Expr::ExtractAmount(op) => op.sigma_serialize(w),
+                    Expr::SelectField(op) => op.sigma_serialize(w),
                     _ => panic!(format!("don't know how to serialize {:?}", expr)),
                 }
             }
@@ -101,6 +105,8 @@ impl SigmaSerializable for Expr {
                 OpCode::FUNC_VALUE => Ok(Expr::FuncValue(FuncValue::sigma_parse(r)?.into())),
                 OpCode::VAL_DEF => Ok(Expr::ValDef(ValDef::sigma_parse(r)?.into())),
                 OpCode::VAL_USE => Ok(Expr::ValUse(ValUse::sigma_parse(r)?.into())),
+                OpCode::EXTRACT_AMOUNT => Ok(Expr::ExtractAmount(ExtractAmount::sigma_parse(r)?)),
+                OpCode::SELECT_FIELD => Ok(Expr::SelectField(SelectField::sigma_parse(r)?)),
                 o => Err(SerializationError::NotImplementedOpCode(o.value())),
             }
         }
diff --git a/ergo-lib/src/serialization/op_code.rs b/ergo-lib/src/serialization/op_code.rs
index d882140b5..ee4d438f6 100644
--- a/ergo-lib/src/serialization/op_code.rs
+++ b/ergo-lib/src/serialization/op_code.rs
@@ -21,6 +21,8 @@ impl OpCode {
     pub const VAL_USE: OpCode = Self::new_op_code(2);
     pub const CONSTANT_PLACEHOLDER: OpCode = Self::new_op_code(3);
 
+    pub const SELECT_FIELD: OpCode = Self::new_op_code(28);
+
     pub const EQ: OpCode = Self::new_op_code(35);
     pub const NEQ: OpCode = Self::new_op_code(36);
 
@@ -32,6 +34,7 @@ impl OpCode {
 
     pub const FOLD: OpCode = Self::new_op_code(64);
 
+    pub const EXTRACT_AMOUNT: OpCode = Self::new_op_code(81);
     pub const EXTRACT_REGISTER_AS: OpCode = Self::new_op_code(86);
 
     pub const PROVE_DLOG: OpCode = Self::new_op_code(93);
diff --git a/ergo-lib/src/serialization/types.rs b/ergo-lib/src/serialization/types.rs
index b60367acf..ba7b4a813 100644
--- a/ergo-lib/src/serialization/types.rs
+++ b/ergo-lib/src/serialization/types.rs
@@ -4,6 +4,7 @@ use super::sigma_byte_writer::SigmaByteWrite;
 use crate::serialization::{
     sigma_byte_reader::SigmaByteRead, SerializationError, SigmaSerializable,
 };
+use crate::types::stuple::STuple;
 use crate::types::stype::SType;
 use sigma_ser::vlq_encode;
 use std::convert::TryInto;
@@ -180,7 +181,7 @@ impl SigmaSerializable for SType {
                     elem_type.sigma_serialize(w)
                 }
             },
-            SType::STuple(items) => match items.clone().as_slice() {
+            SType::STuple(STuple { items }) => match items.clone().as_slice() {
                 [t1, t2] => match (t1, t2) {
                     (p, _) if is_stype_embeddable(p) => {
                         if p == t2 {
diff --git a/ergo-lib/src/types.rs b/ergo-lib/src/types.rs
index 7390ace22..786290f4e 100644
--- a/ergo-lib/src/types.rs
+++ b/ergo-lib/src/types.rs
@@ -6,5 +6,6 @@ pub(crate) mod sbox;
 pub(crate) mod scontext;
 pub(crate) mod sfunc;
 pub(crate) mod smethod;
+pub(crate) mod stuple;
 pub(crate) mod stype_companion;
 pub(crate) mod stype_param;
diff --git a/ergo-lib/src/types/sbox.rs b/ergo-lib/src/types/sbox.rs
index a7e95f139..2812e92e0 100644
--- a/ergo-lib/src/types/sbox.rs
+++ b/ergo-lib/src/types/sbox.rs
@@ -22,6 +22,11 @@ static S_BOX_TYPE_COMPANION_HEAD: STypeCompanionHead = STypeCompanionHead {
     type_name: "Box",
 };
 
+lazy_static! {
+    pub static ref S_BOX_TYPE_COMPANION: STypeCompanion =
+        STypeCompanion::new(&S_BOX_TYPE_COMPANION_HEAD, vec![&GET_REG_METHOD_DESC]);
+}
+
 static GET_REG_EVAL_FN: EvalFn = |obj, args| {
     Ok(Value::Opt(Box::new(
         obj.try_extract_into::<ErgoBox>()?
@@ -50,11 +55,57 @@ lazy_static! {
 }
 
 lazy_static! {
-    pub static ref S_BOX_TYPE_COMPANION: STypeCompanion =
-        STypeCompanion::new(&S_BOX_TYPE_COMPANION_HEAD, vec![&GET_REG_METHOD_DESC]);
+    pub static ref GET_REG_METHOD: SMethod =
+        SMethod::new(&S_BOX_TYPE_COMPANION, &GET_REG_METHOD_DESC,);
 }
 
 lazy_static! {
-    pub static ref GET_REG_METHOD: SMethod =
-        SMethod::new(&S_BOX_TYPE_COMPANION, &GET_REG_METHOD_DESC,);
+    static ref VALUE_METHOD_DESC: SMethodDesc = SMethodDesc {
+        method_id: MethodId(1),
+        name: "getReg",
+        tpe: SType::SFunc(Box::new(SFunc {
+            t_dom: vec![SType::SBox],
+            t_range: SType::SLong,
+            tpe_params: vec![],
+        })),
+        eval_fn: VALUE_EVAL_FN,
+    };
+}
+
+lazy_static! {
+    pub static ref VALUE_METHOD: SMethod = SMethod::new(&S_BOX_TYPE_COMPANION, &VALUE_METHOD_DESC,);
+}
+
+static VALUE_EVAL_FN: EvalFn = |obj, _args| {
+    Ok(Value::Long(
+        obj.try_extract_into::<ErgoBox>()?.value.as_i64(),
+    ))
+};
+
+#[cfg(test)]
+mod tests {
+    use std::rc::Rc;
+
+    use crate::ast::expr::Expr;
+    use crate::ast::global_vars::GlobalVars;
+    use crate::ast::property_call::PropertyCall;
+    use crate::eval::context::Context;
+    use crate::eval::tests::eval_out;
+    use crate::test_util::force_any_val;
+
+    use super::*;
+
+    #[test]
+    fn eval_box_value() {
+        let expr: Expr = Box::new(PropertyCall {
+            obj: Box::new(GlobalVars::SelfBox).into(),
+            method: VALUE_METHOD.clone(),
+        })
+        .into();
+        let ctx = Rc::new(force_any_val::<Context>());
+        assert_eq!(
+            eval_out::<i64>(&expr, ctx.clone()),
+            ctx.self_box.value.as_i64()
+        );
+    }
 }
diff --git a/ergo-lib/src/types/stuple.rs b/ergo-lib/src/types/stuple.rs
new file mode 100644
index 000000000..4bb9da24e
--- /dev/null
+++ b/ergo-lib/src/types/stuple.rs
@@ -0,0 +1,90 @@
+use std::convert::TryFrom;
+use std::convert::TryInto;
+use std::slice::Iter;
+
+use crate::ast::select_field::TupleFieldIndex;
+
+use super::stype::SType;
+
+/// Tuple items with bounds check (2..=255)
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct TupleItems<T>(Vec<T>);
+
+#[allow(clippy::len_without_is_empty)]
+impl<T> TupleItems<T> {
+    /// Create a pair
+    pub fn pair(t1: T, t2: T) -> TupleItems<T> {
+        TupleItems(vec![t1, t2])
+    }
+
+    /// Get the length (quantity)
+    pub fn len(&self) -> usize {
+        self.0.len()
+    }
+
+    /// Get an iterator
+    pub fn iter(&self) -> Iter<T> {
+        self.0.iter()
+    }
+
+    /// Get a slice
+    pub fn as_slice(&self) -> &[T] {
+        self.0.as_slice()
+    }
+
+    pub fn get(&self, index: TupleFieldIndex) -> Option<&T> {
+        let index_usize: usize = index.into();
+        self.0.get(index_usize - 1)
+    }
+}
+
+/// Out of bounds items quantity error
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct STupleItemsOutOfBoundsError();
+
+impl<T> TryFrom<Vec<T>> for TupleItems<T> {
+    type Error = STupleItemsOutOfBoundsError;
+
+    fn try_from(items: Vec<T>) -> Result<Self, Self::Error> {
+        if items.len() >= 2 && items.len() <= 255 {
+            Ok(TupleItems(items))
+        } else {
+            Err(STupleItemsOutOfBoundsError())
+        }
+    }
+}
+
+impl TryFrom<Vec<SType>> for STuple {
+    type Error = STupleItemsOutOfBoundsError;
+
+    fn try_from(value: Vec<SType>) -> Result<Self, Self::Error> {
+        Ok(STuple {
+            items: value.try_into()?,
+        })
+    }
+}
+
+#[derive(PartialEq, Eq, Debug, Clone)]
+pub struct STuple {
+    pub items: TupleItems<SType>,
+}
+
+impl STuple {
+    pub fn pair(t1: SType, t2: SType) -> Self {
+        STuple {
+            items: vec![t1, t2].try_into().unwrap(),
+        }
+    }
+}
+
+// pub struct STupleCompanion();
+
+// static S_TUPLE_TYPE_COMPANION_HEAD: STypeCompanionHead = STypeCompanionHead {
+//     type_id: TypeId(TypeCode::TUPLE.value()),
+//     type_name: "Tuple",
+// };
+
+// lazy_static! {
+//     pub static ref S_TUPLE_TYPE_COMPANION: STypeCompanion =
+//         STypeCompanion::new(&S_TUPLE_TYPE_COMPANION_HEAD, vec![]);
+// }
diff --git a/ergo-lib/src/types/stype.rs b/ergo-lib/src/types/stype.rs
index cf627e380..a2c926ceb 100644
--- a/ergo-lib/src/types/stype.rs
+++ b/ergo-lib/src/types/stype.rs
@@ -1,8 +1,6 @@
 //! SType hierarchy
 
-use std::convert::TryFrom;
 use std::convert::TryInto;
-use std::slice::Iter;
 
 use impl_trait_for_tuples::impl_for_tuples;
 
@@ -16,6 +14,7 @@ use crate::sigma_protocol::sigma_boolean::SigmaProp;
 
 use super::scontext::SContext;
 use super::sfunc::SFunc;
+use super::stuple::STuple;
 use super::stype_companion::STypeCompanion;
 use super::stype_param::STypeVar;
 
@@ -51,7 +50,7 @@ pub enum SType {
     /// Collection of elements of the same type
     SColl(Box<SType>),
     /// Tuple (elements can have different types)
-    STuple(TupleItems<SType>),
+    STuple(STuple),
     /// Function (signature)
     SFunc(Box<SFunc>),
     /// Context object ("CONTEXT" in ErgoScript)
@@ -88,50 +87,9 @@ impl SType {
     }
 }
 
-/// Tuple items with bounds check (2..=255)
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct TupleItems<T>(Vec<T>);
-
-#[allow(clippy::len_without_is_empty)]
-impl<T> TupleItems<T> {
-    // pub fn into_vec(self) -> Vec<T> {
-    //     self.0
-    // }
-
-    /// Create a pair
-    pub fn pair(t1: T, t2: T) -> TupleItems<T> {
-        TupleItems(vec![t1, t2])
-    }
-
-    /// Get the length (quantity)
-    pub fn len(&self) -> usize {
-        self.0.len()
-    }
-
-    /// Get an iterator
-    pub fn iter(&self) -> Iter<T> {
-        self.0.iter()
-    }
-
-    /// Get a slice
-    pub fn as_slice(&self) -> &[T] {
-        self.0.as_slice()
-    }
-}
-
-/// Out of bounds items quantity error
-#[derive(PartialEq, Eq, Debug, Clone)]
-pub struct STupleItemsOutOfBoundsError();
-
-impl<T> TryFrom<Vec<T>> for TupleItems<T> {
-    type Error = STupleItemsOutOfBoundsError;
-
-    fn try_from(items: Vec<T>) -> Result<Self, Self::Error> {
-        if items.len() >= 2 && items.len() <= 255 {
-            Ok(TupleItems(items))
-        } else {
-            Err(STupleItemsOutOfBoundsError())
-        }
+impl From<STuple> for SType {
+    fn from(v: STuple) -> Self {
+        SType::STuple(v)
     }
 }
 


Test patch: