Skip to content
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
5 changes: 5 additions & 0 deletions ergotree-interpreter/src/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@ fn smethod_eval_fn(method: &SMethod) -> Result<EvalFn, EvalError> {
},
sbox::TYPE_CODE => match method.method_id() {
sbox::VALUE_METHOD_ID => self::sbox::VALUE_EVAL_FN,
sbox::PROPOSITION_BYTES_METHOD_ID => self::sbox::PROPOSITION_BYTES_EVAL_FN,
sbox::BYTES_METHOD_ID => self::sbox::BYTES_EVAL_FN,
sbox::BYTES_WITHOUT_REF_METHOD_ID => self::sbox::BYTES_WITHOUT_REF_EVAL_FN,
sbox::ID_METHOD_ID => self::sbox::ID_EVAL_FN,
sbox::CREATION_INFO_METHOD_ID => self::sbox::CREATION_INFO_EVAL_FN,
sbox::GET_REG_METHOD_ID => self::sbox::GET_REG_EVAL_FN,
sbox::TOKENS_METHOD_ID => self::sbox::TOKENS_EVAL_FN,
method_id => {
Expand Down
83 changes: 83 additions & 0 deletions ergotree-interpreter/src/eval/sbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ use crate::eval::EvalError;

use alloc::boxed::Box;
use alloc::string::ToString;
use alloc::vec::Vec;
use ergotree_ir::chain::ergo_box::ErgoBox;
use ergotree_ir::ergo_tree::ErgoTreeVersion;
use ergotree_ir::mir::constant::TryExtractInto;
use ergotree_ir::mir::value::Value;
use ergotree_ir::reference::Ref;
use ergotree_ir::serialization::SigmaSerializable;
use ergotree_ir::types::stype::SType;

use super::EvalFn;
Expand Down Expand Up @@ -75,6 +77,45 @@ pub(crate) static TOKENS_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
Ok(res)
};

// The accessor method-forms (PropertyCall 99:2..6) dispatch through the same box
// accessors as their op-form twins (ExtractScriptBytes/ExtractBytes/ExtractBytesWithNoRef/
// ExtractId/ExtractCreationInfo), so a hand-crafted `PropertyCall(99,N)` tree evaluates
// (JVM `MethodCall.eval` -> `invokeFixed`, methods.scala SBoxMethods, present from
// v5Methods with no version gate) instead of erroring.

pub(crate) static PROPOSITION_BYTES_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
Ok(obj
.try_extract_into::<Ref<'_, ErgoBox>>()?
.script_bytes()?
.into())
};

pub(crate) static BYTES_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
Ok(obj
.try_extract_into::<Ref<'_, ErgoBox>>()?
.sigma_serialize_bytes()?
.into())
};

pub(crate) static BYTES_WITHOUT_REF_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
Ok(obj
.try_extract_into::<Ref<'_, ErgoBox>>()?
.bytes_without_ref()?
.into())
};

pub(crate) static ID_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
let bytes: Vec<i8> = obj.try_extract_into::<Ref<'_, ErgoBox>>()?.box_id().into();
Ok(bytes.into())
};

pub(crate) static CREATION_INFO_EVAL_FN: EvalFn = |_mc, _env, _ctx, obj, _args| {
Ok(obj
.try_extract_into::<Ref<'_, ErgoBox>>()?
.creation_info()
.into())
};

#[allow(clippy::unwrap_used)]
#[allow(clippy::panic)]
#[cfg(test)]
Expand Down Expand Up @@ -116,6 +157,48 @@ mod tests {
);
}

