From 7ade8e1edd66bab9ed1da3b4b9de1654c65e6576 Mon Sep 17 00:00:00 2001 From: Michael Zaikin Date: Tue, 2 Jul 2024 21:35:53 +0100 Subject: [PATCH 1/2] EVM: emit event log when native token withdrawal is applied --- etherlink/CHANGES_KERNEL.md | 1 + .../src/precompiles/withdrawal.rs | 183 +++++++++++++++++- 2 files changed, 179 insertions(+), 5 deletions(-) diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index e3b51df9a097..41ce10c0775c 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -44,6 +44,7 @@ repository, but is part of [`etherlink-mainnet-launch`][mainnet-branch] instead. the installer. (!13827) - FA deposits are applied if FA bridge feature is enabled (disabled on Mainnet Beta and Testnet). (!13835) +- Native token withdrawals emit event log in case of successful execution. (!14014) ## Bug fixes diff --git a/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs b/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs index 860497307c58..37d4c66e3ff5 100644 --- a/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs +++ b/etherlink/kernel_evm/evm_execution/src/precompiles/withdrawal.rs @@ -4,6 +4,10 @@ // // SPDX-License-Identifier: MIT +use std::borrow::Cow; + +use crate::abi::ABI_B22_RIGHT_PADDING; +use crate::abi::ABI_H160_LEFT_PADDING; use crate::handler::EvmHandler; use crate::handler::RouterInterface; use crate::handler::Withdrawal; @@ -11,11 +15,17 @@ use crate::precompiles::tick_model; use crate::precompiles::PrecompileOutcome; use crate::precompiles::WITHDRAWAL_ADDRESS; use crate::read_ticketer; +use crate::storage::withdraw_nonce; use crate::{abi, fail_if_too_much, EthereumError}; use evm::{Context, ExitReason, ExitRevert, ExitSucceed, Transfer}; use host::runtime::Runtime; +use primitive_types::H160; +use primitive_types::H256; +use primitive_types::U256; +use tezos_data_encoding::enc::BinWriter; use tezos_ethereum::wei::mutez_from_wei; use tezos_ethereum::wei::ErrorMutezFromWei; +use tezos_ethereum::Log; use tezos_evm_logging::log; use tezos_evm_logging::Level::Info; use tezos_smart_rollup_encoding::contract::Contract; @@ -31,6 +41,13 @@ use tezos_smart_rollup_encoding::outbox::{OutboxMessage, OutboxMessageTransactio /// The ticks/gas ratio is from benchmarks on `ecrecover`. const WITHDRAWAL_COST: u64 = 880; +/// Keccak256 of Withdrawal(uint256,address,bytes22,uint256) +/// This is main topic (non-anonymous event): https://docs.soliditylang.org/en/latest/abi-spec.html#events +pub const WITHDRAWAL_EVENT_TOPIC: [u8; 32] = [ + 45, 90, 215, 147, 24, 31, 91, 107, 215, 39, 192, 194, 22, 70, 30, 1, 158, 207, 228, + 102, 53, 63, 221, 233, 204, 248, 19, 244, 91, 132, 250, 130, +]; + fn prepare_message( parameters: RouterInterface, destination: Contract, @@ -150,6 +167,15 @@ pub fn withdrawal_precompile( return Ok(revert_withdrawal()) }; + let withdrawal_id = withdraw_nonce::get_and_increment(handler.borrow_host()) + .map_err(|e| { + EthereumError::WrappedError(Cow::from(format!("{:?}", e))) + })?; + + // We use the original amount in order not to lose additional information + let withdrawal_event = + event_log(&transfer.value, &context.caller, &target, withdrawal_id); + let parameters = MichelsonPair::( MichelsonContract(target), ticket, @@ -169,6 +195,11 @@ pub fn withdrawal_precompile( let withdrawals = vec![message]; + // Emit withdrawal event + handler.add_log(withdrawal_event).map_err(|e| { + EthereumError::WrappedError(Cow::from(format!("{:?}", e))) + })?; + Ok(PrecompileOutcome { exit_status: ExitReason::Succeed(ExitSucceed::Returned), output: vec![], @@ -188,21 +219,58 @@ pub fn withdrawal_precompile( } } +/// Construct withdrawal event log from parts: +/// * `amount` - TEZ amount in wei +/// * `sender` - account on L2 +/// * `receiver` - account on L1 +/// * `withdrawal_id` - unique withdrawal ID (incremented on every successful or failed XTZ/FA withdrawal) +fn event_log( + amount: &U256, + sender: &H160, + receiver: &Contract, + withdrawal_id: U256, +) -> Log { + let mut data = Vec::with_capacity(3 * 32); + + data.extend_from_slice(&Into::<[u8; 32]>::into(*amount)); + debug_assert!(data.len() % 32 == 0); + + data.extend_from_slice(&ABI_H160_LEFT_PADDING); + data.extend_from_slice(&sender.0); + debug_assert!(data.len() % 32 == 0); + + // It is safe to unwrap, underlying implementation never fails (always returns Ok(())) + receiver.bin_write(&mut data).unwrap(); + data.extend_from_slice(&ABI_B22_RIGHT_PADDING); + debug_assert!(data.len() % 32 == 0); + + data.extend_from_slice(&Into::<[u8; 32]>::into(withdrawal_id)); + debug_assert!(data.len() % 32 == 0); + + Log { + address: H160::zero(), + topics: vec![H256(WITHDRAWAL_EVENT_TOPIC)], + data, + } +} + #[cfg(test)] mod tests { use crate::{ handler::{ExecutionOutcome, Withdrawal}, precompiles::{ test_helpers::{execute_precompiled, DUMMY_TICKETER}, - withdrawal::WITHDRAWAL_COST, + withdrawal::{WITHDRAWAL_COST, WITHDRAWAL_EVENT_TOPIC}, WITHDRAWAL_ADDRESS, }, }; + use alloy_sol_types::SolEvent; use evm::{ExitReason, ExitRevert, ExitSucceed, Transfer}; use pretty_assertions::assert_eq; - use primitive_types::{H160, U256}; + use primitive_types::{H160, H256, U256}; + use sha3::{Digest, Keccak256}; use std::str::FromStr; - use tezos_ethereum::wei::eth_from_mutez; + use tezos_ethereum::{wei::eth_from_mutez, Log}; use tezos_smart_rollup_encoding::contract::Contract; use tezos_smart_rollup_encoding::michelson::ticket::FA2_1Ticket; use tezos_smart_rollup_encoding::michelson::{ @@ -211,6 +279,17 @@ mod tests { use super::prepare_message; + mod events { + alloy_sol_types::sol! { + event Withdrawal ( + uint256 amount, + address sender, + bytes22 receiver, + uint256 withdrawalId, + ); + } + } + fn make_message(ticketer: &str, target: &str, amount: u64) -> Withdrawal { let target = Contract::from_b58check(target).unwrap(); let ticketer = Contract::from_b58check(ticketer).unwrap(); @@ -230,6 +309,58 @@ mod tests { prepare_message(parameters, ticketer, Some("burn")).unwrap() } + #[test] + fn withdrawal_event_signature() { + assert_eq!( + WITHDRAWAL_EVENT_TOPIC.to_vec(), + Keccak256::digest(b"Withdrawal(uint256,address,bytes22,uint256)").to_vec() + ); + } + + #[test] + fn withdrawal_event_codec() { + assert_eq!(events::Withdrawal::SIGNATURE_HASH.0, WITHDRAWAL_EVENT_TOPIC); + + let amount = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 9, 24, 78, 114, 160, 0, + ]; + let sender = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 118, + ]; + let receiver = [ + 0, 0, 66, 236, 118, 95, 39, 0, 19, 78, 158, 14, 254, 137, 208, 51, 142, 46, + 132, 60, 83, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ]; + let withdrawal_id = [1u8; 32]; + + let log = Log { + address: H160::zero(), + topics: vec![H256(WITHDRAWAL_EVENT_TOPIC)], + data: [amount, sender, receiver, withdrawal_id].concat(), + }; + + let log_data = alloy_primitives::LogData::new_unchecked( + log.topics.iter().map(|x| x.0.into()).collect(), + log.data.clone().into(), + ); + let event = events::Withdrawal::decode_log_data(&log_data, true).unwrap(); + assert_eq!(event.amount, alloy_primitives::U256::from_be_bytes(amount)); + assert_eq!( + event.sender, + alloy_primitives::Address::from_slice(&sender[12..]) + ); + assert_eq!( + event.receiver, + alloy_primitives::FixedBytes::from_slice(&receiver[..22]) + ); + assert_eq!( + event.withdrawalId, + alloy_primitives::U256::from_be_bytes(withdrawal_id) + ); + } + #[test] fn call_withdraw_with_implicit_address() { // Format of input - generated by eg remix to match withdrawal ABI @@ -272,11 +403,32 @@ mod tests { + 1032 // transaction data cost (90 zero bytes + 42 non zero bytes) + WITHDRAWAL_COST; // cost of calling withdrawal precompiled contract + let expected_log = Log { + address: H160::zero(), + topics: vec![H256(WITHDRAWAL_EVENT_TOPIC)], + data: [ + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 9, 24, 78, 114, 160, 0, + ], + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 118, + ], + [ + 0, 0, 66, 236, 118, 95, 39, 0, 19, 78, 158, 14, 254, 137, 208, 51, + 142, 46, 132, 60, 83, 220, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + [0u8; 32], + ] + .concat(), + }; + let expected = ExecutionOutcome { gas_used: expected_gas, reason: ExitReason::Succeed(ExitSucceed::Returned).into(), new_address: None, - logs: vec![], + logs: vec![expected_log], result: Some(expected_output), withdrawals: vec![message], estimated_ticks_used: 880_000, @@ -326,11 +478,32 @@ mod tests { + 1032 // transaction data cost (90 zero bytes + 42 non zero bytes) + WITHDRAWAL_COST; // cost of calling withdrawal precompiled contract + let expected_log = Log { + address: H160::zero(), + topics: vec![H256(WITHDRAWAL_EVENT_TOPIC)], + data: [ + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 9, 24, 78, 114, 160, 0, + ], + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 118, + ], + [ + 1, 36, 102, 103, 169, 49, 254, 11, 210, 251, 28, 182, 4, 247, 20, 96, + 30, 136, 40, 69, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + [0u8; 32], + ] + .concat(), + }; + let expected = ExecutionOutcome { gas_used: expected_gas, reason: ExitReason::Succeed(ExitSucceed::Returned).into(), new_address: None, - logs: vec![], + logs: vec![expected_log], result: Some(expected_output), withdrawals: vec![message], // TODO (#6426): estimate the ticks consumption of precompiled contracts -- GitLab From 5ff0262521f2faab2aee547f8aeec2becf9a73a0 Mon Sep 17 00:00:00 2001 From: Michael Zaikin Date: Tue, 23 Jul 2024 16:55:08 +0100 Subject: [PATCH 2/2] EVM: L1 proxy address added to the FA withdrawal event --- etherlink/CHANGES_KERNEL.md | 2 ++ .../evm_execution/src/fa_bridge/withdrawal.rs | 20 ++++++++++++++----- .../artifacts/MockFaBridgePrecompile.abi | 6 ++++++ .../contracts/src/MockFaBridgePrecompile.sol | 1 + 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/etherlink/CHANGES_KERNEL.md b/etherlink/CHANGES_KERNEL.md index 41ce10c0775c..d35c0655f11b 100644 --- a/etherlink/CHANGES_KERNEL.md +++ b/etherlink/CHANGES_KERNEL.md @@ -22,6 +22,8 @@ ## Internal +- L1 proxy address is added as a field to the FA withdrawal event. (!14260) + ## Version 4f4457e2527cb227a90bb1c56d3a83f39c0f78fd This kernel has been activated on Etherlink Testnet on block diff --git a/etherlink/kernel_evm/evm_execution/src/fa_bridge/withdrawal.rs b/etherlink/kernel_evm/evm_execution/src/fa_bridge/withdrawal.rs index 93761977aa09..526ac36ad537 100644 --- a/etherlink/kernel_evm/evm_execution/src/fa_bridge/withdrawal.rs +++ b/etherlink/kernel_evm/evm_execution/src/fa_bridge/withdrawal.rs @@ -51,10 +51,10 @@ use super::error::FaBridgeError; /// Keccak256 of withdraw(address,uint256,uint256), first 4 bytes pub const WITHDRAW_METHOD_ID: &[u8; 4] = b"\xb5\xc5\xf6\x72"; -/// Keccak256 of Withdrawal(uint256,address,address,bytes22,uint256,uint256) +/// Keccak256 of Withdrawal(uint256,address,address,bytes22,bytes22,uint256,uint256) pub const WITHDRAW_EVENT_TOPIC: &[u8; 32] = b"\ - \x58\x9b\x38\xfe\xb5\xb5\x55\x9d\x56\x4e\xd8\x10\x12\x95\x08\x93\ - \xbf\xc3\x5d\xb6\x6a\x90\x52\x4d\x70\x41\x0d\x1c\x15\xc8\xe5\x6c"; + \xab\x68\x45\x0c\x9e\x54\x6f\x60\x62\xa8\x61\xee\xbf\x8e\xc5\xbb\ + \xd4\x1b\x44\x25\xe2\x6b\x20\x19\x9c\x91\x22\x7c\x7f\x90\x38\xca"; /// L1 proxy contract entrypoint that will be invoked by the outbox message /// execution. @@ -143,9 +143,9 @@ impl FaWithdrawal { /// /// It also contains unique withdrawal identifier. /// - /// Signature: Withdrawal(uint256,address,address,bytes22,uint256,uint256) + /// Signature: Withdrawal(uint256,address,address,bytes22,bytes22,uint256,uint256) pub fn event_log(&self, withdrawal_id: U256) -> Log { - let mut data = Vec::with_capacity(6 * 32); + let mut data = Vec::with_capacity(7 * 32); data.extend_from_slice(&ABI_H160_LEFT_PADDING); data.extend_from_slice(self.sender.as_bytes()); @@ -160,6 +160,10 @@ impl FaWithdrawal { data.extend_from_slice(&ABI_B22_RIGHT_PADDING); debug_assert!(data.len() % 32 == 0); + self.proxy.bin_write(&mut data).unwrap(); + data.extend_from_slice(&ABI_B22_RIGHT_PADDING); + debug_assert!(data.len() % 32 == 0); + data.extend_from_slice(&Into::<[u8; 32]>::into(self.amount)); debug_assert!(data.len() % 32 == 0); @@ -375,6 +379,12 @@ mod tests { withdrawal_event.receiver, alloy_primitives::FixedBytes::<22>::repeat_byte(0) ); + assert_eq!( + withdrawal_event.proxy, + alloy_primitives::FixedBytes::<22>::from_slice(&[ + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ]) + ); assert_eq!(withdrawal_event.amount, convert_u256(&withdrawal.amount)); assert_eq!( withdrawal_event.withdrawalId, diff --git a/etherlink/kernel_evm/evm_execution/tests/contracts/artifacts/MockFaBridgePrecompile.abi b/etherlink/kernel_evm/evm_execution/tests/contracts/artifacts/MockFaBridgePrecompile.abi index 00f07254daa6..259b0aa7438c 100644 --- a/etherlink/kernel_evm/evm_execution/tests/contracts/artifacts/MockFaBridgePrecompile.abi +++ b/etherlink/kernel_evm/evm_execution/tests/contracts/artifacts/MockFaBridgePrecompile.abi @@ -103,6 +103,12 @@ "indexed": false, "internalType": "bytes22" }, + { + "name": "proxy", + "type": "bytes22", + "indexed": false, + "internalType": "bytes22" + }, { "name": "amount", "type": "uint256", diff --git a/etherlink/kernel_evm/evm_execution/tests/contracts/src/MockFaBridgePrecompile.sol b/etherlink/kernel_evm/evm_execution/tests/contracts/src/MockFaBridgePrecompile.sol index 5e38719ad783..753a3bdd09cf 100644 --- a/etherlink/kernel_evm/evm_execution/tests/contracts/src/MockFaBridgePrecompile.sol +++ b/etherlink/kernel_evm/evm_execution/tests/contracts/src/MockFaBridgePrecompile.sol @@ -24,6 +24,7 @@ contract MockFaBridgePrecompile { address sender, address ticketOwner, bytes22 receiver, + bytes22 proxy, uint256 amount, uint256 withdrawalId ); -- GitLab