Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding context.getvarfrominput and context.getvar #803

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ergotree-interpreter/src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,8 @@ fn smethod_eval_fn(method: &SMethod) -> Result<EvalFn, EvalError> {
self::scontext::LAST_BLOCK_UTXO_ROOT_HASH_EVAL_FN
}
scontext::MINER_PUBKEY_PROPERTY_METHOD_ID => self::scontext::MINER_PUBKEY_EVAL_FN,
scontext::GET_VAR_METHOD_ID => self::scontext::GET_VAR_EVAL_FN,
scontext::GET_VAR_FROM_INPUT_METHOD_ID => self::scontext::GET_VAR_FROM_INPUTS_EVAL_FN,
method_id => {
return Err(EvalError::NotFound(format!(
"Eval fn: unknown method id in SContext: {:?}",
Expand Down
246 changes: 246 additions & 0 deletions ergotree-interpreter/src/eval/scontext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,88 @@ pub(crate) static MINER_PUBKEY_EVAL_FN: EvalFn = |_mc, _env, ctx, obj, _args| {
.into())
};

pub(crate) static GET_VAR_EVAL_FN: EvalFn = |mc, _env, ctx, obj, args| {
if obj != Value::Context {
return Err(EvalError::UnexpectedValue(format!(
"Context.getVar: expected object of Value::Context, got {:?}",
obj
)));
}
let var_id = match args.first() {
Some(Value::Byte(id)) => *id as u8,
_ => {
return Err(EvalError::UnexpectedValue(
"getVar: invalid variable id".to_string(),
))
}
};

let expected_type = match mc.tpe().t_range.as_ref() {
SType::SOption(inner_type) => inner_type.as_ref(),
_ => {
return Err(EvalError::UnexpectedValue(
"Expected SOption type".to_string(),
))
}
};

match ctx.extension.values.get(&var_id) {
None => Ok(Value::Opt(Box::new(None))),
Some(v) if v.tpe == *expected_type => Ok(Value::Opt(Box::new(Some(v.v.clone().into())))),
Some(_) => Err(EvalError::UnexpectedValue(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should return None

"getVar: type mismatch".to_string(),
)),
}
};

pub(crate) static GET_VAR_FROM_INPUTS_EVAL_FN: EvalFn = |mc, _env, ctx, obj, args| {
if obj != Value::Context {
return Err(EvalError::UnexpectedValue(format!(
"Context.getVarFromInputs: expected object of Value::Context, got {:?}",
obj
)));
}

let input_index = match args.first() {
Some(Value::Short(idx)) if *idx >= 0 => *idx as usize,
_ => {
return Err(EvalError::UnexpectedValue(
"getVarFromInput: invalid input index".to_string(),
))
}
};
let var_id = match args.get(1) {
Some(Value::Byte(id)) => *id as u8,
_ => {
return Err(EvalError::UnexpectedValue(
"getVarFromInput: invalid variable id".to_string(),
))
}
};

let expected_type = match mc.tpe().t_range.as_ref() {
SType::SOption(inner_type) => inner_type.as_ref(),
_ => {
return Err(EvalError::UnexpectedValue(
"Expected SOption type".to_string(),
))
}
};

if ctx.inputs.get(input_index).is_none() {
return Err(EvalError::NotFound(format!(
"getVarFromInput: input not found at index {}",
input_index
)));
}

match ctx.extension.values.get(&var_id) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is supposed to get from input_index's context extension

None => Ok(Value::Opt(Box::new(None))),
Some(v) if v.tpe == *expected_type => Ok(Value::Opt(Box::new(Some(v.v.clone().into())))),
Some(_v) => Ok(Value::Opt(Box::new(None))),
}
};

#[cfg(test)]
#[cfg(feature = "arbitrary")]
#[allow(clippy::unwrap_used, clippy::expect_used)]
Expand All @@ -107,10 +189,18 @@ mod tests {
use ergotree_ir::chain::context::Context;
use ergotree_ir::chain::ergo_box::ErgoBox;
use ergotree_ir::mir::avl_tree_data::{AvlTreeData, AvlTreeFlags};
use ergotree_ir::mir::constant::Constant;
use ergotree_ir::mir::expr::Expr;
use ergotree_ir::mir::method_call::MethodCall;
use ergotree_ir::mir::property_call::PropertyCall;
use ergotree_ir::mir::value::Value;
use ergotree_ir::serialization::SigmaSerializable;
use ergotree_ir::types::scontext;
use ergotree_ir::types::stype::SType;
use ergotree_ir::types::stype_param::STypeVar;
use proptest::arbitrary::any;
use proptest::prelude::ProptestConfig;
use proptest::proptest;
use sigma_test_util::force_any_val;

fn make_ctx_inputs_includes_self_box() -> Context<'static> {
Expand All @@ -127,6 +217,68 @@ mod tests {
}
}

fn prepare_getvar_from_input_context<T>(
input_idx: i16,
var_idx: u8,
var_val: T,
) -> Context<'static>
where
T: Into<Constant> + Clone,
{
let mut ctx = force_any_val::<Context>();
ctx.extension.values.clear();
ctx.extension.values.insert(var_idx, var_val.into());
let input_box = &*Box::leak(Box::new(force_any_val::<ErgoBox>()));
let other_box = &*Box::leak(Box::new(force_any_val::<ErgoBox>()));
let mut inputs = Vec::new();
if input_idx > 0 {
for _ in 0..input_idx {
inputs.push(other_box);
}
}
inputs.push(input_box);
ctx.inputs = inputs.try_into().unwrap();
ctx
}

pub fn create_method_call(stype: SType, input_idx: i16, var_idx: u8) -> Expr {
let type_args = std::iter::once((STypeVar::t(), stype)).collect();
MethodCall::with_type_args(
Expr::Context,
scontext::GET_VAR_FROM_INPUT_METHOD
.clone()
.with_concrete_types(&type_args),
vec![input_idx.into(), (var_idx as i8).into()],
type_args,
)
.unwrap()
.into()
}

fn prepare_getvar_context<T>(var_idx: u8, var_val: T) -> Context<'static>
where
T: Into<Constant> + Clone,
{
let mut ctx = force_any_val::<Context>();
ctx.extension.values.clear();
ctx.extension.values.insert(var_idx, var_val.into());
ctx
}

pub fn create_getvar_method_call(stype: SType, var_idx: u8) -> Expr {
let type_args = std::iter::once((STypeVar::t(), stype)).collect();
MethodCall::with_type_args(
Expr::Context,
scontext::GET_VAR_METHOD
.clone()
.with_concrete_types(&type_args),
vec![(var_idx as i8).into()],
type_args,
)
.unwrap()
.into()
}

#[test]
fn eval_self_box_index() {
let expr: Expr =
Expand Down Expand Up @@ -186,4 +338,98 @@ mod tests {
};
assert_eq!(eval_out::<AvlTreeData>(&expr, &ctx), avl_tree_data);
}

#[test]
fn eval_getvar_from_input_type_mismatch() {
let ctx = prepare_getvar_from_input_context(0i16, 1u8, 123i32);
let result = eval_out::<Option<Value>>(&create_method_call(SType::SByte, 0, 1), &ctx);
assert_eq!(result, None);
}

#[test]
#[should_panic(expected = "getVar: type mismatch")]
fn eval_getvar_type_mismatch() {
let ctx = prepare_getvar_context(1u8, 123i32);
eval_out::<Option<Value>>(&create_getvar_method_call(SType::SByte, 1), &ctx);
}

proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]

#[test]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rustfmt doesn't work inside proptest macros unfortunately, can you manually fix some of the indent issues?

fn eval_get_var_from_input_int(
input_idx in 0..10i16,
var_idx in any::<u8>(),
var_val in any::<i32>()
) {
let result = eval_out::<Option<Value>>(
&create_method_call(SType::SInt, input_idx, var_idx),
&prepare_getvar_from_input_context(input_idx, var_idx, var_val)
);
assert_eq!(result, Some(Value::Int(var_val)));
}

#[test]
fn eval_get_var_from_input_long(
input_idx in 0..10i16,
var_idx in any::<u8>(),
var_val in any::<i64>()
) {
let result = eval_out::<Option<Value>>(
&create_method_call(SType::SLong, input_idx, var_idx),
&prepare_getvar_from_input_context(input_idx, var_idx, var_val)
);
assert_eq!(result, Some(Value::Long(var_val)));
}

#[test]
fn eval_get_var_from_input_byte(
input_idx in 0..10i16,
var_idx in any::<u8>(),
var_val in any::<i8>()
) {
let result = eval_out::<Option<Value>>(
&create_method_call(SType::SByte, input_idx, var_idx),
&prepare_getvar_from_input_context(input_idx, var_idx, var_val)
);
assert_eq!(result, Some(Value::Byte(var_val)));
}

#[test]
fn eval_get_var_int(
var_idx in any::<u8>(),
var_val in any::<i32>()
) {
let result = eval_out::<Option<Value>>(
&create_getvar_method_call(SType::SInt, var_idx),
&prepare_getvar_context(var_idx, var_val)
);
assert_eq!(result, Some(Value::Int(var_val)));
}

#[test]
fn eval_get_var_long(
var_idx in any::<u8>(),
var_val in any::<i64>()
) {
let result = eval_out::<Option<Value>>(
&create_getvar_method_call(SType::SLong, var_idx),
&prepare_getvar_context(var_idx, var_val)
);
assert_eq!(result, Some(Value::Long(var_val)));
}

#[test]
fn eval_get_var_byte(
var_idx in any::<u8>(),
var_val in any::<i8>()
) {
let result = eval_out::<Option<Value>>(
&create_getvar_method_call(SType::SByte, var_idx),
&prepare_getvar_context(var_idx, var_val)
);
assert_eq!(result, Some(Value::Byte(var_val)));
}

}
}
44 changes: 44 additions & 0 deletions ergotree-ir/src/types/scontext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use super::smethod::SMethod;
use super::smethod::SMethodDesc;
use super::stype::SType;
use super::stype::SType::{SAvlTree, SBox, SByte, SColl, SHeader, SInt, SPreHeader};
use crate::ergo_tree::ErgoTreeVersion;
use crate::types::sfunc::SFunc;
use crate::types::stype_param::STypeVar;
use alloc::vec;
use alloc::vec::Vec;
use lazy_static::lazy_static;
Expand All @@ -30,6 +33,8 @@ lazy_static! {
&SELF_BOX_INDEX_PROPERTY_METHOD_DESC,
&LAST_BLOCK_UTXO_ROOT_HASH_PROPERTY_METHOD_DESC,
&MINER_PUBKEY_PROPERTY_METHOD_DESC,
&GET_VAR_FROM_INPUT_METHOD_DESC,
&GET_VAR_METHOD_DESC,
];
}

