From 7179b65cf62452e58e6c214d2daabe3c995e48c2 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 20 Jan 2025 15:17:11 +0100 Subject: [PATCH 1/2] MIR: add entrypoint to interpret --- contrib/mir/examples/lazy_parse.rs | 8 +- contrib/mir/examples/thread_local_storage.rs | 8 +- contrib/mir/src/ast.rs | 5 + contrib/mir/src/ast/annotations.rs | 4 +- .../src/ast/michelson_address/entrypoint.rs | 15 +- contrib/mir/src/interpreter.rs | 554 +++++++++++++++++- contrib/mir/src/lib.rs | 6 +- contrib/mir/src/typechecker.rs | 103 +++- 8 files changed, 673 insertions(+), 30 deletions(-) diff --git a/contrib/mir/examples/lazy_parse.rs b/contrib/mir/examples/lazy_parse.rs index 2e35b0d0219d..c84a15f3fcd3 100644 --- a/contrib/mir/examples/lazy_parse.rs +++ b/contrib/mir/examples/lazy_parse.rs @@ -41,7 +41,13 @@ fn run_contract(parameter: Micheline, storage: Micheline) { let contract_typechecked = contract_micheline.typecheck_script(&mut ctx).unwrap(); let (_, new_storage) = contract_typechecked - .interpret(&mut ctx, &parser.arena, parameter, storage) + .interpret( + &mut ctx, + &parser.arena, + parameter, + None, + storage, + ) .unwrap(); let TypedValue::Nat(storage_nat) = &new_storage else { unreachable!() diff --git a/contrib/mir/examples/thread_local_storage.rs b/contrib/mir/examples/thread_local_storage.rs index 3aed5423a5ff..c3a0596531ee 100644 --- a/contrib/mir/examples/thread_local_storage.rs +++ b/contrib/mir/examples/thread_local_storage.rs @@ -35,7 +35,13 @@ fn run_contract(parameter: Micheline) { storage.replace_with(|storage| { let storage = Micheline::decode_raw(&parser.arena, storage).unwrap(); let (_, new_storage) = contract_typechecked - .interpret(&mut ctx, &parser.arena, parameter, storage) + .interpret( + &mut ctx, + &parser.arena, + parameter, + None, + storage, + ) .unwrap(); let TypedValue::Nat(storage_nat) = &new_storage else { unreachable!() diff --git a/contrib/mir/src/ast.rs b/contrib/mir/src/ast.rs index 0326174dc0aa..92c0f629d015 100644 --- a/contrib/mir/src/ast.rs +++ b/contrib/mir/src/ast.rs @@ -25,6 +25,7 @@ pub mod overloads; pub use micheline::Micheline; use num_bigint::{BigInt, BigUint}; +use std::collections::HashMap; use std::{ collections::{BTreeMap, BTreeSet}, rc::Rc, @@ -51,6 +52,8 @@ pub use michelson_operation::{ pub use michelson_signature::Signature; pub use or::Or; +use self::entrypoint::Direction; + /// Representation for values of the Michelson `ticket` type. #[derive(Debug, Clone, Eq, PartialEq)] pub struct Ticket<'a> { @@ -650,6 +653,8 @@ pub struct ContractScript<'a> { pub storage: Type, /// Script code. Corresponds to the script's `code` field. pub code: Instruction<'a>, + /// Script entrypoints. + pub annotations: HashMap, (Vec, Type)>, } #[cfg(test)] diff --git a/contrib/mir/src/ast/annotations.rs b/contrib/mir/src/ast/annotations.rs index 3df8a1f2e0c2..dd79d5fdf9dc 100644 --- a/contrib/mir/src/ast/annotations.rs +++ b/contrib/mir/src/ast/annotations.rs @@ -81,7 +81,7 @@ impl std::fmt::Debug for Annotations<'_> { /// A newtype wrapping a field annotation, like `%foo`. This newtype is used to /// enforce some invariants on the type level. It's impossible to construct /// manually, except in tests. -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FieldAnnotation<'a>(Cow<'a, str>); impl<'a> FieldAnnotation<'a> { @@ -96,7 +96,7 @@ impl<'a> FieldAnnotation<'a> { self.0 } - #[cfg(test)] + /// Get the field annotation from a string slice. pub fn from_str_unchecked(s: &'a str) -> Self { FieldAnnotation(Cow::Borrowed(s)) } diff --git a/contrib/mir/src/ast/michelson_address/entrypoint.rs b/contrib/mir/src/ast/michelson_address/entrypoint.rs index 70e520dc8b20..a43bed4458b2 100644 --- a/contrib/mir/src/ast/michelson_address/entrypoint.rs +++ b/contrib/mir/src/ast/michelson_address/entrypoint.rs @@ -21,6 +21,15 @@ use super::ByteReprError; #[derive(Debug, Clone, Eq, PartialOrd, Ord, PartialEq, Hash)] pub struct Entrypoint(String); +/// Enum representing each layer to achieve a given entrypoint +#[derive(Debug, Clone, Eq, PartialEq)] +pub enum Direction { + /// Left + Left, + /// Right + Right, +} + /// A structure mapping from entrypoints to their types. This is simply an alias /// for a [HashMap]. pub type Entrypoints = HashMap; @@ -31,9 +40,9 @@ impl std::fmt::Display for Entrypoint { } } -// NB: default entrypoint is represented as literal "default", because it -// affects comparison for addresses. -const DEFAULT_EP_NAME: &str = "default"; +/// NB: default entrypoint is represented as literal "default", because it +/// affects comparison for addresses. +pub const DEFAULT_EP_NAME: &str = "default"; const MAX_EP_LEN: usize = 31; impl Default for Entrypoint { diff --git a/contrib/mir/src/interpreter.rs b/contrib/mir/src/interpreter.rs index 67d926322bc1..83eb81a53d72 100644 --- a/contrib/mir/src/interpreter.rs +++ b/contrib/mir/src/interpreter.rs @@ -10,6 +10,7 @@ use checked::Checked; use cryptoxide::hashing::{blake2b_256, keccak256, sha256, sha3_256, sha512}; +use entrypoint::DEFAULT_EP_NAME; use num_bigint::{BigInt, BigUint, Sign}; use num_integer::Integer; use num_traits::{Signed, ToPrimitive, Zero}; @@ -20,13 +21,15 @@ use tezos_crypto_rs::blake2b::digest as blake2bdigest; use typed_arena::Arena; use crate::ast::big_map::{BigMap, LazyStorageError}; +use crate::ast::michelson_address::entrypoint::Direction; use crate::ast::*; use crate::bls; use crate::context::Ctx; use crate::gas::{interpret_cost, OutOfGas}; use crate::irrefutable_match::irrefutable_match; +use crate::lexer::Prim; use crate::stack::*; -use crate::typechecker::{typecheck_contract_address, typecheck_value}; +use crate::typechecker::{typecheck_contract_address, typecheck_value, TcError}; /// Errors possible during interpretation. #[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] @@ -78,12 +81,13 @@ impl<'a> ContractScript<'a> { ctx: &mut Ctx<'a>, arena: &'a Arena>, parameter: Micheline<'a>, + annotation: Option>, storage: Micheline<'a>, ) -> Result<(impl Iterator>, TypedValue<'a>), ContractInterpretError<'a>> { - let parameter = typecheck_value(¶meter, ctx, &self.parameter)?; + let wrapped_parameter = self.wrap_parameter(arena, parameter, annotation, ctx)?; let storage = typecheck_value(&storage, ctx, &self.storage)?; - let tc_val = TypedValue::new_pair(parameter, storage); + let tc_val = TypedValue::new_pair(wrapped_parameter, storage); let mut stack = stk![tc_val]; self.code.interpret(ctx, arena, &mut stack)?; use TypedValue as V; @@ -99,6 +103,48 @@ impl<'a> ContractScript<'a> { v => panic!("expected `pair 'a 'b`, got {:?}", v), } } + + /// Wrap the input in the entrypoint with the given name. + pub fn wrap_parameter( + &self, + arena: &'a Arena>, + parameter: Micheline<'a>, + annotation: Option>, + ctx: &mut Ctx<'a>, + ) -> Result, TcError> { + let parsed_annotation = match annotation { + None => { + FieldAnnotation::from_str_unchecked(DEFAULT_EP_NAME) + } + Some(ann) => { + ann + } + }; + if let Some((annotation_path, annotation_type)) = self.annotations.get(&parsed_annotation) { + typecheck_value(¶meter, ctx, annotation_type)?; + Entrypoint::try_from(parsed_annotation)?; + let mut result = parameter; + for direction in annotation_path.into_iter().rev() { + match direction { + Direction::Left => { + result = Micheline::prim1(arena, Prim::Left, result); + } + Direction::Right => { + result = Micheline::prim1(arena, Prim::Right, result); + } + } + } + Ok(typecheck_value( + &result, + ctx, + &self.parameter, + )?) + } else { + Err(TcError::NoSuchEntrypoint(Entrypoint::try_from( + parsed_annotation, + )?)) + } + } } impl<'a> Instruction<'a> { @@ -1765,6 +1811,7 @@ mod interpreter_tests { use crate::bls; use crate::gas::Gas; use chrono::DateTime; + use entrypoint::DEFAULT_EP_NAME; use num_bigint::BigUint; use Instruction::*; use Option::None; @@ -6493,6 +6540,507 @@ mod interpreter_tests { ); } + #[test] + fn three_layered_balanced_entrypoints() { + use crate::parser::test_helpers::parse; + + let code = r#"{parameter (or (or (or (address %A) (bool %B)) (or (string %C) (key %D))) (or (or (nat %E) (signature %F)) (or (timestamp %G) (unit %H)))); + storage unit ; + code { + CDR ; + NIL operation ; + PAIR }}"#; + + let cs_mich = parse(code).unwrap(); + let mut ctx = Ctx::default(); + let cs = cs_mich.typecheck_script(&mut ctx).unwrap(); + + let expected_entrypoints = HashMap::from([ + ( + FieldAnnotation::from_str_unchecked("A"), + ( + vec![Direction::Left, Direction::Left, Direction::Left], + Type::Address, + ), + ), + ( + FieldAnnotation::from_str_unchecked("B"), + ( + vec![Direction::Left, Direction::Left, Direction::Right], + Type::Bool, + ), + ), + ( + FieldAnnotation::from_str_unchecked("C"), + ( + vec![Direction::Left, Direction::Right, Direction::Left], + Type::String, + ), + ), + ( + FieldAnnotation::from_str_unchecked("D"), + ( + vec![Direction::Left, Direction::Right, Direction::Right], + Type::Key, + ), + ), + ( + FieldAnnotation::from_str_unchecked("E"), + ( + vec![Direction::Right, Direction::Left, Direction::Left], + Type::Nat, + ), + ), + ( + FieldAnnotation::from_str_unchecked("F"), + ( + vec![Direction::Right, Direction::Left, Direction::Right], + Type::Signature, + ), + ), + ( + FieldAnnotation::from_str_unchecked("G"), + ( + vec![Direction::Right, Direction::Right, Direction::Left], + Type::Timestamp, + ), + ), + ( + FieldAnnotation::from_str_unchecked("H"), + ( + vec![Direction::Right, Direction::Right, Direction::Right], + Type::Unit, + ), + ), + ( + FieldAnnotation::from_str_unchecked(DEFAULT_EP_NAME), + ( + Vec::new(), + Type::new_or( + Type::new_or( + Type::new_or(Type::Address, Type::Bool), + Type::new_or(Type::String, Type::Key), + ), + Type::new_or( + Type::new_or(Type::Nat, Type::Signature), + Type::new_or(Type::Timestamp, Type::Unit), + ), + ), + ), + ), + ]); + + // Check that each entrypoint is parsed correctly + for (entrypoint, (path, ty)) in expected_entrypoints { + match cs.annotations.get(&entrypoint) { + Some((parsed_path, parsed_ty)) => { + assert_eq!( + parsed_path, &path, + "Incorrect path for entrypoint: {:?}", + entrypoint + ); + assert_eq!( + parsed_ty, &ty, + "Incorrect type for entrypoint: {:?}", + entrypoint + ); + } + _ => panic!("Entrypoint not parsed: {:?}", entrypoint), + } + } + } + + #[test] + fn seven_layered_left_pivoted_entrypoint() { + use crate::parser::test_helpers::parse; + + let code = r#"{parameter (or (address %A) (or (bool %B) (or (string %C) (or (key %D) (or (nat %E) (or (signature %F) (or (timestamp %G) (unit %H)))))))); + storage unit ; + code { + CDR ; + NIL operation ; + PAIR }}"#; + + let cs_mich = parse(code).unwrap(); + let mut ctx = Ctx::default(); + let cs = cs_mich.typecheck_script(&mut ctx).unwrap(); + + let parsed_entrypoints = cs.annotations; + let expected_entrypoints = HashMap::from([ + ( + FieldAnnotation::from_str_unchecked("A"), + (vec![Direction::Left], Type::Address), + ), + ( + FieldAnnotation::from_str_unchecked("B"), + (vec![Direction::Right, Direction::Left], Type::Bool), + ), + ( + FieldAnnotation::from_str_unchecked("C"), + ( + vec![Direction::Right, Direction::Right, Direction::Left], + Type::String, + ), + ), + ( + FieldAnnotation::from_str_unchecked("D"), + ( + vec![ + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Left, + ], + Type::Key, + ), + ), + ( + FieldAnnotation::from_str_unchecked("E"), + ( + vec![ + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Left, + ], + Type::Nat, + ), + ), + ( + FieldAnnotation::from_str_unchecked("F"), + ( + vec![ + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Left, + ], + Type::Signature, + ), + ), + ( + FieldAnnotation::from_str_unchecked("G"), + ( + vec![ + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Left, + ], + Type::Timestamp, + ), + ), + ( + FieldAnnotation::from_str_unchecked("H"), + ( + vec![ + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Right, + Direction::Right, + ], + Type::Unit, + ), + ), + ( + FieldAnnotation::from_str_unchecked(DEFAULT_EP_NAME), + ( + Vec::new(), + Type::new_or( + Type::Address, + Type::new_or( + Type::Bool, + Type::new_or( + Type::String, + Type::new_or( + Type::Key, + Type::new_or( + Type::Nat, + Type::new_or( + Type::Signature, + Type::new_or(Type::Timestamp, Type::Unit), + ), + ), + ), + ), + ), + ), + ), + ), + ]); + + // Check that each entrypoint is parsed correctly + for (entrypoint, (path, ty)) in expected_entrypoints { + match parsed_entrypoints.get(&entrypoint) { + Some((parsed_path, parsed_ty)) => { + assert_eq!( + parsed_path, &path, + "Incorrect path for entrypoint: {:?}", + entrypoint + ); + assert_eq!( + parsed_ty, &ty, + "Incorrect type for entrypoint: {:?}", + entrypoint + ); + } + _ => panic!("Entrypoint not parsed: {:?}", entrypoint), + } + } + } + + #[test] + fn seven_layered_right_pivoted_entrypoint() { + use crate::parser::test_helpers::parse; + + let code = r#"{parameter (or (or (or (or (or (or (or (timestamp %G) (unit %H)) (signature %F)) (nat %E)) (key %D)) (string %C)) (bool %B)) (address %A)); + storage unit ; + code { + CDR ; + NIL operation ; + PAIR }}"#; + + let cs_mich = parse(code).unwrap(); + let mut ctx = Ctx::default(); + let cs = cs_mich.typecheck_script(&mut ctx).unwrap(); + + let parsed_entrypoints = cs.annotations; + let expected_entrypoints = HashMap::from([ + ( + FieldAnnotation::from_str_unchecked("A"), + (vec![Direction::Right], Type::Address), + ), + ( + FieldAnnotation::from_str_unchecked("B"), + (vec![Direction::Left, Direction::Right], Type::Bool), + ), + ( + FieldAnnotation::from_str_unchecked("C"), + ( + vec![Direction::Left, Direction::Left, Direction::Right], + Type::String, + ), + ), + ( + FieldAnnotation::from_str_unchecked("D"), + ( + vec![ + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Right, + ], + Type::Key, + ), + ), + ( + FieldAnnotation::from_str_unchecked("E"), + ( + vec![ + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Right, + ], + Type::Nat, + ), + ), + ( + FieldAnnotation::from_str_unchecked("F"), + ( + vec![ + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Right, + ], + Type::Signature, + ), + ), + ( + FieldAnnotation::from_str_unchecked("G"), + ( + vec![ + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Left, + ], + Type::Timestamp, + ), + ), + ( + FieldAnnotation::from_str_unchecked("H"), + ( + vec![ + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Left, + Direction::Right, + ], + Type::Unit, + ), + ), + ( + FieldAnnotation::from_str_unchecked(DEFAULT_EP_NAME), + ( + Vec::new(), + Type::new_or( + Type::new_or( + Type::new_or( + Type::new_or( + Type::new_or( + Type::new_or( + Type::new_or(Type::Timestamp, Type::Unit), + Type::Signature, + ), + Type::Nat, + ), + Type::Key, + ), + Type::String, + ), + Type::Bool, + ), + Type::Address, + ), + ), + ), + ]); + + // Check that each entrypoint is parsed correctly + for (entrypoint, (path, ty)) in expected_entrypoints { + match parsed_entrypoints.get(&entrypoint) { + Some((parsed_path, parsed_ty)) => { + assert_eq!( + parsed_path, &path, + "Incorrect path for entrypoint: {:?}", + entrypoint + ); + assert_eq!( + parsed_ty, &ty, + "Incorrect type for entrypoint: {:?}", + entrypoint + ); + } + _ => panic!("Entrypoint not parsed: {:?}", entrypoint), + } + } + } + + #[test] + fn explicit_default_entrypoint() { + use crate::parser::test_helpers::parse; + + let code = r#"{parameter (or (or (or (address %A) (bool %B)) (or (string %C) (key %D))) (or (or (nat %default) (signature %F)) (or (timestamp %G) (unit %H)))); + storage unit ; + code { + CDR ; + NIL operation ; + PAIR }}"#; + + let cs_mich = parse(code).unwrap(); + let mut ctx = Ctx::default(); + let cs = cs_mich.typecheck_script(&mut ctx).unwrap(); + + let parsed_entrypoints = cs.annotations; + let expected_entrypoints = HashMap::from([ + ( + FieldAnnotation::from_str_unchecked("A"), + ( + vec![Direction::Left, Direction::Left, Direction::Left], + Type::Address, + ), + ), + ( + FieldAnnotation::from_str_unchecked("B"), + ( + vec![Direction::Left, Direction::Left, Direction::Right], + Type::Bool, + ), + ), + ( + FieldAnnotation::from_str_unchecked("C"), + ( + vec![Direction::Left, Direction::Right, Direction::Left], + Type::String, + ), + ), + ( + FieldAnnotation::from_str_unchecked("D"), + ( + vec![Direction::Left, Direction::Right, Direction::Right], + Type::Key, + ), + ), + ( + FieldAnnotation::from_str_unchecked(DEFAULT_EP_NAME), + ( + vec![Direction::Right, Direction::Left, Direction::Left], + Type::Nat, + ), + ), + ( + FieldAnnotation::from_str_unchecked("F"), + ( + vec![Direction::Right, Direction::Left, Direction::Right], + Type::Signature, + ), + ), + ( + FieldAnnotation::from_str_unchecked("G"), + ( + vec![Direction::Right, Direction::Right, Direction::Left], + Type::Timestamp, + ), + ), + ( + FieldAnnotation::from_str_unchecked("H"), + ( + vec![Direction::Right, Direction::Right, Direction::Right], + Type::Unit, + ), + ), + ]); + + // Check that each entrypoint is parsed correctly + for (entrypoint, (path, ty)) in expected_entrypoints { + match parsed_entrypoints.get(&entrypoint) { + Some((parsed_path, parsed_ty)) => { + assert_eq!( + parsed_path, &path, + "Incorrect path for entrypoint: {:?}", + entrypoint + ); + assert_eq!( + parsed_ty, &ty, + "Incorrect type for entrypoint: {:?}", + entrypoint + ); + } + _ => panic!("Entrypoint not parsed: {:?}", entrypoint), + } + } + } + #[test] fn contract_address_computation() { use tezos_crypto_rs::hash::OperationHash; diff --git a/contrib/mir/src/lib.rs b/contrib/mir/src/lib.rs index 24b8fd1fb72d..c2f3e8d4090e 100644 --- a/contrib/mir/src/lib.rs +++ b/contrib/mir/src/lib.rs @@ -111,7 +111,7 @@ //! // from `parser` for simplicity, you may also opt to create a new one to //! // potentially save a bit of memory (depends on the workload). //! let (operations_iter, new_storage) = contract_typechecked -//! .interpret(&mut ctx, &parser.arena, parameter, storage) +//! .interpret(&mut ctx, &parser.arena, parameter, None, storage) //! .unwrap(); //! let TypedValue::Int(new_storage_int) = &new_storage else { unreachable!() }; //! assert_eq!(new_storage_int, &22698374052006863956975682u128.into()); @@ -371,6 +371,7 @@ mod tests { ctx, &arena, "foo".into(), + None, M::seq( &arena, [ @@ -1261,6 +1262,7 @@ mod multisig_tests { // %sigs seq([some(signature)]), ), + None, // make_initial_storage(), pair( anti_replay_counter(), @@ -1333,6 +1335,7 @@ mod multisig_tests { // %sigs seq([some(signature)]), ), + None, pair( anti_replay_counter(), pair(threshold.clone(), seq([PUBLIC_KEY.into()])), @@ -1388,6 +1391,7 @@ mod multisig_tests { // %sigs seq([some(invalid_signature)]), ), + None, pair( anti_replay_counter(), pair(threshold, seq([PUBLIC_KEY.into()])), diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index 0a1be00156e5..c6f7572aabd4 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -8,12 +8,13 @@ //! Michelson typechecker definitions. Most functions defined as associated //! functions on [Micheline], see there for more. -use crate::ast::michelson_address::entrypoint::{check_ep_name_len, Entrypoints}; +use crate::ast::michelson_address::entrypoint::{check_ep_name_len, Direction, Entrypoints}; use chrono::prelude::DateTime; +use entrypoint::DEFAULT_EP_NAME; use num_bigint::{BigInt, BigUint, TryFromBigIntError}; use num_traits::{Signed, Zero}; use std::collections::hash_map::Entry; -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::rc::Rc; use tezos_crypto_rs::{base58::FromBase58CheckError, hash::FromBytesError}; @@ -168,6 +169,12 @@ impl From> for TcError { } } +impl From for TcError { + fn from(value: ByteReprError) -> Self { + Self::ByteReprError(Type::Bytes, value) + } +} + /// Errors happening when typechecking a value of type `chain_id`. #[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] pub enum ChainIdError { @@ -265,7 +272,7 @@ impl<'a> Micheline<'a> { ) -> Result, TcError> { let entrypoints = self_type .map(|ty| { - let (entrypoints, ty) = parse_parameter_ty_with_entrypoints(ctx, ty)?; + let (entrypoints, _, ty) = parse_parameter_ty_with_entrypoints(ctx, ty)?; ty.ensure_prop(&mut ctx.gas, TypeProperty::Passable)?; Ok::<_, TcError>(entrypoints) }) @@ -287,7 +294,7 @@ impl<'a> Micheline<'a> { /// Interpreting `Micheline` as a contract parameter type, collect its /// entrypoints into [Entrypoints]. pub fn get_entrypoints(&self, ctx: &mut Ctx) -> Result { - let (entrypoints, _) = parse_parameter_ty_with_entrypoints(ctx, self)?; + let (entrypoints, _, _) = parse_parameter_ty_with_entrypoints(ctx, self)?; Ok(entrypoints) } @@ -333,7 +340,7 @@ impl<'a> Micheline<'a> { } } } - let (entrypoints, parameter) = parse_parameter_ty_with_entrypoints( + let (entrypoints, anns, parameter) = parse_parameter_ty_with_entrypoints( ctx, parameter_ty.ok_or(TcError::MissingTopLevelElt(Prim::parameter))?, )?; @@ -361,18 +368,21 @@ impl<'a> Micheline<'a> { code, parameter, storage, + annotations: anns, }) } } pub(crate) fn parse_ty(ctx: &mut Ctx, ty: &Micheline) -> Result { - parse_ty_with_entrypoints(ctx, ty, None) + parse_ty_with_entrypoints(ctx, ty, None, &mut HashMap::new(), Vec::new()) } -fn parse_ty_with_entrypoints( +fn parse_ty_with_entrypoints<'a>( ctx: &mut Ctx, - ty: &Micheline, + ty: &Micheline<'a>, mut entrypoints: Option<&mut Entrypoints>, + routed_annotations: &mut HashMap, (Vec, Type)>, + path: Vec, ) -> Result { use Micheline::*; use Prim::*; @@ -437,8 +447,16 @@ fn parse_ty_with_entrypoints( App(pair, ..) => unexpected()?, App(or, [l, r], _) => Type::new_or( - parse_ty_with_entrypoints(ctx, l, entrypoints.as_deref_mut())?, - parse_ty_with_entrypoints(ctx, r, entrypoints.as_deref_mut())?, + parse_ty_with_entrypoints(ctx, l, entrypoints.as_deref_mut(), routed_annotations, { + let mut new_path = path.clone(); + new_path.push(Direction::Left); + new_path + })?, + parse_ty_with_entrypoints(ctx, r, entrypoints.as_deref_mut(), routed_annotations, { + let mut new_path = path.clone(); + new_path.push(Direction::Right); + new_path + })?, ), App(or, ..) => unexpected()?, @@ -516,6 +534,7 @@ fn parse_ty_with_entrypoints( App(prim @ micheline_unsupported_types!(), ..) => Err(TcError::TodoType(*prim))?, }; + if let Option::Some(eps) = entrypoints { // we just ensured it's an application of some type primitive irrefutable_match!(ty; App, _prim, _args, anns); @@ -523,13 +542,16 @@ fn parse_ty_with_entrypoints( // NB: field annotations may be longer than entrypoints; however // it's not an error to have an overly-long field annotation, it // just doesn't count as an entrypoint. + routed_annotations.insert(field_ann.clone(), (path, parsed_ty.clone())); if let Ok(entrypoint) = Entrypoint::try_from(field_ann) { let entry = eps.entry(entrypoint); match entry { Entry::Occupied(e) => { return Err(TcError::DuplicateEntrypoint(e.key().clone())) } - Entry::Vacant(e) => e.insert(parsed_ty.clone()), + Entry::Vacant(e) => { + e.insert(parsed_ty.clone()); + } }; } } @@ -537,16 +559,33 @@ fn parse_ty_with_entrypoints( Ok(parsed_ty) } -fn parse_parameter_ty_with_entrypoints( +fn parse_parameter_ty_with_entrypoints<'a>( ctx: &mut Ctx, - parameter_ty: &Micheline, -) -> Result<(Entrypoints, Type), TcError> { + parameter_ty: &Micheline<'a>, +) -> Result< + ( + Entrypoints, + HashMap, (Vec, Type)>, + Type, + ), + TcError, +> { let mut entrypoints = Entrypoints::new(); - let parameter = parse_ty_with_entrypoints(ctx, parameter_ty, Some(&mut entrypoints))?; + let mut routed_annotations = HashMap::new(); + let parameter = parse_ty_with_entrypoints( + ctx, + parameter_ty, + Some(&mut entrypoints), + &mut routed_annotations, + Vec::new(), + )?; entrypoints .entry(Entrypoint::default()) .or_insert_with(|| parameter.clone()); - Ok((entrypoints, parameter)) + routed_annotations + .entry(FieldAnnotation::from_str_unchecked(DEFAULT_EP_NAME)) + .or_insert_with(|| (vec![], parameter.clone())); + Ok((entrypoints, routed_annotations, parameter)) } /// Typecheck a sequence of instructions. Assumes the passed stack is valid, i.e. @@ -2685,10 +2724,12 @@ mod typecheck_tests { use super::{Lambda, Or}; use crate::ast::micheline::test_helpers::*; use crate::ast::michelson_address as addr; + use crate::ast::michelson_address::entrypoint::DEFAULT_EP_NAME; use crate::ast::or::Or::{Left, Right}; use crate::gas::Gas; use crate::parser::test_helpers::*; use crate::typechecker::*; + use std::collections::HashMap; use Instruction::*; use Option::None; @@ -6037,7 +6078,11 @@ mod typecheck_tests { Ok(ContractScript { parameter: Type::new_contract(Type::Unit), storage: Type::Unit, - code: Seq(vec![Drop(None), Unit, Failwith(Type::Unit)]) + code: Seq(vec![Drop(None), Unit, Failwith(Type::Unit)]), + annotations: HashMap::from([( + FieldAnnotation::from_str_unchecked(DEFAULT_EP_NAME), + (Vec::new(), Type::new_contract(Type::Unit)) + )]), }) ); } @@ -6472,7 +6517,17 @@ mod typecheck_tests { ISelf("foo".try_into().unwrap()), Unit, Failwith(Type::Unit) - ]) + ]), + annotations: HashMap::from([ + ( + FieldAnnotation::from_str_unchecked(DEFAULT_EP_NAME), + (vec![Direction::Right], Type::Unit) + ), + ( + FieldAnnotation::from_str_unchecked("foo"), + (vec![Direction::Left], Type::Int) + ) + ]), }) ); } @@ -6516,7 +6571,17 @@ mod typecheck_tests { ISelf("default".try_into().unwrap()), Unit, Failwith(Type::Unit) - ]) + ]), + annotations: HashMap::from([ + ( + FieldAnnotation::from_str_unchecked("qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"), + (vec![Direction::Left], Type::Int) + ), + ( + FieldAnnotation::from_str_unchecked(DEFAULT_EP_NAME), + (vec![Direction::Right], Type::Unit) + ), + ]), }) ); } -- GitLab From dae2d663fc962edd3f1de6649a0314d1706324a0 Mon Sep 17 00:00:00 2001 From: Luciano Freitas Date: Mon, 20 Jan 2025 15:19:28 +0100 Subject: [PATCH 2/2] MIR: new example with entrypoint --- contrib/mir/examples/call_entrypoints.rs | 102 +++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 contrib/mir/examples/call_entrypoints.rs diff --git a/contrib/mir/examples/call_entrypoints.rs b/contrib/mir/examples/call_entrypoints.rs new file mode 100644 index 000000000000..e49565b04f29 --- /dev/null +++ b/contrib/mir/examples/call_entrypoints.rs @@ -0,0 +1,102 @@ +/******************************************************************************/ +/* */ +/* SPDX-License-Identifier: MIT */ +/* Copyright (c) [2024] Nomadic Labs */ +/* */ +/******************************************************************************/ + +use mir::ast::*; +use mir::context::Ctx; +use mir::parser::Parser; + +/// A simple contract that peformas arithmetic operations on an integer storage. +static SCRIPT: &str = r#" parameter (or (or (or (or (int %decrement) (int %increment)) (unit %reset)) (int %set)) (unit %double)) ; + storage int; + code { + DUP ; + CDR ; + SWAP ; + CAR ; + IF_LEFT { + IF_LEFT { + IF_LEFT + { + IF_LEFT + { SWAP ; SUB } + { ADD } + } + { + DROP 2; PUSH int 0 + } + } + { SWAP ; DROP } + } + { DROP ; DUP ; ADD}; + NIL operation ; + PAIR + }"#; + +/// We pass storage as a parameter, generally it would be stored somewhere. +fn run_contract<'a>( + parameter: Micheline<'a>, + storage: Micheline<'a>, + annotation: FieldAnnotation<'a>, + contract_typechecked: &ContractScript<'a>, + ctx: &mut Ctx<'a>, + parser: &'a Parser<'a>, +) { + let (_, new_storage) = contract_typechecked + .interpret(ctx, &parser.arena, parameter, Some(annotation), storage) + .unwrap(); + let TypedValue::Int(storage_int) = &new_storage else { + unreachable!() + }; + println!("{storage_int}"); +} + +fn main() { + let parser = Parser::new(); + let contract_micheline = parser.parse_top_level(SCRIPT).unwrap(); + let mut ctx = Ctx::default(); + let contract_typechecked = contract_micheline.typecheck_script(&mut ctx).unwrap(); + run_contract( + 30.into(), + 20.into(), + FieldAnnotation::from_str_unchecked("increment"), + &contract_typechecked, + &mut ctx, + &parser, + ); // prints "50" + run_contract( + 100.into(), + 80.into(), + FieldAnnotation::from_str_unchecked("decrement"), + &contract_typechecked, + &mut ctx, + &parser, + ); // prints "-20" + run_contract( + 7.into(), + 123.into(), + FieldAnnotation::from_str_unchecked("set"), + &contract_typechecked, + &mut ctx, + &parser, + ); // prints "7" + run_contract( + ().into(), + 9.into(), + FieldAnnotation::from_str_unchecked("double"), + &contract_typechecked, + &mut ctx, + &parser, + ); // prints "18" + run_contract( + ().into(), + 27.into(), + FieldAnnotation::from_str_unchecked("reset"), + &contract_typechecked, + &mut ctx, + &parser, + ); // prints "0" +} -- GitLab