diff --git a/contrib/mir/src/ast/big_map.rs b/contrib/mir/src/ast/big_map.rs index 2c5928c3d7fd089d50b7a0e405ba1b4ea3e0b926..6526c07b1670c25a2a434504dcf1ebf16cd692f1 100644 --- a/contrib/mir/src/ast/big_map.rs +++ b/contrib/mir/src/ast/big_map.rs @@ -5,7 +5,7 @@ //! `big_map` typed representation and utilities for working with `big_map`s. use num_bigint::{BigInt, Sign}; -use num_traits::One; +use num_traits::{One, Zero}; use std::{ collections::{btree_map::Entry, BTreeMap}, fmt::Display, @@ -63,6 +63,34 @@ impl BigMapId { } } + /// Increment the mutable id + pub fn incr(&mut self) { + let Zarith(ref int_value) = self.value; + let result = if self.is_temporary() { + int_value - BigInt::one() + } else { + int_value + BigInt::one() + }; + self.value = Zarith(result); + } + + /// Decrement the id + /// + /// If there's no predecessor return false, otherwise true + pub fn dec(&mut self) -> bool { + let Zarith(ref int_value) = self.value; + let result = if self.is_temporary() { + int_value + BigInt::one() + } else { + int_value - BigInt::one() + }; + let has_pred = !result.eq(&BigInt::zero()); + if has_pred { + self.value = Zarith(result); + } + has_pred + } + /// Tells if a big_map id is temporary pub fn is_temporary(&self) -> bool { self.value.0.sign() == Sign::Minus diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 48d1a74c7e64cf944623da9055f6f24ef409b8d3..a54c6ceddd98ad29aa8a02ceaa2d7796927ba782 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -42,7 +42,10 @@ use tezos_tezlink::{ use crate::address::OriginationNonce; use crate::gas::Cost; -use crate::mir_ctx::{convert_big_map_diff, BlockCtx, Ctx, ExecCtx, OperationCtx, TcCtx}; +use crate::mir_ctx::{ + clear_temporary_big_maps, convert_big_map_diff, BlockCtx, Ctx, ExecCtx, OperationCtx, + TcCtx, +}; extern crate alloc; pub mod account_storage; @@ -406,7 +409,7 @@ fn transfer<'a, Host: Runtime>( // operations. let consumed_milligas = ctx.tc_ctx.gas.milligas_consumed_by_operation(); let lazy_storage_diff = - convert_big_map_diff(std::mem::take(ctx.tc_ctx.big_map_diff)); + convert_big_map_diff(std::mem::take(&mut ctx.tc_ctx.big_map_diff)); execute_internal_operations( ctx.tc_ctx, ctx.operation_ctx, @@ -536,7 +539,7 @@ fn handle_storage_with_big_maps<'a, Host: Runtime>( let storage = storage .into_micheline_optimized_legacy(&parser.arena) .encode(); - let lazy_storage_diff = convert_big_map_diff(std::mem::take(ctx.big_map_diff)); + let lazy_storage_diff = convert_big_map_diff(std::mem::take(&mut ctx.big_map_diff)); Ok((storage, lazy_storage_diff)) } @@ -838,7 +841,7 @@ fn apply_batch( } = validation_info; let mut first_failure: Option = None; let mut receipts = Vec::with_capacity(validated_operations.len()); - + let mut next_temporary_id = BigMapId { value: (-1).into() }; for (index, validated_operation) in validated_operations.into_iter().enumerate() { log!( host, @@ -863,6 +866,7 @@ fn apply_batch( origination_nonce, &source_account, validated_operation, + &mut next_temporary_id, block_ctx, ) }; @@ -874,12 +878,24 @@ fn apply_batch( receipts.push(receipt); } + // Clear all the temporaries big_map after the application of the batch + let cleared = clear_temporary_big_maps(host, context, &mut next_temporary_id); + + if let Err(lazy_storage_err) = cleared { + log!( + host, + Error, + "Cleaning the temporary big_map in the storage failed: {lazy_storage_err}" + ) + } + if let Some(failure_idx) = first_failure { receipts[..failure_idx].iter_mut().for_each(|receipt| { OperationResultSum::transform_result_backtrack(&mut receipt.receipt) }); return (receipts, false); } + (receipts, true) } @@ -889,6 +905,7 @@ fn apply_operation( origination_nonce: &mut OriginationNonce, source_account: &TezlinkImplicitAccount, validated_operation: validate::ValidatedOperation, + next_temporary_id: &mut BigMapId, block_ctx: &BlockCtx, ) -> OperationWithMetadata { let mut internal_operations_receipts = Vec::new(); @@ -897,8 +914,8 @@ fn apply_operation( host, context, gas: &mut gas, - big_map_diff: &mut BTreeMap::new(), - next_temporary_id: BigMapId { value: (-1).into() }, + big_map_diff: BTreeMap::new(), + next_temporary_id, }; let parser = Parser::new(); match &validated_operation.content.operation { @@ -1010,6 +1027,7 @@ mod tests { }; use mir::ast::big_map::BigMapId; use mir::ast::{Address, Entrypoint, IntoMicheline, Micheline, Type, TypedValue}; + use mir::context::TypecheckingCtx; use mir::parser::Parser; use mir::typechecker::typecheck_value; use num_traits::ops::checked::CheckedSub; @@ -4563,25 +4581,33 @@ mod tests { .unwrap_or_else(|_| panic!("Contract source code not found for {file}")) } - fn transfer_big_map<'a>( - script_receiver: &str, - init_receiver: &str, + fn big_map_was_removed(ctx: &mut TcCtx<'_, Host>, id: BigMapId) { + let types = ctx + .big_map_get_type(&id) + .expect("Get big_map type should not panic"); + assert_eq!(types, None, "Temporary big_map was not correctly removed"); + } + + struct BigMapTransfer { + sender: TezlinkOriginatedAccount, + receiver: TezlinkOriginatedAccount, + receipts: Vec, + } + + fn transfer_big_map( + ctx: &mut TcCtx<'_, impl Runtime>, + tz1: &Bootstrap, script_sender: &str, init_sender: &str, - expected_sender_big_map: Option, TypedValue<'a>>>, - expected_receiver_big_map: Option, TypedValue<'a>>>, - ) { - let mut host = MockKernelHost::default(); - let context = context::Context::init_context(); - make_default_ctx!(ctx, &mut host, &context); - let tz1 = bootstrap1(); - + script_receiver: &str, + init_receiver: &str, + ) -> BigMapTransfer { let sender_addr = ContractKt1Hash::from_base58_check(CONTRACT_1) .expect("ContractKt1Hash b58 conversion should have succeeded"); let receiver_addr = ContractKt1Hash::from_base58_check(CONTRACT_2) .expect("ContractKt1Hash b58 conversion should have succeeded"); init_account(ctx.host, &tz1.pkh, 1000); - reveal_account(ctx.host, &tz1); + reveal_account(ctx.host, tz1); let parser = Parser::new(); @@ -4621,7 +4647,7 @@ mod tests { let receipts = validate_and_apply_operation( ctx.host, - &context, + ctx.context, OperationHash(H256::zero()), operation, &block_ctx!(), @@ -4631,10 +4657,45 @@ mod tests { "validate_and_apply_operation should not have failed with a kernel error", ); - for r in receipts { + BigMapTransfer { + sender: sender_contract, + receiver: receiver_contract, + receipts, + } + } + + fn test_transfer_big_map<'a>( + script_receiver: &str, + init_receiver: &str, + script_sender: &str, + init_sender: &str, + expected_sender_big_map: Option, TypedValue<'a>>>, + expected_receiver_big_map: Option, TypedValue<'a>>>, + ) { + let mut host = MockKernelHost::default(); + let context = context::Context::init_context(); + make_default_ctx!(ctx, &mut host, &context); + let tz1 = bootstrap1(); + + let result = transfer_big_map( + &mut ctx, + &tz1, + script_sender, + init_sender, + script_receiver, + init_receiver, + ); + + big_map_was_removed(&mut ctx, (-1).into()); + + for r in result.receipts { assert!(r.receipt.is_applied()) } + let parser = Parser::new(); + let sender_contract = result.sender; + let receiver_contract = result.receiver; + if let Some(expected_sender_big_map) = expected_sender_big_map { let storage = sender_contract.storage(ctx.host).unwrap(); let mich_storage = Micheline::decode_raw(&parser.arena, &storage) @@ -4677,14 +4738,21 @@ mod tests { fn big_map_transfer_receiver_drop_sender_fresh() { let script_receiver = read_script("receiver_drop.tz"); let script_sender = read_script("sender_fresh.tz"); - transfer_big_map(&script_receiver, "Unit", &script_sender, "Unit", None, None); + test_transfer_big_map( + &script_receiver, + "Unit", + &script_sender, + "Unit", + None, + None, + ); } #[test] fn big_map_transfer_receiver_drop_sender_stored() { let script_receiver = read_script("receiver_drop.tz"); let script_sender = read_script("sender_stored.tz"); - transfer_big_map( + test_transfer_big_map( &script_receiver, "Unit", &script_sender, @@ -4701,7 +4769,7 @@ mod tests { fn big_map_transfer_receiver_drop_sender_stored_updated() { let script_receiver = read_script("receiver_drop.tz"); let script_sender = read_script("sender_stored_updated.tz"); - transfer_big_map( + test_transfer_big_map( &script_receiver, "Unit", &script_sender, @@ -4718,7 +4786,7 @@ mod tests { fn big_map_transfer_receiver_store_sender_fresh() { let script_receiver = read_script("receiver_store.tz"); let script_sender = read_script("sender_fresh.tz"); - transfer_big_map( + test_transfer_big_map( &script_receiver, "{}", &script_sender, @@ -4735,7 +4803,7 @@ mod tests { fn big_map_transfer_receiver_store_sender_stored() { let script_receiver = read_script("receiver_store.tz"); let script_sender = read_script("sender_stored.tz"); - transfer_big_map( + test_transfer_big_map( &script_receiver, "{}", &script_sender, @@ -4755,7 +4823,7 @@ mod tests { fn big_map_transfer_receiver_store_sender_stored_updated() { let script_receiver = read_script("receiver_store.tz"); let script_sender = read_script("sender_stored_updated.tz"); - transfer_big_map( + test_transfer_big_map( &script_receiver, "{}", &script_sender, @@ -4778,7 +4846,7 @@ mod tests { fn big_map_transfer_receiver_store_updated_sender_fresh() { let script_receiver = read_script("receiver_store_updated.tz"); let script_sender = read_script("sender_fresh.tz"); - transfer_big_map( + test_transfer_big_map( &script_receiver, "{}", &script_sender, @@ -4795,7 +4863,7 @@ mod tests { fn big_map_transfer_receiver_store_updated_sender_stored() { let script_receiver = read_script("receiver_store_updated.tz"); let script_sender = read_script("sender_stored.tz"); - transfer_big_map( + test_transfer_big_map( &script_receiver, "{}", &script_sender, @@ -4815,7 +4883,7 @@ mod tests { fn big_map_transfer_receiver_store_updated_sender_stored_updated() { let script_receiver = read_script("receiver_store_updated.tz"); let script_sender = read_script("sender_stored_updated.tz"); - transfer_big_map( + test_transfer_big_map( &script_receiver, "{}", &script_sender, @@ -5038,4 +5106,108 @@ mod tests { println!("Created_2 OK"); } + + #[test] + fn verify_temp_big_map_content_is_cleaned() { + let mut host = MockKernelHost::default(); + let context = context::Context::init_context(); + make_default_ctx!(ctx, &mut host, &context); + let tz1 = bootstrap1(); + let parser = Parser::new(); + + // A contract that receives a big_map and checks if + // there's a "d" key in the big_map received + let script_receiver = r#" + parameter (big_map string bytes) ; + storage bool ; + code { CAR; PUSH string "d"; MEM; NIL operation; PAIR } + "#; + let script_sender = read_script("sender_stored.tz"); + + // Transfer the big_map in the storage of the sender ({ Elt "d" 0x }) + // And the receiver checks if the big_map received contains the key "d" + // The storage of the receiver contract should change to 'True' after this + // transfer. + let result = transfer_big_map( + &mut ctx, + &tz1, + &script_sender, + "{Elt \"d\" 0x; }", + script_receiver, + "False", + ); + + let receiver = result.receiver; + + for r in result.receipts { + assert!(r.receipt.is_applied()) + } + + // Checks that the storage of the receiver is true + let storage = receiver + .storage(ctx.host) + .expect("Get storage should succeed"); + let storage = Micheline::decode_raw(&parser.arena, &storage) + .expect("Micheline should be decodable"); + let typed_storage = typecheck_value(&storage, &mut ctx, &Type::Bool) + .expect("Typecheck value should succeed"); + assert_eq!(typed_storage, TypedValue::Bool(true)); + + // We redeploy a second contract sender, that will send a different big_map + // to the receiver contract ({Elt "a" 0x}) + let second_sender_contract = + ContractKt1Hash::from_base58_check(CONTRACT_3).unwrap(); + + let _ = init_contract( + ctx.host, + &second_sender_contract, + &script_sender, + &parser + .parse("{Elt \"a\" 0x; }") + .expect("Failed to parse receiver storage"), + &0.into(), + ); + + let operation = make_transfer_operation( + 0, + 2, + 21040, + 5, + tz1.clone(), + 0.into(), + Contract::Originated(second_sender_contract), + Some(Parameter { + entrypoint: Entrypoint::default(), + value: Micheline::from(receiver.kt1().to_base58_check()).encode(), + }), + ); + + // After that call, the storage of the receiver should be 'False' + // as the big_map passed in argument doesn't have the key "d" + let receipts = validate_and_apply_operation( + ctx.host, + ctx.context, + OperationHash(H256::zero()), + operation, + &block_ctx!(), + false, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); + + for r in receipts { + assert!(r.receipt.is_applied()) + } + + // Checks that the storage of the receiver contract is 'False' + let storage = receiver + .storage(ctx.host) + .expect("Get storage should succeed"); + let storage = Micheline::decode_raw(&parser.arena, &storage) + .expect("Micheline should be decodable"); + let typed_storage = typecheck_value(&storage, &mut ctx, &Type::Bool) + .expect("Typecheck value should succeed"); + assert_eq!(typed_storage, TypedValue::Bool(false)); + } } diff --git a/etherlink/kernel_latest/tezos_execution/src/mir_ctx.rs b/etherlink/kernel_latest/tezos_execution/src/mir_ctx.rs index 48df86b0d219bb74b89394836531601279b28ff2..1a3a81cbc1d28757879ea2bd52c853c0b8c8ecda 100644 --- a/etherlink/kernel_latest/tezos_execution/src/mir_ctx.rs +++ b/etherlink/kernel_latest/tezos_execution/src/mir_ctx.rs @@ -42,8 +42,8 @@ pub struct TcCtx<'operation, Host: Runtime> { pub host: &'operation mut Host, pub context: &'operation Context, pub gas: &'operation mut crate::gas::TezlinkOperationGas, - pub big_map_diff: &'operation mut BTreeMap, - pub next_temporary_id: BigMapId, + pub big_map_diff: BTreeMap, + pub next_temporary_id: &'operation mut BigMapId, } pub struct OperationCtx<'operation> { @@ -334,7 +334,7 @@ impl TcCtx<'_, Host> { fn generate_id(&mut self, temporary: bool) -> Result { if temporary { let new_id = self.next_temporary_id.clone(); - self.next_temporary_id = new_id.succ(); + self.next_temporary_id.incr(); Ok(new_id) } else { let next_id_path = next_id_path(self.context)?; @@ -347,6 +347,39 @@ impl TcCtx<'_, Host> { } } +fn remove_big_map( + host: &mut Host, + context: &Context, + id: &BigMapId, +) -> Result<(), LazyStorageError> { + // Remove the key type of the big_map + let key_type_path = key_type_path(context, id)?; + host.store_delete(&key_type_path)?; + + // Remove the value type of the big_map + let value_type_path = value_type_path(context, id)?; + host.store_delete(&value_type_path)?; + + // Removing the content of the big_map + BigMapKeys::remove_keys_in_storage(host, context, id)?; + + Ok(()) +} + +/// Function to clear temporary big_maps create for an operation +/// +/// This function also reset the next temporary id to minus one +pub fn clear_temporary_big_maps( + host: &mut Host, + context: &Context, + next_temp_id: &mut BigMapId, +) -> Result<(), LazyStorageError> { + while next_temp_id.dec() { + remove_big_map(host, context, next_temp_id)?; + } + Ok(()) +} + /// Function to retrieve the hash of a TypedValue. /// Used to retrieve the path where a value is stored in the /// lazy storage. @@ -608,19 +641,11 @@ impl<'a, Host: Runtime> LazyStorage<'a> for TcCtx<'a, Host> { } fn big_map_remove(&mut self, id: &BigMapId) -> Result<(), LazyStorageError> { - // Remove the key type of the big_map - let key_type_path = key_type_path(self.context, id)?; - self.host.store_delete(&key_type_path)?; - - // Remove the value type of the big_map - let value_type_path = value_type_path(self.context, id)?; - self.host.store_delete(&value_type_path)?; - - // Removing the content of the big_map - BigMapKeys::remove_keys_in_storage(self.host, self.context, id)?; + remove_big_map(self.host, self.context, id)?; // Write in the diff that there was a remove self.big_map_diff_remove(id.value.clone()); + Ok(()) } } @@ -643,8 +668,8 @@ pub mod tests { host: $host, context: $context, gas: &mut gas, - big_map_diff: &mut BTreeMap::new(), - next_temporary_id: BigMapId { value: (-1).into() }, + big_map_diff: BTreeMap::new(), + next_temporary_id: &mut BigMapId { value: (-1).into() }, }; }; }