Expand Down Expand Up @@ -158,6 +163,45 @@ lazy_static! {
);
}

pub const GET_VAR_METHOD_ID: MethodId = MethodId(11);
lazy_static! {
static ref GET_VAR_METHOD_DESC: SMethodDesc = SMethodDesc {
method_id: GET_VAR_METHOD_ID,
name: "getVar",
tpe: SFunc {
t_dom: vec![SType::SContext, SType::SByte],
t_range: SType::SOption(SType::STypeVar(STypeVar::t()).into()).into(),
tpe_params: vec![],
},
explicit_type_args: vec![STypeVar::t()],
min_version: ErgoTreeVersion::V3,
};
}
lazy_static! {
pub static ref GET_VAR_METHOD: SMethod =
SMethod::new(STypeCompanion::Context, GET_VAR_METHOD_DESC.clone());
}
pub const GET_VAR_FROM_INPUT_METHOD_ID: MethodId = MethodId(12);
lazy_static! {
static ref GET_VAR_FROM_INPUT_METHOD_DESC: SMethodDesc = SMethodDesc {
method_id: GET_VAR_FROM_INPUT_METHOD_ID,
name: "getVarFromInput",
tpe: SFunc {
t_dom: vec![SType::SContext, SType::SShort, SType::SByte],
t_range: SType::SOption(SType::STypeVar(STypeVar::t()).into()).into(),
tpe_params: vec![],
},
explicit_type_args: vec![STypeVar::t()],
min_version: ErgoTreeVersion::V3
};
}
lazy_static! {
pub static ref GET_VAR_FROM_INPUT_METHOD: SMethod = SMethod::new(
STypeCompanion::Context,
GET_VAR_FROM_INPUT_METHOD_DESC.clone(),
);
}

fn property(name: &'static str, res_tpe: SType, id: MethodId) -> SMethodDesc {
SMethodDesc::property(SType::SContext, name, res_tpe, id)
}
Expand Down
Loading