From 8f300335ec1822da370d482510723921b9be9d53 Mon Sep 17 00:00:00 2001 From: Thomas Plisson Date: Tue, 15 Jul 2025 17:57:45 +0200 Subject: [PATCH 1/2] REVM: Send outbox message entrypoints for XTZ and FA fast withdrawals --- etherlink/kernel_latest/revm/src/helpers.rs | 5 + .../revm/src/precompile_provider.rs | 1 + .../revm/src/send_outbox_message.rs | 178 +++++++++++++++++- 3 files changed, 179 insertions(+), 5 deletions(-) diff --git a/etherlink/kernel_latest/revm/src/helpers.rs b/etherlink/kernel_latest/revm/src/helpers.rs index 6840918f436f..d13218996994 100644 --- a/etherlink/kernel_latest/revm/src/helpers.rs +++ b/etherlink/kernel_latest/revm/src/helpers.rs @@ -4,6 +4,7 @@ // SPDX-License-Identifier: MIT use crate::Error; +use num_bigint::{BigInt, Sign}; use revm::primitives::{alloy_primitives::Keccak256, B256, U256}; use tezos_evm_runtime::runtime::Runtime; use tezos_smart_rollup_host::{ @@ -118,6 +119,10 @@ pub fn u256_to_le_bytes(value: primitive_types::U256) -> Vec { bytes } +pub fn u256_to_bigint(value: U256) -> BigInt { + BigInt::from_bytes_be(Sign::Plus, &value.to_be_bytes::<{ U256::BYTES }>()) +} + pub mod legacy { use crate::{custom, Error}; use alloy_primitives::{self, Address}; diff --git a/etherlink/kernel_latest/revm/src/precompile_provider.rs b/etherlink/kernel_latest/revm/src/precompile_provider.rs index 42aeb3fd693a..e9b1ac784601 100644 --- a/etherlink/kernel_latest/revm/src/precompile_provider.rs +++ b/etherlink/kernel_latest/revm/src/precompile_provider.rs @@ -73,6 +73,7 @@ impl EtherlinkPrecompiles { is_static, inputs, address, + gas_limit, )?; Ok(Some(result)) } diff --git a/etherlink/kernel_latest/revm/src/send_outbox_message.rs b/etherlink/kernel_latest/revm/src/send_outbox_message.rs index 9ee9d5a6795f..ad5517b5378f 100644 --- a/etherlink/kernel_latest/revm/src/send_outbox_message.rs +++ b/etherlink/kernel_latest/revm/src/send_outbox_message.rs @@ -6,11 +6,11 @@ use alloy_sol_types::{sol, SolEvent}; use num_bigint::{BigInt, Sign}; use revm::{ - context::{ContextTr, Transaction}, + context::{Block, ContextTr, Transaction}, interpreter::{Gas, InputsImpl, InstructionResult, InterpreterResult}, primitives::{Address, Bytes, U256}, }; -use tezos_data_encoding::nom::NomReader; +use tezos_data_encoding::{nom::NomReader, types::Zarith}; use tezos_smart_rollup_encoding::michelson::{ MichelsonBytes, MichelsonContract, MichelsonNat, MichelsonOption, MichelsonPair, MichelsonTimestamp, @@ -24,6 +24,7 @@ use tezos_smart_rollup_encoding::{ use crate::{ constants::{FA_WITHDRAWAL_SOL_ADDR, WITHDRAWAL_SOL_ADDR}, database::PrecompileDatabase, + helpers::u256_to_bigint, }; sol! { @@ -42,6 +43,28 @@ sol! { ); } +sol! { + event SendFastWithdrawalInput ( + string target, + string fast_withdrawal_contract, + bytes payload, + uint256 amount, + uint256 withdrawal_id, + ); +} + +sol! { + event SendFastFAWithdrawalInput ( + bytes routing_info, + uint256 amount, + bytes22 ticketer, + bytes content, + string fast_withdrawal_contract_address, + bytes payload, + uint256 withdrawal_id, + ); +} + /// Withdrawal interface of the ticketer contract pub type RouterInterface = MichelsonPair; @@ -84,6 +107,58 @@ pub(crate) fn revert() -> InterpreterResult { } } +#[allow(clippy::too_many_arguments)] +fn prepare_fast_message( + // external + target: Contract, + fast_withdrawal_contract: Contract, + payload: Vec, + content: MichelsonPair>, + // contract + amount: BigInt, + withdrawal_id: U256, + // context + timestamp: U256, + caller: Address, + // db + ticketer: Contract, +) -> Withdrawal { + let ticket = FA2_1Ticket::new(ticketer, content, amount).unwrap(); + + let withdrawal_id_nat = + MichelsonNat::new(Zarith(u256_to_bigint(withdrawal_id))).unwrap(); + + let bytes_payload = MichelsonBytes(payload); + + let caller = MichelsonBytes(caller.to_vec()); + + let timestamp: MichelsonTimestamp = + MichelsonTimestamp(Zarith(u256_to_bigint(timestamp))); + + let target = MichelsonContract(target.clone()); + + let parameters = MichelsonPair( + withdrawal_id_nat, + MichelsonPair( + ticket, + MichelsonPair( + timestamp, + MichelsonPair(target, MichelsonPair(bytes_payload, caller)), + ), + ), + ); + + let entrypoint = Entrypoint::try_from(String::from("default")).unwrap(); + + let message = OutboxMessageTransaction { + parameters, + entrypoint, + destination: fast_withdrawal_contract, + }; + + Withdrawal::Fast(OutboxMessage::AtomicTransactionBatch(vec![message].into())) +} + fn prepare_standard_message( target: Contract, ticketer: Contract, @@ -132,6 +207,7 @@ pub(crate) fn send_outbox_message_precompile( is_static: bool, transfer: &InputsImpl, current: &Address, + gas_limit: u64, ) -> Result where CTX: ContextTr, @@ -154,7 +230,7 @@ where // Decode let (target, amount) = SendWithdrawalInput::abi_decode_data(input_data) .map_err(|e| e.to_string())?; - let target = Contract::from_b58check(&target).unwrap(); + let target = Contract::from_b58check(&target).map_err(|e| e.to_string())?; let amount = BigInt::from_bytes_be( Sign::Plus, &amount.to_be_bytes::<{ U256::BYTES }>(), @@ -179,7 +255,47 @@ where let result = InterpreterResult { result: InstructionResult::Return, - gas: Gas::new(0), + gas: Gas::new(gas_limit), + output: Bytes::new(), + }; + Ok(result) + } + // "0xdf1943c8" is the function selector for `push_fast_withdrawal_to_outbox(string,string,bytes,uint256,uint256)` + [0xdf, 0x19, 0x43, 0xc8, input_data @ ..] => { + // Decode + let (target, fast_withdrawal_contract, payload, amount, withdrawal_id) = + SendFastWithdrawalInput::abi_decode_data(input_data) + .map_err(|e| e.to_string())?; + let target = Contract::from_b58check(&target).map_err(|e| e.to_string())?; + let fast_withdrawal_contract = + Contract::from_b58check(&fast_withdrawal_contract) + .map_err(|e| e.to_string())?; + let amount = BigInt::from_bytes_be( + Sign::Plus, + &amount.to_be_bytes::<{ U256::BYTES }>(), + ); + + // Build + let ticketer = Contract::Originated(context.db().ticketer().unwrap()); + let content = MichelsonPair(0.into(), MichelsonOption(None)); + let withdrawal = prepare_fast_message( + target, + fast_withdrawal_contract, + payload.to_vec(), + content, + amount, + withdrawal_id, + context.block().timestamp(), + context.tx().caller(), + ticketer, + ); + + // Push + context.db_mut().push_withdrawal(withdrawal); + + let result = InterpreterResult { + result: InstructionResult::Return, + gas: Gas::new(gas_limit), output: Bytes::new(), }; Ok(result) @@ -219,7 +335,59 @@ where let result = InterpreterResult { result: InstructionResult::Return, - gas: Gas::new(0), + gas: Gas::new(gas_limit), + output: Bytes::new(), + }; + Ok(result) + } + // "0xb27d7fb8" is the function selector for `push_fast_fa_withdrawal_to_outbox(bytes,uint256,bytes22,bytes,string,bytes,uint256)` + [0xb2, 0x7d, 0x7f, 0xb8, input_data @ ..] => { + // Decode + let ( + routing_info, + amount, + ticketer, + content, + fast_withdrawal_contract_address, + payload, + withdrawal_id, + ) = SendFastFAWithdrawalInput::abi_decode_data(input_data) + .map_err(|e| e.to_string())?; + let (target, _proxy) = parse_l1_routing_info(&routing_info)?; + let (_, ticketer) = + Contract::nom_read(ticketer.as_slice()).map_err(|e| e.to_string())?; + let amount = BigInt::from_bytes_be( + Sign::Plus, + &amount.to_be_bytes::<{ U256::BYTES }>(), + ); + let (_, content) = MichelsonPair::< + MichelsonNat, + MichelsonOption, + >::nom_read(&content) + .map_err(|e| e.to_string())?; + let fast_withdrawal_contract = + Contract::from_b58check(&fast_withdrawal_contract_address) + .map_err(|e| e.to_string())?; + + // Build + let withdrawal = prepare_fast_message( + target, + fast_withdrawal_contract, + payload.to_vec(), + content, + amount, + withdrawal_id, + context.block().timestamp(), + context.tx().caller(), + ticketer, + ); + + // Push + context.db_mut().push_withdrawal(withdrawal); + + let result = InterpreterResult { + result: InstructionResult::Return, + gas: Gas::new(gas_limit), output: Bytes::new(), }; Ok(result) -- GitLab From 503b3b9a658236a2e3806da79166041951b36582 Mon Sep 17 00:00:00 2001 From: Thomas Plisson Date: Thu, 17 Jul 2025 15:47:10 +0200 Subject: [PATCH 2/2] REVM: Base cost for atomic precompiles --- etherlink/kernel_latest/revm/src/constants.rs | 3 +++ .../kernel_latest/revm/src/send_outbox_message.rs | 10 +++++----- etherlink/kernel_latest/revm/src/table.rs | 10 +++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/etherlink/kernel_latest/revm/src/constants.rs b/etherlink/kernel_latest/revm/src/constants.rs index 0d0c5a5f6b78..cdbb4a99eb9d 100644 --- a/etherlink/kernel_latest/revm/src/constants.rs +++ b/etherlink/kernel_latest/revm/src/constants.rs @@ -44,3 +44,6 @@ pub(crate) const CUSTOMS: [Address; 4] = [ SEND_OUTBOX_MESSAGE_PRECOMPILE_ADDRESS, TABLE_PRECOMPILE_ADDRESS, ]; + +// TODO: Properly estimate execution cost for table and outbox precompiles +pub(crate) const PRECOMPILE_BASE_COST: u64 = 2000; diff --git a/etherlink/kernel_latest/revm/src/send_outbox_message.rs b/etherlink/kernel_latest/revm/src/send_outbox_message.rs index ad5517b5378f..fbb89e0bbb5b 100644 --- a/etherlink/kernel_latest/revm/src/send_outbox_message.rs +++ b/etherlink/kernel_latest/revm/src/send_outbox_message.rs @@ -22,7 +22,7 @@ use tezos_smart_rollup_encoding::{ }; use crate::{ - constants::{FA_WITHDRAWAL_SOL_ADDR, WITHDRAWAL_SOL_ADDR}, + constants::{FA_WITHDRAWAL_SOL_ADDR, PRECOMPILE_BASE_COST, WITHDRAWAL_SOL_ADDR}, database::PrecompileDatabase, helpers::u256_to_bigint, }; @@ -255,7 +255,7 @@ where let result = InterpreterResult { result: InstructionResult::Return, - gas: Gas::new(gas_limit), + gas: Gas::new(gas_limit - PRECOMPILE_BASE_COST), output: Bytes::new(), }; Ok(result) @@ -295,7 +295,7 @@ where let result = InterpreterResult { result: InstructionResult::Return, - gas: Gas::new(gas_limit), + gas: Gas::new(gas_limit - PRECOMPILE_BASE_COST), output: Bytes::new(), }; Ok(result) @@ -335,7 +335,7 @@ where let result = InterpreterResult { result: InstructionResult::Return, - gas: Gas::new(gas_limit), + gas: Gas::new(gas_limit - PRECOMPILE_BASE_COST), output: Bytes::new(), }; Ok(result) @@ -387,7 +387,7 @@ where let result = InterpreterResult { result: InstructionResult::Return, - gas: Gas::new(gas_limit), + gas: Gas::new(gas_limit - PRECOMPILE_BASE_COST), output: Bytes::new(), }; Ok(result) diff --git a/etherlink/kernel_latest/revm/src/table.rs b/etherlink/kernel_latest/revm/src/table.rs index 136bec139143..3cc148461bc4 100644 --- a/etherlink/kernel_latest/revm/src/table.rs +++ b/etherlink/kernel_latest/revm/src/table.rs @@ -11,7 +11,7 @@ use revm::{ }; use crate::{ - constants::FA_WITHDRAWAL_SOL_ADDR, + constants::{FA_WITHDRAWAL_SOL_ADDR, PRECOMPILE_BASE_COST}, database::PrecompileDatabase, helpers::legacy::{h160_to_alloy, u256_to_alloy}, send_outbox_message::revert, @@ -80,7 +80,7 @@ where let result = InterpreterResult { result: InstructionResult::Return, - gas: Gas::new(gas_limit), + gas: Gas::new(gas_limit - PRECOMPILE_BASE_COST), output: Bytes::new(), }; Ok(result) @@ -102,7 +102,7 @@ where let result = InterpreterResult { result: InstructionResult::Return, - gas: Gas::new(gas_limit), + gas: Gas::new(gas_limit - PRECOMPILE_BASE_COST), output: Bytes::new(), }; Ok(result) @@ -135,7 +135,7 @@ where let result = InterpreterResult { result: InstructionResult::Return, - gas: Gas::new(gas_limit), + gas: Gas::new(gas_limit - PRECOMPILE_BASE_COST), output, }; Ok(result) @@ -152,7 +152,7 @@ where let result = InterpreterResult { result: InstructionResult::Return, - gas: Gas::new(gas_limit), + gas: Gas::new(gas_limit - PRECOMPILE_BASE_COST), output: Bytes::new(), }; Ok(result) -- GitLab