From aece94284e6ce560d3462ec597147e5d667eb6be Mon Sep 17 00:00:00 2001 From: Nikolay Yakimov Date: Fri, 15 Dec 2023 10:39:45 +0300 Subject: [PATCH 1/4] MIR: Add LazyStorage to Ctx --- contrib/mir/src/ast/big_map.rs | 36 +++++++++++++++++++--------------- contrib/mir/src/context.rs | 12 +++++++++--- contrib/mir/src/lib.rs | 2 +- contrib/mir/src/typechecker.rs | 6 +++--- 4 files changed, 33 insertions(+), 23 deletions(-) diff --git a/contrib/mir/src/ast/big_map.rs b/contrib/mir/src/ast/big_map.rs index d5b3898df4a2..6e366d7eb4ef 100644 --- a/contrib/mir/src/ast/big_map.rs +++ b/contrib/mir/src/ast/big_map.rs @@ -153,22 +153,6 @@ pub trait LazyStorage<'a> { value: Option>, ) -> Result<(), LazyStorageError>; - /// Update big map with multiple changes, generalizes - /// [LazyStorage::big_map_update]. - /// - /// The specified big map id must point to a valid map in the lazy storage. - /// Key and value types must match the type of key of the stored map. - fn big_map_bulk_update( - &mut self, - id: &BigMapId, - entries_iter: impl IntoIterator, Option>)>, - ) -> Result<(), LazyStorageError> { - for (k, v) in entries_iter { - self.big_map_update(id, k, v)? - } - Ok(()) - } - /// Get key and value types of the map. /// /// The specified big map id must point to a valid map in the lazy storage. @@ -194,6 +178,26 @@ pub trait LazyStorage<'a> { fn big_map_remove(&mut self, id: &BigMapId) -> Result<(), LazyStorageError>; } +pub trait LazyStorageBulkUpdate<'a>: LazyStorage<'a> { + /// Update big map with multiple changes, generalizes + /// [LazyStorage::big_map_update]. + /// + /// The specified big map id must point to a valid map in the lazy storage. + /// Key and value types must match the type of key of the stored map. + fn big_map_bulk_update( + &mut self, + id: &BigMapId, + entries_iter: impl IntoIterator, Option>)>, + ) -> Result<(), LazyStorageError> { + for (k, v) in entries_iter { + self.big_map_update(id, k, v)? + } + Ok(()) + } +} + +impl<'a, T: LazyStorage<'a>> LazyStorageBulkUpdate<'a> for T {} + #[derive(Clone, PartialEq, Eq, Debug)] pub struct MapInfo<'a> { map: BTreeMap, TypedValue<'a>>, diff --git a/contrib/mir/src/context.rs b/contrib/mir/src/context.rs index e0c79796919d..c86f230e5f5d 100644 --- a/contrib/mir/src/context.rs +++ b/contrib/mir/src/context.rs @@ -1,4 +1,5 @@ #![allow(clippy::type_complexity)] +use crate::ast::big_map::{InMemoryLazyStorage, LazyStorage}; use crate::ast::michelson_address::entrypoint::Entrypoints; use crate::ast::michelson_address::AddressHash; use crate::ast::michelson_key_hash::KeyHash; @@ -6,7 +7,7 @@ use crate::gas::Gas; use num_bigint::{BigInt, BigUint}; use std::collections::HashMap; -pub struct Ctx { +pub struct Ctx<'a> { pub gas: Gas, pub amount: i64, pub balance: i64, @@ -20,10 +21,14 @@ pub struct Ctx { pub voting_powers: Box BigUint>, pub now: BigInt, pub total_voting_power: BigUint, + // NB: lifetime is mandatory if we want to use types implementing with + // references inside for LazyStorage, and we do due to how Runtime is passed + // as &mut + pub big_map_storage: Box + 'a>, operation_counter: u128, } -impl Ctx { +impl Ctx<'_> { pub fn operation_counter(&mut self) -> u128 { self.operation_counter += 1; self.operation_counter @@ -45,7 +50,7 @@ impl Ctx { } } -impl Default for Ctx { +impl Default for Ctx<'_> { fn default() -> Self { Ctx { gas: Gas::default(), @@ -62,6 +67,7 @@ impl Default for Ctx { lookup_contract: Box::new(|_| None), voting_powers: Box::new(|_| 0u32.into()), total_voting_power: 0u32.into(), + big_map_storage: Box::new(InMemoryLazyStorage::new()), operation_counter: 0, } } diff --git a/contrib/mir/src/lib.rs b/contrib/mir/src/lib.rs index 3be6adcefe64..fbb8367b02a6 100644 --- a/contrib/mir/src/lib.rs +++ b/contrib/mir/src/lib.rs @@ -835,7 +835,7 @@ mod multisig_tests { $ CHAIN_ID='0xf3d48554' $ ANTI_REPLAY_COUNTER='111' */ - fn make_ctx() -> Ctx { + fn make_ctx() -> Ctx<'static> { let mut ctx = Ctx::default(); ctx.self_address = "KT1BFATQpdP5xJGErJyk2vfL46dvFanWz87H".try_into().unwrap(); ctx.chain_id = tezos_crypto_rs::hash::ChainId(hex::decode("f3d48554").unwrap()); diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index 2517afa4d491..7f97358be4e3 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -1982,14 +1982,14 @@ fn validate_u10(n: &BigInt) -> Result { /// (where you specify a getter to obtain the key from an element). /// /// Also charges gas for this check. -struct OrderValidatingIterator<'a, T: Iterator>, I> { +struct OrderValidatingIterator<'a, 'b, T: Iterator>, I> { it: std::iter::Peekable, to_key: fn(&I) -> &TypedValue, container_ty: &'a Type, - ctx: &'a std::cell::RefCell<&'a mut Ctx>, + ctx: &'a std::cell::RefCell<&'a mut Ctx<'b>>, } -impl Iterator for OrderValidatingIterator<'_, T, I> +impl Iterator for OrderValidatingIterator<'_, '_, T, I> where T: Iterator>, { -- GitLab From a6e09154a6d8649ccb639eea74fda51e5bc5c80b Mon Sep 17 00:00:00 2001 From: martoon Date: Fri, 15 Dec 2023 18:43:39 +0400 Subject: [PATCH 2/4] MIR: Extract set and map values typechecking Put them to separate functions. We will need map typechecking later for handling big map typechecking. --- contrib/mir/src/typechecker.rs | 113 +++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 47 deletions(-) diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index 7f97358be4e3..d7a4462af42d 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -1762,54 +1762,10 @@ pub(crate) fn typecheck_value<'a>( .map(|v| typecheck_value(v, ctx, ty)) .collect::>()?, ), - (set_ty @ T::Set(ty), V::Seq(vs)) => { - ctx.gas - .consume(gas::tc_cost::construct_set(ty.size_for_gas(), vs.len())?)?; - let ctx_cell = std::cell::RefCell::new(ctx); - // See the same concern about constructing from ordered sequence as in Map - let set: BTreeSet = OrderValidatingIterator { - it: vs - .iter() - .map(|v| typecheck_value(v, *ctx_cell.borrow_mut(), ty)) - .peekable(), - container_ty: set_ty, - to_key: |x| x, - ctx: &ctx_cell, - } - .collect::>()?; - TV::Set(set) - } - (map_ty @ T::Map(m), V::Seq(vs)) => { + (T::Set(ty), V::Seq(vs)) => TV::Set(typecheck_set(ctx, t, ty, vs)?), + (T::Map(m), V::Seq(vs)) => { let (tk, tv) = m.as_ref(); - ctx.gas - .consume(gas::tc_cost::construct_map(tk.size_for_gas(), vs.len())?)?; - let ctx_cell = std::cell::RefCell::new(ctx); - let tc_elt = - |v: &Micheline<'a>, ctx: &mut Ctx| -> Result<(TypedValue, TypedValue), TcError> { - match v { - Micheline::App(Prim::Elt, [k, v], _) => { - let k = typecheck_value(k, ctx, tk)?; - let v = typecheck_value(v, ctx, tv)?; - Ok((k, v)) - } - _ => Err(TcError::InvalidEltForMap(format!("{v:?}"), t.clone())), - } - }; - // Unfortunately, `BTreeMap` doesn't expose methods to build from an already-sorted - // slice/vec/iterator. FWIW, Rust docs claim that its sorting algorithm is "designed to - // be very fast in cases where the slice is nearly sorted", so hopefully it doesn't add - // too much overhead. - let map: BTreeMap = OrderValidatingIterator { - it: vs - .iter() - .map(|v| tc_elt(v, *ctx_cell.borrow_mut())) - .peekable(), - container_ty: map_ty, - to_key: |(k, _)| k, - ctx: &ctx_cell, - } - .collect::>()?; - TV::Map(map) + TV::Map(typecheck_map(ctx, t, tk, tv, vs, |v| v)?) } (T::Address, V::String(str)) => { ctx.gas.consume(gas::tc_cost::KEY_HASH_READABLE)?; @@ -2022,6 +1978,69 @@ where } } +fn typecheck_set<'a>( + ctx: &mut Ctx, + set_ty: &Type, + elem_ty: &Type, + vs: &[Micheline<'a>], +) -> Result>, TcError> { + ctx.gas.consume(gas::tc_cost::construct_set( + elem_ty.size_for_gas(), + vs.len(), + )?)?; + let ctx_cell = std::cell::RefCell::new(ctx); + // See the same concern about constructing from ordered sequence as in [typecheck_map] + OrderValidatingIterator { + it: vs + .iter() + .map(|v| typecheck_value(v, *ctx_cell.borrow_mut(), elem_ty)) + .peekable(), + container_ty: set_ty, + to_key: |x| x, + ctx: &ctx_cell, + } + .collect::>() +} + +fn typecheck_map<'a, V>( + ctx: &mut Ctx, + map_ty: &Type, + key_type: &Type, + value_type: &Type, + vs: &[Micheline<'a>], + value_mapper: fn(TypedValue<'a>) -> V, +) -> Result, V>, TcError> { + ctx.gas.consume(gas::tc_cost::construct_map( + key_type.size_for_gas(), + vs.len(), + )?)?; + let ctx_cell = std::cell::RefCell::new(ctx); + let tc_elt = |v: &Micheline<'a>, ctx: &mut Ctx| -> Result<(TypedValue<'a>, V), TcError> { + match v { + Micheline::App(Prim::Elt, [k, v], _) => { + let k = typecheck_value(k, ctx, key_type)?; + let v = typecheck_value(v, ctx, value_type)?; + Ok((k, value_mapper(v))) + } + _ => Err(TcError::InvalidEltForMap(format!("{v:?}"), map_ty.clone())), + } + }; + // Unfortunately, `BTreeMap` doesn't expose methods to build from an already-sorted + // slice/vec/iterator. FWIW, Rust docs claim that its sorting algorithm is "designed to + // be very fast in cases where the slice is nearly sorted", so hopefully it doesn't add + // too much overhead. + OrderValidatingIterator { + it: vs + .iter() + .map(|v| tc_elt(v, *ctx_cell.borrow_mut())) + .peekable(), + container_ty: map_ty, + to_key: |(k, _)| k, + ctx: &ctx_cell, + } + .collect::>() +} + /// Ensures type stack is at least of the required length, otherwise returns /// `Err(StackTooShort)`. fn ensure_stack_len(instr: Prim, stack: &TypeStack, l: usize) -> Result<(), TcError> { -- GitLab From 8ed693a0d3fd2c499ba4d24ccf9cde83a35e38e9 Mon Sep 17 00:00:00 2001 From: martoon Date: Tue, 28 Nov 2023 01:51:53 +0400 Subject: [PATCH 3/4] MIR: Add big_map type and value --- contrib/mir/src/ast.rs | 38 +++- contrib/mir/src/ast/big_map.rs | 25 +-- contrib/mir/src/ast/comparable.rs | 4 +- contrib/mir/src/ast/micheline.rs | 3 +- contrib/mir/src/gas.rs | 1 + contrib/mir/src/typechecker.rs | 232 ++++++++++++++++++++-- contrib/mir/src/typechecker/type_props.rs | 9 + 7 files changed, 279 insertions(+), 33 deletions(-) diff --git a/contrib/mir/src/ast.rs b/contrib/mir/src/ast.rs index 766136104e43..0bdd6e3d05a9 100644 --- a/contrib/mir/src/ast.rs +++ b/contrib/mir/src/ast.rs @@ -43,6 +43,8 @@ pub use michelson_list::MichelsonList; pub use michelson_signature::Signature; pub use or::Or; +use self::big_map::BigMap; + #[derive(Debug, Clone, Eq, PartialEq)] pub struct TransferTokens<'a> { pub param: TypedValue<'a>, @@ -87,6 +89,7 @@ pub enum Type { Operation, Set(Rc), Map(Rc<(Type, Type)>), + BigMap(Rc<(Type, Type)>), Or(Rc<(Type, Type)>), Contract(Rc), Address, @@ -112,7 +115,9 @@ impl Type { Nat | Int | Bool | Mutez | String | Unit | Never | Operation | Address | ChainId | Bytes | Key | Signature | KeyHash | Timestamp | Bls12381Fr | Bls12381G1 | Bls12381G2 => 1, - Pair(p) | Or(p) | Map(p) | Lambda(p) => 1 + p.0.size_for_gas() + p.1.size_for_gas(), + Pair(p) | Or(p) | Map(p) | BigMap(p) | Lambda(p) => { + 1 + p.0.size_for_gas() + p.1.size_for_gas() + } Option(x) | List(x) | Set(x) | Contract(x) | Ticket(x) => 1 + x.size_for_gas(), } } @@ -137,6 +142,10 @@ impl Type { Self::Map(Rc::new((k, v))) } + pub fn new_big_map(k: Self, v: Self) -> Self { + Self::BigMap(Rc::new((k, v))) + } + pub fn new_or(l: Self, r: Self) -> Self { Self::Or(Rc::new((l, r))) } @@ -229,6 +238,12 @@ impl<'a> IntoMicheline<'a> for &'_ Type { x.0.into_micheline_optimized_legacy(arena), x.1.into_micheline_optimized_legacy(arena), ), + BigMap(x) => Micheline::prim2( + arena, + Prim::big_map, + x.0.into_micheline_optimized_legacy(arena), + x.1.into_micheline_optimized_legacy(arena), + ), Or(x) => Micheline::prim2( arena, Prim::or, @@ -258,6 +273,7 @@ pub enum TypedValue<'a> { List(MichelsonList), Set(BTreeSet), Map(BTreeMap), + BigMap(BigMap<'a>), Or(Box>), Address(Address), ChainId(ChainId), @@ -281,6 +297,10 @@ impl<'a> IntoMicheline<'a> for TypedValue<'a> { use Micheline as V; use TypedValue as TV; let go = |x: Self| x.into_micheline_optimized_legacy(arena); + let option_into_micheline = |x: Option| match x { + None => V::prim0(Prim::None), + Some(x) => V::prim1(arena, Prim::Some, go(x)), + }; match self { TV::Int(i) => V::Int(i), TV::Nat(u) => V::Int(u.try_into().unwrap()), @@ -301,8 +321,20 @@ impl<'a> IntoMicheline<'a> for TypedValue<'a> { .map(|(key, val)| V::prim2(arena, Prim::Elt, go(key), go(val))), ), ), - TV::Option(None) => V::prim0(Prim::None), - TV::Option(Some(x)) => V::prim1(arena, Prim::Some, go(*x)), + TV::BigMap(m) => { + let id_part = m.id.map(|i| V::Int(i.0)); + let overlay_empty = m.overlay.is_empty(); + let map_part = + V::Seq(arena.alloc_extend(m.overlay.into_iter().map(|(key, val)| { + V::prim2(arena, Prim::Elt, go(key), option_into_micheline(val)) + }))); + match id_part { + Some(id_part) if overlay_empty => id_part, + Some(id_part) => V::prim2(arena, Prim::Pair, id_part, map_part), + None => map_part, + } + } + TV::Option(x) => option_into_micheline(x.map(|v| *v)), TV::Or(or) => match *or { Or::Left(x) => V::prim1(arena, Prim::Left, go(x)), Or::Right(x) => V::prim1(arena, Prim::Right, go(x)), diff --git a/contrib/mir/src/ast/big_map.rs b/contrib/mir/src/ast/big_map.rs index 6e366d7eb4ef..59fe3c208cd5 100644 --- a/contrib/mir/src/ast/big_map.rs +++ b/contrib/mir/src/ast/big_map.rs @@ -17,7 +17,7 @@ use super::{Micheline, Type, TypedValue}; /// Id of big map in the lazy storage. #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct BigMapId(BigInt); +pub struct BigMapId(pub BigInt); impl Display for BigMapId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -35,7 +35,7 @@ pub struct BigMap<'a> { /// /// Big map can be backed by no map in the lazy storage and yet stay fully /// in memory, in such case this field is `None`. - id: Option, + pub id: Option, /// In-memory part, carries the diff that is to be applied to the map in the /// storage. @@ -44,10 +44,10 @@ pub struct BigMap<'a> { /// certain key points like the end of the contract execution this diff is /// dumped into the storage. Change in storage can be applied in-place or, /// if necessary, with copy of the stored map. - overlay: BTreeMap, Option>>, + pub overlay: BTreeMap, Option>>, - key_type: Type, - value_type: Type, + pub key_type: Type, + pub value_type: Type, } impl<'a> BigMap<'a> { @@ -155,8 +155,8 @@ pub trait LazyStorage<'a> { /// Get key and value types of the map. /// - /// The specified big map id must point to a valid map in the lazy storage. - fn big_map_get_type(&self, id: &BigMapId) -> Result<(&Type, &Type), LazyStorageError>; + /// This returns None if the map with such ID is not present in the storage. + fn big_map_get_type(&self, id: &BigMapId) -> Result, LazyStorageError>; /// Allocate a new empty big map. fn big_map_new( @@ -281,9 +281,11 @@ impl<'a> LazyStorage<'a> for InMemoryLazyStorage<'a> { Ok(()) } - fn big_map_get_type(&self, id: &BigMapId) -> Result<(&Type, &Type), LazyStorageError> { - let info = self.access_big_map(id)?; - Ok((&info.key_type, &info.value_type)) + fn big_map_get_type(&self, id: &BigMapId) -> Result, LazyStorageError> { + Ok(self + .big_maps + .get(id) + .map(|info| (&info.key_type, &info.value_type))) } fn big_map_new( @@ -469,14 +471,13 @@ impl<'a> TypedValue<'a> { // Key is comparable as so has no big map, skipping it v.collect_big_maps(put_res) }), + BigMap(m) => put_res(m), Ticket(_) => { // Value is comparable, has no big map } Lambda(_) => { // Can contain only pushable values, thus no big maps } - // TODO: next merge request - // actually take some big maps once they are present in TypedValue Operation(op) => match &mut op.as_mut().operation { crate::ast::Operation::TransferTokens(t) => t.param.collect_big_maps(put_res), crate::ast::Operation::SetDelegate(_) => {} diff --git a/contrib/mir/src/ast/comparable.rs b/contrib/mir/src/ast/comparable.rs index ee7feed83825..a977926234b9 100644 --- a/contrib/mir/src/ast/comparable.rs +++ b/contrib/mir/src/ast/comparable.rs @@ -54,8 +54,8 @@ impl PartialOrd for TypedValue<'_> { // non-comparable types ( - List(..) | Set(..) | Map(..) | Contract(..) | Operation(_) | Ticket(..) - | Lambda(..) | Bls12381Fr(..) | Bls12381G1(..) | Bls12381G2(..), + List(..) | Set(..) | Map(..) | BigMap(..) | Contract(..) | Operation(_) + | Ticket(..) | Lambda(..) | Bls12381Fr(..) | Bls12381G1(..) | Bls12381G2(..), _, ) => None, } diff --git a/contrib/mir/src/ast/micheline.rs b/contrib/mir/src/ast/micheline.rs index 31c019742e73..81513b747a16 100644 --- a/contrib/mir/src/ast/micheline.rs +++ b/contrib/mir/src/ast/micheline.rs @@ -126,8 +126,7 @@ pub trait IntoMicheline<'a> { /// supported. Useful for total match in the typechecker. macro_rules! micheline_unsupported_types { () => { - Prim::big_map - | Prim::chest + Prim::chest | Prim::chest_key | Prim::tx_rollup_l2_address | Prim::sapling_state diff --git a/contrib/mir/src/gas.rs b/contrib/mir/src/gas.rs index 4bf53331cc7f..55fc920b616a 100644 --- a/contrib/mir/src/gas.rs +++ b/contrib/mir/src/gas.rs @@ -522,6 +522,7 @@ pub mod interpret_cost { V::List(..) | V::Set(..) | V::Map(..) + | V::BigMap(..) | V::Contract(_) | V::Operation(_) | V::Ticket(_) diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index d7a4462af42d..a12806109113 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -19,6 +19,7 @@ pub mod type_props; use type_props::TypeProperty; use crate::ast::annotations::{AnnotationError, NO_ANNS}; +use crate::ast::big_map::{BigMap, BigMapId, LazyStorageError}; use crate::ast::micheline::{ micheline_fields, micheline_instructions, micheline_literals, micheline_types, micheline_unsupported_instructions, micheline_unsupported_types, micheline_values, @@ -97,6 +98,10 @@ pub enum TcError { TodoInstr(Prim), #[error("Unhandled type: {0}")] TodoType(Prim), + #[error("big map with ID {0} not found in the lazy storage")] + BigMapNotFound(BigInt), + #[error("lazy storage error: {0:?}")] + LazyStorageError(LazyStorageError), } #[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] @@ -387,6 +392,15 @@ fn parse_ty_with_entrypoints( } App(map, ..) => unexpected()?, + App(big_map, [k, v], _) => { + let k = parse_ty(ctx, k)?; + k.ensure_prop(&mut ctx.gas, TypeProperty::Comparable)?; + let v = parse_ty(ctx, v)?; + v.ensure_prop(&mut ctx.gas, TypeProperty::BigMapValue)?; + Type::new_big_map(k, v) + } + App(big_map, ..) => unexpected()?, + App(bytes, [], _) => Type::Bytes, App(bytes, ..) => unexpected()?, @@ -1726,6 +1740,11 @@ pub(crate) fn typecheck_value<'a>( use Type as T; use TypedValue as TV; ctx.gas.consume(gas::tc_cost::VALUE_STEP)?; + macro_rules! invalid_value_for_type { + () => { + TcError::InvalidValueForType(format!("{v:?}"), t.clone()) + }; + } Ok(match (t, v) { (T::Nat, V::Int(n)) => TV::Nat(BigUint::try_from(n)?), (T::Int, V::Int(n)) => TV::Int(n.clone()), @@ -1767,6 +1786,48 @@ pub(crate) fn typecheck_value<'a>( let (tk, tv) = m.as_ref(); TV::Map(typecheck_map(ctx, t, tk, tv, vs, |v| v)?) } + (T::BigMap(m), v) => { + let (id_opt, vs_opt) = match v { + // All valid instantiations of big map are mentioned in + // https://tezos.gitlab.io/michelson-reference/#type-big_map + V::Int(i) => (Some(i.clone()), None), + V::Seq(vs) => (None, Some(vs)), + V::App(Prim::Pair, [V::Int(i), V::Seq(vs)], _) => (Some(i.clone()), Some(vs)), + _ => return Err(invalid_value_for_type!()), + }; + + let (tk, tv) = m.as_ref(); + + let big_map_id = if let Some(id) = id_opt { + let big_map_id = BigMapId(id.clone()); + let (key_type, value_type) = ctx + .big_map_storage + .big_map_get_type(&big_map_id) + .map_err(TcError::LazyStorageError)? + .ok_or(TcError::BigMapNotFound(id))?; + + // Clone to avoid conflicting borrows of ctx. + let key_type = &key_type.clone(); + let value_type = &value_type.clone(); + ensure_ty_eq(ctx, key_type, tk)?; + ensure_ty_eq(ctx, value_type, tv)?; + Some(big_map_id) + } else { + None + }; + + let overlay = if let Some(vs) = vs_opt { + typecheck_map(ctx, t, tk, tv, vs, Some)? + } else { + BTreeMap::default() + }; + TV::BigMap(BigMap { + id: big_map_id, + overlay, + key_type: tk.clone(), + value_type: tv.clone(), + }) + } (T::Address, V::String(str)) => { ctx.gas.consume(gas::tc_cost::KEY_HASH_READABLE)?; TV::Address( @@ -1863,7 +1924,7 @@ pub(crate) fn typecheck_value<'a>( amount: irrefutable_match!(c.1; TV::Nat), }) } - _ => return Err(TcError::InvalidValueForType(format!("{v:?}"), t.clone())), + _ => return Err(invalid_value_for_type!()), } } (T::Bls12381Fr, V::Int(i)) => { @@ -1872,26 +1933,17 @@ pub(crate) fn typecheck_value<'a>( } (T::Bls12381Fr, V::Bytes(bs)) => { ctx.gas.consume(gas::tc_cost::BLS_FR)?; - TV::Bls12381Fr( - bls::Fr::from_bytes(bs) - .ok_or_else(|| TcError::InvalidValueForType(format!("{v:?}"), t.clone()))?, - ) + TV::Bls12381Fr(bls::Fr::from_bytes(bs).ok_or_else(|| invalid_value_for_type!())?) } (T::Bls12381G1, V::Bytes(bs)) => { ctx.gas.consume(gas::tc_cost::BLS_G1)?; - TV::new_bls12381_g1( - bls::G1::from_bytes(bs) - .ok_or_else(|| TcError::InvalidValueForType(format!("{v:?}"), t.clone()))?, - ) + TV::new_bls12381_g1(bls::G1::from_bytes(bs).ok_or_else(|| invalid_value_for_type!())?) } (T::Bls12381G2, V::Bytes(bs)) => { ctx.gas.consume(gas::tc_cost::BLS_G2)?; - TV::new_bls12381_g2( - bls::G2::from_bytes(bs) - .ok_or_else(|| TcError::InvalidValueForType(format!("{v:?}"), t.clone()))?, - ) + TV::new_bls12381_g2(bls::G2::from_bytes(bs).ok_or_else(|| invalid_value_for_type!())?) } - (t, v) => return Err(TcError::InvalidValueForType(format!("{v:?}"), t.clone())), + (_, _) => return Err(invalid_value_for_type!()), }) } @@ -4542,6 +4594,158 @@ mod typecheck_tests { ); } + #[test] + fn test_invalid_big_map_key_type() { + let mut ctx = Ctx::default(); + assert_eq!( + parse_contract_script(concat!( + "parameter (big_map (list unit) unit);", + "storage nat;", + "code FAILWITH;", + )) + .unwrap() + .typecheck_script(&mut ctx), + Err(TcError::InvalidTypeProperty( + TypeProperty::Comparable, + Type::new_list(Type::Unit) + )) + ); + } + + #[test] + fn test_invalid_big_map_value_type() { + let mut ctx = Ctx::default(); + assert_eq!( + parse_contract_script(concat!( + "parameter (big_map int (contract unit));", + "storage nat;", + "code { UNIT; FAILWITH };", + )) + .unwrap() + .typecheck_script(&mut ctx), + Err(TcError::InvalidTypeProperty( + TypeProperty::BigMapValue, + Type::new_contract(Type::Unit) + )) + ); + } + + #[test] + // Test that the empty sequence cannot be given the type `big_map (list unit) unit` + // because `list unit` is not comparable and therefore `big_map (list unit) unit` is + // not well formed. + fn test_invalid_big_map_value() { + let mut ctx = Ctx::default(); + assert_eq!( + Micheline::Seq(&[]) + .typecheck_value(&mut ctx, &app!(big_map[app!(list[app!(unit)]), app!(unit)])), + Err(TcError::InvalidTypeProperty( + TypeProperty::Comparable, + Type::new_list(Type::Unit) + )) + ); + } + + #[test] + fn test_parsing_big_map_value() { + let mut ctx = Ctx::default(); + let storage = &mut ctx.big_map_storage; + let id0 = storage.big_map_new(&Type::Int, &Type::Int).unwrap(); + storage + .big_map_update(&id0, TypedValue::int(5), Some(TypedValue::int(5))) + .unwrap(); + + // Only ID - ok case + assert_eq!( + typecheck_value( + &Micheline::Int(0.into()), + &mut ctx, + &Type::new_big_map(Type::Int, Type::Int) + ), + Ok(TypedValue::BigMap(BigMap { + id: Some(id0.clone()), + overlay: BTreeMap::new(), + key_type: Type::Int, + value_type: Type::Int + })) + ); + + // Only ID - non-existing big map + assert_eq!( + typecheck_value( + &Micheline::Int(5.into()), + &mut ctx, + &Type::new_big_map(Type::Int, Type::Int) + ), + Err(TcError::BigMapNotFound(5.into())) + ); + + // Only ID - key mismatch + // NB: Octez implementation just says that big map does not exists, + // but this difference seems fine. + assert_eq!( + typecheck_value( + &Micheline::Int(0.into()), + &mut ctx, + &Type::new_big_map(Type::Nat, Type::Int) + ), + Err(TcError::TypesNotEqual(TypesNotEqual(Type::Int, Type::Nat))) + ); + + // Only ID - value mismatch + assert_eq!( + typecheck_value( + &Micheline::Int(0.into()), + &mut ctx, + &Type::new_big_map(Type::Int, Type::Nat) + ), + Err(TcError::TypesNotEqual(TypesNotEqual(Type::Int, Type::Nat))) + ); + + // Only overlay - ok case + assert_eq!( + typecheck_value( + &seq!(app!(Elt[7, 8])), + &mut ctx, + &Type::new_big_map(Type::Int, Type::Int) + ), + Ok(TypedValue::BigMap(BigMap { + id: None, + overlay: BTreeMap::from([(TypedValue::int(7), Some(TypedValue::int(8)))]), + key_type: Type::Int, + value_type: Type::Int + })) + ); + + // Only overlay - key type mismatch case + assert_eq!( + typecheck_value( + &seq!(app!(Elt["a", 8])), + &mut ctx, + &Type::new_big_map(Type::Int, Type::Int) + ), + Err(TcError::InvalidValueForType( + "String(\"a\")".into(), + Type::Int + )) + ); + + // ID and overlay - ok case + assert_eq!( + typecheck_value( + &app!(Pair[0, seq!(app!(Elt[7, 8]))]), + &mut ctx, + &Type::new_big_map(Type::Int, Type::Int) + ), + Ok(TypedValue::BigMap(BigMap { + id: Some(id0), + overlay: BTreeMap::from([(TypedValue::int(7), Some(TypedValue::int(8)))]), + key_type: Type::Int, + value_type: Type::Int + })) + ); + } + #[test] fn test_contract_not_storable() { let mut ctx = Ctx::default(); diff --git a/contrib/mir/src/typechecker/type_props.rs b/contrib/mir/src/typechecker/type_props.rs index 5997b2416b43..4160a992ea32 100644 --- a/contrib/mir/src/typechecker/type_props.rs +++ b/contrib/mir/src/typechecker/type_props.rs @@ -100,6 +100,15 @@ impl Type { | TypeProperty::BigMapValue | TypeProperty::Duplicable => p.1.ensure_prop(gas, prop)?, }, + BigMap(p) => match prop { + TypeProperty::Comparable + | TypeProperty::BigMapValue + | TypeProperty::Packable + | TypeProperty::Pushable => return invalid_type_prop(), + TypeProperty::Passable | TypeProperty::Storable | TypeProperty::Duplicable => { + p.1.ensure_prop(gas, prop)? + } + }, Contract(_) => match prop { TypeProperty::Passable | TypeProperty::Packable | TypeProperty::Duplicable => (), TypeProperty::Comparable -- GitLab From 972e06b11746789f65d14cd2cdb15d39f6608f35 Mon Sep 17 00:00:00 2001 From: martoon Date: Fri, 15 Dec 2023 18:09:21 +0400 Subject: [PATCH 4/4] MIR: Pass only gas to ensure_ty_eq, improve code There is a case where I have to perform extra clonning because `ctx` is needed simultaneously to access gas (mutably) and to access data in the lazy storage, and borrow checker does not allow that. --- contrib/mir/src/typechecker.rs | 42 +++++++++++++++------------------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index a12806109113..fa6231574785 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -26,8 +26,8 @@ use crate::ast::micheline::{ }; use crate::ast::michelson_address::AddressHash; use crate::context::Ctx; -use crate::gas; use crate::gas::OutOfGas; +use crate::gas::{self, Gas}; use crate::irrefutable_match::irrefutable_match; use crate::lexer::Prim; use crate::stack::*; @@ -1207,7 +1207,7 @@ pub(crate) fn typecheck_instruction<'a>( (App(NONE, expect_args!(1), _), _) => unexpected_micheline!(), (App(COMPARE, [], _), [.., u, t]) => { - ensure_ty_eq(ctx, t, u).map_err(|e| match e { + ensure_ty_eq(&mut ctx.gas, t, u).map_err(|e| match e { TcError::TypesNotEqual(e) => TcError::NoMatchingOverload { instr: Prim::COMPARE, stack: stack.clone(), @@ -1237,7 +1237,7 @@ pub(crate) fn typecheck_instruction<'a>( (App(NIL, ..), _) => unexpected_micheline!(), (App(CONS, [], _), [.., T::List(ty1), ty2]) => { - ensure_ty_eq(ctx, ty1, ty2)?; + ensure_ty_eq(&mut ctx.gas, ty1, ty2)?; pop!(); I::Cons } @@ -1278,14 +1278,14 @@ pub(crate) fn typecheck_instruction<'a>( (App(MEM, [], _), [.., T::Set(..), _]) => { let ty_ = pop!(); let ty = pop!(T::Set); - ensure_ty_eq(ctx, &ty, &ty_)?; + ensure_ty_eq(&mut ctx.gas, &ty, &ty_)?; stack.push(T::Bool); I::Mem(overloads::Mem::Set) } (App(MEM, [], _), [.., T::Map(..), _]) => { let kty_ = pop!(); let map_tys = pop!(T::Map); - ensure_ty_eq(ctx, &map_tys.as_ref().0, &kty_)?; + ensure_ty_eq(&mut ctx.gas, &map_tys.as_ref().0, &kty_)?; stack.push(T::Bool); I::Mem(overloads::Mem::Map) } @@ -1296,7 +1296,7 @@ pub(crate) fn typecheck_instruction<'a>( (App(GET, [], _), [.., T::Map(..), _]) => { let kty_ = pop!(); let map_tys = pop!(T::Map); - ensure_ty_eq(ctx, &map_tys.0, &kty_)?; + ensure_ty_eq(&mut ctx.gas, &map_tys.0, &kty_)?; stack.push(T::new_option(map_tys.1.clone())); I::Get(overloads::Get::Map) } @@ -1305,14 +1305,14 @@ pub(crate) fn typecheck_instruction<'a>( (App(GET, expect_args!(0), _), _) => unexpected_micheline!(), (App(UPDATE, [], _), [.., T::Set(ty), T::Bool, ty_]) => { - ensure_ty_eq(ctx, ty, ty_)?; + ensure_ty_eq(&mut ctx.gas, ty, ty_)?; stack.drop_top(2); I::Update(overloads::Update::Set) } (App(UPDATE, [], _), [.., T::Map(m), T::Option(vty_new), kty_]) => { let (kty, vty) = m.as_ref(); - ensure_ty_eq(ctx, kty, kty_)?; - ensure_ty_eq(ctx, vty, vty_new)?; + ensure_ty_eq(&mut ctx.gas, kty, kty_)?; + ensure_ty_eq(&mut ctx.gas, vty, vty_new)?; stack.drop_top(2); I::Update(overloads::Update::Map) } @@ -1387,7 +1387,7 @@ pub(crate) fn typecheck_instruction<'a>( (App(PACK, expect_args!(0), _), _) => unexpected_micheline!(), (App(TRANSFER_TOKENS, [], _), [.., T::Contract(ct), T::Mutez, arg_t]) => { - ensure_ty_eq(ctx, ct, arg_t)?; + ensure_ty_eq(&mut ctx.gas, ct, arg_t)?; stack.drop_top(3); stack.push(T::Operation); I::TransferTokens @@ -1458,7 +1458,7 @@ pub(crate) fn typecheck_instruction<'a>( (App(EXEC, [], _), [.., T::Lambda(_), _]) => { let ty = pop!(); let lam_tys = pop!(T::Lambda); - ensure_ty_eq(ctx, &lam_tys.0, &ty)?; + ensure_ty_eq(&mut ctx.gas, &lam_tys.0, &ty)?; stack.push(lam_tys.1.clone()); I::Exec } @@ -1489,7 +1489,7 @@ pub(crate) fn typecheck_instruction<'a>( }) } }; - ensure_ty_eq(ctx, &pair_ty.0, &ty)?; + ensure_ty_eq(&mut ctx.gas, &pair_ty.0, &ty)?; stack.push(T::new_lambda(pair_ty.1.clone(), lam_ty.1.clone())); I::Apply { arg_ty: ty } } @@ -1533,7 +1533,7 @@ pub(crate) fn typecheck_instruction<'a>( let lt = irrefutable_match!(&tickets.0; Type::Ticket); let rt = irrefutable_match!(&tickets.1; Type::Ticket); - ensure_ty_eq(ctx, lt, rt)?; + ensure_ty_eq(&mut ctx.gas, lt, rt)?; stack[0] = Type::new_option(tickets.0.clone()); I::JoinTickets } @@ -1719,7 +1719,7 @@ pub(crate) fn typecheck_contract_address( // if the entrypoint is present, check that it is of the required // type. - ensure_ty_eq(ctx, typ, contract_entrypoint_type)?; + ensure_ty_eq(&mut ctx.gas, typ, contract_entrypoint_type)?; Ok(Address { entrypoint, @@ -1806,11 +1806,8 @@ pub(crate) fn typecheck_value<'a>( .map_err(TcError::LazyStorageError)? .ok_or(TcError::BigMapNotFound(id))?; - // Clone to avoid conflicting borrows of ctx. - let key_type = &key_type.clone(); - let value_type = &value_type.clone(); - ensure_ty_eq(ctx, key_type, tk)?; - ensure_ty_eq(ctx, value_type, tv)?; + ensure_ty_eq(&mut ctx.gas, key_type, tk)?; + ensure_ty_eq(&mut ctx.gas, value_type, tv)?; Some(big_map_id) } else { None @@ -2129,7 +2126,7 @@ fn unify_stacks( )); } for (ty1, ty2) in stack1.iter().zip(stack2.iter()) { - ensure_ty_eq(ctx, ty1, ty2).map_err(|e| match e { + ensure_ty_eq(&mut ctx.gas, ty1, ty2).map_err(|e| match e { TcError::TypesNotEqual(e) => { TcError::StacksNotEqual(stack1.clone(), stack2.clone(), e.into()) } @@ -2146,9 +2143,8 @@ fn unify_stacks( Ok(()) } -fn ensure_ty_eq(ctx: &mut Ctx, ty1: &Type, ty2: &Type) -> Result<(), TcError> { - ctx.gas - .consume(gas::tc_cost::ty_eq(ty1.size_for_gas(), ty2.size_for_gas())?)?; +fn ensure_ty_eq(gas: &mut Gas, ty1: &Type, ty2: &Type) -> Result<(), TcError> { + gas.consume(gas::tc_cost::ty_eq(ty1.size_for_gas(), ty2.size_for_gas())?)?; if ty1 != ty2 { Err(TypesNotEqual(ty1.clone(), ty2.clone()).into()) } else { -- GitLab