From 6de21efd015e8d7ca1e0c81b5281e5e9179b6cc7 Mon Sep 17 00:00:00 2001 From: arnaud Date: Wed, 4 Jun 2025 17:30:59 +0200 Subject: [PATCH 01/10] Tezlink/Kernel: Remove useless [allow(dead_code)] --- .../kernel_latest/tezos_execution/src/account_storage.rs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/account_storage.rs b/etherlink/kernel_latest/tezos_execution/src/account_storage.rs index 09d9dfd64f72..362011bde984 100644 --- a/etherlink/kernel_latest/tezos_execution/src/account_storage.rs +++ b/etherlink/kernel_latest/tezos_execution/src/account_storage.rs @@ -60,7 +60,6 @@ fn account_path(contract: &Contract) -> Result Date: Wed, 4 Jun 2025 17:49:25 +0200 Subject: [PATCH 02/10] Tezlink/Kernel: Create an account storage from a public key hash --- .../tezos_execution/src/account_storage.rs | 11 +++++++++++ etherlink/kernel_latest/tezos_execution/src/lib.rs | 6 ++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/account_storage.rs b/etherlink/kernel_latest/tezos_execution/src/account_storage.rs index 362011bde984..26d2644e3af4 100644 --- a/etherlink/kernel_latest/tezos_execution/src/account_storage.rs +++ b/etherlink/kernel_latest/tezos_execution/src/account_storage.rs @@ -69,6 +69,17 @@ impl TezlinkImplicitAccount { Ok(path.into()) } + pub fn from_public_key_hash( + context: &context::Context, + pkh: &PublicKeyHash, + ) -> Result { + let index = context::contracts::index(context)?; + // The conversion from pkh to contract should always succeed + let contract = Contract::Implicit(pkh.clone()); + let path = concat(&index, &account_path(&contract)?)?; + Ok(path.into()) + } + /// Get the **counter** for the Tezlink account. pub fn counter( &self, diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 7307d103120b..20c13d3d0f9d 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -7,7 +7,7 @@ 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, PublicKey, PublicKeyHash}; +use tezos_smart_rollup::types::{PublicKey, PublicKeyHash}; use tezos_tezlink::{ operation::{ManagerOperation, Operation, OperationContent}, operation_result::{ @@ -137,9 +137,7 @@ pub fn apply_operation( source ); - let contract = Contract::from_b58check(&source.to_b58check())?; - let mut account = - account_storage::TezlinkImplicitAccount::from_contract(context, &contract)?; + let mut account = TezlinkImplicitAccount::from_public_key_hash(context, source)?; log!(host, Debug, "Verifying that the operation is valid"); -- GitLab From 747459f4c78abe59c7b1f22f5d4258ecda3dc973 Mon Sep 17 00:00:00 2001 From: arnaud Date: Wed, 4 Jun 2025 17:51:07 +0200 Subject: [PATCH 03/10] Tezlink/Kernel: Allocate function returns a boolean to ease transfer futur implementation --- .../kernel_latest/tezos_execution/src/account_storage.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/account_storage.rs b/etherlink/kernel_latest/tezos_execution/src/account_storage.rs index 26d2644e3af4..34d276d6c74f 100644 --- a/etherlink/kernel_latest/tezos_execution/src/account_storage.rs +++ b/etherlink/kernel_latest/tezos_execution/src/account_storage.rs @@ -178,10 +178,10 @@ impl TezlinkImplicitAccount { host: &mut impl Runtime, context: &context::Context, contract: &Contract, - ) -> Result<(), tezos_storage::error::Error> { + ) -> Result { let mut account = Self::from_contract(context, contract)?; if account.allocated(host)? { - return Ok(()); + return Ok(true); } account.set_balance(host, &0_u64.into())?; // Only implicit accounts have counter and manager keys @@ -190,7 +190,7 @@ impl TezlinkImplicitAccount { account.set_counter(host, &0u64.into())?; account.set_manager_public_key_hash(host, pkh)?; } - Ok(()) + Ok(false) } } -- GitLab From 7e7b80b1940bf640ae16f2fd2c3deeddf1676d7b Mon Sep 17 00:00:00 2001 From: arnaud Date: Mon, 28 Apr 2025 15:18:32 +0200 Subject: [PATCH 04/10] Tezlink/Kernel: Add transaction case to tezlink operations --- .../kernel_latest/tezos/src/operation.rs | 45 ++++++++++++++++--- .../kernel_latest/tezos_execution/src/lib.rs | 1 + 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation.rs b/etherlink/kernel_latest/tezos/src/operation.rs index 7e56cdcd5a0a..cf8d5d169482 100644 --- a/etherlink/kernel_latest/tezos/src/operation.rs +++ b/etherlink/kernel_latest/tezos/src/operation.rs @@ -14,23 +14,32 @@ use tezos_crypto_rs::blake2b::digest_256; use tezos_crypto_rs::hash::{HashType, UnknownSignature}; use tezos_data_encoding::types::Narith; use tezos_data_encoding::{ - enc::{BinError, BinResult, BinWriter}, - nom::{error::DecodeError, NomError, NomReader, NomResult}, + enc::{self as tezos_enc, BinError, BinResult, BinWriter}, + nom::{self as tezos_nom, error::DecodeError, NomError, NomReader, NomResult}, }; -use tezos_smart_rollup::types::PublicKey; use tezos_smart_rollup::types::PublicKeyHash; +use tezos_smart_rollup::types::{Contract, PublicKey}; #[derive(PartialEq, Debug)] pub enum OperationContent { - Reveal { pk: PublicKey }, + Reveal { + pk: PublicKey, + }, + Transfer { + amount: Narith, + destination: Contract, + }, } pub const REVEAL_TAG: u8 = 107_u8; +pub const TRANSFER_TAG: u8 = 108_u8; + impl OperationContent { pub fn tag(&self) -> u8 { match self { Self::Reveal { pk: _ } => REVEAL_TAG, + Self::Transfer { .. } => TRANSFER_TAG, } } @@ -40,7 +49,21 @@ impl OperationContent { let (array, pk) = PublicKey::nom_read(bytes)?; NomResult::Ok((array, Self::Reveal { pk })) } - _ => Err(nom::Err::Error(NomError::invalid_tag( + TRANSFER_TAG => { + let (input, amount) = Narith::nom_read(bytes)?; + let (input, destination) = Contract::nom_read(input)?; + // TODO: parameter should be a Michelson expr, for now just use unit + let (input, _parameter) = + tezos_nom::optional_field(|input| Ok((input, ())))(input)?; + NomResult::Ok(( + input, + Self::Transfer { + amount, + destination, + }, + )) + } + _ => Err(nom::Err::Error(tezos_nom::NomError::invalid_tag( bytes, tag.to_string(), ))), @@ -56,6 +79,18 @@ impl BinWriter for OperationContent { pk.bin_write(data)?; Ok(()) } + Self::Transfer { + amount, + destination, + } => { + amount.bin_write(data)?; + destination.bin_write(data)?; + // TODO: parameter should be a Michelson expr, for now just use unit + let closure: for<'a> fn(&(), &'a mut Vec) -> BinResult = + |_, _| Ok(()); + tezos_enc::optional_field(closure)(&None, data)?; + Ok(()) + } } } } diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 20c13d3d0f9d..00ff7509c7df 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -160,6 +160,7 @@ pub fn apply_operation( let manager_result = produce_operation_result(reveal_result); OperationResultSum::Reveal(manager_result) } + OperationContent::Transfer { .. } => todo!(), }; Ok(receipt) -- GitLab From 1067562b95fea25c687e04088c564e4f08608bc4 Mon Sep 17 00:00:00 2001 From: arnaud Date: Mon, 28 Apr 2025 15:42:41 +0200 Subject: [PATCH 05/10] Tezlink/Kernel: Add encoding tezos compatibility test --- .../kernel_latest/tezos/src/operation.rs | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/etherlink/kernel_latest/tezos/src/operation.rs b/etherlink/kernel_latest/tezos/src/operation.rs index cf8d5d169482..a10e78cfd87d 100644 --- a/etherlink/kernel_latest/tezos/src/operation.rs +++ b/etherlink/kernel_latest/tezos/src/operation.rs @@ -316,7 +316,7 @@ mod tests { hash::{HashType, UnknownSignature}, public_key::PublicKey, }; - use tezos_smart_rollup::types::PublicKeyHash; + use tezos_smart_rollup::types::{Contract, PublicKeyHash}; #[test] fn operation_rlp_roundtrip() { @@ -391,4 +391,57 @@ mod tests { assert_eq!(operation, expected_operation); } + + // The operation below is the transfer using the mockup mode of octez-client as follows: + // $ alias mockup-client='octez-client --mode mockup --base-dir /tmp/mockup --protocol PsQuebec' + // $ mockup-client create mockup + // $ TRANSFER_HEX=$(mockup-client transfer 1 from bootstrap2 to bootstrap1 --burn-cap 1 --dry-run | grep Operation: | cut -d x -f 2) + // $ octez-codec decode 021-PsQuebec.operation from "$TRANSFER_HEX" + #[test] + fn tezos_compatibility_for_transfer() { + // The goal of this test is to try to decode an encoding generated by 'octez-codec encode' command + let branch_vec = HashType::b58check_to_hash( + &HashType::BlockHash, + "BLockGenesisGenesisGenesisGenesisGenesisCCCCCeZiLHU", + ) + .unwrap(); + let branch = H256::from_slice(&branch_vec); + let signature = UnknownSignature::from_base58_check("sigT4yGRRhiMZCjGigdhopaXkshKrwDbYrPw3jGFZGkjpvpT57a6KmLa4mFVKBTNHR8NrmyMEt9Pgusac5HLqUoJie2MB5Pd").unwrap(); + let expected_operation = Operation { + branch, + content: ManagerOperation { + source: PublicKeyHash::from_b58check( + "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN", + ) + .unwrap(), + fee: 267_u64.into(), + counter: 1_u64.into(), + operation: OperationContent::Transfer { + amount: 1000000_u64.into(), + destination: Contract::from_b58check( + "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx", + ) + .unwrap(), + }, + gas_limit: 169_u64.into(), + storage_limit: 0_u64.into(), + }, + signature, + }; + + // This bytes sequence comes from the command just above the test + let operation_bytes = hex::decode("8fcf233671b6a04fcf679d2a381c2544ea6c1ea29ba6157776ed842426e5cab86c00e7670f32038107a59a2b9cfefae36ea21f5aa63c8b0201a90100c0843d000002298c03ed7d454a101eb7022bc95f7e5f41ac780026d58f30f5f8caf70878ad4efc82d71cff01b76e584958411e5a89ea2a8908e37ffc28f0af92fa651c32f6cc7362d9c735344d590360864fbf0b156c3443b108").unwrap(); + + let operation = Operation::try_from_bytes(&operation_bytes) + .expect("Decoding operation should have succeded"); + + assert_eq!(operation, expected_operation); + + // Also test the encoding + let kernel_bytes = expected_operation + .to_bytes() + .expect("Operation encoding should have succeed"); + + assert_eq!(operation_bytes, kernel_bytes) + } } -- GitLab From f37daffaa914d2ef14c6ee74520c3039fbda598d Mon Sep 17 00:00:00 2001 From: arnaud Date: Mon, 28 Apr 2025 16:55:12 +0200 Subject: [PATCH 06/10] Tezlink/Kernel: Introduce Transaction receipt in the kernel --- .../tezos/src/operation_result.rs | 41 +++++++++++++++++++ .../kernel_latest/tezos_execution/src/lib.rs | 11 +++-- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index bc2f20ade1a9..3d149faf853b 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -68,6 +68,46 @@ pub struct RevealContent { pk: PublicKey, } +#[derive(PartialEq, Debug, Clone)] +pub struct Empty; + +impl BinWriter for Empty { + fn bin_write(&self, _: &mut Vec) -> tezos_enc::BinResult { + Ok(()) + } +} + +impl NomReader<'_> for Empty { + fn nom_read(input: &'_ [u8]) -> tezos_nom::NomResult<'_, Self> { + Ok((input, Self)) + } +} + +#[derive(PartialEq, Debug, BinWriter, NomReader)] +pub struct TransferSuccess { + pub storage: Option, + pub lazy_storage_diff: Option, + pub balance_updates: Vec, + pub ticket_receipt: Vec, + pub originated_contracts: Vec, + pub consumed_gas: Narith, + pub storage_size: Narith, + pub paid_storage_size_diff: Narith, + pub allocated_destination_contract: bool, +} + +/// Empty struct to implement [OperationKind] trait for Transfer +#[derive(PartialEq, Debug)] +pub struct Transfer; + +impl OperationKind for Transfer { + type Success = TransferSuccess; + + fn kind() -> Self { + Self + } +} + impl OperationKind for Reveal { type Success = RevealSuccess; @@ -110,6 +150,7 @@ pub struct OperationResult { #[derive(PartialEq, Debug, NomReader, BinWriter)] pub enum OperationResultSum { Reveal(OperationResult), + Transfer(OperationResult), } pub fn produce_operation_result( diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 00ff7509c7df..24bcd84d2211 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -11,8 +11,8 @@ use tezos_smart_rollup::types::{PublicKey, PublicKeyHash}; use tezos_tezlink::{ operation::{ManagerOperation, Operation, OperationContent}, operation_result::{ - produce_operation_result, ApplyOperationError, OperationError, - OperationResultSum, Reveal, RevealSuccess, ValidityError, + produce_operation_result, ApplyOperationError, ContentResult, OperationError, + OperationResult, OperationResultSum, Reveal, RevealSuccess, ValidityError, }, }; use thiserror::Error; @@ -160,7 +160,12 @@ pub fn apply_operation( let manager_result = produce_operation_result(reveal_result); OperationResultSum::Reveal(manager_result) } - OperationContent::Transfer { .. } => todo!(), + OperationContent::Transfer { .. } => { + OperationResultSum::Transfer(OperationResult { + balance_updates: vec![], + result: ContentResult::Failed(vec![]), + }) + } }; Ok(receipt) -- GitLab From de9421332ac138c7239933700732f35f7f3311b0 Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 29 Apr 2025 16:35:25 +0200 Subject: [PATCH 07/10] Tezlink/Kernel: Introduce Transfer error and separate both Reveal and Transfer error Separate Transfer and Reveal errors so that we ca continue to use derive NomReader and BinWriter on Reveal error. Transfer error is too complex to derive NomReader and BinWriter --- .../tezos/src/operation_result.rs | 92 ++++++++++++++++++- .../kernel_latest/tezos_execution/src/lib.rs | 23 ++--- 2 files changed, 100 insertions(+), 15 deletions(-) diff --git a/etherlink/kernel_latest/tezos/src/operation_result.rs b/etherlink/kernel_latest/tezos/src/operation_result.rs index 3d149faf853b..a662560b75c9 100644 --- a/etherlink/kernel_latest/tezos/src/operation_result.rs +++ b/etherlink/kernel_latest/tezos/src/operation_result.rs @@ -6,8 +6,12 @@ /// 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 nom::branch::alt; +use nom::bytes::complete::tag; +use nom::sequence::preceded; use std::fmt::Debug; use tezos_data_encoding::enc as tezos_enc; +use tezos_data_encoding::enc::u8; use tezos_data_encoding::nom as tezos_nom; use tezos_data_encoding::types::Narith; use tezos_data_encoding::types::Zarith; @@ -24,12 +28,98 @@ pub enum ValidityError { } #[derive(Debug, PartialEq, Eq, NomReader, BinWriter)] -pub enum ApplyOperationError { +pub enum RevealError { PreviouslyRevealedKey(PublicKey), InconsistentHash(PublicKeyHash), InconsistentPublicKey(PublicKeyHash), } +#[derive(Debug, PartialEq, Eq)] +pub enum TransferError { + BalanceTooLow { + contract: Contract, + balance: Narith, + amount: Narith, + }, + UnspendableContract(Contract), +} + +impl BinWriter for TransferError { + fn bin_write(&self, output: &mut Vec) -> tezos_enc::BinResult { + match self { + Self::BalanceTooLow { + contract, + balance, + amount, + } => { + u8(&0_u8, output)?; + contract.bin_write(output)?; + balance.bin_write(output)?; + amount.bin_write(output)?; + Ok(()) + } + Self::UnspendableContract(contract) => { + u8(&1_u8, output)?; + contract.bin_write(output)?; + Ok(()) + } + } + } +} + +impl NomReader<'_> for TransferError { + fn nom_read(input: &'_ [u8]) -> tezos_nom::NomResult<'_, Self> { + let balance_too_low_parser = preceded(tag(0_u8.to_be_bytes()), |input| { + let (input, contract) = Contract::nom_read(input)?; + let (input, balance) = Narith::nom_read(input)?; + let (input, amount) = Narith::nom_read(input)?; + Ok(( + input, + Self::BalanceTooLow { + contract, + balance, + amount, + }, + )) + }); + let unspendable_contract_parser = preceded(tag(1_u8.to_be_bytes()), |input| { + let (input, contract) = Contract::nom_read(input)?; + Ok((input, Self::UnspendableContract(contract))) + }); + alt((balance_too_low_parser, unspendable_contract_parser))(input) + } +} + +#[derive(Debug, PartialEq, Eq, NomReader, BinWriter)] +pub enum ApplyOperationError { + Reveal(RevealError), + Transfer(TransferError), +} + +impl From for ApplyOperationError { + fn from(value: RevealError) -> Self { + Self::Reveal(value) + } +} + +impl From for ApplyOperationError { + fn from(value: TransferError) -> Self { + Self::Transfer(value) + } +} + +impl From for OperationError { + fn from(value: RevealError) -> Self { + Self::Apply(value.into()) + } +} + +impl From for OperationError { + fn from(value: TransferError) -> Self { + Self::Apply(value.into()) + } +} + #[derive(Debug, PartialEq, Eq, NomReader, BinWriter)] pub enum OperationError { Validation(ValidityError), diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 24bcd84d2211..7cc7148b8187 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -11,8 +11,8 @@ use tezos_smart_rollup::types::{PublicKey, PublicKeyHash}; use tezos_tezlink::{ operation::{ManagerOperation, Operation, OperationContent}, operation_result::{ - produce_operation_result, ApplyOperationError, ContentResult, OperationError, - OperationResult, OperationResultSum, Reveal, RevealSuccess, ValidityError, + produce_operation_result, ContentResult, OperationError, OperationResult, + OperationResultSum, Reveal, RevealError, RevealSuccess, ValidityError, }, }; use thiserror::Error; @@ -85,25 +85,20 @@ fn reveal( let expected_hash = match manager { Manager::Revealed(pk) => { - return Ok(Err(ApplyOperationError::PreviouslyRevealedKey(pk).into())) + return Ok(Err(RevealError::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() - )); + return Ok(Err(RevealError::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())); + return Ok(Err(RevealError::InconsistentPublicKey(expected_hash).into())); } // Set the public key as the manager @@ -180,7 +175,7 @@ mod tests { block::TezBlock, operation::{ManagerOperation, Operation, OperationContent}, operation_result::{ - ApplyOperationError, ContentResult, OperationResult, OperationResultSum, + ContentResult, OperationResult, OperationResultSum, RevealError, RevealSuccess, }, }; @@ -366,7 +361,7 @@ mod tests { let expected_receipt = OperationResultSum::Reveal(OperationResult { balance_updates: vec![], result: ContentResult::Failed(vec![OperationError::Apply( - ApplyOperationError::PreviouslyRevealedKey(pk), + RevealError::PreviouslyRevealedKey(pk).into(), )]), }); assert_eq!(receipt, expected_receipt); @@ -406,7 +401,7 @@ mod tests { let expected_receipt = OperationResultSum::Reveal(OperationResult { balance_updates: vec![], result: ContentResult::Failed(vec![OperationError::Apply( - ApplyOperationError::InconsistentHash(inconsistent_pkh), + RevealError::InconsistentHash(inconsistent_pkh).into(), )]), }); @@ -439,7 +434,7 @@ mod tests { let expected_receipt = OperationResultSum::Reveal(OperationResult { balance_updates: vec![], result: ContentResult::Failed(vec![OperationError::Apply( - ApplyOperationError::InconsistentPublicKey(src), + RevealError::InconsistentPublicKey(src).into(), )]), }); -- GitLab From 35f3007f3cd2987d171fe6b1b8a1253da3b71e6b Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 29 Apr 2025 17:13:26 +0200 Subject: [PATCH 08/10] Tezlink/Kernel: Implement transaction behavior crediting and debiting account implied in the transaction --- etherlink/kernel_latest/Cargo.lock | 1 + .../kernel_latest/tezos_execution/Cargo.toml | 1 + .../kernel_latest/tezos_execution/src/lib.rs | 116 ++++++++++++++++-- 3 files changed, 108 insertions(+), 10 deletions(-) diff --git a/etherlink/kernel_latest/Cargo.lock b/etherlink/kernel_latest/Cargo.lock index 64449cb6bdd2..cc783e76cd5a 100644 --- a/etherlink/kernel_latest/Cargo.lock +++ b/etherlink/kernel_latest/Cargo.lock @@ -2698,6 +2698,7 @@ dependencies = [ "hex", "nom", "num-bigint", + "num-traits", "primitive-types", "tezos-evm-logging-latest", "tezos-evm-runtime-latest", diff --git a/etherlink/kernel_latest/tezos_execution/Cargo.toml b/etherlink/kernel_latest/tezos_execution/Cargo.toml index 6c6340706b4e..27035bbcc73c 100644 --- a/etherlink/kernel_latest/tezos_execution/Cargo.toml +++ b/etherlink/kernel_latest/tezos_execution/Cargo.toml @@ -15,6 +15,7 @@ anyhow.workspace = true tezos_crypto_rs.workspace = true hex.workspace = true num-bigint.workspace = true +num-traits.workspace = true tezos-evm-runtime.workspace = true diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 7cc7148b8187..3245201a6b0c 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -3,16 +3,19 @@ // SPDX-License-Identifier: MIT use account_storage::{Manager, TezlinkImplicitAccount}; +use num_bigint::BigInt; +use num_traits::ops::checked::CheckedSub; use tezos_crypto_rs::{base58::FromBase58CheckError, PublicKeyWithHash}; -use tezos_data_encoding::types::Narith; +use tezos_data_encoding::types::{Narith, Zarith}; use tezos_evm_logging::{log, Level::*}; use tezos_evm_runtime::runtime::Runtime; -use tezos_smart_rollup::types::{PublicKey, PublicKeyHash}; +use tezos_smart_rollup::types::{Contract, PublicKey, PublicKeyHash}; use tezos_tezlink::{ operation::{ManagerOperation, Operation, OperationContent}, operation_result::{ - produce_operation_result, ContentResult, OperationError, OperationResult, - OperationResultSum, Reveal, RevealError, RevealSuccess, ValidityError, + produce_operation_result, Balance, BalanceUpdate, OperationError, + OperationResultSum, Reveal, RevealError, RevealSuccess, TransferError, + TransferSuccess, ValidityError, }, }; use thiserror::Error; @@ -21,6 +24,8 @@ extern crate alloc; pub mod account_storage; pub mod context; +type ExecutionResult = Result, ApplyKernelError>; + #[derive(Error, Debug, PartialEq, Eq)] pub enum ApplyKernelError { #[error("Apply operation failed on a storage manipulation {0}")] @@ -79,7 +84,7 @@ fn reveal( provided_hash: &PublicKeyHash, account: &mut TezlinkImplicitAccount, public_key: &PublicKey, -) -> Result, ApplyKernelError> { +) -> ExecutionResult { log!(host, Debug, "Applying a reveal operation"); let manager = account.manager(host)?; @@ -111,6 +116,95 @@ fn reveal( })) } +/// Function to apply a transfer by modifying balances of both account +fn apply_transfer( + host: &mut Host, + context: &context::Context, + src: &Contract, + amount: &Narith, + dest: &Contract, +) -> ExecutionResult { + let mut source = TezlinkImplicitAccount::from_contract(context, src)?; + let src_balance = source.balance(host)?; + + let new_source_balance = match src_balance.0.checked_sub(&amount.0) { + None => { + log!(host, Debug, "Balance is too low"); + let error = TransferError::BalanceTooLow { + contract: src.clone(), + balance: src_balance, + amount: amount.clone(), + }; + return Ok(Err(error.into())); + } + Some(new_source_balance) => new_source_balance, + }; + + // Allocate the destination (does nothing if it's already allocated) + let was_allocated = TezlinkImplicitAccount::allocate(host, context, dest)?; + + let mut destination = TezlinkImplicitAccount::from_contract(context, dest)?; + + let dest_balance = destination.balance(host)?; + + let new_destination_balance = &dest_balance.0 + &amount.0; + + // Set the new balance for source and destination + source.set_balance(host, &new_source_balance.into())?; + destination.set_balance(host, &new_destination_balance.into())?; + + Ok(Ok(was_allocated)) +} + +/// Function to handle the transfer case of a manager operation +fn transfer( + host: &mut Host, + context: &context::Context, + src: &PublicKeyHash, + amount: &Narith, + dest: &Contract, +) -> ExecutionResult { + log!(host, Debug, "Applying a transfer operation"); + + let src = Contract::Implicit(src.clone()); + + // Apply the transfer and return if the destination was already allocated + let allocated_destination_contract = + match apply_transfer(host, context, &src, amount, dest)? { + Err(err) => return Ok(Err(err)), + Ok(result) => result, + }; + + // Construct the transfer result + let source_update = BigInt::from_biguint(num_bigint::Sign::Minus, amount.into()); + let dest_update = BigInt::from_biguint(num_bigint::Sign::Plus, amount.into()); + + let balance_updates = vec![ + BalanceUpdate { + balance: Balance::Account(src.clone()), + changes: Zarith(source_update), + }, + BalanceUpdate { + balance: Balance::Account(dest.clone()), + changes: Zarith(dest_update), + }, + ]; + + let success = TransferSuccess { + storage: None, + lazy_storage_diff: None, + balance_updates, + 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, + }; + + Ok(Ok(success)) +} + pub fn apply_operation( host: &mut Host, context: &context::Context, @@ -155,11 +249,13 @@ pub fn apply_operation( let manager_result = produce_operation_result(reveal_result); OperationResultSum::Reveal(manager_result) } - OperationContent::Transfer { .. } => { - OperationResultSum::Transfer(OperationResult { - balance_updates: vec![], - result: ContentResult::Failed(vec![]), - }) + OperationContent::Transfer { + amount, + destination, + } => { + let transfer_result = transfer(host, context, source, amount, destination)?; + let manager_result = produce_operation_result(transfer_result); + OperationResultSum::Transfer(manager_result) } }; -- GitLab From 29e30d4bf80041f031ac61d47daf971233093e63 Mon Sep 17 00:00:00 2001 From: arnaud Date: Tue, 29 Apr 2025 17:49:08 +0200 Subject: [PATCH 09/10] Tezlink/Kernel/Tests: Test transfer implementation --- .../kernel_latest/tezos_execution/src/lib.rs | 188 ++++++++++++++++-- 1 file changed, 176 insertions(+), 12 deletions(-) diff --git a/etherlink/kernel_latest/tezos_execution/src/lib.rs b/etherlink/kernel_latest/tezos_execution/src/lib.rs index 3245201a6b0c..c738df4bc9b4 100644 --- a/etherlink/kernel_latest/tezos_execution/src/lib.rs +++ b/etherlink/kernel_latest/tezos_execution/src/lib.rs @@ -264,15 +264,17 @@ pub fn apply_operation( #[cfg(test)] mod tests { + use num_bigint::BigInt; use tezos_crypto_rs::hash::UnknownSignature; + use tezos_data_encoding::types::{Narith, Zarith}; 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, RevealError, - RevealSuccess, + Balance, BalanceUpdate, ContentResult, OperationResult, OperationResultSum, + RevealError, RevealSuccess, TransferError, TransferSuccess, }, }; @@ -281,13 +283,17 @@ mod tests { apply_operation, context, OperationError, ValidityError, }; - fn make_reveal_operation( + const BOOTSTRAP_1: &str = "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx"; + + const BOOTSTRAP_2: &str = "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN"; + + fn make_operation( fee: u64, counter: u64, gas_limit: u64, storage_limit: u64, source: PublicKeyHash, - pk: PublicKey, + content: OperationContent, ) -> Operation { let branch = TezBlock::genesis_block_hash(); // No need a real signature for now @@ -298,7 +304,7 @@ mod tests { source, fee: fee.into(), counter: counter.into(), - operation: OperationContent::Reveal { pk }, + operation: content, gas_limit: gas_limit.into(), storage_limit: storage_limit.into(), }, @@ -306,6 +312,46 @@ mod tests { } } + fn make_reveal_operation( + fee: u64, + counter: u64, + gas_limit: u64, + storage_limit: u64, + source: PublicKeyHash, + pk: PublicKey, + ) -> Operation { + make_operation( + fee, + counter, + gas_limit, + storage_limit, + source, + OperationContent::Reveal { pk }, + ) + } + + fn make_transfer_operation( + fee: u64, + counter: u64, + gas_limit: u64, + storage_limit: u64, + source: PublicKeyHash, + amount: Narith, + destination: Contract, + ) -> Operation { + make_operation( + fee, + counter, + gas_limit, + storage_limit, + source, + OperationContent::Transfer { + amount, + destination, + }, + ) + } + // This function setups an account that will pass the validity checks fn init_account( host: &mut impl Runtime, @@ -338,7 +384,7 @@ mod tests { fn apply_operation_empty_account() { let mut host = MockKernelHost::default(); - let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + let src = PublicKeyHash::from_b58check(BOOTSTRAP_1) .expect("PublicKeyHash b58 conversion should have succeed"); let pk = PublicKey::from_b58check( @@ -367,7 +413,7 @@ mod tests { fn apply_operation_cant_pay_fees() { let mut host = MockKernelHost::default(); - let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + let src = PublicKeyHash::from_b58check(BOOTSTRAP_1) .expect("PublicKeyHash b58 conversion should have succeed"); let _ = init_account(&mut host, &src); @@ -399,7 +445,7 @@ mod tests { fn apply_operation_invalid_counter() { let mut host = MockKernelHost::default(); - let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + let src = PublicKeyHash::from_b58check(BOOTSTRAP_1) .expect("PublicKeyHash b58 conversion should have succeed"); let _ = init_account(&mut host, &src); @@ -431,7 +477,7 @@ mod tests { fn apply_reveal_operation_on_already_revealed_account() { let mut host = MockKernelHost::default(); - let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + let src = PublicKeyHash::from_b58check(BOOTSTRAP_1) .expect("PublicKeyHash b58 conversion should have succeed"); let mut account = init_account(&mut host, &src); @@ -469,7 +515,7 @@ mod tests { fn apply_reveal_operation_with_an_inconsistent_manager() { let mut host = MockKernelHost::default(); - let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + let src = PublicKeyHash::from_b58check(BOOTSTRAP_1) .expect("PublicKeyHash b58 conversion should have succeed"); let mut account = init_account(&mut host, &src); @@ -509,7 +555,7 @@ mod tests { fn apply_reveal_operation_with_an_inconsistent_public_key() { let mut host = MockKernelHost::default(); - let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + let src = PublicKeyHash::from_b58check(BOOTSTRAP_1) .expect("PublicKeyHash b58 conversion should have succeed"); // Even if we don't use it we need to init the account @@ -542,7 +588,7 @@ mod tests { fn apply_reveal_operation() { let mut host = MockKernelHost::default(); - let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") + let src = PublicKeyHash::from_b58check(BOOTSTRAP_1) .expect("PublicKeyHash b58 conversion should have succeed"); let account = init_account(&mut host, &src); @@ -579,4 +625,122 @@ mod tests { assert_eq!(manager, Manager::Revealed(pk)); } + + // Test an invalid transfer operation, source has not enough balance to fullfil the Transfer + #[test] + fn apply_transfer_with_not_enough_balance() { + let mut host = MockKernelHost::default(); + + let src = PublicKeyHash::from_b58check(BOOTSTRAP_1) + .expect("PublicKeyHash b58 conversion should have succeed"); + + let dest = PublicKeyHash::from_b58check(BOOTSTRAP_2) + .expect("PublicKeyHash b58 conversion should have succeed"); + + // Setup accounts with 50 mutez in their balance + let source = init_account(&mut host, &src); + let destination = init_account(&mut host, &dest); + + let operation = make_transfer_operation( + 15, + 1, + 4, + 5, + src, + 100_u64.into(), + Contract::Implicit(dest), + ); + + 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::Transfer(OperationResult { + balance_updates: vec![], + result: ContentResult::Failed(vec![OperationError::Apply( + TransferError::BalanceTooLow { + contract: Contract::from_b58check(BOOTSTRAP_1).unwrap(), + balance: 50_u64.into(), + amount: 100_u64.into(), + } + .into(), + )]), + }); + + // Verify that source and destination balances are unchanged + assert_eq!(source.balance(&host).unwrap(), 50_u64.into()); + assert_eq!(destination.balance(&host).unwrap(), 50_u64.into()); + + assert_eq!(receipt, expected_receipt); + } + + // Bootstrap 1 successfully transfer 30 mutez to Bootstrap 2 + #[test] + fn apply_successful_transfer() { + let mut host = MockKernelHost::default(); + + let src = PublicKeyHash::from_b58check(BOOTSTRAP_1) + .expect("PublicKeyHash b58 conversion should have succeed"); + + let dest = PublicKeyHash::from_b58check(BOOTSTRAP_2) + .expect("PublicKeyHash b58 conversion should have succeed"); + + // Setup accounts with 50 mutez in their balance + let source = init_account(&mut host, &src); + let destination = init_account(&mut host, &dest); + + let operation = make_transfer_operation( + 15, + 1, + 4, + 5, + src, + 30_u64.into(), + Contract::Implicit(dest), + ); + + 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::Transfer(OperationResult { + balance_updates: vec![], + result: ContentResult::Applied(TransferSuccess { + storage: None, + lazy_storage_diff: None, + balance_updates: vec![ + BalanceUpdate { + balance: Balance::Account( + Contract::from_b58check(BOOTSTRAP_1).unwrap(), + ), + changes: Zarith(BigInt::from_biguint( + num_bigint::Sign::Minus, + 30_u64.into(), + )), + }, + BalanceUpdate { + balance: Balance::Account( + Contract::from_b58check(BOOTSTRAP_2).unwrap(), + ), + changes: Zarith(BigInt::from_biguint( + num_bigint::Sign::Plus, + 30_u64.into(), + )), + }, + ], + 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: true, + }), + }); + + // Verify that source and destination balances changed + assert_eq!(source.balance(&host).unwrap(), 20_u64.into()); + assert_eq!(destination.balance(&host).unwrap(), 80_u64.into()); + + assert_eq!(receipt, expected_receipt); + } } -- GitLab From 4c46d9f041e83b878e26c26ba97b79ceef43d879 Mon Sep 17 00:00:00 2001 From: arnaud Date: Wed, 30 Apr 2025 11:32:25 +0200 Subject: [PATCH 10/10] Tezlink/Kernel/Tests: Add a scenario test in the kernel --- etherlink/kernel_latest/kernel/src/block.rs | 177 ++++++++++++++++++-- 1 file changed, 166 insertions(+), 11 deletions(-) diff --git a/etherlink/kernel_latest/kernel/src/block.rs b/etherlink/kernel_latest/kernel/src/block.rs index b018027f5561..08d7018d2f67 100644 --- a/etherlink/kernel_latest/kernel/src/block.rs +++ b/etherlink/kernel_latest/kernel/src/block.rs @@ -624,6 +624,7 @@ mod tests { use primitive_types::{H160, U256}; use std::str::FromStr; use tezos_crypto_rs::hash::UnknownSignature; + use tezos_data_encoding::types::Narith; use tezos_ethereum::block::BlockFees; use tezos_ethereum::transaction::{ TransactionHash, TransactionStatus, TransactionType, TRANSACTION_HASH_SIZE, @@ -647,19 +648,20 @@ mod tests { } use tezos_smart_rollup_host::runtime::Runtime as SdkRuntime; + use tezos_tezlink::block::TezBlock; use tezos_tezlink::operation::ManagerOperation; use tezos_tezlink::operation::Operation; use tezos_tezlink::operation::OperationContent; - pub fn make_reveal_operation( + fn make_operation( fee: u64, counter: u64, gas_limit: u64, storage_limit: u64, source: PublicKeyHash, - pk: PublicKey, + content: OperationContent, ) -> Operation { - let branch = tezos_tezlink::block::TezBlock::genesis_block_hash(); + let branch = TezBlock::genesis_block_hash(); // No need a real signature for now let signature = UnknownSignature::from_base58_check("sigSPESPpW4p44JK181SmFCFgZLVvau7wsJVN85bv5ciigMu7WSRnxs9H2NydN5ecxKHJBQTudFPrUccktoi29zHYsuzpzBX").unwrap(); Operation { @@ -668,7 +670,7 @@ mod tests { source, fee: fee.into(), counter: counter.into(), - operation: OperationContent::Reveal { pk }, + operation: content, gas_limit: gas_limit.into(), storage_limit: storage_limit.into(), }, @@ -676,6 +678,46 @@ mod tests { } } + fn make_reveal_operation( + fee: u64, + counter: u64, + gas_limit: u64, + storage_limit: u64, + source: PublicKeyHash, + pk: PublicKey, + ) -> Operation { + make_operation( + fee, + counter, + gas_limit, + storage_limit, + source, + OperationContent::Reveal { pk }, + ) + } + + fn make_transaction_operation( + fee: u64, + counter: u64, + gas_limit: u64, + storage_limit: u64, + source: PublicKeyHash, + amount: Narith, + destination: Contract, + ) -> Operation { + make_operation( + fee, + counter, + gas_limit, + storage_limit, + source, + OperationContent::Transfer { + amount, + destination, + }, + ) + } + fn blueprint(transactions: Vec) -> Blueprint { Blueprint { transactions: Transactions::EthTxs(transactions), @@ -731,6 +773,9 @@ mod tests { const DUMMY_BASE_FEE_PER_GAS: u64 = MINIMUM_BASE_FEE_PER_GAS; const DUMMY_DA_FEE: u64 = DA_FEE_PER_BYTE; + const TEZLINK_BOOTSTRAP_1: &str = "tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx"; + const TEZLINK_BOOTSTRAP_2: &str = "tz1gjaF81ZRRvdzjobyfVNsAeSC6PScjfQwN"; + fn dummy_evm_config(evm_configuration: Config) -> EvmChainConfig { EvmChainConfig::create_config( DUMMY_CHAIN_ID, @@ -991,20 +1036,21 @@ mod tests { let chain_config = dummy_tez_config(); let mut config = dummy_configuration(); - let contract = Contract::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") - .expect("Contract creation should have succeeded"); - let context = context::Context::from(&TEZLINK_SAFE_STORAGE_ROOT_PATH) .expect("Context creation should have succeeded"); + let contract = Contract::from_b58check(TEZLINK_BOOTSTRAP_1) + .expect("Contract creation should have succeed"); + let account = TezlinkImplicitAccount::from_contract(&context, &contract) .expect("Account interface should be correct"); + // Allocate bootstrap 1 TezlinkImplicitAccount::allocate(&mut host, &context, &contract) .expect("Contract initialization should have succeeded"); - let src = PublicKeyHash::from_b58check("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx") - .expect("PublicKeyHash b58 conversion should have succeeded"); + let src = PublicKeyHash::from_b58check(TEZLINK_BOOTSTRAP_1) + .expect("PublicKeyHash b58 conversion should have succeed"); let pk = PublicKey::from_b58check( "edpkuBknW28nW72KG6RoHtYW7p12T6GKc7nAbwYX5m8Wd9sDVC9yav", @@ -1017,11 +1063,12 @@ mod tests { assert_eq!(Manager::NotRevealed(src.clone()), manager); - let operation = make_reveal_operation(0, 1, 0, 0, src, pk.clone()); + // Reveal bootstrap 1 manager + let reveal = make_reveal_operation(0, 1, 0, 0, src, pk.clone()); store_blueprints::<_, MichelsonChainConfig>( &mut host, - vec![tezlink_blueprint(vec![operation])], + vec![tezlink_blueprint(vec![reveal])], ); produce(&mut host, &chain_config, &mut config, None, None) @@ -1038,6 +1085,114 @@ mod tests { assert_eq!(Manager::Revealed(pk), manager); } + #[test] + // Test a scenario where bootstrap 1 reveal its manager and then send mutez to bootstrap 2 + fn test_produce_tezlink_block_with_reveal_and_transfer() { + let mut host = MockKernelHost::default(); + + let chain_config = dummy_tez_config(); + let mut config = dummy_configuration(); + + let context = context::Context::from(&TEZLINK_SAFE_STORAGE_ROOT_PATH) + .expect("Context creation should have succeed"); + + let bootstrap1_contract = Contract::from_b58check(TEZLINK_BOOTSTRAP_1) + .expect("Contract creation should have succeed"); + + let mut bootstrap1 = + TezlinkImplicitAccount::from_contract(&context, &bootstrap1_contract) + .expect("Account interface should be correct"); + + // Allocate bootstrap 1 and give some mutez for a transfer + TezlinkImplicitAccount::allocate(&mut host, &context, &bootstrap1_contract) + .expect("Contract initialization should have succeed"); + + bootstrap1 + .set_balance(&mut host, &50_u64.into()) + .expect("Set balance should have suceed"); + + // Drop the mutable access to bootstrap1 + let bootstrap1 = bootstrap1; + + let manager = bootstrap1 + .manager(&host) + .expect("Retrieve manager should have succeed"); + + // Verify that bootstrap 1 is not revealed + assert!(matches!(manager, Manager::NotRevealed(_))); + + let src = PublicKeyHash::from_b58check(TEZLINK_BOOTSTRAP_1) + .expect("PublicKeyHash b58 conversion should have succeed"); + + let pk = PublicKey::from_b58check( + "edpkuBknW28nW72KG6RoHtYW7p12T6GKc7nAbwYX5m8Wd9sDVC9yav", + ) + .expect("Public key creation should have succeed"); + + // Reveal bootstrap 1 manager + let reveal = make_reveal_operation(0, 1, 0, 0, src.clone(), pk.clone()); + + // Bootstrap 1 will transfer 35 mutez to bootstrap 2 + + let bootstrap2_contract = Contract::from_b58check(TEZLINK_BOOTSTRAP_2) + .expect("Contract creation should have succeed"); + + let bootstrap2 = + TezlinkImplicitAccount::from_contract(&context, &bootstrap2_contract) + .expect("Contract creation should have succeed"); + + // Verify that bootstrap 2 is not allocated + assert!(!bootstrap2 + .allocated(&host) + .expect("Checking allocation should have succeed")); + + // Bootstrap 1 transfer 35 mutez to bootstrap 2 + let transfer = make_transaction_operation( + 0, + 1, + 0, + 0, + src.clone(), + 35_u64.into(), + bootstrap2_contract, + ); + + // Bootstrap 1 reveals its manager and then + store_blueprints::<_, MichelsonChainConfig>( + &mut host, + vec![tezlink_blueprint(vec![reveal, transfer])], + ); + + produce(&mut host, &chain_config, &mut config, None, None) + .expect("The block production should have succeeded."); + let computation = produce(&mut host, &chain_config, &mut config, None, None) + .expect("The block production should have succeeded."); + assert_eq!(ComputationResult::Finished, computation); + assert_eq!(U256::from(0), read_current_number(&host).unwrap()); + + // Bootstrap 1 should be revealed + + let manager = bootstrap1 + .manager(&host) + .expect("Retrieve manager should have succeed"); + + assert_eq!(Manager::Revealed(pk), manager); + + // Bootstrap 2 should be allocated + assert!(bootstrap2 + .allocated(&host) + .expect("Checking allocation should have succeed")); + + // Bootstrap 1 should have sent 35 mutez to Bootstrap 2 + let bootstrap1_balance = bootstrap1.balance(&host).unwrap(); + + assert_eq!(bootstrap1_balance, 15_u64.into()); + + let bootstrap2_balance = bootstrap2.balance(&host).unwrap(); + + assert_eq!(bootstrap2_balance, 35_u64.into()); + } + #[test] // Test if the invalid transactions are producing receipts fn test_invalid_transactions_receipt_status() { -- GitLab