diff --git a/Cargo.lock b/Cargo.lock index 5c858dfa0b2..c52f872d244 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8612,6 +8612,7 @@ dependencies = [ "reth-codecs", "reth-ethereum-forks", "reth-primitives-traits", + "reth-scroll-primitives", "reth-scroll-revm", "reth-static-file-types", "reth-testing-utils", @@ -9130,12 +9131,21 @@ dependencies = [ name = "reth-scroll-primitives" version = "1.1.2" dependencies = [ + "alloy-consensus", + "alloy-eips", "alloy-primitives", + "alloy-rlp", + "alloy-serde", "arbitrary", + "bincode", "bytes", "modular-bitfield", "poseidon-bn254", + "proptest", + "proptest-arbitrary-interop", + "rand 0.8.5", "reth-codecs", + "reth-codecs-derive", "serde", "test-fuzz", ] diff --git a/crates/optimism/bin/src/lib.rs b/crates/optimism/bin/src/lib.rs index 3e9550949c1..b3e274e678f 100644 --- a/crates/optimism/bin/src/lib.rs +++ b/crates/optimism/bin/src/lib.rs @@ -24,11 +24,9 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -// The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] -// Don't use the crate if `scroll` feature is used. #![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] -#![cfg(not(feature = "scroll"))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(all(feature = "optimism", not(feature = "scroll")))] /// Re-exported from `reth_optimism_cli`. pub mod cli { diff --git a/crates/optimism/bin/src/main.rs b/crates/optimism/bin/src/main.rs index 7ba70272f77..4fb016b3475 100644 --- a/crates/optimism/bin/src/main.rs +++ b/crates/optimism/bin/src/main.rs @@ -1,9 +1,7 @@ #![allow(missing_docs, rustdoc::missing_crate_level_docs)] -// The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] -// Don't use the crate if `scroll` feature is used. #![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] -#![cfg(not(feature = "scroll"))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(all(feature = "optimism", not(feature = "scroll")))] use clap::Parser; use reth_node_builder::{engine_tree_config::TreeConfig, EngineNodeLauncher}; diff --git a/crates/optimism/cli/src/lib.rs b/crates/optimism/cli/src/lib.rs index b02c128cbf3..900d4ddf4c9 100644 --- a/crates/optimism/cli/src/lib.rs +++ b/crates/optimism/cli/src/lib.rs @@ -7,11 +7,9 @@ )] #![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -// The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] -// Don't use the crate if `scroll` feature is used. #![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] -#![cfg(not(feature = "scroll"))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(all(feature = "optimism", not(feature = "scroll")))] /// Optimism chain specification parser. pub mod chainspec; diff --git a/crates/optimism/consensus/Cargo.toml b/crates/optimism/consensus/Cargo.toml index 0dffceaddca..bfe2e3d5266 100644 --- a/crates/optimism/consensus/Cargo.toml +++ b/crates/optimism/consensus/Cargo.toml @@ -36,3 +36,4 @@ reth-optimism-chainspec.workspace = true [features] optimism = ["reth-primitives/optimism"] +scroll = [] diff --git a/crates/optimism/consensus/src/lib.rs b/crates/optimism/consensus/src/lib.rs index e8b7959dd27..f0468abaef5 100644 --- a/crates/optimism/consensus/src/lib.rs +++ b/crates/optimism/consensus/src/lib.rs @@ -6,8 +6,9 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] +#![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] // The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] +#![cfg(all(feature = "optimism", not(feature = "scroll")))] use alloy_consensus::{Header, EMPTY_OMMER_ROOT_HASH}; use alloy_primitives::{B64, U256}; diff --git a/crates/optimism/evm/src/lib.rs b/crates/optimism/evm/src/lib.rs index 78aa706eaf6..661696aedd1 100644 --- a/crates/optimism/evm/src/lib.rs +++ b/crates/optimism/evm/src/lib.rs @@ -7,11 +7,9 @@ )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] -// The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] -// Don't use the crate if `scroll` feature is used. #![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] -#![cfg(not(feature = "scroll"))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(all(feature = "optimism", not(feature = "scroll")))] extern crate alloc; diff --git a/crates/optimism/node/src/lib.rs b/crates/optimism/node/src/lib.rs index 8bf8225bdef..f228ae1fa2f 100644 --- a/crates/optimism/node/src/lib.rs +++ b/crates/optimism/node/src/lib.rs @@ -6,11 +6,9 @@ issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/" )] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -// The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] -// Don't use the crate if `scroll` feature is used. #![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] -#![cfg(not(feature = "scroll"))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(all(feature = "optimism", not(feature = "scroll")))] /// CLI argument parsing for the optimism node. pub mod args; diff --git a/crates/optimism/node/tests/e2e/main.rs b/crates/optimism/node/tests/e2e/main.rs index b3143d7c40b..d0c12c52cd6 100644 --- a/crates/optimism/node/tests/e2e/main.rs +++ b/crates/optimism/node/tests/e2e/main.rs @@ -3,7 +3,7 @@ #![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] #![cfg(not(feature = "scroll"))] -#[cfg(feature = "optimism")] +#[cfg(all(feature = "optimism", not(feature = "scroll")))] mod p2p; const fn main() {} diff --git a/crates/optimism/node/tests/it/main.rs b/crates/optimism/node/tests/it/main.rs index 2bfdeffc1c4..6ac5cf6cf1c 100644 --- a/crates/optimism/node/tests/it/main.rs +++ b/crates/optimism/node/tests/it/main.rs @@ -3,10 +3,10 @@ #![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] #![cfg(not(feature = "scroll"))] -#[cfg(feature = "optimism")] +#[cfg(all(feature = "optimism", not(feature = "scroll")))] mod builder; -#[cfg(feature = "optimism")] +#[cfg(all(feature = "optimism", not(feature = "scroll")))] mod priority; const fn main() {} diff --git a/crates/optimism/payload/src/lib.rs b/crates/optimism/payload/src/lib.rs index ee1f0a81dce..ab7f9897bc8 100644 --- a/crates/optimism/payload/src/lib.rs +++ b/crates/optimism/payload/src/lib.rs @@ -8,11 +8,9 @@ #![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![allow(clippy::useless_let_if_seq)] -// The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] -// Don't use the crate if `scroll` feature is used. #![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] -#![cfg(not(feature = "scroll"))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(all(feature = "optimism", not(feature = "scroll")))] pub mod builder; pub use builder::OpPayloadBuilder; diff --git a/crates/optimism/rpc/src/lib.rs b/crates/optimism/rpc/src/lib.rs index d8ee748783b..0f925459aba 100644 --- a/crates/optimism/rpc/src/lib.rs +++ b/crates/optimism/rpc/src/lib.rs @@ -7,11 +7,9 @@ )] #![cfg_attr(all(not(test), feature = "optimism"), warn(unused_crate_dependencies))] #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -// The `optimism` feature must be enabled to use this crate. -#![cfg(feature = "optimism")] -// Don't use the crate if `scroll` feature is used. #![cfg_attr(feature = "scroll", allow(unused_crate_dependencies))] -#![cfg(not(feature = "scroll"))] +// The `optimism` feature must be enabled to use this crate. +#![cfg(all(feature = "optimism", not(feature = "scroll")))] pub mod error; pub mod eth; diff --git a/crates/primitives-traits/Cargo.toml b/crates/primitives-traits/Cargo.toml index 27aa92f4edb..87a96bec480 100644 --- a/crates/primitives-traits/Cargo.toml +++ b/crates/primitives-traits/Cargo.toml @@ -97,9 +97,9 @@ serde = [ "bytes/serde", "rand/serde", "reth-codecs?/serde", - "revm-primitives/serde", "roaring/serde", "revm-primitives/serde", + "reth-scroll-primitives?/serde" ] reth-codec = [ "dep:reth-codecs", diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index 12066447ec2..84e5a5c5f6e 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -30,6 +30,7 @@ alloy-trie = { workspace = true, features = ["serde"] } # scroll revm-primitives = { package = "reth-scroll-revm", path = "../scroll/revm", features = ["serde"] } +reth-scroll-primitives = { workspace = true, optional = true, features = ["serde"] } # optimism op-alloy-rpc-types = { workspace = true, optional = true } @@ -132,7 +133,8 @@ arbitrary = [ "op-alloy-rpc-types?/arbitrary", "reth-codecs?/arbitrary", "alloy-trie/arbitrary", - "reth-trie-common/arbitrary" + "reth-trie-common/arbitrary", + "reth-scroll-primitives?/arbitrary" ] secp256k1 = ["dep:secp256k1"] c-kzg = [ @@ -170,7 +172,8 @@ serde-bincode-compat = [ scroll = [ "reth-trie-common/scroll", "reth-primitives-traits/scroll", - "reth-testing-utils/scroll" + "reth-testing-utils/scroll", + "reth-scroll-primitives" ] [[bench]] diff --git a/crates/primitives/src/alloy_compat.rs b/crates/primitives/src/alloy_compat.rs index a72c83996c0..f190316bcc8 100644 --- a/crates/primitives/src/alloy_compat.rs +++ b/crates/primitives/src/alloy_compat.rs @@ -123,34 +123,64 @@ impl TryFrom for TransactionSigned { let (tx, signature, hash) = tx.into_parts(); (Transaction::Eip7702(tx), signature, hash) } - #[cfg(feature = "optimism")] - AnyTxEnvelope::Unknown(alloy_network::UnknownTxEnvelope { hash, inner }) => { + #[cfg(any(feature = "optimism", feature = "scroll"))] + AnyTxEnvelope::Unknown(alloy_network::UnknownTxEnvelope { hash: _hash, inner }) => { use alloy_consensus::Transaction as _; - if inner.ty() == crate::TxType::Deposit { - let fields: op_alloy_rpc_types::OpTransactionFields = inner - .fields - .clone() - .deserialize_into::() - .map_err(|e| ConversionError::Custom(e.to_string()))?; - ( - Transaction::Deposit(op_alloy_consensus::TxDeposit { - source_hash: fields.source_hash.ok_or_else(|| { - ConversionError::Custom("MissingSourceHash".to_string()) - })?, - from: tx.from, - to: revm_primitives::TxKind::from(inner.to()), - mint: fields.mint.filter(|n| *n != 0), - value: inner.value(), - gas_limit: inner.gas_limit(), - is_system_transaction: fields.is_system_tx.unwrap_or(false), - input: inner.input().clone(), - }), - op_alloy_consensus::TxDeposit::signature(), - hash, - ) - } else { - return Err(ConversionError::Custom("unknown transaction type".to_string())) + match TryInto::::try_into(inner.ty()) + .map_err(|e| ConversionError::Custom(e.to_string()))? + { + #[cfg(all(feature = "optimism", not(feature = "scroll")))] + crate::TxType::Deposit => { + let fields: op_alloy_rpc_types::OpTransactionFields = inner + .fields + .clone() + .deserialize_into::() + .map_err(|e| ConversionError::Custom(e.to_string()))?; + ( + Transaction::Deposit(op_alloy_consensus::TxDeposit { + source_hash: fields.source_hash.ok_or_else(|| { + ConversionError::Custom("MissingSourceHash".to_string()) + })?, + from: tx.from, + to: revm_primitives::TxKind::from(inner.to()), + mint: fields.mint.filter(|n| *n != 0), + value: inner.value(), + gas_limit: inner.gas_limit(), + is_system_transaction: fields.is_system_tx.unwrap_or(false), + input: inner.input().clone(), + }), + op_alloy_consensus::TxDeposit::signature(), + _hash, + ) + } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + crate::TxType::L1Message => { + let fields = + inner + .fields + .clone() + .deserialize_into::() + .map_err(|e| ConversionError::Custom(e.to_string()))?; + ( + Transaction::L1Message(reth_scroll_primitives::TxL1Message { + queue_index: fields.queue_index, + gas_limit: inner.gas_limit(), + to: inner.to().ok_or(ConversionError::Custom( + "Scroll L1 message transaction do not support create transaction" + .to_string(), + ))?, + value: inner.value(), + sender: fields.sender, + input: inner.input().clone(), + }), + reth_scroll_primitives::TxL1Message::signature(), + _hash, + ) + } + _ => { + return Err(ConversionError::Custom("unknown transaction type".to_string())) + } } } _ => return Err(ConversionError::Custom("unknown transaction type".to_string())), @@ -161,7 +191,7 @@ impl TryFrom for TransactionSigned { } #[cfg(test)] -#[cfg(feature = "optimism")] +#[cfg(all(feature = "optimism", not(feature = "scroll")))] mod tests { use super::*; use alloy_primitives::{address, Address, B256, U256}; @@ -270,3 +300,48 @@ mod tests { } } } + +#[cfg(test)] +#[cfg(all(feature = "scroll", not(feature = "optimism")))] +mod tests { + use super::*; + use alloy_primitives::{address, U256}; + + #[test] + fn test_scroll_l1_message_tx() { + // https://scrollscan.com/tx/0x36199419dbdb7823235de4b73e3d90a61c7b1f343b7a682a271c3055249e81f9 + let input = r#"{ + "hash":"0x36199419dbdb7823235de4b73e3d90a61c7b1f343b7a682a271c3055249e81f9", + "nonce":"0x0", + "blockHash":"0x4aca26460c31be3948e8466681ad87891326a964422c9370d4c1913f3bed4b10", + "blockNumber":"0xabf06f", + "transactionIndex":"0x0", + "from":"0x7885bcbd5cecef1336b5300fb5186a12ddd8c478", + "to":"0x781e90f1c8fc4611c9b7497c3b47f99ef6969cbc", + "value":"0x0", + "gasPrice":"0x0", + "gas":"0x1e8480", + "input":"0x8ef1332e000000000000000000000000c186fa914353c44b2e33ebe05f21846f1048beda0000000000000000000000003bad7ad0728f9917d1bf08af5782dcbd516cdd96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e76ab00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044493a4f84f464e58d4bfa93bcc57abfb14dbe1b8ff46cd132b5709aab227f269727943d2f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "r":"0x0", + "s":"0x0", + "v":"0x0", + "type":"0x7e", + "queueIndex":"0xe76ab", + "sender":"0x7885bcbd5cecef1336b5300fb5186a12ddd8c478" + }"#; + let alloy_tx: WithOtherFields> = + serde_json::from_str(input).expect("failed to deserialize"); + + let TransactionSigned { transaction: reth_tx, .. } = + alloy_tx.try_into().expect("failed to convert"); + if let Transaction::L1Message(l1_message_tx) = reth_tx { + assert_eq!(l1_message_tx.queue_index, 0xe76ab); + assert_eq!(l1_message_tx.gas_limit, 0x1e8480); + assert_eq!(l1_message_tx.to, address!("781e90f1c8fc4611c9b7497c3b47f99ef6969cbc")); + assert_eq!(l1_message_tx.value, U256::ZERO); + assert_eq!(l1_message_tx.sender, address!("7885bcbd5cecef1336b5300fb5186a12ddd8c478")); + } else { + panic!("Expected L1 message transaction"); + } + } +} diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 203880209a2..ae840f7f84f 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -19,6 +19,11 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "optimism")] +use op_alloy_consensus as _; +#[cfg(feature = "scroll")] +use reth_scroll_primitives as _; + extern crate alloc; #[cfg(feature = "alloy-compat")] diff --git a/crates/primitives/src/receipt.rs b/crates/primitives/src/receipt.rs index 77a44dc39e5..53adb85eda0 100644 --- a/crates/primitives/src/receipt.rs +++ b/crates/primitives/src/receipt.rs @@ -40,7 +40,7 @@ pub struct Receipt { /// Log send from contracts. pub logs: Vec, /// Deposit nonce for Optimism deposit transactions - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] pub deposit_nonce: Option, /// Deposit receipt version for Optimism deposit transactions /// @@ -48,7 +48,7 @@ pub struct Receipt { /// The deposit receipt version was introduced in Canyon to indicate an update to how /// receipt hashes should be computed when set. The state transition process /// ensures this is only set for post-Canyon deposit transactions. - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] pub deposit_receipt_version: Option, } @@ -228,7 +228,7 @@ impl<'a> arbitrary::Arbitrary<'a> for Receipt { let logs = Vec::::arbitrary(u)?; // Only receipts for deposit transactions may contain a deposit nonce - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] let (deposit_nonce, deposit_receipt_version) = if tx_type == TxType::Deposit { let deposit_nonce = Option::::arbitrary(u)?; let deposit_nonce_version = @@ -243,9 +243,9 @@ impl<'a> arbitrary::Arbitrary<'a> for Receipt { success, cumulative_gas_used, logs, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_nonce, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_receipt_version, }) } @@ -308,7 +308,7 @@ impl ReceiptWithBloom { let logs = alloy_rlp::Decodable::decode(b)?; let receipt = match tx_type { - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] TxType::Deposit => { let remaining = |b: &[u8]| rlp_head.payload_length - (started_len - b.len()) > 0; let deposit_nonce = @@ -330,9 +330,9 @@ impl ReceiptWithBloom { success, cumulative_gas_used, logs, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_nonce: None, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_receipt_version: None, }, }; @@ -392,7 +392,7 @@ impl Decodable for ReceiptWithBloom { buf.advance(1); Self::decode_receipt(buf, TxType::Eip7702) } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] op_alloy_consensus::DEPOSIT_TX_TYPE_ID => { buf.advance(1); Self::decode_receipt(buf, TxType::Deposit) @@ -465,7 +465,7 @@ impl ReceiptWithBloomEncoder<'_> { rlp_head.payload_length += self.bloom.length(); rlp_head.payload_length += self.receipt.logs.length(); - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] if self.receipt.tx_type == TxType::Deposit { if let Some(deposit_nonce) = self.receipt.deposit_nonce { rlp_head.payload_length += deposit_nonce.length(); @@ -485,7 +485,7 @@ impl ReceiptWithBloomEncoder<'_> { self.receipt.cumulative_gas_used.encode(out); self.bloom.encode(out); self.receipt.logs.encode(out); - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] if self.receipt.tx_type == TxType::Deposit { if let Some(deposit_nonce) = self.receipt.deposit_nonce { deposit_nonce.encode(out) @@ -527,10 +527,14 @@ impl ReceiptWithBloomEncoder<'_> { TxType::Eip7702 => { out.put_u8(EIP7702_TX_TYPE_ID); } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] TxType::Deposit => { out.put_u8(op_alloy_consensus::DEPOSIT_TX_TYPE_ID); } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + TxType::L1Message => { + out.put_u8(reth_scroll_primitives::L1_MESSAGE_TRANSACTION_TYPE); + } } out.put_slice(payload.as_ref()); } @@ -596,9 +600,9 @@ mod tests { bytes!("0100ff"), )], success: false, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_nonce: None, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_receipt_version: None, }, bloom: [0; 256].into(), @@ -630,9 +634,9 @@ mod tests { bytes!("0100ff"), )], success: false, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_nonce: None, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_receipt_version: None, }, bloom: [0; 256].into(), @@ -642,7 +646,7 @@ mod tests { assert_eq!(receipt, expected); } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] #[test] fn decode_deposit_receipt_regolith_roundtrip() { let data = hex!("7ef9010c0182b741b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0833d3bbf"); @@ -668,7 +672,7 @@ mod tests { assert_eq!(buf, &data[..]); } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] #[test] fn decode_deposit_receipt_canyon_roundtrip() { let data = hex!("7ef9010d0182b741b9010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0833d3bbf01"); @@ -712,9 +716,9 @@ mod tests { Bytes::from(vec![1; 0xffffff]), ), ], - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_nonce: None, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_receipt_version: None, }; @@ -732,9 +736,9 @@ mod tests { success: true, cumulative_gas_used: 21000, logs: vec![], - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_nonce: None, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_receipt_version: None, }, bloom: Bloom::default(), @@ -754,9 +758,9 @@ mod tests { success: true, cumulative_gas_used: 21000, logs: vec![], - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_nonce: None, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] deposit_receipt_version: None, }, bloom: Bloom::default(), diff --git a/crates/primitives/src/transaction/compat.rs b/crates/primitives/src/transaction/compat.rs index 883c89c45f5..65ea1489655 100644 --- a/crates/primitives/src/transaction/compat.rs +++ b/crates/primitives/src/transaction/compat.rs @@ -1,6 +1,6 @@ use crate::{Transaction, TransactionSigned}; use alloy_primitives::{Address, TxKind, U256}; -#[cfg(feature = "optimism")] +#[cfg(all(feature = "optimism", not(feature = "scroll")))] use op_alloy_consensus::DepositTransaction; use revm_primitives::{AuthorizationList, TxEnv}; @@ -12,7 +12,7 @@ pub trait FillTxEnv { impl FillTxEnv for TransactionSigned { fn fill_tx_env(&self, tx_env: &mut TxEnv, sender: Address) { - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] let envelope = alloy_eips::eip2718::Encodable2718::encoded_2718(self); tx_env.caller = sender; @@ -88,7 +88,7 @@ impl FillTxEnv for TransactionSigned { tx_env.authorization_list = Some(AuthorizationList::Signed(tx.authorization_list.clone())); } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Transaction::Deposit(tx) => { tx_env.access_list.clear(); tx_env.gas_limit = tx.gas_limit; @@ -109,9 +109,25 @@ impl FillTxEnv for TransactionSigned { }; return; } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Transaction::L1Message(tx) => { + tx_env.access_list.clear(); + tx_env.gas_limit = tx.gas_limit; + tx_env.gas_price = U256::ZERO; + tx_env.gas_priority_fee = None; + tx_env.transact_to = tx.to(); + tx_env.value = tx.value; + tx_env.data = tx.input.clone(); + tx_env.chain_id = None; + tx_env.nonce = None; + tx_env.authorization_list = None; + + // TODO (scroll): fill in the Scroll fields when revm fork is introduced in Reth. + // + } } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] if !self.is_deposit() { tx_env.optimism = revm_primitives::OptimismFields { source_hash: None, diff --git a/crates/primitives/src/transaction/mod.rs b/crates/primitives/src/transaction/mod.rs index b8a3f4a719b..e3868b9c1c9 100644 --- a/crates/primitives/src/transaction/mod.rs +++ b/crates/primitives/src/transaction/mod.rs @@ -19,12 +19,14 @@ use derive_more::{AsRef, Deref}; use once_cell as _; #[cfg(not(feature = "std"))] use once_cell::sync::{Lazy as LazyLock, OnceCell as OnceLock}; -#[cfg(feature = "optimism")] +#[cfg(all(feature = "optimism", not(feature = "scroll")))] use op_alloy_consensus::DepositTransaction; -#[cfg(feature = "optimism")] +#[cfg(all(feature = "optimism", not(feature = "scroll")))] use op_alloy_consensus::TxDeposit; use rayon::prelude::{IntoParallelIterator, ParallelIterator}; use reth_primitives_traits::{InMemorySize, SignedTransaction}; +#[cfg(all(feature = "scroll", not(feature = "optimism")))] +use reth_scroll_primitives::l1_transaction::TxL1Message; use revm_primitives::{AuthorizationList, TxEnv}; use serde::{Deserialize, Serialize}; use signature::decode_with_eip155_chain_id; @@ -123,11 +125,14 @@ pub enum Transaction { /// functionality to the EOA. Eip7702(TxEip7702), /// Optimism deposit transaction. - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Deposit(TxDeposit), + /// Scroll L1 messaging transaction. + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + L1Message(TxL1Message), } -#[cfg(feature = "optimism")] +#[cfg(all(feature = "optimism", not(feature = "scroll")))] impl DepositTransaction for Transaction { fn source_hash(&self) -> Option { match self { @@ -177,11 +182,16 @@ impl<'a> arbitrary::Arbitrary<'a> for Transaction { let tx = TxEip7702::arbitrary(u)?; Self::Eip7702(tx) } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] TxType::Deposit => { let tx = TxDeposit::arbitrary(u)?; Self::Deposit(tx) } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + TxType::L1Message => { + let tx = TxL1Message::arbitrary(u)?; + Self::L1Message(tx) + } }; // Otherwise we might overflow when calculating `v` on `recalculate_hash` @@ -205,8 +215,10 @@ impl Transaction { Self::Eip1559(tx) => tx.signature_hash(), Self::Eip4844(tx) => tx.signature_hash(), Self::Eip7702(tx) => tx.signature_hash(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(_) => B256::ZERO, + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(_) => B256::ZERO, } } @@ -218,8 +230,10 @@ impl Transaction { Self::Eip1559(TxEip1559 { chain_id: ref mut c, .. }) | Self::Eip4844(TxEip4844 { chain_id: ref mut c, .. }) | Self::Eip7702(TxEip7702 { chain_id: ref mut c, .. }) => *c = chain_id, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(_) => { /* noop */ } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(_) => { /* noop */ } } } @@ -231,8 +245,10 @@ impl Transaction { Self::Eip1559(_) => TxType::Eip1559, Self::Eip4844(_) => TxType::Eip4844, Self::Eip7702(_) => TxType::Eip7702, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(_) => TxType::Deposit, + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(_) => TxType::L1Message, } } @@ -285,8 +301,10 @@ impl Transaction { Self::Eip1559(tx) => tx.encode_for_signing(out), Self::Eip4844(tx) => tx.encode_for_signing(out), Self::Eip7702(tx) => tx.encode_for_signing(out), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(_) => {} + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(_) => {} } } @@ -307,8 +325,10 @@ impl Transaction { Self::Eip7702(set_code_tx) => { set_code_tx.eip2718_encode(signature, out); } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(deposit_tx) => deposit_tx.eip2718_encode(out), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(l1_message_tx) => l1_message_tx.eip2718_encode(out), } } @@ -320,8 +340,10 @@ impl Transaction { Self::Eip1559(tx) => tx.gas_limit = gas_limit, Self::Eip4844(tx) => tx.gas_limit = gas_limit, Self::Eip7702(tx) => tx.gas_limit = gas_limit, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.gas_limit = gas_limit, + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.gas_limit = gas_limit, } } @@ -333,8 +355,10 @@ impl Transaction { Self::Eip1559(tx) => tx.nonce = nonce, Self::Eip4844(tx) => tx.nonce = nonce, Self::Eip7702(tx) => tx.nonce = nonce, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(_) => { /* noop */ } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(_) => { /* noop */ } } } @@ -346,8 +370,10 @@ impl Transaction { Self::Eip1559(tx) => tx.value = value, Self::Eip4844(tx) => tx.value = value, Self::Eip7702(tx) => tx.value = value, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.value = value, + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.value = value, } } @@ -359,8 +385,10 @@ impl Transaction { Self::Eip1559(tx) => tx.input = input, Self::Eip4844(tx) => tx.input = input, Self::Eip7702(tx) => tx.input = input, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.input = input, + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.input = input, } } @@ -394,6 +422,13 @@ impl Transaction { matches!(self, Self::Eip7702(_)) } + /// Returns true if the transaction is a Scroll L1 messaging transaction. + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + #[inline] + pub fn is_l1_message(&self) -> bool { + matches!(self, Self::L1Message(_)) + } + /// Returns the [`TxLegacy`] variant if the transaction is a legacy transaction. pub const fn as_legacy(&self) -> Option<&TxLegacy> { match self { @@ -445,8 +480,10 @@ impl InMemorySize for Transaction { Self::Eip1559(tx) => tx.size(), Self::Eip4844(tx) => tx.size(), Self::Eip7702(tx) => tx.size(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.size(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.size(), } } } @@ -476,10 +513,14 @@ impl reth_codecs::Compact for Transaction { Self::Eip7702(tx) => { tx.to_compact(buf); } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => { tx.to_compact(buf); } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => { + tx.to_compact(buf); + } } identifier } @@ -524,11 +565,16 @@ impl reth_codecs::Compact for Transaction { let (tx, buf) = TxEip7702::from_compact(buf, buf.len()); (Self::Eip7702(tx), buf) } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] op_alloy_consensus::DEPOSIT_TX_TYPE_ID => { let (tx, buf) = TxDeposit::from_compact(buf, buf.len()); (Self::Deposit(tx), buf) } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + reth_scroll_primitives::L1_MESSAGE_TRANSACTION_TYPE => { + let (tx, buf) = TxL1Message::from_compact(buf, buf.len()); + (Self::L1Message(tx), buf) + } _ => unreachable!( "Junk data in database: unknown Transaction variant: {identifier}" ), @@ -553,8 +599,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.chain_id(), Self::Eip4844(tx) => tx.chain_id(), Self::Eip7702(tx) => tx.chain_id(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.chain_id(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.chain_id(), } } @@ -565,8 +613,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.nonce(), Self::Eip4844(tx) => tx.nonce(), Self::Eip7702(tx) => tx.nonce(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.nonce(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.nonce(), } } @@ -577,8 +627,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.gas_limit(), Self::Eip4844(tx) => tx.gas_limit(), Self::Eip7702(tx) => tx.gas_limit(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.gas_limit(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.gas_limit(), } } @@ -589,8 +641,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.gas_price(), Self::Eip4844(tx) => tx.gas_price(), Self::Eip7702(tx) => tx.gas_price(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.gas_price(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.gas_price(), } } @@ -601,8 +655,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.max_fee_per_gas(), Self::Eip4844(tx) => tx.max_fee_per_gas(), Self::Eip7702(tx) => tx.max_fee_per_gas(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.max_fee_per_gas(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.max_fee_per_gas(), } } @@ -613,8 +669,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.max_priority_fee_per_gas(), Self::Eip4844(tx) => tx.max_priority_fee_per_gas(), Self::Eip7702(tx) => tx.max_priority_fee_per_gas(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.max_priority_fee_per_gas(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.max_priority_fee_per_gas(), } } @@ -625,8 +683,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.max_fee_per_blob_gas(), Self::Eip4844(tx) => tx.max_fee_per_blob_gas(), Self::Eip7702(tx) => tx.max_fee_per_blob_gas(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.max_fee_per_blob_gas(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.max_fee_per_blob_gas(), } } @@ -637,8 +697,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.priority_fee_or_price(), Self::Eip4844(tx) => tx.priority_fee_or_price(), Self::Eip7702(tx) => tx.priority_fee_or_price(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.priority_fee_or_price(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.priority_fee_or_price(), } } @@ -649,8 +711,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.effective_gas_price(base_fee), Self::Eip4844(tx) => tx.effective_gas_price(base_fee), Self::Eip7702(tx) => tx.effective_gas_price(base_fee), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.effective_gas_price(base_fee), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.effective_gas_price(base_fee), } } @@ -658,8 +722,10 @@ impl alloy_consensus::Transaction for Transaction { match self { Self::Legacy(_) | Self::Eip2930(_) => false, Self::Eip1559(_) | Self::Eip4844(_) | Self::Eip7702(_) => true, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(_) => false, + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.is_dynamic_fee(), } } @@ -670,8 +736,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.kind(), Self::Eip4844(tx) => tx.kind(), Self::Eip7702(tx) => tx.kind(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.kind(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.kind(), } } @@ -682,8 +750,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.value(), Self::Eip4844(tx) => tx.value(), Self::Eip7702(tx) => tx.value(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.value(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.value(), } } @@ -694,8 +764,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.input(), Self::Eip4844(tx) => tx.input(), Self::Eip7702(tx) => tx.input(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.input(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.input(), } } @@ -706,8 +778,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.ty(), Self::Eip4844(tx) => tx.ty(), Self::Eip7702(tx) => tx.ty(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.ty(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.ty(), } } @@ -718,8 +792,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.access_list(), Self::Eip4844(tx) => tx.access_list(), Self::Eip7702(tx) => tx.access_list(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.access_list(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.access_list(), } } @@ -730,8 +806,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.blob_versioned_hashes(), Self::Eip4844(tx) => tx.blob_versioned_hashes(), Self::Eip7702(tx) => tx.blob_versioned_hashes(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.blob_versioned_hashes(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.blob_versioned_hashes(), } } @@ -742,8 +820,10 @@ impl alloy_consensus::Transaction for Transaction { Self::Eip1559(tx) => tx.authorization_list(), Self::Eip4844(tx) => tx.authorization_list(), Self::Eip7702(tx) => tx.authorization_list(), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit(tx) => tx.authorization_list(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message(tx) => tx.authorization_list(), } } } @@ -799,10 +879,15 @@ impl TransactionSignedNoHash { pub fn recover_signer(&self) -> Option
{ // Optimism's Deposit transaction does not have a signature. Directly return the // `from` address. - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction { return Some(from) } + // Scroll L1 messages don't have a signature. + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + if let Transaction::L1Message(TxL1Message { sender, .. }) = self.transaction { + return Some(sender) + } let signature_hash = self.signature_hash(); recover_signer(&self.signature, signature_hash) @@ -827,7 +912,7 @@ impl TransactionSignedNoHash { // Optimism's Deposit transaction does not have a signature. Directly return the // `from` address. - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] { if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction { return Some(from) @@ -841,6 +926,10 @@ impl TransactionSignedNoHash { return Some(Address::ZERO) } } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + if let Transaction::L1Message(TxL1Message { sender, .. }) = self.transaction { + return Some(sender) + } recover_signer_unchecked(&self.signature, keccak256(buffer)) } @@ -1086,10 +1175,14 @@ impl TransactionSigned { pub fn recover_signer(&self) -> Option
{ // Optimism's Deposit transaction does not have a signature. Directly return the // `from` address. - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction { return Some(from) } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + if let Transaction::L1Message(TxL1Message { sender, .. }) = self.transaction { + return Some(sender) + } let signature_hash = self.signature_hash(); recover_signer(&self.signature, signature_hash) } @@ -1102,10 +1195,14 @@ impl TransactionSigned { pub fn recover_signer_unchecked(&self) -> Option
{ // Optimism's Deposit transaction does not have a signature. Directly return the // `from` address. - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] if let Transaction::Deposit(TxDeposit { from, .. }) = self.transaction { return Some(from) } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + if let Transaction::L1Message(TxL1Message { sender, .. }) = self.transaction { + return Some(sender) + } let signature_hash = self.signature_hash(); recover_signer_unchecked(&self.signature, signature_hash) } @@ -1366,8 +1463,10 @@ impl reth_primitives_traits::FillTxEnv for TransactionSigned { tx_env.authorization_list = Some(AuthorizationList::Signed(tx.authorization_list.clone())); } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Transaction::Deposit(_) => {} + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Transaction::L1Message(_) => {} } } } @@ -1531,8 +1630,10 @@ impl Encodable2718 for TransactionSigned { Transaction::Eip7702(set_code_tx) => { set_code_tx.eip2718_encoded_length(&self.signature) } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Transaction::Deposit(deposit_tx) => deposit_tx.eip2718_encoded_length(), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Transaction::L1Message(l1_message_tx) => l1_message_tx.eip2718_encoded_length(), } } @@ -1565,11 +1666,16 @@ impl Decodable2718 for TransactionSigned { let (tx, signature, hash) = TxEip4844::rlp_decode_signed(buf)?.into_parts(); Ok(Self { transaction: Transaction::Eip4844(tx), signature, hash: hash.into() }) } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] TxType::Deposit => Ok(Self::new_unhashed( Transaction::Deposit(TxDeposit::rlp_decode(buf)?), TxDeposit::signature(), )), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + TxType::L1Message => Ok(Self::new_unhashed( + Transaction::L1Message(TxL1Message::rlp_decode(buf)?), + TxL1Message::signature(), + )), } } @@ -1621,18 +1727,22 @@ impl<'a> arbitrary::Arbitrary<'a> for TransactionSigned { ) .unwrap(); - #[cfg(feature = "optimism")] // Both `Some(0)` and `None` values are encoded as empty string byte. This introduces // ambiguity in roundtrip tests. Patch the mint value of deposit transaction here, so that // it's `None` if zero. + #[cfg(all(feature = "optimism", not(feature = "scroll")))] if let Transaction::Deposit(ref mut tx_deposit) = transaction { if tx_deposit.mint == Some(0) { tx_deposit.mint = None; } } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] let signature = if transaction.is_deposit() { TxDeposit::signature() } else { signature }; + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + let signature = + if transaction.is_l1_message() { TxL1Message::signature() } else { signature }; + Ok(Self::new_unhashed(transaction, signature)) } } @@ -1751,8 +1861,10 @@ pub mod serde_bincode_compat { Eip1559(TxEip1559<'a>), Eip4844(Cow<'a, TxEip4844>), Eip7702(TxEip7702<'a>), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Deposit(op_alloy_consensus::serde_bincode_compat::TxDeposit<'a>), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + L1Message(Cow<'a, reth_scroll_primitives::l1_transaction::TxL1Message>), } impl<'a> From<&'a super::Transaction> for Transaction<'a> { @@ -1763,10 +1875,12 @@ pub mod serde_bincode_compat { super::Transaction::Eip1559(tx) => Self::Eip1559(TxEip1559::from(tx)), super::Transaction::Eip4844(tx) => Self::Eip4844(Cow::Borrowed(tx)), super::Transaction::Eip7702(tx) => Self::Eip7702(TxEip7702::from(tx)), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] super::Transaction::Deposit(tx) => { Self::Deposit(op_alloy_consensus::serde_bincode_compat::TxDeposit::from(tx)) } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + super::Transaction::L1Message(tx) => Self::L1Message(Cow::Borrowed(tx)), } } } @@ -1779,8 +1893,10 @@ pub mod serde_bincode_compat { Transaction::Eip1559(tx) => Self::Eip1559(tx.into()), Transaction::Eip4844(tx) => Self::Eip4844(tx.into_owned()), Transaction::Eip7702(tx) => Self::Eip7702(tx.into()), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Transaction::Deposit(tx) => Self::Deposit(tx.into()), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Transaction::L1Message(tx) => Self::L1Message(tx.into_owned()), } } } diff --git a/crates/primitives/src/transaction/pooled.rs b/crates/primitives/src/transaction/pooled.rs index 2bd344ea2a1..ea9e91455ed 100644 --- a/crates/primitives/src/transaction/pooled.rs +++ b/crates/primitives/src/transaction/pooled.rs @@ -69,9 +69,12 @@ impl PooledTransactionsElement { } // Not supported because missing blob sidecar tx @ TransactionSigned { transaction: Transaction::Eip4844(_), .. } => Err(tx), - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] // Not supported because deposit transactions are never pooled tx @ TransactionSigned { transaction: Transaction::Deposit(_), .. } => Err(tx), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + // Not supported because l1 message transactions are never pooled + tx @ TransactionSigned { transaction: Transaction::L1Message(_), .. } => Err(tx), } } @@ -384,8 +387,10 @@ impl Decodable2718 for PooledTransactionsElement { )), Transaction::Eip1559(tx) => Ok(Self::Eip1559( Signed::new_unchecked(tx, typed_tx.signature, hash))), Transaction::Eip7702(tx) => Ok(Self::Eip7702( Signed::new_unchecked(tx, typed_tx.signature, hash))), - #[cfg(feature = "optimism")] - Transaction::Deposit(_) => Err(RlpError::Custom("Optimism deposit transaction cannot be decoded to PooledTransactionsElement").into()) + #[cfg(all(feature = "optimism", not(feature = "scroll")))] + Transaction::Deposit(_) => Err(RlpError::Custom("Optimism deposit transaction cannot be decoded to PooledTransactionsElement").into()), + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Transaction::L1Message(_) => Err(RlpError::Custom("Scroll L1 message transaction cannot be decoded to PooledTransactionsElement").into()) } } } diff --git a/crates/primitives/src/transaction/tx_type.rs b/crates/primitives/src/transaction/tx_type.rs index 784a976ab79..9a2b0119124 100644 --- a/crates/primitives/src/transaction/tx_type.rs +++ b/crates/primitives/src/transaction/tx_type.rs @@ -68,9 +68,13 @@ pub enum TxType { #[display("eip7702 (4)")] Eip7702 = 4_isize, /// Optimism Deposit transaction. - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] #[display("deposit (126)")] Deposit = 126_isize, + /// Scroll L1 message transaction. + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + #[display("l1 message (126)")] + L1Message = 126_isize, } impl TxType { @@ -82,8 +86,10 @@ impl TxType { match self { Self::Legacy => false, Self::Eip2930 | Self::Eip1559 | Self::Eip4844 | Self::Eip7702 => true, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit => false, + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message => false, } } } @@ -131,8 +137,10 @@ impl From for u8 { TxType::Eip1559 => EIP1559_TX_TYPE_ID, TxType::Eip4844 => EIP4844_TX_TYPE_ID, TxType::Eip7702 => EIP7702_TX_TYPE_ID, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] TxType::Deposit => op_alloy_consensus::DEPOSIT_TX_TYPE_ID, + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + TxType::L1Message => reth_scroll_primitives::L1_MESSAGE_TRANSACTION_TYPE, } } } @@ -147,11 +155,16 @@ impl TryFrom for TxType { type Error = &'static str; fn try_from(value: u8) -> Result { - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] if value == Self::Deposit { return Ok(Self::Deposit) } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + if value == Self::L1Message { + return Ok(Self::L1Message) + } + if value == Self::Legacy { return Ok(Self::Legacy) } else if value == Self::Eip2930 { @@ -205,11 +218,16 @@ impl reth_codecs::Compact for TxType { buf.put_u8(EIP7702_TX_TYPE_ID); COMPACT_EXTENDED_IDENTIFIER_FLAG } - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] Self::Deposit => { buf.put_u8(op_alloy_consensus::DEPOSIT_TX_TYPE_ID); COMPACT_EXTENDED_IDENTIFIER_FLAG } + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + Self::L1Message => { + buf.put_u8(reth_scroll_primitives::L1_MESSAGE_TRANSACTION_TYPE); + COMPACT_EXTENDED_IDENTIFIER_FLAG + } } } @@ -228,8 +246,10 @@ impl reth_codecs::Compact for TxType { match extended_identifier { EIP4844_TX_TYPE_ID => Self::Eip4844, EIP7702_TX_TYPE_ID => Self::Eip7702, - #[cfg(feature = "optimism")] + #[cfg(all(feature = "optimism", not(feature = "scroll")))] op_alloy_consensus::DEPOSIT_TX_TYPE_ID => Self::Deposit, + #[cfg(all(feature = "scroll", not(feature = "optimism")))] + reth_scroll_primitives::L1_MESSAGE_TRANSACTION_TYPE => Self::L1Message, _ => panic!("Unsupported TxType identifier: {extended_identifier}"), } } @@ -292,9 +312,16 @@ mod tests { #[case(U64::from(EIP4844_TX_TYPE_ID), Ok(TxType::Eip4844))] #[case(U64::from(EIP7702_TX_TYPE_ID), Ok(TxType::Eip7702))] #[cfg_attr( - feature = "optimism", + all(feature = "optimism", not(feature = "scroll")), case(U64::from(op_alloy_consensus::DEPOSIT_TX_TYPE_ID), Ok(TxType::Deposit)) )] + #[cfg_attr( + all(feature = "scroll", not(feature = "optimism")), + case( + U64::from(reth_scroll_primitives::L1_MESSAGE_TRANSACTION_TYPE), + Ok(TxType::L1Message) + ) + )] #[case(U64::MAX, Err("invalid tx type"))] fn test_u64_to_tx_type(#[case] input: U64, #[case] expected: Result) { let tx_type_result = TxType::try_from(input); @@ -307,7 +334,8 @@ mod tests { #[case(TxType::Eip1559, COMPACT_IDENTIFIER_EIP1559, vec![])] #[case(TxType::Eip4844, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![EIP4844_TX_TYPE_ID])] #[case(TxType::Eip7702, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![EIP7702_TX_TYPE_ID])] - #[cfg_attr(feature = "optimism", case(TxType::Deposit, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![op_alloy_consensus::DEPOSIT_TX_TYPE_ID]))] + #[cfg_attr(all(feature = "optimism", not(feature = "scroll")), case(TxType::Deposit, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![op_alloy_consensus::DEPOSIT_TX_TYPE_ID]))] + #[cfg_attr(all(feature = "scroll", not(feature = "optimism")), case(TxType::L1Message, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![reth_scroll_primitives::L1_MESSAGE_TRANSACTION_TYPE]))] fn test_txtype_to_compact( #[case] tx_type: TxType, #[case] expected_identifier: usize, @@ -326,7 +354,8 @@ mod tests { #[case(TxType::Eip1559, COMPACT_IDENTIFIER_EIP1559, vec![])] #[case(TxType::Eip4844, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![EIP4844_TX_TYPE_ID])] #[case(TxType::Eip7702, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![EIP7702_TX_TYPE_ID])] - #[cfg_attr(feature = "optimism", case(TxType::Deposit, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![op_alloy_consensus::DEPOSIT_TX_TYPE_ID]))] + #[cfg_attr(all(feature = "optimism", not(feature = "scroll")), case(TxType::Deposit, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![op_alloy_consensus::DEPOSIT_TX_TYPE_ID]))] + #[cfg_attr(all(feature = "scroll", not(feature = "optimism")), case(TxType::L1Message, COMPACT_EXTENDED_IDENTIFIER_FLAG, vec![reth_scroll_primitives::L1_MESSAGE_TRANSACTION_TYPE]))] fn test_txtype_from_compact( #[case] expected_type: TxType, #[case] identifier: usize, @@ -345,7 +374,8 @@ mod tests { #[case(&[EIP4844_TX_TYPE_ID], Ok(TxType::Eip4844))] #[case(&[EIP7702_TX_TYPE_ID], Ok(TxType::Eip7702))] #[case(&[u8::MAX], Err(alloy_rlp::Error::InputTooShort))] - #[cfg_attr(feature = "optimism", case(&[op_alloy_consensus::DEPOSIT_TX_TYPE_ID], Ok(TxType::Deposit)))] + #[cfg_attr(all(feature = "optimism", not(feature = "scroll")), case(&[op_alloy_consensus::DEPOSIT_TX_TYPE_ID], Ok(TxType::Deposit)))] + #[cfg_attr(all(feature = "scroll", not(feature = "optimism")), case(&[reth_scroll_primitives::L1_MESSAGE_TRANSACTION_TYPE], Ok(TxType::L1Message)))] fn decode_tx_type(#[case] input: &[u8], #[case] expected: Result) { let tx_type_result = TxType::decode(&mut &input[..]); assert_eq!(tx_type_result, expected) diff --git a/crates/revm/Cargo.toml b/crates/revm/Cargo.toml index ecccb30475e..ede5c78f45b 100644 --- a/crates/revm/Cargo.toml +++ b/crates/revm/Cargo.toml @@ -63,6 +63,7 @@ serde = [ "alloy-primitives/serde", "alloy-consensus/serde", "reth-primitives-traits/serde", + "reth-scroll-primitives?/serde" ] scroll = [ "reth-scroll-primitives", diff --git a/crates/scroll/primitives/Cargo.toml b/crates/scroll/primitives/Cargo.toml index a8c17e0ff51..4a023027641 100644 --- a/crates/scroll/primitives/Cargo.toml +++ b/crates/scroll/primitives/Cargo.toml @@ -13,35 +13,74 @@ workspace = true [dependencies] # alloy +alloy-consensus.workspace = true +alloy-eips.workspace = true alloy-primitives.workspace = true +alloy-rlp.workspace = true +alloy-serde.workspace = true # reth -reth-codecs.workspace = true +reth-codecs = { workspace = true, optional = true } +reth-codecs-derive = { workspace = true, optional = true } # scroll poseidon-bn254 = { workspace = true, features = ["bn254"] } -# misc -arbitrary = { workspace = true, features = ["derive"], optional = true } - # required by reth-codecs bytes.workspace = true -modular-bitfield.workspace = true +modular-bitfield = { workspace = true, optional = true } serde.workspace = true +# misc +arbitrary = { workspace = true, features = ["derive"], optional = true } + [dev-dependencies] alloy-primitives = { workspace = true, features = ["arbitrary"] } arbitrary = { workspace = true, features = ["derive"] } +bincode.workspace = true +rand.workspace = true +reth-codecs = { workspace = true, features = ["test-utils"] } + +proptest-arbitrary-interop.workspace = true +proptest.workspace = true test-fuzz.workspace = true [features] -default = ["std"] +default = [ + "reth-codec", + "std" +] std = [ "serde/std", - "alloy-primitives/std" + "alloy-primitives/std", + "reth-codecs/std", + "alloy-consensus/std", + "alloy-eips/std", + "alloy-rlp/std", + "alloy-serde/std", + "bytes/std", + "proptest/std", + "rand/std" ] arbitrary = [ "dep:arbitrary", "alloy-primitives/arbitrary", + "alloy-consensus/arbitrary", + "alloy-eips/arbitrary", + "alloy-serde/arbitrary", "reth-codecs/arbitrary" ] +reth-codec = [ + "dep:reth-codecs", + "dep:reth-codecs-derive", + "modular-bitfield", + "std" +] +serde = [ + "alloy-primitives/serde", + "alloy-consensus/serde", + "alloy-eips/serde", + "bytes/serde", + "rand/serde", + "reth-codecs/serde" +] diff --git a/crates/scroll/primitives/src/account_extension.rs b/crates/scroll/primitives/src/account_extension.rs index c48181f9ea2..6c732c18c38 100644 --- a/crates/scroll/primitives/src/account_extension.rs +++ b/crates/scroll/primitives/src/account_extension.rs @@ -1,13 +1,13 @@ use crate::{hash_code, POSEIDON_EMPTY}; use alloy_primitives::B256; -use reth_codecs::Compact; use serde::{Deserialize, Serialize}; /// The extension for a Scroll account's representation in storage. /// /// The extension is used in order to maintain backwards compatibility if more fields need to be /// added to the account (see [reth codecs](reth_codecs::test_utils) for details). -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize, Compact)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[cfg_attr(feature = "reth-codec", derive(reth_codecs::Compact))] #[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] pub struct AccountExtension { /// Size in bytes of the account's bytecode. diff --git a/crates/scroll/primitives/src/l1_transaction.rs b/crates/scroll/primitives/src/l1_transaction.rs new file mode 100644 index 00000000000..3f71d1e8075 --- /dev/null +++ b/crates/scroll/primitives/src/l1_transaction.rs @@ -0,0 +1,308 @@ +//! Scroll L1 message transaction + +use alloy_consensus::Transaction; +use alloy_primitives::{ + private::alloy_rlp::{Encodable, Header}, + Address, Bytes, ChainId, PrimitiveSignature as Signature, TxKind, B256, U256, +}; +use alloy_rlp::Decodable; +use bytes::BufMut; +use serde::{Deserialize, Serialize}; +#[cfg(any(test, feature = "reth-codec"))] +use {reth_codecs::Compact, reth_codecs_derive::add_arbitrary_tests}; + +/// L1 message transaction type id, 0x7e in hex. +pub const L1_MESSAGE_TRANSACTION_TYPE: u8 = 126; + +/// A message transaction sent from the settlement layer to the L2 for execution. +/// +/// The signature of the L1 message is already verified on the L1 and as such doesn't contain +/// a signature field. Gas for the transaction execution on Scroll is already paid for on the L1. +/// +/// # Bincode compatibility +/// +/// `bincode` crate doesn't work with optionally serializable serde fields and some of the execution +/// types require optional serialization for RPC compatibility. Since `TxL1Message` doesn't +/// contain optionally serializable fields, no `bincode` compatible bridge implementation is +/// required. +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] +#[cfg_attr(any(test, feature = "serde"), derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(any(test, feature = "serde"), serde(rename_all = "camelCase"))] +#[cfg_attr(any(test, feature = "arbitrary"), derive(arbitrary::Arbitrary))] +#[cfg_attr(any(test, feature = "reth-codec"), derive(Compact))] +#[cfg_attr(any(test, feature = "reth-codec"), add_arbitrary_tests(compact, rlp))] +pub struct TxL1Message { + /// The queue index of the message in the L1 contract queue. + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity"))] + pub queue_index: u64, + /// The gas limit for the transaction. Gas is paid for when message is sent from the L1. + #[cfg_attr(feature = "serde", serde(with = "alloy_serde::quantity", rename = "gas"))] + pub gas_limit: u64, + /// The destination for the transaction. `Address` is used in place of `TxKind` since contract + /// creations aren't allowed via L1 message transactions. + pub to: Address, + /// The value sent. + pub value: U256, + /// The L1 sender of the transaction. + pub sender: Address, + /// The input of the transaction. + pub input: Bytes, +} + +impl TxL1Message { + /// Returns an empty signature for the [`TxL1Message`], which don't include a signature. + pub fn signature() -> Signature { + Signature::new(U256::ZERO, U256::ZERO, false) + } + + /// Returns the destination of the transaction as a [`TxKind`]. + pub const fn to(&self) -> TxKind { + TxKind::Call(self.to) + } + + /// Decodes the inner [`TxL1Message`] fields from RLP bytes. + /// + /// NOTE: This assumes a RLP header has already been decoded, and _just_ decodes the following + /// RLP fields in the following order: + /// + /// - `queue_index` + /// - `gas_limit` + /// - `to` + /// - `value` + /// - `input` + /// - `sender` + pub fn rlp_decode_fields(buf: &mut &[u8]) -> alloy_rlp::Result { + Ok(Self { + queue_index: Decodable::decode(buf)?, + gas_limit: Decodable::decode(buf)?, + to: Decodable::decode(buf)?, + value: Decodable::decode(buf)?, + input: Decodable::decode(buf)?, + sender: Decodable::decode(buf)?, + }) + } + + /// Decodes the transaction from RLP bytes. + pub fn rlp_decode(buf: &mut &[u8]) -> alloy_rlp::Result { + let header = Header::decode(buf)?; + if !header.list { + return Err(alloy_rlp::Error::UnexpectedString); + } + let remaining = buf.len(); + + let this = Self::rlp_decode_fields(buf)?; + + if buf.len() + header.payload_length != remaining { + return Err(alloy_rlp::Error::ListLengthMismatch { + expected: header.payload_length, + got: remaining - buf.len(), + }); + } + + Ok(this) + } + + /// Outputs the length of the transaction's fields, without a RLP header. + fn rlp_encoded_fields_length(&self) -> usize { + self.queue_index.length() + + self.gas_limit.length() + + self.to.length() + + self.value.length() + + self.input.0.length() + + self.sender.length() + } + + /// Encode the fields of the transaction without a RLP header. + /// + fn rlp_encode_fields(&self, out: &mut dyn BufMut) { + self.queue_index.encode(out); + self.gas_limit.encode(out); + self.to.encode(out); + self.value.encode(out); + self.input.encode(out); + self.sender.encode(out); + } + + /// Create a RLP header for the transaction. + fn rlp_header(&self) -> Header { + Header { list: true, payload_length: self.rlp_encoded_fields_length() } + } + + /// RLP encodes the transaction. + pub fn rlp_encode(&self, out: &mut dyn BufMut) { + self.rlp_header().encode(out); + self.rlp_encode_fields(out); + } + + /// Get the length of the transaction when RLP encoded. + pub fn rlp_encoded_length(&self) -> usize { + self.rlp_header().length_with_payload() + } + + /// Get the length of the transaction when EIP-2718 encoded. This is the + /// 1 byte type flag + the length of the RLP encoded transaction. + pub fn eip2718_encoded_length(&self) -> usize { + self.rlp_encoded_length() + 1 + } + + /// EIP-2718 encode the transaction. + pub fn eip2718_encode(&self, out: &mut dyn BufMut) { + out.put_u8(L1_MESSAGE_TRANSACTION_TYPE); + self.rlp_encode(out) + } + + /// Calculates the in-memory size of the [`TxL1Message`] transaction. + #[inline] + pub fn size(&self) -> usize { + size_of::() + // queue_index + size_of::() + // gas_limit + size_of::
() + // to + size_of::() + // value + self.input.len() + // input + size_of::
() // sender + } +} + +impl Encodable for TxL1Message { + fn encode(&self, out: &mut dyn BufMut) { + self.rlp_encode(out) + } + + fn length(&self) -> usize { + self.rlp_encoded_length() + } +} + +impl Decodable for TxL1Message { + fn decode(buf: &mut &[u8]) -> alloy_rlp::Result { + Self::rlp_decode(buf) + } +} + +impl Transaction for TxL1Message { + fn chain_id(&self) -> Option { + None + } + + fn nonce(&self) -> u64 { + 0u64 + } + + fn gas_limit(&self) -> u64 { + self.gas_limit + } + + fn gas_price(&self) -> Option { + None + } + + fn max_fee_per_gas(&self) -> u128 { + 0 + } + + fn max_priority_fee_per_gas(&self) -> Option { + None + } + + fn max_fee_per_blob_gas(&self) -> Option { + None + } + + fn priority_fee_or_price(&self) -> u128 { + 0 + } + + fn effective_gas_price(&self, _base_fee: Option) -> u128 { + 0 + } + + fn is_dynamic_fee(&self) -> bool { + false + } + + fn kind(&self) -> TxKind { + self.to() + } + + fn value(&self) -> U256 { + self.value + } + + fn input(&self) -> &Bytes { + &self.input + } + + fn ty(&self) -> u8 { + L1_MESSAGE_TRANSACTION_TYPE + } + + fn access_list(&self) -> Option<&alloy_eips::eip2930::AccessList> { + None + } + + fn blob_versioned_hashes(&self) -> Option<&[B256]> { + None + } + + fn authorization_list(&self) -> Option<&[alloy_eips::eip7702::SignedAuthorization]> { + None + } +} + +/// Scroll specific transaction fields +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ScrollL1MessageTransactionFields { + /// The index of the transaction in the message queue. + #[serde(with = "alloy_serde::quantity")] + pub queue_index: u64, + /// The sender of the transaction on the L1. + pub sender: Address, +} + +#[cfg(test)] +mod tests { + use super::TxL1Message; + use alloy_primitives::{address, bytes, hex, Bytes, U256}; + use arbitrary::Arbitrary; + use bytes::BytesMut; + use rand::Rng; + use reth_codecs::{test_utils::UnusedBits, validate_bitflag_backwards_compat}; + + #[test] + fn test_bincode_roundtrip() { + let mut bytes = [0u8; 1024]; + rand::thread_rng().fill(bytes.as_mut_slice()); + let tx = TxL1Message::arbitrary(&mut arbitrary::Unstructured::new(&bytes)).unwrap(); + + let encoded = bincode::serialize(&tx).unwrap(); + let decoded: TxL1Message = bincode::deserialize(&encoded).unwrap(); + assert_eq!(decoded, tx); + } + + #[test] + fn test_eip2718_encode() { + let tx = + TxL1Message { + queue_index: 947883, + gas_limit: 2000000, + to: address!("781e90f1c8fc4611c9b7497c3b47f99ef6969cbc"), + value: U256::ZERO, + sender: address!("7885bcbd5cecef1336b5300fb5186a12ddd8c478"), + input: bytes!("8ef1332e000000000000000000000000c186fa914353c44b2e33ebe05f21846f1048beda0000000000000000000000003bad7ad0728f9917d1bf08af5782dcbd516cdd96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e76ab00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044493a4f84f464e58d4bfa93bcc57abfb14dbe1b8ff46cd132b5709aab227f269727943d2f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"), + } + ; + let bytes = Bytes::from_static(&hex!("7ef9015a830e76ab831e848094781e90f1c8fc4611c9b7497c3b47f99ef6969cbc80b901248ef1332e000000000000000000000000c186fa914353c44b2e33ebe05f21846f1048beda0000000000000000000000003bad7ad0728f9917d1bf08af5782dcbd516cdd96000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e76ab00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000044493a4f84f464e58d4bfa93bcc57abfb14dbe1b8ff46cd132b5709aab227f269727943d2f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000947885bcbd5cecef1336b5300fb5186a12ddd8c478")); + + let mut encoded = BytesMut::default(); + tx.eip2718_encode(&mut encoded); + + assert_eq!(encoded, bytes.as_ref()) + } + + #[test] + fn test_compaction_backwards_compatibility() { + assert_eq!(TxL1Message::bitflag_encoded_bytes(), 2); + validate_bitflag_backwards_compat!(TxL1Message, UnusedBits::NotZero); + } +} diff --git a/crates/scroll/primitives/src/lib.rs b/crates/scroll/primitives/src/lib.rs index 2a18616b307..2f2796db6e2 100644 --- a/crates/scroll/primitives/src/lib.rs +++ b/crates/scroll/primitives/src/lib.rs @@ -8,5 +8,10 @@ mod execution_context; pub use account_extension::AccountExtension; mod account_extension; +pub use l1_transaction::{ + ScrollL1MessageTransactionFields, TxL1Message, L1_MESSAGE_TRANSACTION_TYPE, +}; +pub mod l1_transaction; + pub use poseidon::{hash_code, POSEIDON_EMPTY}; mod poseidon; diff --git a/crates/scroll/revm/Cargo.toml b/crates/scroll/revm/Cargo.toml index b4326366779..7f1d25b9e9a 100644 --- a/crates/scroll/revm/Cargo.toml +++ b/crates/scroll/revm/Cargo.toml @@ -33,7 +33,8 @@ c-kzg = ["revm/c-kzg"] optimism = ["revm/optimism"] serde = [ "revm/serde", - "serde/std" + "serde/std", + "reth-scroll-primitives/serde" ] scroll = [] test-utils = ["revm/test-utils"] diff --git a/crates/storage/provider/Cargo.toml b/crates/storage/provider/Cargo.toml index 9db25d5ee09..6ca1e9c33ef 100644 --- a/crates/storage/provider/Cargo.toml +++ b/crates/storage/provider/Cargo.toml @@ -118,6 +118,7 @@ serde = [ "reth-codecs/serde", "reth-optimism-primitives?/serde", "reth-primitives-traits/serde", + "reth-scroll-primitives?/serde" ] test-utils = [ "reth-db/test-utils", diff --git a/crates/trie/db/Cargo.toml b/crates/trie/db/Cargo.toml index ef9517fb8b9..533a49cb48c 100644 --- a/crates/trie/db/Cargo.toml +++ b/crates/trie/db/Cargo.toml @@ -75,7 +75,8 @@ serde = [ "alloy-consensus/serde", "alloy-primitives/serde", "revm/serde", - "similar-asserts/serde" + "similar-asserts/serde", + "reth-scroll-primitives/serde" ] test-utils = [ "triehash",