diff --git a/etherlink/kernel_latest/kernel/src/chains.rs b/etherlink/kernel_latest/kernel/src/chains.rs index 0063d0b14ef545e5e455c8e04411cd4948d2f6d3..ce24fcd3b551c5c47a8e1e1f7e651e812786944d 100644 --- a/etherlink/kernel_latest/kernel/src/chains.rs +++ b/etherlink/kernel_latest/kernel/src/chains.rs @@ -511,8 +511,11 @@ impl ChainConfigTrait for MichelsonChainConfig { // Try to apply the operation with the tezos_execution crate, return a receipt // on whether it failed or not - let receipt = - tezos_execution::apply_operation(host, &context, operation.clone())?; + let receipt = tezos_execution::validate_and_apply_operation( + host, + &context, + operation.clone(), + )?; // Compute the hash of the operation let hash = operation.hash()?; diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 45f1303a79fce79d9799c46e82f08288f71c743f..3bfee9f081f3119dbca595891497e2ac97711b6d 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -45,6 +45,9 @@ pub enum RevealError { PreviouslyRevealedKey(PublicKey), InconsistentHash(PublicKeyHash), InconsistentPublicKey(PublicKeyHash), + UnretrievableManager, + FailedToIncrementCounter, + FailedToWriteManager, } #[derive(thiserror::Error, Debug, PartialEq, Eq, BinWriter, NomReader)] @@ -72,6 +75,28 @@ pub enum TransferError { // TODO: #8003 propagate the error generated by MIR #[error("Mir failed to typecheck the contract with {0}")] MirTypecheckingError(String), + #[error("Failed to allocate destination")] + FailedToAllocateDestination, + #[error("Failed to compute balance update due to new balance overflow")] + FailedToComputeBalanceUpdate, + #[error("Failed to apply balance changes")] + FailedToApplyBalanceChanges, + #[error("Failed to fetch destination account")] + FailedToFetchDestinationAccount, + #[error("Failed to fetch contract code")] + FailedToFetchContractCode, + #[error("Failed to fetch contract storage")] + FailedToFetchContractStorage, + #[error("Failed to fetch destination balance")] + FailedToFetchDestinationBalance, + #[error("Failed to update contract storage")] + FailedToUpdateContractStorage, + #[error("Failed to update destination balance")] + FailedToUpdateDestinationBalance, + #[error("An internal operation failed: {0}")] + FailedToApplyInternalOperation(String), + #[error("Failed to increment counter")] + FailedToIncrementCounter, } impl From for TransferError { diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 73bdf839d1da8c8cf6207314d58ce0f4aab607d2..40deaf51793f9861985457e9f36c5efd51e39c5e 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -4,6 +4,7 @@ use account_storage::TezlinkAccount; use account_storage::{Manager, TezlinkImplicitAccount, TezlinkOriginatedAccount}; +use context::Context; use mir::ast::{AddressHash, Entrypoint, OperationInfo, TransferTokens}; use mir::{ ast::{IntoMicheline, Micheline}, @@ -19,7 +20,7 @@ use tezos_evm_logging::{log, Level::*, Verbosity}; use tezos_evm_runtime::{runtime::Runtime, safe_storage::SafeStorage}; use tezos_smart_rollup::types::{Contract, PublicKey, PublicKeyHash}; use tezos_tezlink::operation::Operation; -use tezos_tezlink::operation_result::{ApplyOperationError, TransferTarget}; +use tezos_tezlink::operation_result::TransferTarget; use tezos_tezlink::{ operation::{ ManagerOperation, OperationContent, Parameter, RevealContent, TransferContent, @@ -31,14 +32,13 @@ use tezos_tezlink::{ }, }; use thiserror::Error; +use validate::ValidationInfo; extern crate alloc; pub mod account_storage; pub mod context; mod validate; -type ExecutionResult = Result, ApplyKernelError>; - #[derive(Error, Debug, PartialEq, Eq)] pub enum ApplyKernelError { #[error("Host failed with a runtime error {0}")] @@ -80,38 +80,42 @@ fn reveal( provided_hash: &PublicKeyHash, account: &mut TezlinkImplicitAccount, public_key: &PublicKey, -) -> ExecutionResult { +) -> Result { log!(host, Debug, "Applying a reveal operation"); - let manager = account.manager(host)?; + let manager = account + .manager(host) + .map_err(|_| RevealError::UnretrievableManager)?; let expected_hash = match manager { - Manager::Revealed(pk) => { - return Ok(Err(RevealError::PreviouslyRevealedKey(pk).into())) - } + Manager::Revealed(pk) => return Err(RevealError::PreviouslyRevealedKey(pk)), Manager::NotRevealed(pkh) => pkh, }; // Ensure that the source of the operation is equal to the retrieved hash. if &expected_hash != provided_hash { - return Ok(Err(RevealError::InconsistentHash(expected_hash).into())); + return Err(RevealError::InconsistentHash(expected_hash)); } // Check the public key let pkh_from_pk = public_key.pk_hash(); if expected_hash != pkh_from_pk { - return Ok(Err(RevealError::InconsistentPublicKey(expected_hash).into())); + return Err(RevealError::InconsistentPublicKey(expected_hash)); } // Set the public key as the manager - account.set_manager_public_key(host, public_key)?; + account + .set_manager_public_key(host, public_key) + .map_err(|_| RevealError::FailedToWriteManager)?; // TODO : Counter Increment should be done after successful validation (see issue #8031) - account.increment_counter(host)?; + account + .increment_counter(host) + .map_err(|_| RevealError::FailedToIncrementCounter)?; log!(host, Debug, "Reveal operation succeed"); - Ok(Ok(RevealSuccess { + Ok(RevealSuccess { consumed_gas: 0_u64.into(), - })) + }) } fn contract_from_address(address: AddressHash) -> Result { @@ -133,57 +137,50 @@ pub fn transfer_tez( host: &mut Host, src_contract: &Contract, src_account: &mut impl TezlinkAccount, + src_balance: &Narith, amount: &Narith, dest_contract: &Contract, dest_account: &mut impl TezlinkAccount, -) -> ExecutionResult { +) -> Result<(TransferSuccess, AppliedBalanceChanges), TransferError> { let (src_update, dest_update) = compute_balance_updates(src_contract, dest_contract, amount) - .map_err(ApplyKernelError::BigIntError)?; + .map_err(|_| TransferError::FailedToComputeBalanceUpdate)?; - // Check source balance - let current_src_balance = src_account.balance(host)?.0; - let new_source_balance = match current_src_balance.checked_sub(&amount.0) { - None => { - log!(host, Debug, "Balance is too low"); - let error = TransferError::BalanceTooLow(BalanceTooLow { - contract: src_contract.clone(), - balance: current_src_balance.into(), - amount: amount.clone(), - }); - return Ok(Err(error.into())); - } - Some(new_source_balance) => new_source_balance, - }; - apply_balance_changes( + let applied_balance_changes = apply_balance_changes( host, + src_contract, src_account, - new_source_balance, + src_balance, dest_account, &amount.0, )?; - Ok(Ok(TransferSuccess { - storage: None, - lazy_storage_diff: None, - balance_updates: vec![src_update, dest_update], - ticket_receipt: vec![], - originated_contracts: vec![], - consumed_gas: 0_u64.into(), - storage_size: 0_u64.into(), - paid_storage_size_diff: 0_u64.into(), - allocated_destination_contract: false, - })) + Ok(( + TransferSuccess { + storage: None, + lazy_storage_diff: None, + balance_updates: vec![src_update, dest_update], + ticket_receipt: vec![], + originated_contracts: vec![], + consumed_gas: 0_u64.into(), + storage_size: 0_u64.into(), + paid_storage_size_diff: 0_u64.into(), + allocated_destination_contract: false, + }, + applied_balance_changes, + )) } +#[allow(clippy::too_many_arguments)] pub fn execute_internal_operations<'a, Host: Runtime>( host: &mut Host, context: &context::Context, internal_operations: impl Iterator>, sender_contract: &Contract, sender_account: &mut TezlinkOriginatedAccount, + sender_balance: &Narith, parser: &'a Parser<'a>, ctx: &mut Ctx<'a>, -) -> ExecutionResult<()> { +) -> Result<(), TransferError> { for internal_op in internal_operations { log!( host, @@ -198,12 +195,14 @@ pub fn execute_internal_operations<'a, Host: Runtime>( amount, }) => { let amount = Narith(amount.try_into().unwrap_or(BigUint::ZERO)); - let dest_contract = contract_from_address(destination_address.hash)?; + let dest_contract = contract_from_address(destination_address.hash) + .map_err(|_| TransferError::FailedToFetchDestinationAccount)?; transfer( host, context, sender_contract, sender_account, + sender_balance, &amount, &dest_contract, destination_address.entrypoint, @@ -213,27 +212,19 @@ pub fn execute_internal_operations<'a, Host: Runtime>( ) } _ => { - return Ok(Err(ApplyOperationError::UnSupportedOperation( + return Err(TransferError::FailedToApplyInternalOperation( "Unsupported internal operation".to_string(), - ) - .into())); + )); } }?; - match internal_receipt { - Ok(receipt) => { - log!( - host, - Debug, - "Internal operation executed successfully: {:?}", - receipt - ); - } - Err(error) => { - return Ok(Err(error)); - } - } + log!( + host, + Debug, + "Internal operation executed successfully: {:?}", + internal_receipt + ); } - Ok(Ok(())) + Ok(()) } /// Handles manager transfer operations for both implicit and originated contracts but with a MIR context. @@ -243,87 +234,95 @@ pub fn transfer<'a, Host: Runtime>( context: &context::Context, src_contract: &Contract, src_account: &mut impl TezlinkAccount, + src_balance: &Narith, amount: &Narith, dest_contract: &Contract, entrypoint: Entrypoint, param: Micheline<'a>, parser: &'a Parser<'a>, ctx: &mut Ctx<'a>, -) -> ExecutionResult { +) -> Result { match dest_contract { Contract::Implicit(pkh) => { if param != Micheline::from(()) || !entrypoint.is_default() { - return Ok(Err(TransferError::NonSmartContractExecutionCall.into())); + return Err(TransferError::NonSmartContractExecutionCall); } // Allocate the implicit account if it doesn't exist let allocated = - TezlinkImplicitAccount::allocate(host, context, dest_contract)?; - match transfer_tez( + TezlinkImplicitAccount::allocate(host, context, dest_contract) + .map_err(|_| TransferError::FailedToAllocateDestination)?; + let mut dest_account = + TezlinkImplicitAccount::from_public_key_hash(context, pkh) + .map_err(|_| TransferError::FailedToFetchDestinationAccount)?; + transfer_tez( host, src_contract, src_account, + src_balance, amount, dest_contract, - &mut TezlinkImplicitAccount::from_public_key_hash(context, pkh)?, - )? { - Ok(success) => Ok(Ok(TransferSuccess { - allocated_destination_contract: allocated, - ..success - })), - Err(error) => Ok(Err(error)), - } + &mut dest_account, + ) + .map(|(success, _applied_balance_changes)| TransferSuccess { + allocated_destination_contract: allocated, + ..success + }) } Contract::Originated(_) => { let mut dest_account = - TezlinkOriginatedAccount::from_contract(context, dest_contract)?; - let receipt = match transfer_tez( + TezlinkOriginatedAccount::from_contract(context, dest_contract) + .map_err(|_| TransferError::FailedToFetchDestinationAccount)?; + let (receipt, applied_balance_changes) = transfer_tez( host, src_contract, src_account, + src_balance, amount, dest_contract, &mut dest_account, - )? { - Ok(success) => success, - Err(error) => return Ok(Err(error)), - }; + )?; ctx.sender = address_from_contract(src_contract.clone()); - let code = dest_account.code(host)?; - let storage = dest_account.storage(host)?; - let result = - execute_smart_contract(code, storage, entrypoint, param, parser, ctx); - match result { - Ok((internal_operations, new_storage)) => { - dest_account.set_storage(host, &new_storage)?; - let _internal_receipt = execute_internal_operations( - host, - context, - internal_operations, - dest_contract, - &mut dest_account, - parser, - ctx, - )?; - Ok(Ok(TransferSuccess { - storage: Some(new_storage), - ..receipt - })) - } - Err(err) => Ok(Err(err.into())), - } + let code = dest_account + .code(host) + .map_err(|_| TransferError::FailedToFetchContractCode)?; + let storage = dest_account + .storage(host) + .map_err(|_| TransferError::FailedToFetchContractStorage)?; + let (internal_operations, new_storage) = + execute_smart_contract(code, storage, entrypoint, param, parser, ctx)?; + dest_account + .set_storage(host, &new_storage) + .map_err(|_| TransferError::FailedToUpdateContractStorage)?; + let _internal_receipt = execute_internal_operations( + host, + context, + internal_operations, + dest_contract, + &mut dest_account, + &applied_balance_changes.new_dest_balance, + parser, + ctx, + ); + Ok(TransferSuccess { + storage: Some(new_storage), + ..receipt + }) } } } // Handles manager transfer operations. +#[allow(clippy::too_many_arguments)] pub fn transfer_external( host: &mut Host, context: &context::Context, src: &PublicKeyHash, + src_account: &mut TezlinkImplicitAccount, + src_balance: &Narith, amount: &Narith, dest: &Contract, parameter: Option, -) -> ExecutionResult { +) -> Result { log!( host, Debug, @@ -335,15 +334,11 @@ pub fn transfer_external( ); let src_contract = Contract::Implicit(src.clone()); - let mut src_account = TezlinkImplicitAccount::from_public_key_hash(context, src)?; let parser = Parser::new(); let (entrypoint, value) = match parameter { Some(param) => ( param.entrypoint, - match Micheline::decode_raw(&parser.arena, ¶m.value) { - Ok(value) => value, - Err(err) => return Ok(Err(TransferError::from(err).into())), - }, + Micheline::decode_raw(&parser.arena, ¶m.value)?, ), None => (Entrypoint::default(), Micheline::from(())), }; @@ -354,7 +349,8 @@ pub fn transfer_external( host, context, &src_contract, - &mut src_account, + src_account, + src_balance, amount, dest, entrypoint, @@ -362,8 +358,9 @@ pub fn transfer_external( &parser, &mut ctx, ); - // TODO : Counter Increment should be done after successful validation (see issue #8031) - src_account.increment_counter(host)?; + src_account + .increment_counter(host) + .map_err(|_| TransferError::FailedToIncrementCounter)?; success } @@ -420,19 +417,47 @@ fn compute_balance_updates( Ok((src_update, dest_update)) } +pub struct AppliedBalanceChanges { + #[allow(dead_code)] + new_src_balance: Narith, + new_dest_balance: Narith, +} + /// Applies balance changes by updating both source and destination accounts. fn apply_balance_changes( host: &mut impl Runtime, + src_contract: &Contract, src_account: &mut impl TezlinkAccount, - new_src_balance: num_bigint::BigUint, + src_balance: &Narith, dest_account: &mut impl TezlinkAccount, amount: &num_bigint::BigUint, -) -> Result<(), ApplyKernelError> { - src_account.set_balance(host, &new_src_balance.into())?; - let dest_balance = dest_account.balance(host)?.0; - let new_dest_balance = &dest_balance + amount; - dest_account.set_balance(host, &new_dest_balance.into())?; - Ok(()) +) -> Result { + let new_src_balance = match src_balance.0.checked_sub(amount) { + None => { + log!(host, Debug, "Balance is too low"); + return Err(TransferError::BalanceTooLow(BalanceTooLow { + contract: src_contract.clone(), + balance: src_balance.clone(), + amount: amount.into(), + })); + } + Some(new_source_balance) => new_source_balance.into(), + }; + src_account + .set_balance(host, &new_src_balance) + .map_err(|_| TransferError::FailedToApplyBalanceChanges)?; + let dest_balance = dest_account + .balance(host) + .map_err(|_| TransferError::FailedToFetchDestinationBalance)? + .0; + let new_dest_balance = (&dest_balance + amount).into(); + dest_account + .set_balance(host, &new_dest_balance) + .map_err(|_| TransferError::FailedToUpdateDestinationBalance)?; + Ok(AppliedBalanceChanges { + new_src_balance, + new_dest_balance, + }) } /// Executes the entrypoint logic of an originated smart contract and returns the new storage. @@ -466,7 +491,7 @@ fn execute_smart_contract<'a>( Ok((internal_operations, new_storage)) } -pub fn apply_operation( +pub fn validate_and_apply_operation( host: &mut Host, context: &context::Context, operation: Operation, @@ -490,85 +515,95 @@ pub fn apply_operation( safe_host.start()?; - let mut account = TezlinkImplicitAccount::from_public_key_hash(context, source)?; - log!(safe_host, Debug, "Verifying that the operation is valid"); - let validity_result = validate::is_valid_tezlink_operation( - &safe_host, - &account, - &operation.branch, - operation.content.into(), - operation.signature, - )?; - - let new_balance = match validity_result { - Ok(new_balance) => new_balance, - Err(validity_err) => { - log!( - safe_host, - Debug, - "Operation is invalid, exiting apply_operation" - ); - // TODO: Don't force the receipt to a reveal receipt - let receipt = produce_operation_result::( - vec![], - Err(OperationError::Validation(validity_err)), - ); - safe_host.revert()?; - return Ok(OperationResultSum::Reveal(receipt)); - } - }; + let mut validation_info = + match validate::validate_operation(&safe_host, context, &operation)? { + Ok(validation_info) => validation_info, + Err(validity_err) => { + log!( + safe_host, + Debug, + "Operation is invalid, exiting apply_operation" + ); + // TODO: Don't force the receipt to a reveal receipt + let receipt = produce_operation_result::( + vec![], + Err(OperationError::Validation(validity_err)), + ); + safe_host.revert()?; + return Ok(OperationResultSum::Reveal(receipt)); + } + }; log!(safe_host, Debug, "Operation is valid"); log!(safe_host, Debug, "Updates balance to pay fees"); - account.set_balance(&mut safe_host, &new_balance)?; - - let (src_delta, block_fees) = - compute_fees_balance_updates(source, &manager_operation.fee) - .map_err(ApplyKernelError::BigIntError)?; + validation_info + .source_account + .set_balance(&mut safe_host, &validation_info.new_source_balance)?; safe_host.promote()?; safe_host.promote_trace()?; safe_host.start()?; - let receipt = match manager_operation.operation { - OperationContent::Reveal(RevealContent { pk, proof: _ }) => { - let reveal_result = reveal(&mut safe_host, source, &mut account, &pk)?; - let manager_result = - produce_operation_result(vec![src_delta, block_fees], reveal_result); + let receipt = + apply_operation(&mut safe_host, context, &manager_operation, validation_info); + + if is_applied(&receipt) { + safe_host.promote()?; + safe_host.promote_trace()?; + } else { + safe_host.revert()?; + } + + Ok(receipt) +} + +fn apply_operation( + host: &mut Host, + context: &Context, + operation: &ManagerOperation, + validation_info: ValidationInfo, +) -> OperationResultSum { + let ValidationInfo { + new_source_balance, + mut source_account, + balance_updates: validation_balance_updates, + } = validation_info; + match operation.operation { + OperationContent::Reveal(RevealContent { ref pk, proof: _ }) => { + let reveal_result = reveal(host, &operation.source, &mut source_account, pk); + let manager_result = produce_operation_result( + validation_balance_updates, + reveal_result.map_err(|e| e.into()), + ); OperationResultSum::Reveal(manager_result) } OperationContent::Transfer(TransferContent { - amount, - destination, - parameters, + ref amount, + ref destination, + ref parameters, }) => { let transfer_result = transfer_external( - &mut safe_host, + host, context, - source, - &amount, - &destination, - parameters, - )?; + &operation.source, + &mut source_account, + &new_source_balance, + amount, + destination, + parameters.clone(), + ); let manager_result = produce_operation_result( - vec![src_delta, block_fees], - transfer_result.map(TransferTarget::ToContrat), + validation_balance_updates, + transfer_result + .map(TransferTarget::ToContrat) + .map_err(|e| e.into()), ); OperationResultSum::Transfer(manager_result) } - }; - - if is_applied(&receipt) { - safe_host.promote()?; - safe_host.promote_trace()?; - } else { - safe_host.revert()?; } - - Ok(receipt) } #[cfg(test)] @@ -596,7 +631,7 @@ mod tests { use crate::{ account_storage::{Manager, TezlinkAccount}, - apply_operation, context, OperationError, + context, validate_and_apply_operation, OperationError, }; #[derive(Clone)] @@ -814,9 +849,14 @@ mod tests { let operation = make_reveal_operation(15, 1, 4, 5, source); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Reveal(OperationResult { balance_updates: vec![], @@ -844,9 +884,14 @@ mod tests { // Fees are too high for source's balance let operation = make_reveal_operation(100, 1, 4, 5, source); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Reveal(OperationResult { balance_updates: vec![], @@ -874,9 +919,14 @@ mod tests { // Counter is incoherent for source's counter let operation = make_reveal_operation(15, 15, 4, 5, source); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Reveal(OperationResult { balance_updates: vec![], @@ -917,9 +967,14 @@ mod tests { // Applying the operation let operation = make_reveal_operation(15, 1, 4, 5, source.clone()); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); // Reveal operation should fail let expected_receipt = OperationResultSum::Reveal(OperationResult { @@ -967,9 +1022,14 @@ mod tests { let operation = make_reveal_operation(15, 1, 4, 5, source.clone()); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Reveal(OperationResult { balance_updates: vec![ @@ -1019,9 +1079,14 @@ mod tests { let operation = make_reveal_operation(15, 1, 4, 5, source.clone()); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Reveal(OperationResult { balance_updates: vec![], @@ -1056,9 +1121,14 @@ mod tests { let operation = make_reveal_operation(15, 1, 4, 5, source.clone()); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Reveal(OperationResult { balance_updates: vec![ @@ -1114,9 +1184,14 @@ mod tests { None, ); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Transfer(OperationResult { balance_updates: vec![ @@ -1178,9 +1253,14 @@ mod tests { None, ); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Transfer(OperationResult { balance_updates: vec![ @@ -1257,9 +1337,14 @@ mod tests { None, ); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Transfer(OperationResult { balance_updates: vec![ @@ -1354,8 +1439,9 @@ mod tests { value: Micheline::from(requested_amount as i128).encode(), }), ); - let res = apply_operation(&mut host, &context, operation) - .expect("apply_operation should not have failed with a kernel error"); + let res = validate_and_apply_operation(&mut host, &context, operation).expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); println!("Result: {:?}", res); assert_eq!( faucet.balance(&host).unwrap(), @@ -1399,9 +1485,14 @@ mod tests { }), ); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let storage = Some(storage_value); @@ -1490,9 +1581,14 @@ mod tests { }), ); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Transfer(OperationResult { balance_updates: vec![ @@ -1555,9 +1651,14 @@ mod tests { }), ); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Transfer(OperationResult { balance_updates: vec![ @@ -1611,9 +1712,14 @@ mod tests { }), ); - let receipt = - apply_operation(&mut host, &context::Context::init_context(), operation) - .expect("apply_operation should not have failed with a kernel error"); + let receipt = validate_and_apply_operation( + &mut host, + &context::Context::init_context(), + operation, + ) + .expect( + "validate_and_apply_operation should not have failed with a kernel error", + ); let expected_receipt = OperationResultSum::Transfer(OperationResult { balance_updates: vec![ diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 6681cc00585f4de2826078eb39c016cf5e2289d0..646997c2d4eb2c437d94e976f2199e9e4ffaf744 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -2,20 +2,21 @@ // // SPDX-License-Identifier: MIT -use tezos_crypto_rs::hash::UnknownSignature; use tezos_data_encoding::types::Narith; use tezos_evm_logging::log; use tezos_evm_runtime::runtime::Runtime; use tezos_smart_rollup::types::PublicKey; -use tezos_tezlink::enc_wrappers::BlockHash; use tezos_tezlink::{ - operation::{verify_signature, ManagerOperation, OperationContent, RevealContent}, + operation::{ + verify_signature, ManagerOperation, Operation, OperationContent, RevealContent, + }, operation_result::{CounterError, ValidityError}, }; use crate::{ account_storage::{Manager, TezlinkImplicitAccount}, - ApplyKernelError, + context::Context, + ApplyKernelError, BalanceUpdate, }; impl TezlinkImplicitAccount { @@ -115,13 +116,22 @@ fn check_storage_limit( } } -pub fn is_valid_tezlink_operation( +pub struct ValidationInfo { + pub new_source_balance: Narith, + pub source_account: TezlinkImplicitAccount, + pub balance_updates: Vec, +} + +pub fn validate_operation( host: &Host, - account: &TezlinkImplicitAccount, - branch: &BlockHash, - operation: ManagerOperation, - signature: UnknownSignature, -) -> Result, ApplyKernelError> { + context: &Context, + operation: &Operation, +) -> Result, ApplyKernelError> { + let branch = &operation.branch; + let content: ManagerOperation = operation.content.clone().into(); + let signature = &operation.signature; + let account = TezlinkImplicitAccount::from_public_key_hash(context, &content.source)?; + // Account must exist in the durable storage if !account.allocated(host)? { log!( @@ -132,12 +142,12 @@ pub fn is_valid_tezlink_operation( return Ok(Err(ValidityError::EmptyImplicitContract)); } - if let Err(err) = account.check_counter_increment(host, &operation.counter)? { + if let Err(err) = account.check_counter_increment(host, &content.counter)? { // Counter verification failed, return the error return Ok(Err(err)); } - let pk = match get_revealed_key(host, account, &operation.operation)? { + let pk = match get_revealed_key(host, &account, &content.operation)? { Err(err) => { // Retrieve public key failed, return the error return Ok(Err(err)); @@ -146,7 +156,7 @@ pub fn is_valid_tezlink_operation( }; // TODO: hard gas limit per operation is a Tezos constant, for now we took the one from ghostnet - if let Err(err) = check_gas_limit(&1040000_u64.into(), &operation.gas_limit) { + if let Err(err) = check_gas_limit(&1040000_u64.into(), &content.gas_limit) { // Gas limit verification failed, return the error log!( host, @@ -157,7 +167,7 @@ pub fn is_valid_tezlink_operation( } // TODO: hard storage limit per operation is a Tezos constant, for now we took the one from ghostnet - if let Err(err) = check_storage_limit(&60000_u64.into(), &operation.storage_limit) { + if let Err(err) = check_storage_limit(&60000_u64.into(), &content.storage_limit) { // Storage limit verification failed, return the error log!( host, @@ -168,7 +178,7 @@ pub fn is_valid_tezlink_operation( } // The manager account must be solvent to pay the announced fees. - let new_balance = match account.simulate_spending(host, &operation.fee)? { + let new_balance = match account.simulate_spending(host, &content.fee)? { Some(new_balance) => new_balance, None => { log!( @@ -176,20 +186,28 @@ pub fn is_valid_tezlink_operation( tezos_evm_logging::Level::Debug, "Invalid operation: Can't pay the fees" ); - return Ok(Err(ValidityError::CantPayFees(operation.fee))); + return Ok(Err(ValidityError::CantPayFees(content.fee))); } }; - let verify = verify_signature(&pk, branch, &operation.into(), signature)?; + let verify = verify_signature(&pk, branch, &operation.content, signature.clone())?; - if verify { - Ok(Ok(new_balance)) - } else { + if !verify { log!( host, tezos_evm_logging::Level::Debug, "Invalid operation: Signature is invalid" ); - Ok(Err(ValidityError::InvalidSignature)) + return Ok(Err(ValidityError::InvalidSignature)); } + + let (src_delta, block_fees) = + crate::compute_fees_balance_updates(&content.source, &content.fee) + .map_err(ApplyKernelError::BigIntError)?; + + Ok(Ok(ValidationInfo { + new_source_balance: new_balance, + source_account: account, + balance_updates: vec![src_delta, block_fees], + })) }