diff --git a/contrib/mir/MIR- Run TZT.out b/contrib/mir/MIR- Run TZT.out index ed0e2edfe4f96c9926a2702bfb4ccf0972c0ec9c..afde5b0cc205d61f8dd81f89bc3b23c11a6029bd 100644 --- a/contrib/mir/MIR- Run TZT.out +++ b/contrib/mir/MIR- Run TZT.out @@ -365,18 +365,18 @@ "map_mapstringnat_00.tzt": true, "map_mapstringnat_01.tzt": true, "map_mapstringnat_02.tzt": true, - "mem_bigmapnatnat_00.tzt": false, - "mem_bigmapnatnat_01.tzt": false, - "mem_bigmapnatnat_02.tzt": false, - "mem_bigmapnatnat_03.tzt": false, - "mem_bigmapnatnat_04.tzt": false, - "mem_bigmapnatnat_05.tzt": false, - "mem_bigmapstringnat_00.tzt": false, - "mem_bigmapstringnat_01.tzt": false, - "mem_bigmapstringnat_02.tzt": false, - "mem_bigmapstringnat_03.tzt": false, - "mem_bigmapstringnat_04.tzt": false, - "mem_bigmapstringnat_05.tzt": false, + "mem_bigmapnatnat_00.tzt": true, + "mem_bigmapnatnat_01.tzt": true, + "mem_bigmapnatnat_02.tzt": true, + "mem_bigmapnatnat_03.tzt": true, + "mem_bigmapnatnat_04.tzt": true, + "mem_bigmapnatnat_05.tzt": true, + "mem_bigmapstringnat_00.tzt": true, + "mem_bigmapstringnat_01.tzt": true, + "mem_bigmapstringnat_02.tzt": true, + "mem_bigmapstringnat_03.tzt": true, + "mem_bigmapstringnat_04.tzt": true, + "mem_bigmapstringnat_05.tzt": true, "mem_mapintint_00.tzt": true, "mem_mapnatnat_00.tzt": true, "mem_mapnatnat_01.tzt": true, diff --git a/contrib/mir/src/ast/big_map.rs b/contrib/mir/src/ast/big_map.rs index b9bbcce27fac7071a3636635246db19617560945..14abd77f57bf8e77c9761a81ea4b274ea0688267 100644 --- a/contrib/mir/src/ast/big_map.rs +++ b/contrib/mir/src/ast/big_map.rs @@ -210,14 +210,29 @@ impl<'a, T: LazyStorage<'a> + ?Sized> LazyStorageBulkUpdate<'a> for T {} /// A `big_map` representation with metadata, used in [InMemoryLazyStorage]. #[derive(Clone, PartialEq, Eq, Debug)] -struct MapInfo<'a> { +pub(crate) struct MapInfo<'a> { map: BTreeMap, TypedValue<'a>>, key_type: Type, value_type: Type, } +impl<'a> MapInfo<'a> { + /// Construct a new, empty, in-memory storage. + pub fn new( + map: BTreeMap, TypedValue<'a>>, + key_type: Type, + value_type: Type, + ) -> Self { + MapInfo { + map, + key_type, + value_type, + } + } +} + /// Simple implementation for [LazyStorage]. -#[derive(Clone)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct InMemoryLazyStorage<'a> { next_id: BigInt, big_maps: BTreeMap>, @@ -232,6 +247,16 @@ impl<'a> InMemoryLazyStorage<'a> { } } + /// Construct in tzt with given big maps. + pub(crate) fn with_big_maps(big_maps: BTreeMap>) -> Self { + let next_id = big_maps + .keys() + .max() + .map(|id| id.0.clone() + 1) + .unwrap_or_else(|| 0.into()); + InMemoryLazyStorage { next_id, big_maps } + } + fn get_next_id(&mut self) -> BigMapId { let id = BigMapId(self.next_id.clone()); self.next_id += 1; diff --git a/contrib/mir/src/context.rs b/contrib/mir/src/context.rs index 06a08abea8863d91bfb4008e75878f316fcc7229..c606364e451d1e34d4b09a0faabd190239e7f811 100644 --- a/contrib/mir/src/context.rs +++ b/contrib/mir/src/context.rs @@ -83,7 +83,7 @@ pub struct Ctx<'a> { operation_counter: u128, } -impl Ctx<'_> { +impl<'a> Ctx<'a> { /// Increment the internal operation counter and return it. Used as a nonce /// for operations. pub fn operation_counter(&mut self) -> u128 { @@ -103,6 +103,12 @@ impl Ctx<'_> { self.lookup_contract = Box::new(move |ah| map.get(ah).cloned()); } + /// Set a resonable implementation for [Self::big_map_storage] by providing + /// something that implements the [LazyStorage] trait. + pub fn set_big_map_storage(&mut self, v: impl LazyStorage<'a> + 'a) { + self.big_map_storage = Box::new(v); + } + /// Set a reasonable implementation for [Self::voting_powers] and a /// consistent value for [Self::total_voting_power] by providing something /// that converts into [`HashMap`], mapping key hashes to diff --git a/contrib/mir/src/lexer.rs b/contrib/mir/src/lexer.rs index 9260f20af36080fd39500b8a1fd8123152e364a3..499925d538f641db2a669ef727102f456a2753a8 100644 --- a/contrib/mir/src/lexer.rs +++ b/contrib/mir/src/lexer.rs @@ -150,6 +150,8 @@ defprim! { now, source, sender, + big_maps, + Big_map, } /// Either a Micheline primitive, TZT primitive, or a macro lexeme. diff --git a/contrib/mir/src/syntax.lalrpop b/contrib/mir/src/syntax.lalrpop index 71995bad31c1537f70673c2c34c42075e72d807a..99dc3a862e11119323b41a82b1042a802689ae7b 100644 --- a/contrib/mir/src/syntax.lalrpop +++ b/contrib/mir/src/syntax.lalrpop @@ -45,6 +45,8 @@ extern { "balance" => Tok::Noun(TztPrim(TzP::balance)), "self" => Tok::Noun(TztPrim(TzP::self_)), "other_contracts" => Tok::Noun(TztPrim(TzP::other_contracts)), + "big_map" => Tok::Noun(TztPrim(TzP::Big_map)), + "big_maps" => Tok::Noun(TztPrim(TzP::big_maps)), "now" => Tok::Noun(TztPrim(TzP::now)), "source" => Tok::Noun(TztPrim(TzP::source)), "sender" => Tok::Noun(TztPrim(TzP::sender)), @@ -148,6 +150,14 @@ otherContracts : Vec<(Micheline<'a>, Micheline<'a>)> = { "{" "}" => s } +bigMapsIndexed : (Micheline<'a>, Micheline<'a>, Micheline<'a>, Micheline<'a>) = { + "big_map" => (i, kty, vty, elts) +} + +bigMaps : Vec<(Micheline<'a>, Micheline<'a>, Micheline<'a>, Micheline<'a>)> = { + "{" > "}" => s +} + tztStackEltSeq = semicolonSepSeq; tztStack : Vec<(Micheline<'a>, Micheline<'a>)> = { @@ -175,6 +185,7 @@ timestamp : i64 = { use ErrorExpectation::*; use InterpreterErrorExpectation::*; + tztEntity : TztEntity<'a> = { "code" => Code(<>), "input" => Input(s), @@ -195,6 +206,7 @@ tztEntity : TztEntity<'a> = { "now" => TztEntity::Now(t), "source" => TztEntity::Source(<>), "sender" => TztEntity::SenderAddr(<>), + "big_maps" => TztEntity::BigMaps(<>), } pub(crate) tztTestEntities : Vec> = semicolonSepSeq; diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index cef03f53a9538f244a6f8454344a1005350388a6..0a1be00156e54ba09231019947cfe7e5f50d57d0 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -7674,7 +7674,10 @@ mod typecheck_tests { &mut Ctx::default(), stk ), - Err(TcError::InvalidValueForType("String(\"ABCD\")".to_string(), Type::Timestamp)) + Err(TcError::InvalidValueForType( + "String(\"ABCD\")".to_string(), + Type::Timestamp + )) ); let stk = &mut tc_stk![]; @@ -7684,7 +7687,10 @@ mod typecheck_tests { &mut Ctx::default(), stk ), - Err(TcError::InvalidValueForType("String(\"3.5\")".to_string(), Type::Timestamp)) + Err(TcError::InvalidValueForType( + "String(\"3.5\")".to_string(), + Type::Timestamp + )) ); } diff --git a/contrib/mir/src/tzt.rs b/contrib/mir/src/tzt.rs index c1fe2fb578b7b5011e661a5820f2b6c2f16eb6b8..03d8b6a3c4161ab7bbdf36461009e8756e474e84 100644 --- a/contrib/mir/src/tzt.rs +++ b/contrib/mir/src/tzt.rs @@ -10,6 +10,7 @@ mod expectation; use num_bigint::BigInt; +use std::collections::BTreeMap; use std::collections::HashMap; use std::fmt; use typed_arena::Arena; @@ -17,6 +18,7 @@ use typed_arena::Arena; use crate::ast::michelson_address::entrypoint::Entrypoints; use crate::ast::michelson_address::AddressHash; use crate::ast::*; +use crate::ast::big_map::{BigMapId,InMemoryLazyStorage, MapInfo}; use crate::context::*; use crate::interpreter::*; use crate::gas; @@ -27,6 +29,7 @@ use crate::stack::*; use crate::syntax::tztTestEntitiesParser; use crate::typechecker::*; use crate::tzt::expectation::*; +use crate::lexer::Prim; /// Test's input stack represented as a [Vec] of pairs of type and typechecked /// value. The top of the stack is the _leftmost_ element. @@ -98,6 +101,9 @@ pub struct TztTest<'a> { pub self_addr: Option, /// Other known contracts, as defined by `other_contracts` field. pub other_contracts: Option>, + /// mapping between integers representing big_map indices and descriptions of big maps + /// as defined by the `big_maps` field. + pub big_maps: Option>, /// Initial value for "now" in the context. pub now: Option, /// Address that directly or indirectly initiated the current transaction @@ -135,9 +141,11 @@ fn typecheck_stack<'a>( stk: Vec<(Micheline<'a>, Micheline<'a>)>, self_param: Option<(AddressHash, Option)>, m_other_contracts: Option>, + m_big_maps: Option>, ) -> Result)>, TcError> { let mut ctx = Ctx::default(); populate_ctx_with_known_contracts(&mut ctx, self_param, m_other_contracts); + ctx.set_big_map_storage(m_big_maps.unwrap_or_default()); stk.into_iter() .map(|(t, v)| { @@ -183,6 +191,7 @@ impl<'a> TryFrom>> for TztTest<'a> { let mut m_parameter: Option = None; let mut m_self: Option = None; let mut m_other_contracts: Option> = None; + let mut m_big_maps: Option> = None; let mut m_now : Option = None; let mut m_source : Option = None; let mut m_sender : Option = None; @@ -215,6 +224,7 @@ impl<'a> TryFrom>> for TztTest<'a> { Now(n) => set_tzt_field("now", &mut m_now, n.into())?, Source(v) => set_tzt_field("source", &mut m_source, v)?, SenderAddr(v) => set_tzt_field("sender", &mut m_sender, v)?, + BigMaps(v) => set_tzt_field("big_maps", &mut m_big_maps, v)?, } } @@ -281,6 +291,49 @@ impl<'a> TryFrom>> for TztTest<'a> { None => None, }; + let big_maps = match m_big_maps { + Some(bm) => { + let mut a = BTreeMap::new(); + for (idx, key_ty, val_ty, elts) in bm { + let idx = BigMapId(irrefutable_match!( + typecheck_value(&idx, &mut Ctx::default(), &Type::Int)?; + TypedValue::Int) + .clone()); + let key_ty = parse_ty(&mut Ctx::default(), &key_ty)?; + let val_ty = parse_ty(&mut Ctx::default(), &val_ty)?; + let elts = match elts { + Micheline::Seq(elts) => elts, + _ => return Err("Big map elements must be a sequence".into()), + }; + let descr: BTreeMap, TypedValue<'a>> = elts + .into_iter() + .map(|elt| { + match elt { + // If Micheline::App stores its arguments in a Vec, + // pattern match with a condition to ensure length is 2 + Micheline::App(Prim::Elt, ref kv, _) if kv.len() == 2 => { + let (k_raw, v_raw) = (&kv[0], &kv[1]); + let k = typecheck_value(k_raw, &mut Ctx::default(), &key_ty).unwrap(); + let v = typecheck_value(v_raw, &mut Ctx::default(), &val_ty).unwrap(); + Ok((k, v)) + } + _ => { return Err("Each big map element must be of the form `Elt `."); } + } + }) + .collect::, _>>()?; + + a.insert(idx, MapInfo::new( + descr, + key_ty, + val_ty, + )); + } + let storage = InMemoryLazyStorage::with_big_maps(a); + Some(storage) + } + None => None, + }; + // Once we have self_addr and parameter, we typecheck the output stack // after populating the context's known_contracts with the self address. // Later we may add the Tzt specs `other_contracts` fields. At that point @@ -291,6 +344,7 @@ impl<'a> TryFrom>> for TztTest<'a> { stk, self_addr.clone().map(|x| (x, parameter.clone())), other_contracts.clone(), + big_maps.clone(), )?)), TztError(error_exp) => Some(ExpectError(error_exp)), }, @@ -305,6 +359,7 @@ impl<'a> TryFrom>> for TztTest<'a> { stk, self_addr.clone().map(|x| (x, parameter.clone())), other_contracts.clone(), + big_maps.clone(), )?) }, None => None, @@ -327,6 +382,7 @@ impl<'a> TryFrom>> for TztTest<'a> { parameter, self_addr, other_contracts, + big_maps, now: m_now, source, sender, @@ -426,6 +482,7 @@ pub(crate) enum TztEntity<'a> { Now(i64), Source(Micheline<'a>), SenderAddr(Micheline<'a>), + BigMaps(Vec<(Micheline<'a>, Micheline<'a>, Micheline<'a>, Micheline<'a>)>), } /// Possible values for the "output" expectation field in a Tzt test. This is a @@ -499,6 +556,8 @@ pub fn run_tzt_test<'a>( test.other_contracts.clone(), ); + ctx.set_big_map_storage(test.big_maps.unwrap_or_default()); + ctx.now = test.now.clone().unwrap_or(Ctx::default().now); let execution_result = diff --git a/contrib/mir/tzt_runner/main.rs b/contrib/mir/tzt_runner/main.rs index 2b810d81ee723ff7b4b9d80079803044775efe0f..11164b1e5625b6611f08935e5a7e740f6e33f716 100644 --- a/contrib/mir/tzt_runner/main.rs +++ b/contrib/mir/tzt_runner/main.rs @@ -24,7 +24,6 @@ fn run_test(file: &str) -> Result<(), String> { let tzt_test = parser .parse_tzt_test(&contents) .map_err(|e| e.to_string())?; - let arena = Arena::new(); run_tzt_test(tzt_test, &arena).map_err(|e| format!("{}", e)) }