From 0e4d8336832cb6bec607109f052c778de3cbcf7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 11:02:13 +0200 Subject: [PATCH 01/17] Tezlink/Kernel/Transfer: rename apply_operation into validate_and_apply_operation Because it does both validation and application. --- etherlink/kernel_latest/kernel/src/chains.rs | 7 +- .../kernel_latest/tezos_execution/src/lib.rs | 163 +++++++++++++----- 2 files changed, 122 insertions(+), 48 deletions(-) diff --git a/etherlink/kernel_latest/kernel/src/chains.rs b/etherlink/kernel_latest/kernel/src/chains.rs index 0063d0b14ef5..ce24fcd3b551 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_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 73bdf839d1da..4c0f5974927c 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -466,7 +466,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, @@ -596,7 +596,7 @@ mod tests { use crate::{ account_storage::{Manager, TezlinkAccount}, - apply_operation, context, OperationError, + context, validate_and_apply_operation, OperationError, }; #[derive(Clone)] @@ -814,9 +814,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 +849,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 +884,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 +932,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 +987,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 +1044,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 +1086,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 +1149,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 +1218,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 +1302,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 +1404,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 +1450,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 +1546,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 +1616,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 +1677,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![ -- GitLab From 900a7927d2f76f2ab151d5338a36c2c8510c635d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 11:09:59 +0200 Subject: [PATCH 02/17] Tezlink/Kernel/Transfer: introduce ValidationInfo --- etherlink/kernel_latest/tezos_execution/src/lib.rs | 10 ++++------ .../kernel_latest/tezos_execution/src/validate.rs | 10 ++++++++-- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 4c0f5974927c..3cd8b45f0999 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -494,16 +494,14 @@ pub fn validate_and_apply_operation( log!(safe_host, Debug, "Verifying that the operation is valid"); - let validity_result = validate::is_valid_tezlink_operation( + let validation_info = match 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, + )? { + Ok(validation_info) => validation_info, Err(validity_err) => { log!( safe_host, @@ -523,7 +521,7 @@ pub fn validate_and_apply_operation( log!(safe_host, Debug, "Operation is valid"); log!(safe_host, Debug, "Updates balance to pay fees"); - account.set_balance(&mut safe_host, &new_balance)?; + account.set_balance(&mut safe_host, &validation_info.new_source_balance)?; let (src_delta, block_fees) = compute_fees_balance_updates(source, &manager_operation.fee) diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 6681cc00585f..d3f9e55be78b 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -115,13 +115,17 @@ fn check_storage_limit( } } +pub struct ValidationInfo { + pub new_source_balance: Narith, +} + pub fn is_valid_tezlink_operation( host: &Host, account: &TezlinkImplicitAccount, branch: &BlockHash, operation: ManagerOperation, signature: UnknownSignature, -) -> Result, ApplyKernelError> { +) -> Result, ApplyKernelError> { // Account must exist in the durable storage if !account.allocated(host)? { log!( @@ -183,7 +187,9 @@ pub fn is_valid_tezlink_operation( let verify = verify_signature(&pk, branch, &operation.into(), signature)?; if verify { - Ok(Ok(new_balance)) + Ok(Ok(ValidationInfo { + new_source_balance: new_balance, + })) } else { log!( host, -- GitLab From c753241a8a1d845d7f98d112e8950b163a4b957a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 11:25:49 +0200 Subject: [PATCH 03/17] Tezlink/Kernel/Transfer: rename is_valid_tezlink_operation into validate_operation "is_" prefix suggest it returns a boolean and "tezlink" is implicit everywhere in this crate. --- etherlink/kernel_latest/tezos_execution/src/lib.rs | 2 +- etherlink/kernel_latest/tezos_execution/src/validate.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 3cd8b45f0999..f3c59f74f051 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -494,7 +494,7 @@ pub fn validate_and_apply_operation( log!(safe_host, Debug, "Verifying that the operation is valid"); - let validation_info = match validate::is_valid_tezlink_operation( + let validation_info = match validate::validate_operation( &safe_host, &account, &operation.branch, diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index d3f9e55be78b..e86d1c1b44b5 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -119,7 +119,7 @@ pub struct ValidationInfo { pub new_source_balance: Narith, } -pub fn is_valid_tezlink_operation( +pub fn validate_operation( host: &Host, account: &TezlinkImplicitAccount, branch: &BlockHash, -- GitLab From de954ef83909b49be567b2413ea7b36207fb7b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 11:22:01 +0200 Subject: [PATCH 04/17] Tezlink/Kernel/Transfer: validate_operation initializes and returns the source account This is to avoid initializing the source account both in validation and application. --- .../kernel_latest/tezos_execution/src/lib.rs | 17 +++++++++++------ .../tezos_execution/src/validate.rs | 10 ++++++++-- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index f3c59f74f051..950c7188e09c 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -490,13 +490,11 @@ pub fn validate_and_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 validation_info = match validate::validate_operation( + let mut validation_info = match validate::validate_operation( &safe_host, - &account, + context, &operation.branch, operation.content.into(), operation.signature, @@ -521,7 +519,9 @@ pub fn validate_and_apply_operation( log!(safe_host, Debug, "Operation is valid"); log!(safe_host, Debug, "Updates balance to pay fees"); - account.set_balance(&mut safe_host, &validation_info.new_source_balance)?; + validation_info + .source_account + .set_balance(&mut safe_host, &validation_info.new_source_balance)?; let (src_delta, block_fees) = compute_fees_balance_updates(source, &manager_operation.fee) @@ -533,7 +533,12 @@ pub fn validate_and_apply_operation( let receipt = match manager_operation.operation { OperationContent::Reveal(RevealContent { pk, proof: _ }) => { - let reveal_result = reveal(&mut safe_host, source, &mut account, &pk)?; + let reveal_result = reveal( + &mut safe_host, + source, + &mut validation_info.source_account, + &pk, + )?; let manager_result = produce_operation_result(vec![src_delta, block_fees], reveal_result); OperationResultSum::Reveal(manager_result) diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index e86d1c1b44b5..9e6fd991d42e 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -15,6 +15,7 @@ use tezos_tezlink::{ use crate::{ account_storage::{Manager, TezlinkImplicitAccount}, + context::Context, ApplyKernelError, }; @@ -117,15 +118,19 @@ fn check_storage_limit( pub struct ValidationInfo { pub new_source_balance: Narith, + pub source_account: TezlinkImplicitAccount, } pub fn validate_operation( host: &Host, - account: &TezlinkImplicitAccount, + context: &Context, branch: &BlockHash, operation: ManagerOperation, signature: UnknownSignature, ) -> Result, ApplyKernelError> { + let account = + TezlinkImplicitAccount::from_public_key_hash(context, &operation.source)?; + // Account must exist in the durable storage if !account.allocated(host)? { log!( @@ -141,7 +146,7 @@ pub fn validate_operation( return Ok(Err(err)); } - let pk = match get_revealed_key(host, account, &operation.operation)? { + let pk = match get_revealed_key(host, &account, &operation.operation)? { Err(err) => { // Retrieve public key failed, return the error return Ok(Err(err)); @@ -189,6 +194,7 @@ pub fn validate_operation( if verify { Ok(Ok(ValidationInfo { new_source_balance: new_balance, + source_account: account, })) } else { log!( -- GitLab From 9f5fd1fe0798366cdf4d02777bbcced80c573311 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 11:37:45 +0200 Subject: [PATCH 05/17] Tezlink/Kernel/Transfer: pass full operation to validate_operation Unfortunately introduces a pair of `clone`. --- .../kernel_latest/tezos_execution/src/lib.rs | 41 ++++++++----------- .../tezos_execution/src/validate.rs | 30 +++++++------- 2 files changed, 33 insertions(+), 38 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 950c7188e09c..f125d283d772 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -492,29 +492,24 @@ pub fn validate_and_apply_operation( log!(safe_host, Debug, "Verifying that the operation is valid"); - let mut validation_info = match validate::validate_operation( - &safe_host, - context, - &operation.branch, - operation.content.into(), - operation.signature, - )? { - 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)); - } - }; + 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"); diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 9e6fd991d42e..56dcd2db4a34 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -2,14 +2,14 @@ // // 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}, }; @@ -124,12 +124,12 @@ pub struct ValidationInfo { pub fn validate_operation( host: &Host, context: &Context, - branch: &BlockHash, - operation: ManagerOperation, - signature: UnknownSignature, + operation: &Operation, ) -> Result, ApplyKernelError> { - let account = - TezlinkImplicitAccount::from_public_key_hash(context, &operation.source)?; + 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)? { @@ -141,12 +141,12 @@ pub fn validate_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)); @@ -155,7 +155,7 @@ pub fn validate_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, @@ -166,7 +166,7 @@ pub fn validate_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, @@ -177,7 +177,7 @@ pub fn validate_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!( @@ -185,11 +185,11 @@ pub fn validate_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, &content.into(), signature.clone())?; if verify { Ok(Ok(ValidationInfo { -- GitLab From c098f0809af4fb33176e033c7c4753b4fcc0901f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 11:54:04 +0200 Subject: [PATCH 06/17] Tezlink/Kernel/Transfer: validate_operation computes balance updates --- .../kernel_latest/tezos_execution/src/lib.rs | 8 ++----- .../tezos_execution/src/validate.rs | 24 ++++++++++++------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index f125d283d772..d3ec30fa3711 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -518,10 +518,6 @@ pub fn validate_and_apply_operation( .source_account .set_balance(&mut safe_host, &validation_info.new_source_balance)?; - let (src_delta, block_fees) = - compute_fees_balance_updates(source, &manager_operation.fee) - .map_err(ApplyKernelError::BigIntError)?; - safe_host.promote()?; safe_host.promote_trace()?; safe_host.start()?; @@ -535,7 +531,7 @@ pub fn validate_and_apply_operation( &pk, )?; let manager_result = - produce_operation_result(vec![src_delta, block_fees], reveal_result); + produce_operation_result(validation_info.balance_updates, reveal_result); OperationResultSum::Reveal(manager_result) } OperationContent::Transfer(TransferContent { @@ -552,7 +548,7 @@ pub fn validate_and_apply_operation( parameters, )?; let manager_result = produce_operation_result( - vec![src_delta, block_fees], + validation_info.balance_updates, transfer_result.map(TransferTarget::ToContrat), ); OperationResultSum::Transfer(manager_result) diff --git a/etherlink/kernel_latest/tezos_execution/src/validate.rs b/etherlink/kernel_latest/tezos_execution/src/validate.rs index 56dcd2db4a34..646997c2d4eb 100644 --- a/etherlink/kernel_latest/tezos_execution/src/validate.rs +++ b/etherlink/kernel_latest/tezos_execution/src/validate.rs @@ -16,7 +16,7 @@ use tezos_tezlink::{ use crate::{ account_storage::{Manager, TezlinkImplicitAccount}, context::Context, - ApplyKernelError, + ApplyKernelError, BalanceUpdate, }; impl TezlinkImplicitAccount { @@ -119,6 +119,7 @@ fn check_storage_limit( pub struct ValidationInfo { pub new_source_balance: Narith, pub source_account: TezlinkImplicitAccount, + pub balance_updates: Vec, } pub fn validate_operation( @@ -189,19 +190,24 @@ pub fn validate_operation( } }; - let verify = verify_signature(&pk, branch, &content.into(), signature.clone())?; + let verify = verify_signature(&pk, branch, &operation.content, signature.clone())?; - if verify { - Ok(Ok(ValidationInfo { - new_source_balance: new_balance, - source_account: account, - })) - } 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], + })) } -- GitLab From 991d3be81d94e16d2eed2163c5fc14809960f7ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 12:16:04 +0200 Subject: [PATCH 07/17] Tezlink/Kernel/Transfer: extract apply_operation --- .../kernel_latest/tezos_execution/src/lib.rs | 70 +++++++++++-------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index d3ec30fa3711..3b506237c612 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}, @@ -31,6 +32,7 @@ use tezos_tezlink::{ }, }; use thiserror::Error; +use validate::ValidationInfo; extern crate alloc; pub mod account_storage; @@ -522,47 +524,57 @@ pub fn validate_and_apply_operation( 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 validation_info.source_account, - &pk, - )?; + 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, +) -> Result { + 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_info.balance_updates, reveal_result); - OperationResultSum::Reveal(manager_result) + produce_operation_result(validation_balance_updates, reveal_result); + Ok(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, + amount, + destination, + parameters.clone(), )?; let manager_result = produce_operation_result( - validation_info.balance_updates, + validation_balance_updates, transfer_result.map(TransferTarget::ToContrat), ); - OperationResultSum::Transfer(manager_result) + Ok(OperationResultSum::Transfer(manager_result)) } - }; - - if is_applied(&receipt) { - safe_host.promote()?; - safe_host.promote_trace()?; - } else { - safe_host.revert()?; } - - Ok(receipt) } #[cfg(test)] -- GitLab From aea01577b1c81a6f063b5cb0f53d025bd33fbe1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 12:37:21 +0200 Subject: [PATCH 08/17] Tezlink/Kernel/Transfer: remove nested results in reveal --- .../tezos/src/operation_result.rs | 3 ++ .../kernel_latest/tezos_execution/src/lib.rs | 34 +++++++++++-------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 45f1303a79fc..d7f8bcffbf95 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)] diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 3b506237c612..af217ccf57eb 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -82,38 +82,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 { @@ -550,9 +554,11 @@ fn apply_operation( } = 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); + 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()), + ); Ok(OperationResultSum::Reveal(manager_result)) } OperationContent::Transfer(TransferContent { -- GitLab From 21ab4e1feecbb96e9c2d1c7af47dc9bfdaf49831 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 14:36:17 +0200 Subject: [PATCH 09/17] Tezlink/Kernel/Transfer: propagate sender balance in transfer functions --- .../kernel_latest/tezos_execution/src/lib.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index af217ccf57eb..5c1b7c898023 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -139,6 +139,7 @@ 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, @@ -148,7 +149,7 @@ pub fn transfer_tez( .map_err(ApplyKernelError::BigIntError)?; // Check source balance - let current_src_balance = src_account.balance(host)?.0; + let current_src_balance = &src_balance.0; let new_source_balance = match current_src_balance.checked_sub(&amount.0) { None => { log!(host, Debug, "Balance is too low"); @@ -181,12 +182,14 @@ pub fn transfer_tez( })) } +#[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<()> { @@ -210,6 +213,7 @@ pub fn execute_internal_operations<'a, Host: Runtime>( context, sender_contract, sender_account, + sender_balance, &amount, &dest_contract, destination_address.entrypoint, @@ -249,6 +253,7 @@ 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, @@ -268,6 +273,7 @@ pub fn transfer<'a, Host: Runtime>( host, src_contract, src_account, + src_balance, amount, dest_contract, &mut TezlinkImplicitAccount::from_public_key_hash(context, pkh)?, @@ -286,6 +292,7 @@ pub fn transfer<'a, Host: Runtime>( host, src_contract, src_account, + src_balance, amount, dest_contract, &mut dest_account, @@ -298,6 +305,8 @@ pub fn transfer<'a, Host: Runtime>( let storage = dest_account.storage(host)?; let result = execute_smart_contract(code, storage, entrypoint, param, parser, ctx); + // TODO: this has already been computed by transfer_tez, avoid refetching it from storage + let dest_balance = dest_account.balance(host)?; match result { Ok((internal_operations, new_storage)) => { dest_account.set_storage(host, &new_storage)?; @@ -307,6 +316,7 @@ pub fn transfer<'a, Host: Runtime>( internal_operations, dest_contract, &mut dest_account, + &dest_balance, parser, ctx, )?; @@ -326,6 +336,7 @@ pub fn transfer_external( host: &mut Host, context: &context::Context, src: &PublicKeyHash, + src_balance: &Narith, amount: &Narith, dest: &Contract, parameter: Option, @@ -361,6 +372,7 @@ pub fn transfer_external( context, &src_contract, &mut src_account, + src_balance, amount, dest, entrypoint, @@ -548,7 +560,7 @@ fn apply_operation( validation_info: ValidationInfo, ) -> Result { let ValidationInfo { - new_source_balance: _, + new_source_balance, mut source_account, balance_updates: validation_balance_updates, } = validation_info; @@ -570,6 +582,7 @@ fn apply_operation( host, context, &operation.source, + &new_source_balance, amount, destination, parameters.clone(), -- GitLab From eb2c24bac8f445f249091ea981d0cf85d0a49e77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 13:06:51 +0200 Subject: [PATCH 10/17] Tezlink/Kernel/Transfer: remove nested results in transfer_tez --- .../tezos/src/operation_result.rs | 6 +++ .../kernel_latest/tezos_execution/src/lib.rs | 46 +++++++++---------- 2 files changed, 29 insertions(+), 23 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index d7f8bcffbf95..e8e2633e687e 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -75,6 +75,12 @@ 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, } 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 5c1b7c898023..03dc9d6e0d32 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -143,22 +143,21 @@ pub fn transfer_tez( amount: &Narith, dest_contract: &Contract, dest_account: &mut impl TezlinkAccount, -) -> ExecutionResult { +) -> Result { 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_balance.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 { + return Err(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, }; @@ -168,8 +167,9 @@ pub fn transfer_tez( new_source_balance, dest_account, &amount.0, - )?; - Ok(Ok(TransferSuccess { + ) + .map_err(|_| TransferError::FailedToApplyBalanceChanges)?; + Ok(TransferSuccess { storage: None, lazy_storage_diff: None, balance_updates: vec![src_update, dest_update], @@ -179,7 +179,7 @@ pub fn transfer_tez( storage_size: 0_u64.into(), paid_storage_size_diff: 0_u64.into(), allocated_destination_contract: false, - })) + }) } #[allow(clippy::too_many_arguments)] @@ -269,7 +269,7 @@ pub fn transfer<'a, Host: Runtime>( // Allocate the implicit account if it doesn't exist let allocated = TezlinkImplicitAccount::allocate(host, context, dest_contract)?; - match transfer_tez( + let success = transfer_tez( host, src_contract, src_account, @@ -277,13 +277,13 @@ pub fn transfer<'a, Host: Runtime>( 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)), - } + ) + .map_err(|e| e.into()) + .map(|success| TransferSuccess { + allocated_destination_contract: allocated, + ..success + }); + Ok(success) } Contract::Originated(_) => { let mut dest_account = @@ -296,18 +296,18 @@ pub fn transfer<'a, Host: Runtime>( amount, dest_contract, &mut dest_account, - )? { - Ok(success) => success, - Err(error) => return Ok(Err(error)), + ) { + Ok(receipt) => receipt, + Err(e) => { + return Ok(Err(e.into())); + } }; 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); // TODO: this has already been computed by transfer_tez, avoid refetching it from storage let dest_balance = dest_account.balance(host)?; - match result { + match execute_smart_contract(code, storage, entrypoint, param, parser, ctx) { Ok((internal_operations, new_storage)) => { dest_account.set_storage(host, &new_storage)?; let _internal_receipt = execute_internal_operations( @@ -319,7 +319,7 @@ pub fn transfer<'a, Host: Runtime>( &dest_balance, parser, ctx, - )?; + ); Ok(Ok(TransferSuccess { storage: Some(new_storage), ..receipt -- GitLab From e6fc200b6179b2eb789ee2dfcb31a744d321e4d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 13:20:27 +0200 Subject: [PATCH 11/17] Tezlink/Kernel/Transfer: remove nested results in transfer --- .../tezos/src/operation_result.rs | 10 +++ .../kernel_latest/tezos_execution/src/lib.rs | 89 ++++++++++--------- 2 files changed, 55 insertions(+), 44 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index e8e2633e687e..32e4f611edb6 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -81,6 +81,16 @@ pub enum TransferError { 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 contract balance")] + FailedToFetchDestinationContractBalance, + #[error("Failed to update contract storage")] + FailedToUpdateContractStorage, } 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 03dc9d6e0d32..6d72ad2fafe9 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -228,7 +228,7 @@ pub fn execute_internal_operations<'a, Host: Runtime>( ) .into())); } - }?; + }; match internal_receipt { Ok(receipt) => { log!( @@ -239,7 +239,7 @@ pub fn execute_internal_operations<'a, Host: Runtime>( ); } Err(error) => { - return Ok(Err(error)); + return Ok(Err(error.into())); } } } @@ -260,35 +260,36 @@ pub fn transfer<'a, Host: Runtime>( 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)?; - let success = transfer_tez( + TezlinkImplicitAccount::allocate(host, context, dest_contract) + .map_err(|_| TransferError::FailedToAllocateDestination)?; + transfer_tez( host, src_contract, src_account, src_balance, amount, dest_contract, - &mut TezlinkImplicitAccount::from_public_key_hash(context, pkh)?, + &mut TezlinkImplicitAccount::from_public_key_hash(context, pkh) + .map_err(|_| TransferError::FailedToFetchDestinationAccount)?, ) - .map_err(|e| e.into()) .map(|success| TransferSuccess { allocated_destination_contract: allocated, ..success - }); - Ok(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 = transfer_tez( host, src_contract, src_account, @@ -296,37 +297,37 @@ pub fn transfer<'a, Host: Runtime>( amount, dest_contract, &mut dest_account, - ) { - Ok(receipt) => receipt, - Err(e) => { - return Ok(Err(e.into())); - } - }; + )?; ctx.sender = address_from_contract(src_contract.clone()); - let code = dest_account.code(host)?; - let storage = dest_account.storage(host)?; + let code = dest_account + .code(host) + .map_err(|_| TransferError::FailedToFetchContractCode)?; + let storage = dest_account + .storage(host) + .map_err(|_| TransferError::FailedToFetchContractStorage)?; // TODO: this has already been computed by transfer_tez, avoid refetching it from storage - let dest_balance = dest_account.balance(host)?; - match execute_smart_contract(code, storage, entrypoint, param, parser, ctx) { - 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, - &dest_balance, - parser, - ctx, - ); - Ok(Ok(TransferSuccess { - storage: Some(new_storage), - ..receipt - })) - } - Err(err) => Ok(Err(err.into())), - } + let dest_balance = dest_account + .balance(host) + .map_err(|_| TransferError::FailedToFetchDestinationContractBalance)?; + 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, + &dest_balance, + parser, + ctx, + ); + Ok(TransferSuccess { + storage: Some(new_storage), + ..receipt + }) } } } @@ -379,10 +380,10 @@ pub fn transfer_external( value, &parser, &mut ctx, - ); - // TODO : Counter Increment should be done after successful validation (see issue #8031) + ) + .map_err(|e| e.into()); src_account.increment_counter(host)?; - success + Ok(success) } /// Prepares balance updates when accounting fees in the format expected by the Tezos operation. -- GitLab From 97982ec40326665996cf6307ac3febb1e3c30648 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 13:43:35 +0200 Subject: [PATCH 12/17] Tezlink/Kernel/Transfer: remove nested results in execute_internal_operations --- .../tezos/src/operation_result.rs | 2 ++ .../kernel_latest/tezos_execution/src/lib.rs | 35 ++++++++----------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 32e4f611edb6..251d2dc10b8f 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -91,6 +91,8 @@ pub enum TransferError { FailedToFetchDestinationContractBalance, #[error("Failed to update contract storage")] FailedToUpdateContractStorage, + #[error("An internal operation failed: {0}")] + FailedToApplyInternalOperation(String), } 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 6d72ad2fafe9..96ccf48f64ad 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -20,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, @@ -192,7 +192,7 @@ pub fn execute_internal_operations<'a, Host: Runtime>( sender_balance: &Narith, parser: &'a Parser<'a>, ctx: &mut Ctx<'a>, -) -> ExecutionResult<()> { +) -> Result<(), TransferError> { for internal_op in internal_operations { log!( host, @@ -207,7 +207,8 @@ 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, @@ -223,27 +224,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.into())); - } - } + }?; + 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. -- GitLab From 9d52383ecf033f69164d04243ead0638add32f08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 13:55:04 +0200 Subject: [PATCH 13/17] Tezlink/Kernel/Transfer: remove nested results in transfer_external --- .../tezos/src/operation_result.rs | 2 ++ .../kernel_latest/tezos_execution/src/lib.rs | 30 +++++++++---------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 251d2dc10b8f..23ba7e525b9b 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -93,6 +93,8 @@ pub enum TransferError { FailedToUpdateContractStorage, #[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 96ccf48f64ad..717e8d44296a 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -39,8 +39,6 @@ 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}")] @@ -326,15 +324,17 @@ pub fn transfer<'a, Host: Runtime>( } // 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, @@ -346,15 +346,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(())), }; @@ -365,7 +361,7 @@ pub fn transfer_external( host, context, &src_contract, - &mut src_account, + src_account, src_balance, amount, dest, @@ -373,10 +369,11 @@ pub fn transfer_external( value, &parser, &mut ctx, - ) - .map_err(|e| e.into()); - src_account.increment_counter(host)?; - Ok(success) + ); + src_account + .increment_counter(host) + .map_err(|_| TransferError::FailedToIncrementCounter)?; + success } /// Prepares balance updates when accounting fees in the format expected by the Tezos operation. @@ -576,14 +573,17 @@ fn apply_operation( host, context, &operation.source, + &mut source_account, &new_source_balance, amount, destination, parameters.clone(), - )?; + ); let manager_result = produce_operation_result( validation_balance_updates, - transfer_result.map(TransferTarget::ToContrat), + transfer_result + .map(TransferTarget::ToContrat) + .map_err(|e| e.into()), ); Ok(OperationResultSum::Transfer(manager_result)) } -- GitLab From 4a3151fe7616c0242fb76ccb0447c2a169289d23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 13:58:22 +0200 Subject: [PATCH 14/17] Tezlink/Kernel/Transfer: remove nested results in apply_operation --- etherlink/kernel_latest/tezos_execution/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 717e8d44296a..9d3790c70747 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -532,7 +532,7 @@ pub fn validate_and_apply_operation( safe_host.start()?; let receipt = - apply_operation(&mut safe_host, context, &manager_operation, validation_info)?; + apply_operation(&mut safe_host, context, &manager_operation, validation_info); if is_applied(&receipt) { safe_host.promote()?; @@ -549,7 +549,7 @@ fn apply_operation( context: &Context, operation: &ManagerOperation, validation_info: ValidationInfo, -) -> Result { +) -> OperationResultSum { let ValidationInfo { new_source_balance, mut source_account, @@ -562,7 +562,7 @@ fn apply_operation( validation_balance_updates, reveal_result.map_err(|e| e.into()), ); - Ok(OperationResultSum::Reveal(manager_result)) + OperationResultSum::Reveal(manager_result) } OperationContent::Transfer(TransferContent { ref amount, @@ -585,7 +585,7 @@ fn apply_operation( .map(TransferTarget::ToContrat) .map_err(|e| e.into()), ); - Ok(OperationResultSum::Transfer(manager_result)) + OperationResultSum::Transfer(manager_result) } } } -- GitLab From 0991ddc1b3a8734aaee76051485fcc22eb9b012f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 14:49:18 +0200 Subject: [PATCH 15/17] Tezlink/Kernel/Transfer: propagate destination balance --- .../kernel_latest/tezos_execution/src/lib.rs | 59 +++++++++++-------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 9d3790c70747..9d9da1c70667 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -141,7 +141,7 @@ pub fn transfer_tez( amount: &Narith, dest_contract: &Contract, dest_account: &mut impl TezlinkAccount, -) -> Result { +) -> Result<(TransferSuccess, AppliedBalanceChanges), TransferError> { let (src_update, dest_update) = compute_balance_updates(src_contract, dest_contract, amount) .map_err(|_| TransferError::FailedToComputeBalanceUpdate)?; @@ -159,7 +159,7 @@ pub fn transfer_tez( } Some(new_source_balance) => new_source_balance, }; - apply_balance_changes( + let applied_balance_changes = apply_balance_changes( host, src_account, new_source_balance, @@ -167,17 +167,20 @@ pub fn transfer_tez( &amount.0, ) .map_err(|_| TransferError::FailedToApplyBalanceChanges)?; - 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)] @@ -271,7 +274,7 @@ pub fn transfer<'a, Host: Runtime>( &mut TezlinkImplicitAccount::from_public_key_hash(context, pkh) .map_err(|_| TransferError::FailedToFetchDestinationAccount)?, ) - .map(|success| TransferSuccess { + .map(|(success, _applied_balance_changes)| TransferSuccess { allocated_destination_contract: allocated, ..success }) @@ -280,7 +283,7 @@ pub fn transfer<'a, Host: Runtime>( let mut dest_account = TezlinkOriginatedAccount::from_contract(context, dest_contract) .map_err(|_| TransferError::FailedToFetchDestinationAccount)?; - let receipt = transfer_tez( + let (receipt, applied_balance_changes) = transfer_tez( host, src_contract, src_account, @@ -296,10 +299,6 @@ pub fn transfer<'a, Host: Runtime>( let storage = dest_account .storage(host) .map_err(|_| TransferError::FailedToFetchContractStorage)?; - // TODO: this has already been computed by transfer_tez, avoid refetching it from storage - let dest_balance = dest_account - .balance(host) - .map_err(|_| TransferError::FailedToFetchDestinationContractBalance)?; let (internal_operations, new_storage) = execute_smart_contract(code, storage, entrypoint, param, parser, ctx)?; dest_account @@ -311,7 +310,7 @@ pub fn transfer<'a, Host: Runtime>( internal_operations, dest_contract, &mut dest_account, - &dest_balance, + &applied_balance_changes.new_dest_balance, parser, ctx, ); @@ -429,6 +428,12 @@ 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, @@ -436,12 +441,16 @@ fn apply_balance_changes( new_src_balance: num_bigint::BigUint, dest_account: &mut impl TezlinkAccount, amount: &num_bigint::BigUint, -) -> Result<(), ApplyKernelError> { - src_account.set_balance(host, &new_src_balance.into())?; +) -> Result { + let new_src_balance = new_src_balance.into(); + src_account.set_balance(host, &new_src_balance)?; 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(()) + let new_dest_balance = (&dest_balance + amount).into(); + dest_account.set_balance(host, &new_dest_balance)?; + Ok(AppliedBalanceChanges { + new_src_balance, + new_dest_balance, + }) } /// Executes the entrypoint logic of an originated smart contract and returns the new storage. -- GitLab From 8f6e98bdb549a834cf612af9c88fa3eebfdf1779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Wed, 30 Jul 2025 15:24:29 +0200 Subject: [PATCH 16/17] Tezlink/Kernel/Transfer: source balance computed in apply_balance_changes --- .../tezos/src/operation_result.rs | 6 ++- .../kernel_latest/tezos_execution/src/lib.rs | 49 ++++++++++--------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 23ba7e525b9b..3bfee9f081f3 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -87,10 +87,12 @@ pub enum TransferError { FailedToFetchContractCode, #[error("Failed to fetch contract storage")] FailedToFetchContractStorage, - #[error("Failed to fetch destination contract balance")] - FailedToFetchDestinationContractBalance, + #[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")] diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 9d9da1c70667..0e1e84387934 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -146,27 +146,14 @@ pub fn transfer_tez( compute_balance_updates(src_contract, dest_contract, amount) .map_err(|_| TransferError::FailedToComputeBalanceUpdate)?; - // Check source balance - let current_src_balance = &src_balance.0; - let new_source_balance = match current_src_balance.checked_sub(&amount.0) { - None => { - log!(host, Debug, "Balance is too low"); - return Err(TransferError::BalanceTooLow(BalanceTooLow { - contract: src_contract.clone(), - balance: current_src_balance.into(), - amount: amount.clone(), - })); - } - Some(new_source_balance) => new_source_balance, - }; let applied_balance_changes = apply_balance_changes( host, + src_contract, src_account, - new_source_balance, + src_balance, dest_account, &amount.0, - ) - .map_err(|_| TransferError::FailedToApplyBalanceChanges)?; + )?; Ok(( TransferSuccess { storage: None, @@ -437,16 +424,34 @@ pub struct AppliedBalanceChanges { /// 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 { - let new_src_balance = new_src_balance.into(); - src_account.set_balance(host, &new_src_balance)?; - let dest_balance = dest_account.balance(host)?.0; +) -> 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)?; + dest_account + .set_balance(host, &new_dest_balance) + .map_err(|_| TransferError::FailedToUpdateDestinationBalance)?; Ok(AppliedBalanceChanges { new_src_balance, new_dest_balance, -- GitLab From fa780d211e55c5bb577b81b7733c695bf62d677d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Cauderlier?= Date: Thu, 31 Jul 2025 12:25:42 +0000 Subject: [PATCH 17/17] Tezlink/Kernel/Transfer: name dest_account --- etherlink/kernel_latest/tezos_execution/src/lib.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 0e1e84387934..40deaf51793f 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -251,6 +251,9 @@ pub fn transfer<'a, Host: Runtime>( let allocated = 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, @@ -258,8 +261,7 @@ pub fn transfer<'a, Host: Runtime>( src_balance, amount, dest_contract, - &mut TezlinkImplicitAccount::from_public_key_hash(context, pkh) - .map_err(|_| TransferError::FailedToFetchDestinationAccount)?, + &mut dest_account, ) .map(|(success, _applied_balance_changes)| TransferSuccess { allocated_destination_contract: allocated, -- GitLab