// The accessor method-forms (PropertyCall 99:2..6) must dispatch (instead of erroring)
// and return the same value as their op-form twins
// (ExtractScriptBytes/ExtractBytes/ExtractBytesWithNoRef/ExtractId/ExtractCreationInfo) —
// the JVM evaluates them via `MethodCall.eval`'s `invokeFixed` reflection over
// `commonBoxMethods` (methods.scala SBoxMethods), present from v5Methods with no gate.
#[test]
fn eval_box_accessor_method_forms() {
use ergotree_ir::serialization::SigmaSerializable;
use sigma_util::AsVecI8;

let ctx = force_any_val::<Context>();
let pc = |m: &ergotree_ir::types::smethod::SMethod| -> Expr {
PropertyCall::new(GlobalVars::SelfBox.into(), m.clone())
.unwrap()
.into()
};

// propositionBytes (99:2) == ExtractScriptBytes
assert_eq!(
eval_out::<Vec<i8>>(&pc(&sbox::PROPOSITION_BYTES_METHOD), &ctx),
ctx.self_box.script_bytes().unwrap()
);
// bytes (99:3) == ExtractBytes (the box's serialized content on this branch)
assert_eq!(
eval_out::<Vec<i8>>(&pc(&sbox::BYTES_METHOD), &ctx),
ctx.self_box.sigma_serialize_bytes().unwrap().as_vec_i8()
);
// bytesWithoutRef (99:4) == ExtractBytesWithNoRef
assert_eq!(
eval_out::<Vec<i8>>(&pc(&sbox::BYTES_WITHOUT_REF_METHOD), &ctx),
ctx.self_box.bytes_without_ref().unwrap()
);
// id (99:5) == ExtractId
let id: Vec<i8> = ctx.self_box.box_id().into();
assert_eq!(eval_out::<Vec<i8>>(&pc(&sbox::ID_METHOD), &ctx), id);
// creationInfo (99:6) == ExtractCreationInfo
assert_eq!(
eval_out::<(i32, Vec<i8>)>(&pc(&sbox::CREATION_INFO_METHOD), &ctx),
ctx.self_box.creation_info()
);
}

