From d2c6883136646621e9ae10c87f9d7654d245d245 Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 27 May 2025 17:49:56 +0200 Subject: [PATCH 1/6] Tezlink/Kernel: Rename Tezlink types in operation_result for more consistency The manager notion has been removed in operation_result as it's the only operation supported for Tezlink. It has not been removed from operation as this code should be moved in the rust sdk someday. --- .../tezos/src/operation_result.rs | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 5675497b7014..32729e62850f 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -5,6 +5,7 @@ //! Tezos operations /// The whole module is inspired of `src/proto_alpha/lib_protocol/apply_result.ml` to represent the result of an operation +/// In Tezlink, operation is equivalent to manager operation because there is no other type of operation that interests us. use std::fmt::Debug; use tezos_data_encoding::enc as tezos_enc; use tezos_data_encoding::nom as tezos_nom; @@ -23,7 +24,7 @@ pub enum ValidityError { } #[derive(Debug, PartialEq, Eq, NomReader, BinWriter)] -pub enum ManagerError { +pub enum ApplyOperationError { PreviouslyRevealedKey(PublicKey), InconsistentHash(PublicKeyHash), InconsistentPublicKey(PublicKeyHash), @@ -32,7 +33,7 @@ pub enum ManagerError { #[derive(Debug, PartialEq, Eq, NomReader, BinWriter)] pub enum OperationError { Validation(ValidityError), - Manager(ManagerError), + Apply(ApplyOperationError), } impl From for OperationError { @@ -41,19 +42,19 @@ impl From for OperationError { } } -impl From for OperationError { - fn from(value: ManagerError) -> Self { - Self::Manager(value) +impl From for OperationError { + fn from(value: ApplyOperationError) -> Self { + Self::Apply(value) } } -pub trait ManagerKind { +pub trait OperationKind { type Success: PartialEq + Debug + BinWriter + for<'a> NomReader<'a>; fn kind() -> Self; } -/// Empty struct to implement [ManagerKind] trait for Reveal +/// Empty struct to implement [OperationKind] trait for Reveal #[derive(PartialEq, Debug)] pub struct Reveal; @@ -67,7 +68,7 @@ pub struct RevealContent { pk: PublicKey, } -impl ManagerKind for Reveal { +impl OperationKind for Reveal { type Success = RevealSuccess; fn kind() -> Self { @@ -75,10 +76,10 @@ impl ManagerKind for Reveal { } } -// Inspired from `src/proto_alpha/lib_protocol/apply_operation_result.ml` +// Inspired from `operation_result` in `src/proto_alpha/lib_protocol/apply_operation_result.ml` // Still need to implement Backtracked and Skipped #[derive(PartialEq, Debug, BinWriter, NomReader)] -pub enum OperationStatus { +pub enum ContentResult { Applied(M::Success), Failed(Vec), } @@ -87,25 +88,26 @@ pub enum OperationStatus { #[derive(PartialEq, Debug, NomReader, BinWriter)] pub enum Balance { Account(Contract), - BlockFees, + Block, } -/// Depending of the sign of [credited], the account is credited or debited +/// Depending of the sign of [changes], the balance is credited or debited #[derive(PartialEq, Debug, NomReader, BinWriter)] pub struct BalanceUpdate { pub balance: Balance, - pub credited: Zarith, + pub changes: Zarith, } -// Inspired from `src/proto_alpha/lib_protocol/apply_results.ml` +// Inspired from `Manager_operation_result` case in 'kind contents_result type +// from `src/proto_alpha/lib_protocol/apply_results.ml` file. // Still need to implement internal_results #[derive(PartialEq, Debug, NomReader, BinWriter)] -pub struct ManagerReceipt { +pub struct OperationResult { pub balance_updates: Vec, - pub result: OperationStatus, + pub result: ContentResult, } #[derive(PartialEq, Debug, NomReader, BinWriter)] -pub enum OperationReceipt { - RevealReceipt(ManagerReceipt), +pub enum OperationResultSum { + Reveal(OperationResult), } -- GitLab From df912fd733bf4d879557b8b74b977227be3dd6b7 Mon Sep 17 00:00:00 2001 From: arnaud Date: Mon, 7 Apr 2025 10:27:14 +0200 Subject: [PATCH 2/6] Tezlink/Kernel: Check the validity of a tezos operation This is the first commit to verify the validity of a tezos operation. This is still a draft as there's no check on the signature for example. --- etherlink/kernel_latest/Cargo.lock | 3 + .../kernel_latest/tezos_execution/Cargo.toml | 3 + .../kernel_latest/tezos_execution/src/lib.rs | 106 ++++++++++++++++++ 3 files changed, 112 insertions(+) diff --git a/etherlink/kernel_latest/Cargo.lock b/etherlink/kernel_latest/Cargo.lock index c7ed4deaa0cc..f39004ae114d 100644 --- a/etherlink/kernel_latest/Cargo.lock +++ b/etherlink/kernel_latest/Cargo.lock @@ -2055,10 +2055,12 @@ dependencies = [ name = "tezos-execution-latest" version = "0.1.0" dependencies = [ + "anyhow", "hex", "nom", "num-bigint", "primitive-types", + "tezos-evm-logging-latest", "tezos-evm-runtime-latest", "tezos-smart-rollup", "tezos-smart-rollup-host", @@ -2066,6 +2068,7 @@ dependencies = [ "tezos_crypto_rs", "tezos_data_encoding", "tezos_tezlink_latest", + "thiserror 1.0.69", ] [[package]] diff --git a/etherlink/kernel_latest/tezos_execution/Cargo.toml b/etherlink/kernel_latest/tezos_execution/Cargo.toml index d2f77c6b78c5..6c6340706b4e 100644 --- a/etherlink/kernel_latest/tezos_execution/Cargo.toml +++ b/etherlink/kernel_latest/tezos_execution/Cargo.toml @@ -9,6 +9,8 @@ edition = "2021" license = "MIT" [dependencies] +thiserror.workspace = true +anyhow.workspace = true tezos_crypto_rs.workspace = true hex.workspace = true @@ -25,3 +27,4 @@ nom.workspace = true primitive-types.workspace = true tezos-smart-rollup.workspace = true tezos_tezlink.workspace = true +tezos-evm-logging.workspace = true diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index d6c4e42c0c55..bb3f658010a0 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -2,6 +2,112 @@ // // SPDX-License-Identifier: MIT +use account_storage::TezlinkImplicitAccount; +use tezos_crypto_rs::base58::FromBase58CheckError; +use tezos_data_encoding::types::Narith; +use tezos_evm_logging::{log, Level::*}; +use tezos_evm_runtime::runtime::Runtime; +use tezos_smart_rollup::types::Contract; +use tezos_tezlink::{ + operation::{ManagerOperation, Operation}, + operation_result::ValidityError, +}; +use thiserror::Error; + extern crate alloc; pub mod account_storage; pub mod context; + +#[derive(Error, Debug, PartialEq, Eq)] +pub enum ApplyKernelError { + #[error("Apply operation failed on a storage manipulation {0}")] + StorageError(tezos_storage::error::Error), + #[error("Apply operation failed because of a b58 conversion {0}")] + Base58Error(String), +} + +// 'FromBase58CheckError' doesn't implement PartialEq and Eq +// Use the String representation instead +impl From for ApplyKernelError { + fn from(err: FromBase58CheckError) -> Self { + Self::Base58Error(err.to_string()) + } +} + +impl From for ApplyKernelError { + fn from(value: tezos_storage::error::Error) -> Self { + Self::StorageError(value) + } +} + +fn is_valid_tezlink_operation( + host: &Host, + account: &TezlinkImplicitAccount, + fee: &Narith, + expected_counter: &Narith, +) -> Result, ApplyKernelError> { + // Account must exist in the durable storage + if !account.allocated(host)? { + return Ok(Err(ValidityError::EmptyImplicitContract)); + } + + // The manager account must be solvent to pay the announced fees. + // TODO: account balance should not be stored as U256 + let balance = account.balance(host)?; + + if balance.0 < fee.0 { + return Ok(Err(ValidityError::CantPayFees(balance))); + } + + let previous_counter = account.counter(host)?; + + // The provided counter value must be the successor of the manager's counter. + let succ_counter = Narith(&previous_counter.0 + 1_u64); + + if &succ_counter != expected_counter { + return Ok(Err(ValidityError::InvalidCounter(previous_counter))); + } + + Ok(Ok(())) +} + +// TOOD: apply_operation function should return an operation receipt +pub fn apply_operation( + host: &mut Host, + context: &context::Context, + operation: &Operation, +) -> Result<(), ApplyKernelError> { + let ManagerOperation { + source, + fee, + counter, + gas_limit: _, + storage_limit: _, + operation: _, + } = &operation.content; + + log!( + host, + Debug, + "Going to run a Tezos Manager Operation from {}", + source + ); + + let contract = Contract::from_b58check(&source.to_b58check())?; + let account = + account_storage::TezlinkImplicitAccount::from_contract(context, &contract)?; + + log!(host, Debug, "Verifying that the operation is valid"); + + let validity_result = is_valid_tezlink_operation(host, &account, fee, counter)?; + + // TODO: ignoring the validity error for now + if let Err(_validity_err) = validity_result { + log!(host, Debug, "Operation is invalid, exiting apply_operation"); + return Ok(()); + } + + log!(host, Debug, "Operation is valid"); + + Ok(()) +} -- GitLab From e621e3d8866f5fdbdfd3e8dc0891be7f74c10d32 Mon Sep 17 00:00:00 2001 From: arnaud Date: Mon, 14 Apr 2025 11:18:49 +0200 Subject: [PATCH 3/6] Tezlink/Kernel: apply operation returns an operation receipt --- .../tezos/src/operation_result.rs | 15 ++++++++++++ .../kernel_latest/tezos_execution/src/lib.rs | 24 +++++++++++++------ 2 files changed, 32 insertions(+), 7 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 32729e62850f..bc2f20ade1a9 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -111,3 +111,18 @@ pub struct OperationResult { pub enum OperationResultSum { Reveal(OperationResult), } + +pub fn produce_operation_result( + result: Result, +) -> OperationResult { + match result { + Ok(success) => OperationResult { + balance_updates: vec![], + result: ContentResult::Applied(success), + }, + Err(operation_error) => OperationResult { + balance_updates: vec![], + result: ContentResult::Failed(vec![operation_error]), + }, + } +} diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index bb3f658010a0..6fa723ce47c9 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -10,7 +10,10 @@ use tezos_evm_runtime::runtime::Runtime; use tezos_smart_rollup::types::Contract; use tezos_tezlink::{ operation::{ManagerOperation, Operation}, - operation_result::ValidityError, + operation_result::{ + produce_operation_result, OperationError, OperationResultSum, Reveal, + RevealSuccess, ValidityError, + }, }; use thiserror::Error; @@ -71,12 +74,11 @@ fn is_valid_tezlink_operation( Ok(Ok(())) } -// TOOD: apply_operation function should return an operation receipt pub fn apply_operation( host: &mut Host, context: &context::Context, operation: &Operation, -) -> Result<(), ApplyKernelError> { +) -> Result { let ManagerOperation { source, fee, @@ -101,13 +103,21 @@ pub fn apply_operation( let validity_result = is_valid_tezlink_operation(host, &account, fee, counter)?; - // TODO: ignoring the validity error for now - if let Err(_validity_err) = validity_result { + if let Err(validity_err) = validity_result { log!(host, Debug, "Operation is invalid, exiting apply_operation"); - return Ok(()); + // TODO: Don't force the receipt to a reveal receipt + let receipt = produce_operation_result::(Err( + OperationError::Validation(validity_err), + )); + return Ok(OperationResultSum::Reveal(receipt)); } log!(host, Debug, "Operation is valid"); - Ok(()) + // TODO: Don't force the receipt to a reveal receipt + let dummy_result = produce_operation_result::(Ok(RevealSuccess { + consumed_gas: 0_u64.into(), + })); + + Ok(OperationResultSum::Reveal(dummy_result)) } -- GitLab From 114590110cbcff9c83ea29750dd7f0e94f50bbda Mon Sep 17 00:00:00 2001 From: arnaud Date: Wed, 9 Apr 2025 13:50:46 +0200 Subject: [PATCH 4/6] Tezlink/Kernel/Tests: Test the validation of an operation --- .../kernel_latest/tezos_execution/src/lib.rs | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 6fa723ce47c9..09cc38f2acdf 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -121,3 +121,164 @@ pub fn apply_operation( Ok(OperationResultSum::Reveal(dummy_result)) } + +#[cfg(test)] +mod tests { + use tezos_crypto_rs::hash::UnknownSignature; + use tezos_evm_runtime::runtime::{MockKernelHost, Runtime}; + use tezos_smart_rollup::types::{Contract, PublicKey, PublicKeyHash}; + use tezos_tezlink::{ + block::TezBlock, + operation::{ManagerOperation, Operation, OperationContent}, + operation_result::{ContentResult, OperationResult, OperationResultSum}, + }; + + use crate::{ + account_storage::TezlinkImplicitAccount, apply_operation, context, + OperationError, ValidityError, + }; + + fn make_reveal_operation( + fee: u64, + counter: u64, + gas_limit: u64, + storage_limit: u64, + source: PublicKeyHash, + pk: PublicKey, + ) -> Operation { + let branch = TezBlock::genesis_block_hash(); + // No need a real signature for now + let signature = UnknownSignature::from_base58_check("sigSPESPpW4p44JK181SmFCFgZLVvau7wsJVN85bv5ciigMu7WSRnxs9H2NydN5ecxKHJBQTudFPrUccktoi29zHYsuzpzBX").unwrap(); + Operation { + branch, + content: ManagerOperation { + source, + fee: fee.into(), + counter: counter.into(), + operation: OperationContent::Reveal { pk }, + gas_limit: gas_limit.into(), + storage_limit: storage_limit.into(), + }, + signature, + } + } + + // This function setups an account that will pass the validity checks + fn init_account( + host: &mut impl Runtime, + src: &PublicKeyHash, + ) -> TezlinkImplicitAccount { + // Setting the account in TezlinkImplicitAccount + let contract = Contract::from_b58check(&src.to_b58check()) + .expect("Contract b58 conversion should have succeed"); + + let context = context::Context::init_context(); + + // Allocate the account + TezlinkImplicitAccount::allocate(host, &context, &contract) + .expect("Account initialization should have succeed"); + + let mut account = TezlinkImplicitAccount::from_contract(&context, &contract) + .expect("Account creation should have succeed"); + + // Setting the balance to pass the validity check + account + .set_balance(host, &50_u64.into()) + .expect("Set balance should have succeed"); + + account + } + + // Test an operation on an account that has no entry in `/context/contracts/index` + // This should fail as an EmptyImplicitContract + #[test] + fn apply_operation_empty_account() { + let mut host = MockKernelHost::default(); + + let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + .expect("PublicKeyHash b58 conversion should have succeed"); + + let pk = PublicKey::from_b58check( + "edpkuBknW28nW72KG6RoHtYW7p12T6GKc7nAbwYX5m8Wd9sDVC9yav", + ) + .expect("Public key creation should have succeed"); + + let operation = make_reveal_operation(15, 1, 4, 5, src, pk); + + let receipt = + apply_operation(&mut host, &context::Context::init_context(), &operation) + .expect("apply_operation should not have failed with a kernel error"); + + let expected_receipt = OperationResultSum::Reveal(OperationResult { + balance_updates: vec![], + result: ContentResult::Failed(vec![OperationError::Validation( + ValidityError::EmptyImplicitContract, + )]), + }); + + assert_eq!(receipt, expected_receipt); + } + + // Test that increasing the fees makes the operation fails + #[test] + fn apply_operation_cant_pay_fees() { + let mut host = MockKernelHost::default(); + + let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + .expect("PublicKeyHash b58 conversion should have succeed"); + + let _ = init_account(&mut host, &src); + + let pk = PublicKey::from_b58check( + "edpkuBknW28nW72KG6RoHtYW7p12T6GKc7nAbwYX5m8Wd9sDVC9yav", + ) + .expect("Public key creation should have succeed"); + + // Fees are too high for source's balance + let operation = make_reveal_operation(100, 1, 4, 5, src, pk); + + let receipt = + apply_operation(&mut host, &context::Context::init_context(), &operation) + .expect("apply_operation should not have failed with a kernel error"); + + let expected_receipt = OperationResultSum::Reveal(OperationResult { + balance_updates: vec![], + result: ContentResult::Failed(vec![OperationError::Validation( + ValidityError::CantPayFees(50_u64.into()), + )]), + }); + + assert_eq!(receipt, expected_receipt); + } + + // Test that a wrong counter should make the operation fails + #[test] + fn apply_operation_invalid_counter() { + let mut host = MockKernelHost::default(); + + let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + .expect("PublicKeyHash b58 conversion should have succeed"); + + let _ = init_account(&mut host, &src); + + let pk = PublicKey::from_b58check( + "edpkuBknW28nW72KG6RoHtYW7p12T6GKc7nAbwYX5m8Wd9sDVC9yav", + ) + .expect("Public key creation should have succeed"); + + // Counter is incoherent for source's counter + let operation = make_reveal_operation(15, 15, 4, 5, src, pk); + + let receipt = + apply_operation(&mut host, &context::Context::init_context(), &operation) + .expect("apply_operation should not have failed with a kernel error"); + + let expected_receipt = OperationResultSum::Reveal(OperationResult { + balance_updates: vec![], + result: ContentResult::Failed(vec![OperationError::Validation( + ValidityError::InvalidCounter(0_u64.into()), + )]), + }); + assert_eq!(receipt, expected_receipt); + } +} -- GitLab From 7f7b3f091e996b6aa68d13bb7bba459dfd5240a9 Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 8 Apr 2025 15:42:54 +0200 Subject: [PATCH 5/6] Tezlink/Kernel: Implement the reveal --- .../kernel_latest/tezos_execution/src/lib.rs | 71 +++++++++++++++---- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 09cc38f2acdf..fc6af41fd4c3 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -2,17 +2,17 @@ // // SPDX-License-Identifier: MIT -use account_storage::TezlinkImplicitAccount; -use tezos_crypto_rs::base58::FromBase58CheckError; +use account_storage::{Manager, TezlinkImplicitAccount}; +use tezos_crypto_rs::{base58::FromBase58CheckError, PublicKeyWithHash}; use tezos_data_encoding::types::Narith; use tezos_evm_logging::{log, Level::*}; use tezos_evm_runtime::runtime::Runtime; -use tezos_smart_rollup::types::Contract; +use tezos_smart_rollup::types::{Contract, PublicKey, PublicKeyHash}; use tezos_tezlink::{ - operation::{ManagerOperation, Operation}, + operation::{ManagerOperation, Operation, OperationContent}, operation_result::{ - produce_operation_result, OperationError, OperationResultSum, Reveal, - RevealSuccess, ValidityError, + produce_operation_result, ApplyOperationError, OperationError, + OperationResultSum, Reveal, RevealSuccess, ValidityError, }, }; use thiserror::Error; @@ -74,6 +74,48 @@ fn is_valid_tezlink_operation( Ok(Ok(())) } +fn reveal( + host: &mut Host, + provided_hash: &PublicKeyHash, + account: &mut TezlinkImplicitAccount, + public_key: &PublicKey, +) -> Result, ApplyKernelError> { + log!(host, Debug, "Applying a reveal operation"); + let manager = account.manager(host)?; + + let expected_hash = match manager { + Manager::Revealed(pk) => { + return Ok(Err(ApplyOperationError::PreviouslyRevealedKey(pk).into())) + } + 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( + ApplyOperationError::InconsistentHash(expected_hash).into() + )); + } + + // Check the public key + let pkh_from_pk = public_key.pk_hash(); + if expected_hash != pkh_from_pk { + return Ok(Err(ApplyOperationError::InconsistentPublicKey( + expected_hash, + ) + .into())); + } + + // Set the public key as the manager + account.set_manager_public_key(host, public_key)?; + + log!(host, Debug, "Reveal operation succeed"); + + Ok(Ok(RevealSuccess { + consumed_gas: 0_u64.into(), + })) +} + pub fn apply_operation( host: &mut Host, context: &context::Context, @@ -85,7 +127,7 @@ pub fn apply_operation( counter, gas_limit: _, storage_limit: _, - operation: _, + operation: content, } = &operation.content; log!( @@ -96,7 +138,7 @@ pub fn apply_operation( ); let contract = Contract::from_b58check(&source.to_b58check())?; - let account = + let mut account = account_storage::TezlinkImplicitAccount::from_contract(context, &contract)?; log!(host, Debug, "Verifying that the operation is valid"); @@ -114,12 +156,15 @@ pub fn apply_operation( log!(host, Debug, "Operation is valid"); - // TODO: Don't force the receipt to a reveal receipt - let dummy_result = produce_operation_result::(Ok(RevealSuccess { - consumed_gas: 0_u64.into(), - })); + let receipt = match content { + OperationContent::Reveal { pk } => { + let reveal_result = reveal(host, source, &mut account, pk)?; + let manager_result = produce_operation_result(reveal_result); + OperationResultSum::Reveal(manager_result) + } + }; - Ok(OperationResultSum::Reveal(dummy_result)) + Ok(receipt) } #[cfg(test)] -- GitLab From 40ec2ddf122a91789c0998fe02f86b98c77c0228 Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 8 Apr 2025 18:22:20 +0200 Subject: [PATCH 6/6] Tezlink/Kernel/Tests: Add test to verify that the reveal works fine --- .../kernel_latest/tezos_execution/src/lib.rs | 164 +++++++++++++++++- 1 file changed, 161 insertions(+), 3 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index fc6af41fd4c3..7307d103120b 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -175,12 +175,15 @@ mod tests { use tezos_tezlink::{ block::TezBlock, operation::{ManagerOperation, Operation, OperationContent}, - operation_result::{ContentResult, OperationResult, OperationResultSum}, + operation_result::{ + ApplyOperationError, ContentResult, OperationResult, OperationResultSum, + RevealSuccess, + }, }; use crate::{ - account_storage::TezlinkImplicitAccount, apply_operation, context, - OperationError, ValidityError, + account_storage::{Manager, TezlinkImplicitAccount}, + apply_operation, context, OperationError, ValidityError, }; fn make_reveal_operation( @@ -326,4 +329,159 @@ mod tests { }); assert_eq!(receipt, expected_receipt); } + + // At this point, tests are focused on the content of the operation. We should not revert with ValidityError anymore. + // Test a reveal operation on an already revealed account + #[test] + fn apply_reveal_operation_on_already_revealed_account() { + let mut host = MockKernelHost::default(); + + let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + .expect("PublicKeyHash b58 conversion should have succeed"); + + let mut account = init_account(&mut host, &src); + + // Setting the manager key of this account to its public_key, this account + // will be considered as revealed and the reveal operation should fail + let pk = PublicKey::from_b58check( + "edpkuBknW28nW72KG6RoHtYW7p12T6GKc7nAbwYX5m8Wd9sDVC9yav", + ) + .expect("Public key creation should have succeed"); + + account + .set_manager_public_key(&mut host, &pk) + .expect("Setting manager field should have succeed"); + + // Applying the operation + let operation = make_reveal_operation(15, 1, 4, 5, src, pk.clone()); + let receipt = + apply_operation(&mut host, &context::Context::init_context(), &operation) + .expect("apply_operation should not have failed with a kernel error"); + + // Reveal operation should fail + let expected_receipt = OperationResultSum::Reveal(OperationResult { + balance_updates: vec![], + result: ContentResult::Failed(vec![OperationError::Apply( + ApplyOperationError::PreviouslyRevealedKey(pk), + )]), + }); + assert_eq!(receipt, expected_receipt); + } + + // Test an invalid reveal operation where the manager is inconsistent for source + // (where source is different of the manager field) + #[test] + fn apply_reveal_operation_with_an_inconsistent_manager() { + let mut host = MockKernelHost::default(); + + let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + .expect("PublicKeyHash b58 conversion should have succeed"); + + let mut account = init_account(&mut host, &src); + + // Set the an inconsistent manager with the source + let inconsistent_pkh = + PublicKeyHash::from_b58check("tz1UEQcU7M43yUECMpKGJcxCVwHRaP819qhN") + .expect("PublicKeyHash b58 conversion should have succeed"); + + account + .set_manager_public_key_hash(&mut host, &inconsistent_pkh) + .expect("Setting manager field should have succeed"); + + let pk = PublicKey::from_b58check( + "edpkuBknW28nW72KG6RoHtYW7p12T6GKc7nAbwYX5m8Wd9sDVC9yav", + ) + .expect("Public key creation should have succeed"); + + let operation = make_reveal_operation(15, 1, 4, 5, src, pk); + + let receipt = + apply_operation(&mut host, &context::Context::init_context(), &operation) + .expect("apply_operation should not have failed with a kernel error"); + + let expected_receipt = OperationResultSum::Reveal(OperationResult { + balance_updates: vec![], + result: ContentResult::Failed(vec![OperationError::Apply( + ApplyOperationError::InconsistentHash(inconsistent_pkh), + )]), + }); + + assert_eq!(receipt, expected_receipt); + } + + // Test an invalid operation where the provided public key is inconsistent for the source + #[test] + fn apply_reveal_operation_with_an_inconsistent_public_key() { + let mut host = MockKernelHost::default(); + + let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + .expect("PublicKeyHash b58 conversion should have succeed"); + + // Even if we don't use it we need to init the account + let _ = init_account(&mut host, &src); + + // Wrong public key for source + let pk = PublicKey::from_b58check( + "edpkuT1qccDweCHnvgjLuNUHERpZmEaFZfbWvTzj2BxmTgQBZjaDFD", + ) + .expect("Public key creation should have succeed"); + + let operation = make_reveal_operation(15, 1, 4, 5, src.clone(), pk); + + let receipt = + apply_operation(&mut host, &context::Context::init_context(), &operation) + .expect("apply_operation should not have failed with a kernel error"); + + let expected_receipt = OperationResultSum::Reveal(OperationResult { + balance_updates: vec![], + result: ContentResult::Failed(vec![OperationError::Apply( + ApplyOperationError::InconsistentPublicKey(src), + )]), + }); + + assert_eq!(receipt, expected_receipt); + } + + // Test a valid reveal operation, the manager should go from NotRevealed to Revealed + #[test] + fn apply_reveal_operation() { + let mut host = MockKernelHost::default(); + + let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + .expect("PublicKeyHash b58 conversion should have succeed"); + + let account = init_account(&mut host, &src); + + let manager = account + .manager(&host) + .expect("Read manager should have succeed"); + + assert_eq!(manager, Manager::NotRevealed(src.clone())); + + let pk = PublicKey::from_b58check( + "edpkuBknW28nW72KG6RoHtYW7p12T6GKc7nAbwYX5m8Wd9sDVC9yav", + ) + .expect("Public key creation should have succeed"); + + let operation = make_reveal_operation(15, 1, 4, 5, src, pk.clone()); + + let receipt = + apply_operation(&mut host, &context::Context::init_context(), &operation) + .expect("apply_operation should not have failed with a kernel error"); + + let expected_receipt = OperationResultSum::Reveal(OperationResult { + balance_updates: vec![], + result: ContentResult::Applied(RevealSuccess { + consumed_gas: 0_u64.into(), + }), + }); + + assert_eq!(receipt, expected_receipt); + + let manager = account + .manager(&host) + .expect("Read manager should have succeed"); + + assert_eq!(manager, Manager::Revealed(pk)); + } } -- GitLab