From 12468802c32b7b9502b391aedfbd2d5c82884b07 Mon Sep 17 00:00:00 2001 From: ztepler Date: Wed, 12 Jun 2024 14:49:36 +0300 Subject: [PATCH] EVM: refactor xtz native withdrawals Removed `Withdrawal` struct from the `ethereum` crate that used to keep withdrawals after precompile execution. Replaced it with a `Withdrawal` type alias for `OutboxMessage`. Moved the `post_withdrawal` logic to the withdrawal_precompile. These modifications are necessary to support FA withdrawals in future merge requests. --- etherlink/CHANGES_KERNEL.md | 1 + etherlink/kernel_evm/Cargo.lock | 2 +- etherlink/kernel_evm/ethereum/Cargo.toml | 1 - etherlink/kernel_evm/ethereum/src/lib.rs | 1 - etherlink/kernel_evm/ethereum/src/wei.rs | 12 ++ .../kernel_evm/ethereum/src/withdrawal.rs | 33 ---- .../kernel_evm/evm_execution/src/handler.rs | 11 +- etherlink/kernel_evm/evm_execution/src/lib.rs | 14 +- .../evm_execution/src/precompiles/mod.rs | 13 +- .../src/precompiles/withdrawal.rs | 164 +++++++++++++----- .../src/transaction_layer_data.rs | 2 +- etherlink/kernel_evm/kernel/Cargo.toml | 2 + etherlink/kernel_evm/kernel/src/apply.rs | 85 +-------- etherlink/kernel_evm/kernel/src/block.rs | 8 - .../kernel_evm/kernel/src/configuration.rs | 3 +- etherlink/kernel_evm/kernel/src/lib.rs | 143 +++++++++++++++ etherlink/kernel_evm/kernel/src/storage.rs | 6 - 17 files changed, 324 insertions(+), 177 deletions(-) delete mode 100644 etherlink/kernel_evm/ethereum/src/withdrawal.rs diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index 00b411f9ed88..7f2bc0caf9f3 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -14,6 +14,7 @@ - Add FA deposit structure and helper methods for its parsing and formatting. (!13720) - Add ticket table to account for FA deposits. (!12072) +- Refactor withdrawals handling to keep `OutboxMessage` in the `ExecutionOutcome`. (!13751) - Compress h256 hash when encoding. Transaction encoded with `r` or `s` hash compressed were impacted. (!13654) diff --git a/etherlink/kernel_evm/Cargo.lock b/etherlink/kernel_evm/Cargo.lock index 1512081eab0a..b1df97feeda2 100644 --- a/etherlink/kernel_evm/Cargo.lock +++ b/etherlink/kernel_evm/Cargo.lock @@ -640,6 +640,7 @@ dependencies = [ "hex", "libsecp256k1", "num-traits", + "pretty_assertions", "primitive-types", "proptest", "rlp", @@ -2251,7 +2252,6 @@ dependencies = [ "sha3", "tezos-smart-rollup-encoding", "tezos_crypto_rs", - "tezos_data_encoding", "thiserror", ] diff --git a/etherlink/kernel_evm/ethereum/Cargo.toml b/etherlink/kernel_evm/ethereum/Cargo.toml index 657fd964775b..289a3cd4682a 100644 --- a/etherlink/kernel_evm/ethereum/Cargo.toml +++ b/etherlink/kernel_evm/ethereum/Cargo.toml @@ -25,7 +25,6 @@ bytes.workspace = true sha3.workspace = true tezos_crypto_rs.workspace = true -tezos_data_encoding = "0.5" libsecp256k1.workspace = true tezos-smart-rollup-encoding.workspace = true diff --git a/etherlink/kernel_evm/ethereum/src/lib.rs b/etherlink/kernel_evm/ethereum/src/lib.rs index 8d0c0299a572..8dafd07e6d60 100644 --- a/etherlink/kernel_evm/ethereum/src/lib.rs +++ b/etherlink/kernel_evm/ethereum/src/lib.rs @@ -11,7 +11,6 @@ pub mod transaction; pub mod tx_common; pub mod tx_signature; pub mod wei; -pub mod withdrawal; pub use ethbloom::{Bloom, Input}; pub use ethereum::Log; diff --git a/etherlink/kernel_evm/ethereum/src/wei.rs b/etherlink/kernel_evm/ethereum/src/wei.rs index 4febc7fc300a..e447e0175304 100644 --- a/etherlink/kernel_evm/ethereum/src/wei.rs +++ b/etherlink/kernel_evm/ethereum/src/wei.rs @@ -1,4 +1,5 @@ // SPDX-FileCopyrightText: 2023 Nomadic Labs +// SPDX-FileCopyrightText: 2024 PK Lab // // SPDX-License-Identifier: MIT @@ -16,3 +17,14 @@ pub fn eth_from_mutez(mutez: u64) -> Wei { // Mutez is 10^6, Wei is 10^18 U256::from(mutez) * U256::exp10(12) } + +pub fn mutez_from_wei(wei: Wei) -> Result { + // Wei is 10^18, Mutez is 10^6 + let amount: U256 = U256::checked_div(wei, U256::exp10(12)).unwrap(); + + if amount < U256::from(u64::max_value()) { + Ok(amount.as_u64()) + } else { + Err(primitive_types::Error::Overflow) + } +} diff --git a/etherlink/kernel_evm/ethereum/src/withdrawal.rs b/etherlink/kernel_evm/ethereum/src/withdrawal.rs deleted file mode 100644 index c60f145ffefa..000000000000 --- a/etherlink/kernel_evm/ethereum/src/withdrawal.rs +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-FileCopyrightText: 2023 TriliTech -// -// SPDX-License-Identifier: MIT - -//! Withdrawals to layer 1 from the EVM kernel - -use primitive_types::U256; -use tezos_data_encoding::nom::NomReader; -use tezos_smart_rollup_encoding::contract::Contract; - -/// A single withdrawal from the rollup to an account on layer one. -#[derive(Debug, Eq, PartialEq)] -pub struct Withdrawal { - /// The target address on layer one. - pub target: Contract, - /// The amount in wei we wish to transfer. This has to be - /// translated into CTEZ or whatever currency is used for - /// paying for L2XTZ. - pub amount: U256, -} - -impl Withdrawal { - /// De-serialize a contract address from bytes (binary format). - pub fn address_from_bytes(bytes: &[u8]) -> Option { - Some(Contract::nom_read(bytes).ok()?.1) - } - - /// De-serialize a contract address from string given as bytes - /// (use case). The bytes present the address in textual format. - pub fn address_from_str(s: &str) -> Option { - Contract::from_b58check(s).ok() - } -} diff --git a/etherlink/kernel_evm/evm_execution/src/handler.rs b/etherlink/kernel_evm/evm_execution/src/handler.rs index 11b43d8e5ce7..067ff0b182f4 100644 --- a/etherlink/kernel_evm/evm_execution/src/handler.rs +++ b/etherlink/kernel_evm/evm_execution/src/handler.rs @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2022-2024 TriliTech // SPDX-FileCopyrightText: 2023-2024 Functori +// SPDX-FileCopyrightText: 2023-2024 PK Lab // // SPDX-License-Identifier: MIT @@ -37,8 +38,10 @@ use sha3::{Digest, Keccak256}; use std::cmp::min; use std::fmt::Debug; use tezos_ethereum::block::BlockConstants; -use tezos_ethereum::withdrawal::Withdrawal; use tezos_evm_logging::{log, Level::*}; +use tezos_smart_rollup_encoding::michelson::ticket::FA2_1Ticket; +use tezos_smart_rollup_encoding::michelson::{MichelsonContract, MichelsonPair}; +use tezos_smart_rollup_encoding::outbox::OutboxMessage; use tezos_smart_rollup_storage::StorageError; /// Extends ExitReason with our own errors. It avoids using @@ -55,6 +58,12 @@ impl From for ExtendedExitReason { } } +/// Withdrawal interface of the ticketer contract +pub type RouterInterface = MichelsonPair; + +/// Outbox message that implements RouterInterface, ready to be encoded and posted +pub type Withdrawal = OutboxMessage; + /// Outcome of making the [EvmHandler] run an Ethereum transaction /// /// Be it contract -call, -create or simple transfer, the handler will update the world diff --git a/etherlink/kernel_evm/evm_execution/src/lib.rs b/etherlink/kernel_evm/evm_execution/src/lib.rs index 2308cd0450bf..a83a99548bac 100644 --- a/etherlink/kernel_evm/evm_execution/src/lib.rs +++ b/etherlink/kernel_evm/evm_execution/src/lib.rs @@ -10,10 +10,11 @@ use account_storage::{AccountStorageError, EthereumAccountStorage}; use alloc::borrow::Cow; use alloc::collections::TryReserveError; +use crypto::hash::{ContractKt1Hash, HashTrait}; use evm::executor::stack::PrecompileFailure; use evm::ExitReason; use handler::EvmHandler; -use host::runtime::Runtime; +use host::{path::RefPath, runtime::Runtime}; use primitive_types::{H160, U256}; use tezos_ethereum::block::BlockConstants; use tezos_evm_logging::{log, Level::*}; @@ -269,6 +270,17 @@ where } } +pub const NATIVE_TOKEN_TICKETER_PATH: RefPath = + RefPath::assert_from(b"/evm/world_state/ticketer"); + +/// Reads the ticketer address set by the installer, if any, encoded in b58. +pub fn read_ticketer(host: &impl Runtime) -> Option { + let ticketer = host.store_read_all(&NATIVE_TOKEN_TICKETER_PATH).ok()?; + + let kt1_b58 = String::from_utf8(ticketer.to_vec()).ok()?; + ContractKt1Hash::from_b58check(&kt1_b58).ok() +} + #[cfg(test)] mod test { use crate::account_storage::EthereumAccount; diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles/mod.rs b/etherlink/kernel_evm/evm_execution/src/precompiles/mod.rs index aa3c7495ac06..59a048a73efb 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles/mod.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles/mod.rs @@ -21,7 +21,7 @@ mod modexp; mod withdrawal; mod zero_knowledge; -use crate::handler::EvmHandler; +use crate::handler::{EvmHandler, Withdrawal}; use crate::EthereumError; use alloc::collections::btree_map::BTreeMap; use blake2::blake2f_precompile; @@ -32,7 +32,6 @@ use host::runtime::Runtime; use identity::identity_precompile; use modexp::modexp_precompile; use primitive_types::H160; -use tezos_ethereum::withdrawal::Withdrawal; use withdrawal::withdrawal_precompile; use zero_knowledge::{ecadd_precompile, ecmul_precompile, ecpairing_precompile}; @@ -242,8 +241,10 @@ mod test_helpers { use crate::handler::EvmHandler; use crate::handler::ExecutionOutcome; use crate::EthereumError; + use crate::NATIVE_TOKEN_TICKETER_PATH; use evm::Config; use evm::Transfer; + use host::runtime::Runtime; use primitive_types::{H160, U256}; use tezos_ethereum::block::BlockConstants; use tezos_ethereum::block::BlockFees; @@ -251,6 +252,7 @@ mod test_helpers { use super::precompile_set; const DUMMY_ALLOCATED_TICKS: u64 = 100_000_000; + pub const DUMMY_TICKETER: &str = "KT1TxqZ8QtKvLu3V3JH7Gx58n7Co8pgtpQU5"; pub fn set_balance( host: &mut MockHost, @@ -298,6 +300,8 @@ mod test_helpers { let config = Config::shanghai(); let gas_price = U256::from(21000); + set_ticketer(&mut mock_runtime, DUMMY_TICKETER); + if let Some(Transfer { source, value, .. }) = transfer { set_balance( &mut mock_runtime, @@ -336,4 +340,9 @@ mod test_helpers { is_static, ) } + + fn set_ticketer(host: &mut MockHost, address: &str) { + host.store_write(&NATIVE_TOKEN_TICKETER_PATH, address.as_bytes(), 0) + .unwrap(); + } } diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs b/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs index 7c0a19d36fb2..76ee51370691 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs @@ -1,24 +1,52 @@ // SPDX-FileCopyrightText: 2022-2023 TriliTech +// SPDX-FileCopyrightText: 2023-2024 PK Lab // SPDX-FileCopyrightText: 2024 Functori // // SPDX-License-Identifier: MIT use crate::handler::EvmHandler; +use crate::handler::RouterInterface; +use crate::handler::Withdrawal; use crate::precompiles::tick_model; use crate::precompiles::PrecompileOutcome; +use crate::read_ticketer; use crate::{abi, fail_if_too_much, EthereumError}; use evm::{Context, ExitReason, ExitRevert, ExitSucceed, Transfer}; use host::runtime::Runtime; -use primitive_types::U256; -use tezos_ethereum::withdrawal::Withdrawal; +use tezos_ethereum::wei::mutez_from_wei; use tezos_evm_logging::log; use tezos_evm_logging::Level::Info; +use tezos_smart_rollup_encoding::contract::Contract; +use tezos_smart_rollup_encoding::entrypoint::Entrypoint; +use tezos_smart_rollup_encoding::michelson::ticket::FA2_1Ticket; +use tezos_smart_rollup_encoding::michelson::{ + MichelsonContract, MichelsonOption, MichelsonPair, +}; +use tezos_smart_rollup_encoding::outbox::{OutboxMessage, OutboxMessageTransaction}; /// Cost of doing a withdrawal. A valid call to this precompiled contract /// takes almost 880000 ticks, and one gas unit takes 1000 ticks. /// The ticks/gas ratio is from benchmarks on `ecrecover`. const WITHDRAWAL_COST: u64 = 880; +fn prepare_message( + parameters: RouterInterface, + destination: Contract, + entrypoint: Option<&str>, +) -> Option { + let entrypoint = + Entrypoint::try_from(String::from(entrypoint.unwrap_or("default"))).ok()?; + + let message = OutboxMessageTransaction { + parameters, + entrypoint, + destination, + }; + + let outbox_message = OutboxMessage::AtomicTransactionBatch(vec![message].into()); + Some(outbox_message) +} + /// Implementation of Etherlink specific withdrawals precompiled contract. pub fn withdrawal_precompile( handler: &mut EvmHandler, @@ -57,44 +85,64 @@ pub fn withdrawal_precompile( return Ok(revert_withdrawal()) }; - let minimal_amount = U256::from(10).pow(U256::from(12)); - if transfer.value < minimal_amount { - log!( - handler.borrow_host(), - Info, - "Withdrawal precompiled contract: transfer less than 1mutez" - ); - return Ok(revert_withdrawal()); - } - match input { - [0xcd, 0xa4, 0xfe, 0xe2, rest @ ..] => { - let Some(address_str) = abi::string_parameter(rest, 0) else { + // "cda4fee2" is the function selector for `withdraw_base58(string)` + [0xcd, 0xa4, 0xfe, 0xe2, input_data @ ..] => { + let Some(address_str) = abi::string_parameter(input_data, 0) else { log!(handler.borrow_host(), Info, "Withdrawal precompiled contract: unable to get address argument"); return Ok(revert_withdrawal()) }; + let Some(target) = Contract::from_b58check(address_str).ok() else { + log!(handler.borrow_host(), Info, "Withdrawal precompiled contract: invalid target address string"); + return Ok(revert_withdrawal()) + }; + + let Some(amount) = mutez_from_wei(transfer.value).ok() else { + log!(handler.borrow_host(), Info, "Withdrawal precompiled contract: amount is too large"); + return Ok(revert_withdrawal()) + }; + log!( handler.borrow_host(), Info, - "Withdrawal to {:?}", - address_str + "Withdrawal of {} to {:?}", + amount, + target ); - let Some(target) = Withdrawal::address_from_str(address_str) else { - log!(handler.borrow_host(), Info, "Withdrawal precompiled contract: invalid target address string"); + let Some(ticketer) = read_ticketer(handler.borrow_host()) else { + log!(handler.borrow_host(), Info, "Withdrawal precompiled contract: failed to read ticketer"); + return Ok(revert_withdrawal()) + }; + + let Some(ticket) = FA2_1Ticket::new( + Contract::Originated(ticketer.clone()), + MichelsonPair(0.into(), MichelsonOption(None)), + amount, + ).ok() else { + log!(handler.borrow_host(), Info, "Withdrawal precompiled contract: ticket amount is invalid"); return Ok(revert_withdrawal()) }; - // TODO Check that the outbox ain't full yet + let parameters = MichelsonPair::( + MichelsonContract(target), + ticket, + ); + + let Some(message) = prepare_message( + parameters, + Contract::Originated(ticketer), + Some("burn"), + ) else { + log!(handler.borrow_host(), Info, "Withdrawal precompiled contract: failed to encode outbox message"); + return Ok(revert_withdrawal()); + }; // TODO we need to measure number of ticks and translate this number into // Ethereum gas units - let withdrawals = vec![Withdrawal { - target, - amount: transfer.value, - }]; + let withdrawals = vec![message]; Ok(PrecompileOutcome { exit_status: ExitReason::Succeed(ExitSucceed::Returned), @@ -118,14 +166,43 @@ pub fn withdrawal_precompile( #[cfg(test)] mod tests { use crate::{ - handler::ExecutionOutcome, - precompiles::{test_helpers::execute_precompiled, withdrawal::WITHDRAWAL_COST}, + handler::{ExecutionOutcome, Withdrawal}, + precompiles::{ + test_helpers::{execute_precompiled, DUMMY_TICKETER}, + withdrawal::WITHDRAWAL_COST, + }, }; use evm::{ExitReason, ExitRevert, ExitSucceed, Transfer}; + use pretty_assertions::assert_eq; use primitive_types::{H160, U256}; use std::str::FromStr; - use tezos_ethereum::withdrawal::Withdrawal; + use tezos_ethereum::wei::eth_from_mutez; use tezos_smart_rollup_encoding::contract::Contract; + use tezos_smart_rollup_encoding::michelson::ticket::FA2_1Ticket; + use tezos_smart_rollup_encoding::michelson::{ + MichelsonContract, MichelsonOption, MichelsonPair, + }; + + use super::prepare_message; + + fn make_message(ticketer: &str, target: &str, amount: u64) -> Withdrawal { + let target = Contract::from_b58check(target).unwrap(); + let ticketer = Contract::from_b58check(ticketer).unwrap(); + + let ticket = FA2_1Ticket::new( + ticketer.clone(), + MichelsonPair(0.into(), MichelsonOption(None)), + amount, + ) + .unwrap(); + + let parameters = MichelsonPair::( + MichelsonContract(target), + ticket, + ); + + prepare_message(parameters, ticketer, Some("burn")).unwrap() + } #[test] fn call_withdraw_with_implicit_address() { @@ -136,6 +213,7 @@ mod tests { // 4. A Layer 1 contract address, hex-encoded // 5. Zero padding for hex-encoded address + // cast calldata "withdraw_base58(string)" "tz1RjtZUVeLhADFHDL8UwDZA6vjWWhojpu5w": let input: &[u8] = &hex::decode( "cda4fee2\ 0000000000000000000000000000000000000000000000000000000000000020\ @@ -147,19 +225,22 @@ mod tests { let source = H160::from_low_u64_be(118u64); let target = H160::from_str("ff00000000000000000000000000000000000001").unwrap(); - let value = U256::from(10).pow(U256::from(13)); + let value_mutez = 10; let transfer = Some(Transfer { source, target, - value, + value: eth_from_mutez(value_mutez), }); let result = execute_precompiled(target, input, transfer, Some(25000)); let expected_output = vec![]; - let expected_target = - Contract::from_b58check("tz1RjtZUVeLhADFHDL8UwDZA6vjWWhojpu5w").unwrap(); + let message = make_message( + DUMMY_TICKETER, + "tz1RjtZUVeLhADFHDL8UwDZA6vjWWhojpu5w", + value_mutez, + ); let expected_gas = 21000 // base cost, no additional cost for withdrawal + 1032 // transaction data cost (90 zero bytes + 42 non zero bytes) @@ -172,10 +253,7 @@ mod tests { new_address: None, logs: vec![], result: Some(expected_output), - withdrawals: vec![Withdrawal { - target: expected_target, - amount: value, - }], + withdrawals: vec![message], estimated_ticks_used: 880_000, }; @@ -202,20 +280,22 @@ mod tests { let source = H160::from_low_u64_be(118u64); let target = H160::from_str("ff00000000000000000000000000000000000001").unwrap(); - let value = U256::from(10).pow(U256::from(13)); + let value_mutez = 10; let transfer = Some(Transfer { source, target, - value, + value: eth_from_mutez(value_mutez), }); let result = execute_precompiled(target, input, transfer, Some(25000)); let expected_output = vec![]; - - let expected_target = - Contract::from_b58check("KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc").unwrap(); + let message = make_message( + DUMMY_TICKETER, + "KT1BuEZtb68c1Q4yjtckcNjGELqWt56Xyesc", + value_mutez, + ); let expected_gas = 21000 // base cost, no additional cost for withdrawal + 1032 // transaction data cost (90 zero bytes + 42 non zero bytes) @@ -228,10 +308,7 @@ mod tests { new_address: None, logs: vec![], result: Some(expected_output), - withdrawals: vec![Withdrawal { - target: expected_target, - amount: value, - }], + withdrawals: vec![message], // TODO (#6426): estimate the ticks consumption of precompiled contracts estimated_ticks_used: 880_000, }; @@ -251,7 +328,6 @@ mod tests { .unwrap(); // 1. Fails with no transfer - let target = H160::from_str("ff00000000000000000000000000000000000001").unwrap(); let transfer: Option = None; diff --git a/etherlink/kernel_evm/evm_execution/src/transaction_layer_data.rs b/etherlink/kernel_evm/evm_execution/src/transaction_layer_data.rs index 12221202b450..956892ab3004 100644 --- a/etherlink/kernel_evm/evm_execution/src/transaction_layer_data.rs +++ b/etherlink/kernel_evm/evm_execution/src/transaction_layer_data.rs @@ -4,12 +4,12 @@ // SPDX-License-Identifier: MIT use crate::access_record::AccessRecord; +use crate::handler::Withdrawal; use evm::executor::stack::Log; use evm::gasometer::Gasometer; use evm::Config; use primitive_types::H160; use std::collections::BTreeSet; -use tezos_ethereum::withdrawal::Withdrawal; /// Data related to the current transaction layer pub struct TransactionLayerData<'config> { diff --git a/etherlink/kernel_evm/kernel/Cargo.toml b/etherlink/kernel_evm/kernel/Cargo.toml index 3cabb2d577f5..2d287d70d8c4 100644 --- a/etherlink/kernel_evm/kernel/Cargo.toml +++ b/etherlink/kernel_evm/kernel/Cargo.toml @@ -64,6 +64,8 @@ proptest.workspace = true # Generally getrandom is not supposed to be used in wasm env, this trick is just to overcome build errors getrandom = { version = "=0.2.15", features = ["custom"] } +pretty_assertions.workspace = true + [features] default = ["panic-hook"] panic-hook = [] diff --git a/etherlink/kernel_evm/kernel/src/apply.rs b/etherlink/kernel_evm/kernel/src/apply.rs index f3e0582b7bfd..7d50bba365f5 100644 --- a/etherlink/kernel_evm/kernel/src/apply.rs +++ b/etherlink/kernel_evm/kernel/src/apply.rs @@ -2,6 +2,7 @@ // SPDX-FileCopyrightText: 2023 Functori // SPDX-FileCopyrightText: 2022-2024 TriliTech // SPDX-FileCopyrightText: 2023 Nomadic Labs +// SPDX-FileCopyrightText: 2023-2024 PK Lab // // SPDX-License-Identifier: MIT @@ -10,28 +11,18 @@ use evm::{ExitError, ExitReason, ExitSucceed}; use evm_execution::account_storage::{ account_path, EthereumAccount, EthereumAccountStorage, }; -use evm_execution::handler::{ExecutionOutcome, ExtendedExitReason}; +use evm_execution::handler::{ExecutionOutcome, ExtendedExitReason, RouterInterface}; use evm_execution::precompiles::PrecompileBTreeMap; use evm_execution::run_transaction; use evm_execution::trace::{TracerConfig, TracerInput}; use primitive_types::{H160, U256}; -use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_ethereum::block::BlockConstants; use tezos_ethereum::transaction::{TransactionHash, TransactionType}; use tezos_ethereum::tx_common::EthereumTransactionCommon; use tezos_ethereum::tx_signature::TxSignature; -use tezos_ethereum::withdrawal::Withdrawal; use tezos_evm_logging::{log, Level::*}; use tezos_indexable_storage::IndexableStorage; -use tezos_smart_rollup::outbox::OutboxQueue; -use tezos_smart_rollup_encoding::contract::Contract; -use tezos_smart_rollup_encoding::entrypoint::Entrypoint; -use tezos_smart_rollup_encoding::michelson::ticket::{FA2_1Ticket, Ticket}; -use tezos_smart_rollup_encoding::michelson::{ - MichelsonContract, MichelsonOption, MichelsonPair, -}; -use tezos_smart_rollup_encoding::outbox::OutboxMessage; -use tezos_smart_rollup_encoding::outbox::OutboxMessageTransaction; +use tezos_smart_rollup::outbox::{OutboxMessage, OutboxQueue}; use tezos_smart_rollup_host::path::{Path, RefPath}; use tezos_smart_rollup_host::runtime::Runtime; @@ -474,66 +465,6 @@ fn apply_deposit( pub const WITHDRAWAL_OUTBOX_QUEUE: RefPath = RefPath::assert_from(b"/evm/world_state/__outbox_queue"); -fn post_withdrawals( - host: &mut Host, - outbox_queue: &OutboxQueue<'_, impl Path>, - withdrawals: &Vec, - ticketer: &Option, -) -> Result<(), Error> { - if withdrawals.is_empty() { - return Ok(()); - }; - - let destination = match ticketer { - Some(x) => Contract::Originated(x.clone()), - None => return Err(Error::InvalidParsing), - }; - let entrypoint = Entrypoint::try_from(String::from("burn"))?; - - for withdrawal in withdrawals { - // Wei is 10^18, whereas mutez is 10^6. - let amount: U256 = - U256::checked_div(withdrawal.amount, U256::from(10).pow(U256::from(12))) - // If we reach the unwrap_or it will fail at the next step because - // we cannot create a ticket with no amount. But by construction - // it should not happen, we do not divide by 0. - .unwrap_or(U256::zero()); - - let amount = if amount < U256::from(u64::max_value()) { - amount.as_u64() - } else { - // Users can withdraw only mutez, converted to ETH, thus the - // maximum value of `amount` is `Int64.max_int` which fit - // in a u64. - return Err(Error::InvalidConversion); - }; - - let ticket: FA2_1Ticket = Ticket::new( - destination.clone(), - MichelsonPair(0.into(), MichelsonOption(None)), - amount, - )?; - let parameters = MichelsonPair::( - MichelsonContract(withdrawal.target.clone()), - ticket, - ); - - let withdrawal = OutboxMessageTransaction { - parameters, - entrypoint: entrypoint.clone(), - destination: destination.clone(), - }; - - let outbox_message = - OutboxMessage::AtomicTransactionBatch(vec![withdrawal].into()); - - let len = outbox_queue.queue_message(host, outbox_message)?; - log!(host, Debug, "Length of the outbox queue: {}", len); - } - - Ok(()) -} - pub struct ExecutionInfo { pub receipt_info: TransactionReceiptInfo, pub object_info: TransactionObjectInfo, @@ -566,7 +497,6 @@ pub fn handle_transaction_result( accounts_index: &mut IndexableStorage, transaction_result: TransactionResult, pay_fees: bool, - ticketer: &Option, sequencer_pool_address: Option, ) -> Result { let TransactionResult { @@ -587,7 +517,11 @@ pub fn handle_transaction_result( log!(host, Benchmarking, "gas_used: {:?}", outcome.gas_used); log!(host, Benchmarking, "reason: {:?}", outcome.reason); fee_updates.modify_outcome(outcome); - post_withdrawals(host, outbox_queue, &outcome.withdrawals, ticketer)? + for message in outcome.withdrawals.drain(..) { + let outbox_message: OutboxMessage = message; + let len = outbox_queue.queue_message(host, outbox_message)?; + log!(host, Debug, "Length of the outbox queue: {}", len); + } } if pay_fees { @@ -645,7 +579,6 @@ pub fn apply_transaction( accounts_index: &mut IndexableStorage, allocated_ticks: u64, retriable: bool, - ticketer: &Option, sequencer_pool_address: Option, limits: &Limits, trace_input: &Option, @@ -695,7 +628,6 @@ pub fn apply_transaction( accounts_index, tx_result, true, - ticketer, sequencer_pool_address, )?; Ok(ExecutionResult::Valid(execution_result)) @@ -713,7 +645,6 @@ pub fn apply_transaction( accounts_index, tx_result, false, - ticketer, sequencer_pool_address, )?; Ok(ExecutionResult::Retriable(execution_result)) diff --git a/etherlink/kernel_evm/kernel/src/block.rs b/etherlink/kernel_evm/kernel/src/block.rs index ec87a0170a55..13808f915a3a 100644 --- a/etherlink/kernel_evm/kernel/src/block.rs +++ b/etherlink/kernel_evm/kernel/src/block.rs @@ -27,7 +27,6 @@ use evm_execution::precompiles; use evm_execution::precompiles::PrecompileBTreeMap; use evm_execution::trace::TracerInput; use primitive_types::{H160, H256, U256}; -use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_ethereum::block::BlockFees; use tezos_evm_logging::{log, Level::*}; use tezos_indexable_storage::IndexableStorage; @@ -75,7 +74,6 @@ fn compute( evm_account_storage: &mut EthereumAccountStorage, accounts_index: &mut IndexableStorage, is_first_block_of_reboot: bool, - ticketer: &Option, sequencer_pool_address: Option, limits: &Limits, trace_input: &Option, @@ -134,7 +132,6 @@ fn compute( accounts_index, allocated_ticks, retriable, - ticketer, sequencer_pool_address, limits, trace_input, @@ -252,7 +249,6 @@ fn compute_bip( accounts_index: &mut IndexableStorage, tick_counter: &mut TickCounter, first_block_of_reboot: &mut bool, - ticketer: &Option, sequencer_pool_address: Option, limits: &Limits, trace_input: &Option, @@ -266,7 +262,6 @@ fn compute_bip( evm_account_storage, accounts_index, *first_block_of_reboot, - ticketer, sequencer_pool_address, limits, trace_input, @@ -425,7 +420,6 @@ pub fn produce( &mut accounts_index, &mut tick_counter, &mut first_block_of_reboot, - &config.tezos_contracts.ticketer, sequencer_pool_address, &config.limits, &trace_input, @@ -504,7 +498,6 @@ pub fn produce( &mut accounts_index, &mut tick_counter, &mut first_block_of_reboot, - &config.tezos_contracts.ticketer, sequencer_pool_address, &config.limits, &trace_input, @@ -1382,7 +1375,6 @@ mod tests { &mut evm_account_storage, &mut accounts_index, true, - &None, None, &limits, &None, diff --git a/etherlink/kernel_evm/kernel/src/configuration.rs b/etherlink/kernel_evm/kernel/src/configuration.rs index 2341d4c7c38f..3b779a4ea463 100644 --- a/etherlink/kernel_evm/kernel/src/configuration.rs +++ b/etherlink/kernel_evm/kernel/src/configuration.rs @@ -4,10 +4,11 @@ use crate::{ is_enable_fa_bridge, read_admin, read_delayed_transaction_bridge, read_kernel_governance, read_kernel_security_governance, read_maximum_allowed_ticks, read_maximum_gas_per_transaction, - read_sequencer_governance, read_ticketer, sequencer, + read_sequencer_governance, sequencer, }, tick_model::constants::{MAXIMUM_GAS_LIMIT, MAX_ALLOWED_TICKS}, }; +use evm_execution::read_ticketer; use tezos_crypto_rs::hash::ContractKt1Hash; use tezos_evm_logging::{log, Level::*}; use tezos_smart_rollup_debug::Runtime; diff --git a/etherlink/kernel_evm/kernel/src/lib.rs b/etherlink/kernel_evm/kernel/src/lib.rs index 4aa1f03bcb64..6fe7ad4d5f8f 100644 --- a/etherlink/kernel_evm/kernel/src/lib.rs +++ b/etherlink/kernel_evm/kernel/src/lib.rs @@ -345,6 +345,10 @@ mod tests { use crate::blueprint_storage::store_inbox_blueprint_by_number; use crate::configuration::{Configuration, Limits}; use crate::fees; + use crate::main; + use crate::mock_internal::MockInternal; + use crate::safe_storage::SafeStorage; + use crate::storage::store_chain_id; use crate::{ blueprint::Blueprint, inbox::{Transaction, TransactionContent}, @@ -352,14 +356,28 @@ mod tests { upgrade::KernelUpgrade, }; use evm_execution::account_storage::{self, EthereumAccountStorage}; + use evm_execution::handler::RouterInterface; + use evm_execution::utilities::keccak256_hash; + use evm_execution::NATIVE_TOKEN_TICKETER_PATH; + use pretty_assertions::assert_eq; use primitive_types::{H160, U256}; + use tezos_data_encoding::nom::NomReader; use tezos_ethereum::block::BlockFees; use tezos_ethereum::{ transaction::{TransactionHash, TransactionType}, tx_common::EthereumTransactionCommon, }; + + use tezos_smart_rollup::michelson::ticket::FA2_1Ticket; + use tezos_smart_rollup::michelson::{ + MichelsonContract, MichelsonOption, MichelsonPair, + }; + use tezos_smart_rollup::outbox::{OutboxMessage, OutboxMessageTransaction}; + use tezos_smart_rollup::types::{Contract, Entrypoint}; use tezos_smart_rollup_core::PREIMAGE_HASH_SIZE; use tezos_smart_rollup_debug::Runtime; + use tezos_smart_rollup_encoding::inbox::ExternalMessageFrame; + use tezos_smart_rollup_encoding::smart_rollup::SmartRollupAddress; use tezos_smart_rollup_encoding::timestamp::Timestamp; use tezos_smart_rollup_host::path::RefPath; use tezos_smart_rollup_mock::MockHost; @@ -615,4 +633,129 @@ mod tests { assert!(base_fee.is_ok()); assert_eq!(curr_base_fee, base_fee.unwrap()); } + + #[test] + fn test_xtz_withdrawal_applied() { + // init host + let mut mock_host = MockHost::default(); + let mut internal = MockInternal(); + let mut safe_storage = SafeStorage { + host: &mut mock_host, + internal: &mut internal, + }; + safe_storage + .store_write_all( + &NATIVE_TOKEN_TICKETER_PATH, + b"KT1DWVsu4Jtu2ficZ1qtNheGPunm5YVniegT", + ) + .unwrap(); + store_chain_id(&mut safe_storage, DUMMY_CHAIN_ID).unwrap(); + + // run level in order to initialize outbox counter (by SOL message) + let level = safe_storage.host.run_level(|_| ()); + + // provision sender account + let sender = H160::from_str("af1276cbb260bb13deddb4209ae99ae6e497f446").unwrap(); + let sender_initial_balance = U256::from(10000000000000000000u64); + let mut evm_account_storage = account_storage::init_account_storage().unwrap(); + set_balance( + &mut safe_storage, + &mut evm_account_storage, + &sender, + sender_initial_balance, + ); + + // cast calldata "withdraw_base58(string)" "tz1RjtZUVeLhADFHDL8UwDZA6vjWWhojpu5w": + let data = hex::decode( + "cda4fee2\ + 0000000000000000000000000000000000000000000000000000000000000020\ + 0000000000000000000000000000000000000000000000000000000000000024\ + 747a31526a745a5556654c6841444648444c385577445a4136766a5757686f6a70753577\ + 00000000000000000000000000000000000000000000000000000000", + ) + .unwrap(); + + // create and sign precompile call + let gas_price = U256::from(40000000000u64); + let to = H160::from_str("ff00000000000000000000000000000000000001").unwrap(); + let tx = EthereumTransactionCommon::new( + TransactionType::Legacy, + Some(DUMMY_CHAIN_ID), + 0, + gas_price, + gas_price, + 2000000, + Some(to), + U256::from(1000000000000000000u64), + data, + vec![], + None, + ); + + // corresponding caller's address is 0xaf1276cbb260bb13deddb4209ae99ae6e497f446 + let tx_payload = tx + .sign_transaction( + "dcdff53b4f013dbcdc717f89fe3bf4d8b10512aae282b48e01d7530470382701" + .to_string(), + ) + .unwrap() + .to_bytes(); + + let tx_hash = keccak256_hash(&tx_payload); + + // encode as external message and submit to inbox + let mut contents = Vec::new(); + contents.push(0x00); // simple tx tag + contents.extend_from_slice(tx_hash.as_bytes()); + contents.extend_from_slice(&tx_payload); + + let message = ExternalMessageFrame::Targetted { + address: SmartRollupAddress::from_b58check( + "sr163Lv22CdE8QagCwf48PWDTquk6isQwv57", + ) + .unwrap(), + contents, + }; + + safe_storage.host.add_external(message); + + // run kernel twice to get to the stage with block creation: + main(&mut safe_storage).expect("Kernel error"); + main(&mut safe_storage).expect("Kernel error"); + + // verify outbox is not empty + let outbox = safe_storage.host.outbox_at(level + 1); + assert!(!outbox.is_empty()); + + // check message contents: + let message_bytes = &outbox[0]; + let (remaining, decoded_message) = + OutboxMessage::nom_read(message_bytes.as_slice()).unwrap(); + assert!(remaining.is_empty()); + + let ticketer = + Contract::from_b58check("KT1DWVsu4Jtu2ficZ1qtNheGPunm5YVniegT").unwrap(); + let ticket = FA2_1Ticket::new( + ticketer.clone(), + MichelsonPair(0.into(), MichelsonOption(None)), + 1000000u64, + ) + .unwrap(); + let receiver = + Contract::from_b58check("tz1RjtZUVeLhADFHDL8UwDZA6vjWWhojpu5w").unwrap(); + let parameters: RouterInterface = + MichelsonPair(MichelsonContract(receiver), ticket); + let destination = ticketer; + let entrypoint = Entrypoint::try_from("burn".to_string()).unwrap(); + + let expected_transaction = OutboxMessageTransaction { + parameters, + destination, + entrypoint, + }; + let expected_message = + OutboxMessage::AtomicTransactionBatch(vec![expected_transaction].into()); + + assert_eq!(expected_message, decoded_message); + } } diff --git a/etherlink/kernel_evm/kernel/src/storage.rs b/etherlink/kernel_evm/kernel/src/storage.rs index 098675de268b..4cc461e44712 100644 --- a/etherlink/kernel_evm/kernel/src/storage.rs +++ b/etherlink/kernel_evm/kernel/src/storage.rs @@ -40,7 +40,6 @@ pub const STORAGE_VERSION_PATH: RefPath = RefPath::assert_from(b"/evm/storage_ve const KERNEL_VERSION_PATH: RefPath = RefPath::assert_from(b"/evm/kernel_version"); -pub const TICKETER: RefPath = RefPath::assert_from(b"/evm/world_state/ticketer"); pub const ADMIN: RefPath = RefPath::assert_from(b"/evm/admin"); pub const SEQUENCER_GOVERNANCE: RefPath = RefPath::assert_from(b"/evm/sequencer_governance"); @@ -823,11 +822,6 @@ fn read_b58_kt1(host: &Host, path: &OwnedPath) -> Option(host: &mut Host) -> Option { - read_b58_kt1(host, &TICKETER.into()) -} - pub fn read_admin(host: &mut Host) -> Option { read_b58_kt1(host, &ADMIN.into()) } -- GitLab