#[test]
fn eval_reg_out() {
let type_args = std::iter::once((STypeVar::t(), SType::SLong)).collect();
Expand Down
123 changes: 123 additions & 0 deletions ergotree-ir/src/types/sbox.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,16 @@ pub const TYPE_CODE: TypeCode = TypeCode::SBOX;
pub static TYPE_NAME: &str = "Box";
/// Box.value property
pub const VALUE_METHOD_ID: MethodId = MethodId(1);
/// Box.propositionBytes property (JVM `PropositionBytesMethod`, op-form `ExtractScriptBytes`)
pub const PROPOSITION_BYTES_METHOD_ID: MethodId = MethodId(2);
/// Box.bytes property (JVM `BytesMethod`, op-form `ExtractBytes`)
pub const BYTES_METHOD_ID: MethodId = MethodId(3);
/// Box.bytesWithoutRef property (JVM `BytesWithoutRefMethod`, op-form `ExtractBytesWithNoRef`)
pub const BYTES_WITHOUT_REF_METHOD_ID: MethodId = MethodId(4);
/// Box.id property (JVM `IdMethod`, op-form `ExtractId`)
pub const ID_METHOD_ID: MethodId = MethodId(5);
/// Box.creationInfo property (JVM `creationInfoMethod`, op-form `ExtractCreationInfo`)
pub const CREATION_INFO_METHOD_ID: MethodId = MethodId(6);
/// Box.Rx property
pub const GET_REG_METHOD_ID: MethodId = MethodId(7);
/// Box.tokens property
Expand All @@ -32,6 +42,11 @@ lazy_static! {
vec![
GET_REG_METHOD_DESC.clone(),
VALUE_METHOD_DESC.clone(),
PROPOSITION_BYTES_METHOD_DESC.clone(),
BYTES_METHOD_DESC.clone(),
BYTES_WITHOUT_REF_METHOD_DESC.clone(),
ID_METHOD_DESC.clone(),
CREATION_INFO_METHOD_DESC.clone(),
TOKENS_METHOD_DESC.clone()
]
;
Expand All @@ -53,6 +68,100 @@ lazy_static! {
pub static ref VALUE_METHOD: SMethod = SMethod::new(STypeCompanion::Box, VALUE_METHOD_DESC.clone(),);
}

// The byte-array accessor method-forms (propositionBytes / bytes / bytesWithoutRef / id)
// mirror the JVM `commonBoxMethods` entries (methods.scala, SBoxMethods): each is a zero-arg
// PropertyCall `SFunc(SBox, Coll[Byte])`, present from v5Methods with NO version gate, evaluated
// JVM-side via `MethodCall.eval`'s `invokeFixed` reflection over the op-form's `costKind`.
lazy_static! {
static ref PROPOSITION_BYTES_METHOD_DESC: SMethodDesc = SMethodDesc {
method_id: PROPOSITION_BYTES_METHOD_ID,
name: "propositionBytes",
tpe: SFunc {
t_dom: vec![SType::SBox],
t_range: Box::new(SType::SColl(SType::SByte.into())),
tpe_params: vec![],
},
explicit_type_args: vec![],
min_version: ErgoTreeVersion::V0
};
/// Box.propositionBytes
pub static ref PROPOSITION_BYTES_METHOD: SMethod =
SMethod::new(STypeCompanion::Box, PROPOSITION_BYTES_METHOD_DESC.clone(),);
}

lazy_static! {
static ref BYTES_METHOD_DESC: SMethodDesc = SMethodDesc {
method_id: BYTES_METHOD_ID,
name: "bytes",
tpe: SFunc {
t_dom: vec![SType::SBox],
t_range: Box::new(SType::SColl(SType::SByte.into())),
tpe_params: vec![],
},
explicit_type_args: vec![],
min_version: ErgoTreeVersion::V0
};
/// Box.bytes
pub static ref BYTES_METHOD: SMethod =
SMethod::new(STypeCompanion::Box, BYTES_METHOD_DESC.clone(),);
}

lazy_static! {
static ref BYTES_WITHOUT_REF_METHOD_DESC: SMethodDesc = SMethodDesc {
method_id: BYTES_WITHOUT_REF_METHOD_ID,
name: "bytesWithoutRef",
tpe: SFunc {
t_dom: vec![SType::SBox],
t_range: Box::new(SType::SColl(SType::SByte.into())),
tpe_params: vec![],
},
explicit_type_args: vec![],
min_version: ErgoTreeVersion::V0
};
/// Box.bytesWithoutRef
pub static ref BYTES_WITHOUT_REF_METHOD: SMethod =
SMethod::new(STypeCompanion::Box, BYTES_WITHOUT_REF_METHOD_DESC.clone(),);
}

lazy_static! {
static ref ID_METHOD_DESC: SMethodDesc = SMethodDesc {
method_id: ID_METHOD_ID,
name: "id",
tpe: SFunc {
t_dom: vec![SType::SBox],
t_range: Box::new(SType::SColl(SType::SByte.into())),
tpe_params: vec![],
},
explicit_type_args: vec![],
min_version: ErgoTreeVersion::V0
};
/// Box.id
pub static ref ID_METHOD: SMethod =
SMethod::new(STypeCompanion::Box, ID_METHOD_DESC.clone(),);
}

// creationInfo returns `(Int, Coll[Byte])` — the tx block height paired with the serialized
// transaction id followed by the box's output index (JVM `ExtractCreationInfo.OpType`).
lazy_static! {
static ref CREATION_INFO_METHOD_DESC: SMethodDesc = SMethodDesc {
method_id: CREATION_INFO_METHOD_ID,
name: "creationInfo",
tpe: SFunc {
t_dom: vec![SType::SBox],
t_range: Box::new(SType::STuple(STuple::pair(
SType::SInt,
SType::SColl(SType::SByte.into())
))),
tpe_params: vec![],
},
explicit_type_args: vec![],
min_version: ErgoTreeVersion::V0
};
/// Box.creationInfo
pub static ref CREATION_INFO_METHOD: SMethod =
SMethod::new(STypeCompanion::Box, CREATION_INFO_METHOD_DESC.clone(),);
}

lazy_static! {
static ref GET_REG_METHOD_DESC: SMethodDesc = SMethodDesc {
method_id: GET_REG_METHOD_ID,
Expand Down Expand Up @@ -104,6 +213,20 @@ mod tests {
#[test]
fn test_from_ids() {
assert!(SMethod::from_ids(TYPE_CODE, VALUE_METHOD_ID).map(|e| e.name()) == Ok("value"));
assert!(
SMethod::from_ids(TYPE_CODE, PROPOSITION_BYTES_METHOD_ID).map(|e| e.name())
== Ok("propositionBytes")
);
assert!(SMethod::from_ids(TYPE_CODE, BYTES_METHOD_ID).map(|e| e.name()) == Ok("bytes"));
assert!(
SMethod::from_ids(TYPE_CODE, BYTES_WITHOUT_REF_METHOD_ID).map(|e| e.name())
== Ok("bytesWithoutRef")
);
assert!(SMethod::from_ids(TYPE_CODE, ID_METHOD_ID).map(|e| e.name()) == Ok("id"));
assert!(
SMethod::from_ids(TYPE_CODE, CREATION_INFO_METHOD_ID).map(|e| e.name())
== Ok("creationInfo")
);
assert!(SMethod::from_ids(TYPE_CODE, GET_REG_METHOD_ID).map(|e| e.name()) == Ok("getReg"));
assert!(SMethod::from_ids(TYPE_CODE, TOKENS_METHOD_ID).map(|e| e.name()) == Ok("tokens"));
}
Expand Down
Loading