diff --git a/contrib/mir/src/ast.rs b/contrib/mir/src/ast.rs index 52159c51db81cbd2eb0e25c85421777608088f5c..78474427a4f51acca39fab1a906c4786448512b6 100644 --- a/contrib/mir/src/ast.rs +++ b/contrib/mir/src/ast.rs @@ -162,6 +162,7 @@ pub enum TypedValue { Or(Box>), Address(Address), ChainId(ChainId), + Contract(Address), } pub fn typed_value_to_value_optimized(tv: TypedValue) -> Value { @@ -197,6 +198,7 @@ pub fn typed_value_to_value_optimized(tv: TypedValue) -> Value { TV::Or(x) => V::new_or(x.map(typed_value_to_value_optimized)), TV::Address(x) => V::Bytes(x.to_bytes_vec()), TV::ChainId(x) => V::Bytes(x.into()), + TV::Contract(x) => typed_value_to_value_optimized(TV::Address(x)), } } @@ -279,6 +281,8 @@ pub enum Instruction { Iter(T::IterOverload, Vec>), IfLeft(Vec>, Vec>), ChainId, + /// `ISelf` because `Self` is a reserved keyword + ISelf, } pub type ParsedAST = Vec; diff --git a/contrib/mir/src/ast/michelson_address/entrypoint.rs b/contrib/mir/src/ast/michelson_address/entrypoint.rs index 521735414e70660b8b906bef7b015bd94c29051e..e1a0306efa86179544a7293422abe07469af5982 100644 --- a/contrib/mir/src/ast/michelson_address/entrypoint.rs +++ b/contrib/mir/src/ast/michelson_address/entrypoint.rs @@ -10,6 +10,12 @@ use super::AddressError; #[derive(Debug, Clone, Eq, PartialOrd, Ord, PartialEq)] pub struct Entrypoint(String); +impl std::fmt::Display for Entrypoint { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + // NB: default entrypoint is represented as literal "default", because it // affects comparision for addresses. const DEFAULT_EP_NAME: &str = "default"; diff --git a/contrib/mir/src/context.rs b/contrib/mir/src/context.rs index 1177d7012e421be59783fd05226a3502fcf8fbd4..5f234d3f48ad556f4198a6bc4969d2add8e438dc 100644 --- a/contrib/mir/src/context.rs +++ b/contrib/mir/src/context.rs @@ -1,3 +1,4 @@ +use crate::ast::michelson_address::AddressHash; use crate::gas::Gas; #[derive(Debug)] @@ -5,6 +6,7 @@ pub struct Ctx { pub gas: Gas, pub amount: i64, pub chain_id: tezos_crypto_rs::hash::ChainId, + pub self_address: AddressHash, } impl Default for Ctx { @@ -14,6 +16,7 @@ impl Default for Ctx { amount: 0, // the default chain id is NetXynUjJNZm7wi, which is also the default chain id of octez-client in mockup mode chain_id: tezos_crypto_rs::hash::ChainId(vec![0xf3, 0xd4, 0x85, 0x54]), + self_address: "KT1BEqzn5Wx8uJrZNvuS9DVHmLvG9td3fDLi".try_into().unwrap(), } } } diff --git a/contrib/mir/src/gas.rs b/contrib/mir/src/gas.rs index bbccd5f1f7714dd65cabdd8d0aa806e35ad39eaf..d65bffb4d48d6c46c13167590523f889bdf1f912 100644 --- a/contrib/mir/src/gas.rs +++ b/contrib/mir/src/gas.rs @@ -178,6 +178,7 @@ pub mod interpret_cost { pub const NIL: u32 = 10; pub const CONS: u32 = 15; pub const CHAIN_ID: u32 = 15; + pub const SELF: u32 = 10; pub const INTERPRET_RET: u32 = 15; // corresponds to KNil in the Tezos protocol pub const LOOP_ENTER: u32 = 10; // corresponds to KLoop_in in the Tezos protocol diff --git a/contrib/mir/src/interpreter.rs b/contrib/mir/src/interpreter.rs index 64f337c98bf75f904006f430d4423db7fca238d3..945830fa85578a97a5292d3ebd2a9fb711c5c8e7 100644 --- a/contrib/mir/src/interpreter.rs +++ b/contrib/mir/src/interpreter.rs @@ -360,6 +360,13 @@ fn interpret_one( ctx.gas.consume(interpret_cost::CHAIN_ID)?; stack.push(V::ChainId(ctx.chain_id.clone())); } + I::ISelf => { + ctx.gas.consume(interpret_cost::SELF)?; + stack.push(V::Contract(Address { + hash: ctx.self_address.clone(), + entrypoint: Entrypoint::default(), + })); + } I::Seq(nested) => interpret(nested, ctx, stack)?, } Ok(()) @@ -1306,4 +1313,20 @@ mod interpreter_tests { interpret_cost::CHAIN_ID + interpret_cost::INTERPRET_RET ); } + + #[test] + fn self_instr() { + let stk = &mut stk![]; + let ctx = &mut Ctx { + self_address: "KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT".try_into().unwrap(), + ..Ctx::default() + }; + assert_eq!(interpret(&vec![ISelf], ctx, stk), Ok(())); + assert_eq!( + stk, + &stk![TypedValue::Contract( + "KT18amZmM5W7qDWVt2pH6uj7sCEd3kbzLrHT".try_into().unwrap() + )] + ); + } } diff --git a/contrib/mir/src/lexer.rs b/contrib/mir/src/lexer.rs index 3b480adf10f5a102f76ed1a7477c8b95c3bd20ee..2dedc04138ce3974f237dd662d7f3bc2b2b376b2 100644 --- a/contrib/mir/src/lexer.rs +++ b/contrib/mir/src/lexer.rs @@ -98,6 +98,7 @@ defprim! { address, chain_id, CHAIN_ID, + SELF, } defprim! { diff --git a/contrib/mir/src/lib.rs b/contrib/mir/src/lib.rs index 56582f8b7c35360af2a23c67ff0e930204814083..373236d5f05dce59487c87f62173e2cbe5a175ad 100644 --- a/contrib/mir/src/lib.rs +++ b/contrib/mir/src/lib.rs @@ -40,7 +40,7 @@ mod tests { fn interpret_test_expect_success() { let ast = parser::parse(FIBONACCI_SRC).unwrap(); let ast = ast - .typecheck(&mut Ctx::default(), &mut tc_stk![Type::Nat]) + .typecheck(&mut Ctx::default(), None, &mut tc_stk![Type::Nat]) .unwrap(); let mut istack = stk![TypedValue::Nat(10)]; assert!(ast.interpret(&mut Ctx::default(), &mut istack).is_ok()); @@ -51,7 +51,7 @@ mod tests { fn interpret_mutez_push_add() { let ast = parser::parse("{ PUSH mutez 100; PUSH mutez 500; ADD }").unwrap(); let mut ctx = Ctx::default(); - let ast = ast.typecheck(&mut ctx, &mut tc_stk![]).unwrap(); + let ast = ast.typecheck(&mut ctx, None, &mut tc_stk![]).unwrap(); let mut istack = stk![]; assert!(ast.interpret(&mut ctx, &mut istack).is_ok()); assert_eq!(istack, stk![TypedValue::Mutez(600)]); @@ -61,7 +61,7 @@ mod tests { fn interpret_test_gas_consumption() { let ast = parser::parse(FIBONACCI_SRC).unwrap(); let ast = ast - .typecheck(&mut Ctx::default(), &mut tc_stk![Type::Nat]) + .typecheck(&mut Ctx::default(), None, &mut tc_stk![Type::Nat]) .unwrap(); let mut istack = stk![TypedValue::Nat(5)]; let mut ctx = Ctx::default(); @@ -75,7 +75,7 @@ mod tests { fn interpret_test_gas_out_of_gas() { let ast = parser::parse(FIBONACCI_SRC).unwrap(); let ast = ast - .typecheck(&mut Ctx::default(), &mut tc_stk![Type::Nat]) + .typecheck(&mut Ctx::default(), None, &mut tc_stk![Type::Nat]) .unwrap(); let mut istack = stk![TypedValue::Nat(5)]; let mut ctx = Ctx { @@ -92,7 +92,7 @@ mod tests { fn typecheck_test_expect_success() { let ast = parser::parse(FIBONACCI_SRC).unwrap(); let mut stack = tc_stk![Type::Nat]; - assert!(ast.typecheck(&mut Ctx::default(), &mut stack).is_ok()); + assert!(ast.typecheck(&mut Ctx::default(), None, &mut stack).is_ok()); assert_eq!(stack, tc_stk![Type::Int]) } @@ -103,7 +103,7 @@ mod tests { let mut ctx = Ctx::default(); let start_milligas = ctx.gas.milligas(); report_gas(&mut ctx, |ctx| { - assert!(ast.typecheck(ctx, &mut stack).is_ok()); + assert!(ast.typecheck(ctx, None, &mut stack).is_ok()); }); assert_eq!(start_milligas - ctx.gas.milligas(), 12680); } @@ -117,7 +117,7 @@ mod tests { ..Ctx::default() }; assert_eq!( - ast.typecheck(&mut ctx, &mut stack), + ast.typecheck(&mut ctx, None, &mut stack), Err(typechecker::TcError::OutOfGas(crate::gas::OutOfGas)) ); } @@ -128,7 +128,7 @@ mod tests { let ast = parser::parse(FIBONACCI_ILLTYPED_SRC).unwrap(); let mut stack = tc_stk![Type::Nat]; assert_eq!( - ast.typecheck(&mut Ctx::default(), &mut stack), + ast.typecheck(&mut Ctx::default(), None, &mut stack), Err(TcError::NoMatchingOverload { instr: crate::lexer::Prim::DUP, stack: stk![Type::Int, Type::Int, Type::Int], diff --git a/contrib/mir/src/syntax.lalrpop b/contrib/mir/src/syntax.lalrpop index 1cc84f0f270e326f17baeaf4dc147e77252a189e..730f68571bc38032b0d05026727a51f34cfd4c5c 100644 --- a/contrib/mir/src/syntax.lalrpop +++ b/contrib/mir/src/syntax.lalrpop @@ -91,6 +91,7 @@ extern { "ITER" => Tok::Prim(PT::Prim(Prim::ITER)), "IF_LEFT" => Tok::Prim(PT::Prim(Prim::IF_LEFT)), "CHAIN_ID" => Tok::Prim(PT::Prim(Prim::CHAIN_ID)), + "SELF" => Tok::Prim(PT::Prim(Prim::SELF)), "(" => Tok::LParen, ")" => Tok::RParen, "{" => Tok::LBrace, @@ -200,6 +201,7 @@ atomic_instruction: ParsedInstruction = { "UNPAIR" => Unpair, "CONS" => Cons, "CHAIN_ID" => Instruction::ChainId, + "SELF" => ISelf, } instruction: ParsedInstruction = { diff --git a/contrib/mir/src/typechecker.rs b/contrib/mir/src/typechecker.rs index 16a60f707940ac132be8230b439788c1df10da1b..ab3381409afdfc916f7c768a35b879861838a060 100644 --- a/contrib/mir/src/typechecker.rs +++ b/contrib/mir/src/typechecker.rs @@ -13,6 +13,7 @@ pub mod type_props; use type_props::TypeProperty; +use crate::ast::michelson_address::AddressHash; use crate::ast::*; use crate::context::Ctx; use crate::gas; @@ -56,6 +57,12 @@ pub enum TcError { AddressError(#[from] AddressError), #[error("invalid value for chain_id: {0}")] ChainIdError(#[from] ChainIdError), + #[error("SELF instruction is forbidden in this context")] + SelfForbidden, + #[error("no such entrypoint: {0}")] + NoSuchEntrypoint(Entrypoint), + #[error("unexpected implicit account parameter type: {0:?}")] + UnexpectedImplicitAccountType(Type), } #[derive(Debug, PartialEq, Eq, Clone, thiserror::Error)] @@ -122,7 +129,7 @@ impl ContractScript { parameter.ensure_prop(&mut ctx.gas, TypeProperty::Passable)?; storage.ensure_prop(&mut ctx.gas, TypeProperty::Storable)?; let mut stack = tc_stk![Type::new_pair(parameter.clone(), storage.clone())]; - let code = self.code.typecheck(ctx, &mut stack)?; + let code = self.code.typecheck(ctx, Some(¶meter), &mut stack)?; unify_stacks( ctx, &mut tc_stk![Type::new_pair( @@ -141,15 +148,19 @@ impl ContractScript { impl ParsedInstruction { /// Typecheck an individual instruction. Validates the passed stack types. + /// + /// When `self_type` is `None`, `SELF` instruction is forbidden (e.g. like + /// in lambdas). pub fn typecheck( self, ctx: &mut Ctx, + self_type: Option<&Type>, opt_stack: &mut FailingTypeStack, ) -> Result { if let Ok(stack) = opt_stack.access_mut(()) { stack.iter().try_for_each(|ty| verify_ty(ctx, ty))?; } - typecheck_instruction(self, ctx, opt_stack) + typecheck_instruction(self, ctx, self_type, opt_stack) } } @@ -190,13 +201,20 @@ fn verify_ty(ctx: &mut Ctx, t: &Type) -> Result<(), TcError> { /// Typecheck a sequence of instructions. Assumes the passed stack is valid, i.e. /// doesn't contain illegal types like `set operation` or `contract operation`. +/// +/// When `self_type` is `None`, `SELF` instruction is forbidden (e.g. like +/// in lambdas). +/// +/// Self type is carried as an argument, not as part of context, because it has +/// to be locally overridden during typechecking. fn typecheck( ast: ParsedAST, ctx: &mut Ctx, + self_type: Option<&Type>, opt_stack: &mut FailingTypeStack, ) -> Result { ast.into_iter() - .map(|i| typecheck_instruction(i, ctx, opt_stack)) + .map(|i| typecheck_instruction(i, ctx, self_type, opt_stack)) .collect() } @@ -211,9 +229,16 @@ macro_rules! nothing_to_none { /// Typecheck a single instruction. Assumes passed stack is valid, i.e. doesn't /// contain illegal types like `set operation` or `contract operation`. +/// +/// When `self_type` is `None`, `SELF` instruction is forbidden (e.g. like +/// in lambdas). +/// +/// Self type is carried as an argument, not as part of context, because it has +/// to be locally overridden during typechecking. fn typecheck_instruction( i: ParsedInstruction, ctx: &mut Ctx, + self_type: Option<&Type>, opt_stack: &mut FailingTypeStack, ) -> Result { use Instruction as I; @@ -307,7 +332,7 @@ fn typecheck_instruction( // Here we split off the protected portion of the stack, typecheck the code with the // remaining unprotected part, then append the protected portion back on top. let mut protected = stack.split_off(protected_height); - let nested = typecheck(nested, ctx, opt_stack)?; + let nested = typecheck(nested, ctx, self_type, opt_stack)?; opt_stack .access_mut(TcError::FailNotInTail)? .append(&mut protected); @@ -346,8 +371,8 @@ fn typecheck_instruction( // Clone the stack so that we have a copy to run one branch on. // We can run the other branch on the live stack. let mut f_opt_stack = opt_stack.clone(); - let nested_t = typecheck(nested_t, ctx, opt_stack)?; - let nested_f = typecheck(nested_f, ctx, &mut f_opt_stack)?; + let nested_t = typecheck(nested_t, ctx, self_type, opt_stack)?; + let nested_f = typecheck(nested_f, ctx, self_type, &mut f_opt_stack)?; // If stacks unify after typecheck, all is good. unify_stacks(ctx, opt_stack, f_opt_stack)?; I::If(nested_t, nested_f) @@ -362,8 +387,8 @@ fn typecheck_instruction( let mut some_stack: TypeStack = stack.clone(); some_stack.push(*ty); let mut some_opt_stack = FailingTypeStack::Ok(some_stack); - let when_none = typecheck(when_none, ctx, opt_stack)?; - let when_some = typecheck(when_some, ctx, &mut some_opt_stack)?; + let when_none = typecheck(when_none, ctx, self_type, opt_stack)?; + let when_some = typecheck(when_some, ctx, self_type, &mut some_opt_stack)?; // If stacks unify, all is good unify_stacks(ctx, opt_stack, some_opt_stack)?; I::IfNone(when_none, when_some) @@ -379,8 +404,8 @@ fn typecheck_instruction( // push it to the cons stack cons_stack.push(*ty); let mut cons_opt_stack = FailingTypeStack::Ok(cons_stack); - let when_cons = typecheck(when_cons, ctx, &mut cons_opt_stack)?; - let when_nil = typecheck(when_nil, ctx, opt_stack)?; + let when_cons = typecheck(when_cons, ctx, self_type, &mut cons_opt_stack)?; + let when_nil = typecheck(when_nil, ctx, self_type, opt_stack)?; // If stacks unify, all is good unify_stacks(ctx, opt_stack, cons_opt_stack)?; I::IfCons(when_cons, when_nil) @@ -396,8 +421,8 @@ fn typecheck_instruction( stack.push(tl); right_stack.push(tr); let mut opt_right_stack = FailingTypeStack::Ok(right_stack); - let when_left = typecheck(when_left, ctx, opt_stack)?; - let when_right = typecheck(when_right, ctx, &mut opt_right_stack)?; + let when_left = typecheck(when_left, ctx, self_type, opt_stack)?; + let when_right = typecheck(when_right, ctx, self_type, &mut opt_right_stack)?; // If stacks unify, all is good unify_stacks(ctx, opt_stack, opt_right_stack)?; I::IfLeft(when_left, when_right) @@ -418,7 +443,7 @@ fn typecheck_instruction( // Pop the bool off the top pop!(); // Typecheck body with the current stack - let nested = typecheck(nested, ctx, opt_stack)?; + let nested = typecheck(nested, ctx, self_type, opt_stack)?; // If the starting stack and result stack unify, all is good. unify_stacks(ctx, opt_stack, opt_copy)?; // pop the remaining bool off (if not failed) @@ -436,7 +461,7 @@ fn typecheck_instruction( // push the element type to the top of the inner stack and typecheck inner_stack.push(ty); let mut opt_inner_stack = FailingTypeStack::Ok(inner_stack); - let nested = typecheck(nested, ctx, &mut opt_inner_stack)?; + let nested = typecheck(nested, ctx, self_type, &mut opt_inner_stack)?; // If the starting stack (sans list) and result stack unify, all is good. unify_stacks(ctx, opt_stack, opt_inner_stack)?; I::Iter(overloads::Iter::List, nested) @@ -449,7 +474,7 @@ fn typecheck_instruction( // push the element type to the top of the inner stack and typecheck inner_stack.push(T::Pair(kty_vty_box)); let mut opt_inner_stack = FailingTypeStack::Ok(inner_stack); - let nested = typecheck(nested, ctx, &mut opt_inner_stack)?; + let nested = typecheck(nested, ctx, self_type, &mut opt_inner_stack)?; // If the starting stack (sans map) and result stack unify, all is good. unify_stacks(ctx, opt_stack, opt_inner_stack)?; I::Iter(overloads::Iter::Map, nested) @@ -590,7 +615,14 @@ fn typecheck_instruction( I::ChainId } - (I::Seq(nested), ..) => I::Seq(typecheck(nested, ctx, opt_stack)?), + (I::ISelf, ..) => { + stack.push(T::new_contract( + self_type.ok_or(TcError::SelfForbidden)?.clone(), + )); + I::ISelf + } + + (I::Seq(nested), ..) => I::Seq(typecheck(nested, ctx, self_type, opt_stack)?), }) } @@ -683,6 +715,31 @@ fn typecheck_value(ctx: &mut Ctx, t: &Type, v: Value) -> Result { + let t_addr = irrefutable_match!(typecheck_value(ctx, &T::Address, addr)?; TV::Address); + match t_addr.hash { + AddressHash::Tz1(_) + | AddressHash::Tz2(_) + | AddressHash::Tz3(_) + | AddressHash::Tz4(_) => { + if !t_addr.is_default_ep() { + return Err(TcError::NoSuchEntrypoint(t_addr.entrypoint)); + } + ctx.gas.consume(gas::tc_cost::ty_eq( + ty.size_for_gas(), + T::Unit.size_for_gas(), + )?)?; + match ty.as_ref() { + T::Unit => {} + ty => return Err(TcError::UnexpectedImplicitAccountType(ty.clone())), + } + } + AddressHash::Kt1(_) | AddressHash::Sr1(_) => { + // TODO: verify against ctx + } + } + TV::Contract(t_addr) + } (T::ChainId, V::String(str)) => { ctx.gas.consume(gas::tc_cost::CHAIN_ID_READABLE)?; TV::ChainId( @@ -768,6 +825,15 @@ mod typecheck_tests { use crate::typechecker::*; use Instruction::*; + /// hack to simplify syntax in tests + fn typecheck_instruction( + i: ParsedInstruction, + ctx: &mut Ctx, + opt_stack: &mut FailingTypeStack, + ) -> Result { + super::typecheck_instruction(i, ctx, None, opt_stack) + } + #[test] fn test_dup() { let mut stack = tc_stk![Type::Nat]; @@ -820,8 +886,8 @@ mod typecheck_tests { let expected_stack = tc_stk![]; let mut ctx = Ctx::default(); assert_eq!( - typecheck(vec![Drop(None)], &mut ctx, &mut stack), - Ok(vec![Drop(None)]) + typecheck_instruction(Drop(None), &mut ctx, &mut stack), + Ok(Drop(None)) ); assert_eq!(stack, expected_stack); assert_eq!(ctx.gas.milligas(), Gas::default().milligas() - 440); @@ -1759,7 +1825,7 @@ mod typecheck_tests { assert_eq!( parse("GET") .unwrap() - .typecheck(&mut Ctx::default(), &mut stack), + .typecheck(&mut Ctx::default(), None, &mut stack), Err(TcError::InvalidTypeProperty( TypeProperty::Comparable, Type::new_list(Type::Int) @@ -1813,7 +1879,7 @@ mod typecheck_tests { assert_eq!( parse("UPDATE") .unwrap() - .typecheck(&mut Ctx::default(), &mut stack), + .typecheck(&mut Ctx::default(), None, &mut stack), Err(TcError::InvalidTypeProperty( TypeProperty::Comparable, Type::new_list(Type::Int) @@ -2578,4 +2644,19 @@ mod typecheck_tests { Ok(Instruction::ChainId) ); } + + #[test] + fn self_instr() { + let stk = &mut tc_stk![]; + assert_eq!( + super::typecheck_instruction( + parse("SELF").unwrap(), + &mut Ctx::default(), + Some(&Type::Nat), + stk + ), + Ok(Instruction::ISelf) + ); + assert_eq!(stk, &tc_stk![Type::new_contract(Type::Nat)]); + } } diff --git a/contrib/mir/src/tzt.rs b/contrib/mir/src/tzt.rs index 9e4311516901e4f6b931d78e144b23c0e70fc65e..698c18f2ae3cd14f2941de568fddda415446d5d4 100644 --- a/contrib/mir/src/tzt.rs +++ b/contrib/mir/src/tzt.rs @@ -219,6 +219,7 @@ pub enum TztOutput { fn execute_tzt_test_code( code: ParsedInstruction, ctx: &mut Ctx, + parameter: Option<&Type>, input: Vec<(Type, TypedValue)>, ) -> Result<(FailingTypeStack, IStack), TestError> { // Build initial stacks (type and value) for running the test from the test input @@ -233,7 +234,7 @@ fn execute_tzt_test_code( // This value along with the test expectation // from the test file will be used to decide if // the test was a success or a fail. - let typechecked_code = code.typecheck(ctx, &mut t_stack)?; + let typechecked_code = code.typecheck(ctx, parameter, &mut t_stack)?; let mut i_stack: IStack = TopIsFirst::from(vals).0; typechecked_code.interpret(ctx, &mut i_stack)?; Ok((t_stack, i_stack)) @@ -247,7 +248,8 @@ pub fn run_tzt_test(test: TztTest) -> Result<(), TztTestError> { gas: crate::gas::Gas::default(), amount: test.amount.unwrap_or_default(), chain_id: test.chain_id.unwrap_or(Ctx::default().chain_id), + self_address: Ctx::default().self_address, }; - let execution_result = execute_tzt_test_code(test.code, &mut ctx, test.input); + let execution_result = execute_tzt_test_code(test.code, &mut ctx, None, test.input); check_expectation(&mut ctx, test.output, execution_result) }