diff --git a/cmd/ethrex/build_l2.rs b/cmd/ethrex/build_l2.rs
index c67c2976a04..dc6890bd9da 100644
--- a/cmd/ethrex/build_l2.rs
+++ b/cmd/ethrex/build_l2.rs
@@ -101,6 +101,10 @@ pub fn download_script() {
&Path::new("../../crates/l2/contracts/src/l1/CommonBridge.sol"),
"CommonBridge",
),
+ (
+ &Path::new("../../crates/l2/contracts/src/l1/Router.sol"),
+ "Router",
+ ),
];
for (path, name) in l1_contracts {
compile_contract_to_bytecode(
@@ -171,6 +175,7 @@ fn write_empty_bytecode_files(output_contracts_path: &Path) {
"SP1Verifier",
"OnChainProposer",
"CommonBridge",
+ "Router",
"CommonBridgeL2",
"L2ToL1Messenger",
"UpgradeableSystemContract",
diff --git a/cmd/ethrex/l2/command.rs b/cmd/ethrex/l2/command.rs
index 3616aa5f67a..28028d67213 100644
--- a/cmd/ethrex/l2/command.rs
+++ b/cmd/ethrex/l2/command.rs
@@ -202,8 +202,8 @@ pub enum Command {
#[arg(
long,
value_parser = parse_private_key,
- env = "SEQUENCER_PRIVATE_KEY",
- help = "The private key of the sequencer",
+ env = "SEQUENCER_PRIVATE_KEY",
+ help = "The private key of the sequencer",
help_heading = "Sequencer account options",
group = "sequencer_signing",
)]
@@ -449,7 +449,7 @@ impl Command {
.await?;
// Get withdrawal hashes
- let message_hashes = state_diff
+ let l1_message_hashes = state_diff
.l1_messages
.iter()
.map(get_l1_message_hash)
@@ -488,7 +488,9 @@ impl Command {
last_block: new_block.number,
state_root: new_block.state_root,
privileged_transactions_hash: H256::zero(),
- message_hashes,
+ l1_message_hashes,
+ // TODO: Check if this is restorable
+ l2_to_l2_messages: vec![],
blobs_bundle: BlobsBundle::empty(),
commit_tx: None,
verify_tx: None,
diff --git a/cmd/ethrex/l2/deployer.rs b/cmd/ethrex/l2/deployer.rs
index ffb86e50717..50343b26d20 100644
--- a/cmd/ethrex/l2/deployer.rs
+++ b/cmd/ethrex/l2/deployer.rs
@@ -333,6 +333,23 @@ pub struct DeployerOptions {
help = "The L1 address of the L2 native token (e.g., USDC, USDT, DAI, etc. Use address(0) for ETH)"
)]
pub native_token_l1_address: Address,
+ #[arg(
+ long = "router.deploy",
+ default_value = "false",
+ env = "ETHREX_SHARED_BRIDGE_DEPLOY_ROUTER",
+ help_heading = "Deployer options",
+ help = "If set, the deployer will deploy the shared bridge router contract. Default to false",
+ conflicts_with = "router"
+ )]
+ pub deploy_router: bool,
+ #[arg(
+ long = "router.address",
+ value_name = "ADDRESS",
+ env = "ETHREX_SHARED_BRIDGE_ROUTER_ADDRESS",
+ help_heading = "Deployer options",
+ help = "The address of the shared bridge router"
+ )]
+ pub router: Option
,
}
impl Default for DeployerOptions {
@@ -416,6 +433,8 @@ impl Default for DeployerOptions {
inclusion_max_wait: 3000,
use_compiled_genesis: true,
native_token_l1_address: H160::zero(),
+ router: None,
+ deploy_router: false,
}
}
}
@@ -459,6 +478,13 @@ pub enum DeployerError {
Genesis,
}
+/// Bytecode of the Router contract.
+/// This is generated by the [build script](./build.rs).
+const ROUTER_BYTECODE: &[u8] = include_bytes!(concat!(
+ env!("OUT_DIR"),
+ "/contracts/solc_out/Router.bytecode"
+));
+
/// Bytecode of the OnChainProposer contract.
/// This is generated by the [build script](./build.rs).
const ON_CHAIN_PROPOSER_BYTECODE: &[u8] = include_bytes!(concat!(
@@ -500,7 +526,9 @@ const INITIALIZE_ON_CHAIN_PROPOSER_SIGNATURE: &str = "initialize(bool,address,ad
const INITIALIZE_BRIDGE_ADDRESS_SIGNATURE: &str = "initializeBridgeAddress(address)";
const TRANSFER_OWNERSHIP_SIGNATURE: &str = "transferOwnership(address)";
const ACCEPT_OWNERSHIP_SIGNATURE: &str = "acceptOwnership()";
-const BRIDGE_INITIALIZER_SIGNATURE: &str = "initialize(address,address,uint256,address)";
+const BRIDGE_INITIALIZER_SIGNATURE: &str = "initialize(address,address,uint256,address,address)";
+const ROUTER_INITIALIZER_SIGNATURE: &str = "initialize(address)";
+const ROUTER_REGISTER_SIGNATURE: &str = "register(uint256,address)";
// deposit(uint256 _amount, address _l2Recipient)
const NATIVE_TOKEN_DEPOSIT_SIGNATURE: &str = "deposit(uint256,address)";
@@ -517,6 +545,7 @@ pub struct ContractAddresses {
pub tdx_verifier_address: Address,
pub sequencer_registry_address: Address,
pub aligned_aggregator_address: Address,
+ pub router: Option,
}
pub async fn deploy_l1_contracts(
@@ -535,9 +564,32 @@ pub async fn deploy_l1_contracts(
Some(opts.maximum_allowed_max_fee_per_blob_gas),
)?;
+ let genesis: Genesis = if opts.use_compiled_genesis {
+ serde_json::from_str(LOCAL_DEVNETL2_GENESIS_CONTENTS).map_err(|_| DeployerError::Genesis)?
+ } else {
+ read_genesis_file(
+ opts.genesis_l2_path
+ .to_str()
+ .ok_or(DeployerError::FailedToGetStringFromPath)?,
+ )
+ };
+
let contract_addresses = deploy_contracts(ð_client, &opts, &signer).await?;
- initialize_contracts(contract_addresses, ð_client, &opts, &signer).await?;
+ initialize_contracts(contract_addresses, ð_client, &opts, &genesis, &signer).await?;
+
+ if contract_addresses.router.is_some() {
+ let _ = register_chain(
+ ð_client,
+ contract_addresses,
+ genesis.config.chain_id,
+ &signer,
+ )
+ .await
+ .inspect_err(|err| {
+ warn!(%err, "Could not register chain in shared bridge router");
+ });
+ }
if opts.deposit_rich {
if opts.native_token_l1_address != Address::zero() {
@@ -573,8 +625,6 @@ async fn deploy_contracts(
) -> Result {
trace!("Deploying contracts");
- info!("Deploying OnChainProposer");
-
let salt = if opts.randomize_contract_deployment {
H256::random().as_bytes().to_vec()
} else {
@@ -584,6 +634,31 @@ async fn deploy_contracts(
.to_vec()
};
+ let deployed_router = if opts.deploy_router {
+ info!("Deploying Router");
+
+ let bytecode = ROUTER_BYTECODE.to_vec();
+ if bytecode.is_empty() {
+ return Err(DeployerError::BytecodeNotFound);
+ }
+
+ let router_deployment =
+ deploy_with_proxy_from_bytecode(deployer, eth_client, &bytecode, &salt).await?;
+ info!(
+ "Router deployed:\n Proxy -> address={:#x}, tx_hash={:#x}\n Impl -> address={:#x}, tx_hash={:#x}",
+ router_deployment.proxy_address,
+ router_deployment.proxy_tx_hash,
+ router_deployment.implementation_address,
+ router_deployment.implementation_tx_hash,
+ );
+
+ Some(router_deployment.proxy_address)
+ } else {
+ None
+ };
+
+ info!("Deploying OnChainProposer");
+
trace!("Attempting to deploy OnChainProposer contract");
let bytecode = if opts.deploy_based_contracts {
ON_CHAIN_PROPOSER_BASED_BYTECODE.to_vec()
@@ -696,6 +771,7 @@ async fn deploy_contracts(
tdx_verifier_address,
sequencer_registry_address: sequencer_registry_deployment.proxy_address,
aligned_aggregator_address: opts.aligned_aggregator_address,
+ router: opts.router.or(deployed_router),
})
}
@@ -755,22 +831,13 @@ async fn initialize_contracts(
contract_addresses: ContractAddresses,
eth_client: &EthClient,
opts: &DeployerOptions,
+ genesis: &Genesis,
initializer: &Signer,
) -> Result<(), DeployerError> {
trace!("Initializing contracts");
trace!(committer_l1_address = %opts.committer_l1_address, "Using committer L1 address for OnChainProposer initialization");
- let genesis: Genesis = if opts.use_compiled_genesis {
- serde_json::from_str(LOCAL_DEVNETL2_GENESIS_CONTENTS).map_err(|_| DeployerError::Genesis)?
- } else {
- read_genesis_file(
- opts.genesis_l2_path
- .to_str()
- .ok_or(DeployerError::FailedToGetStringFromPath)?,
- )
- };
-
let sp1_vk = read_vk(&opts.sp1_vk_path);
let risc0_vk = read_vk(&opts.risc0_vk_path);
@@ -834,6 +901,22 @@ async fn initialize_contracts(
};
info!(tx_hash = %format!("{initialize_tx_hash:#x}"), "SequencerRegistry initialized");
} else {
+ if let Some(router) = contract_addresses.router
+ && opts.deploy_router
+ {
+ let calldata_values = vec![Value::Address(deployer_address)];
+ let router_initialization_calldata =
+ encode_calldata(ROUTER_INITIALIZER_SIGNATURE, &calldata_values)?;
+ let initialize_tx_hash = initialize_contract(
+ router,
+ router_initialization_calldata,
+ initializer,
+ eth_client,
+ )
+ .await?;
+ info!(tx_hash = %format!("{initialize_tx_hash:#x}"), "Router initialized");
+ }
+
// Initialize only OnChainProposer without Based config
let calldata_values = vec![
Value::Bool(opts.validium),
@@ -936,6 +1019,7 @@ async fn initialize_contracts(
Value::Address(contract_addresses.on_chain_proposer_address),
Value::Uint(opts.inclusion_max_wait.into()),
Value::Address(opts.native_token_l1_address),
+ Value::Address(contract_addresses.router.unwrap_or_default()),
];
let bridge_initialization_calldata =
encode_calldata(BRIDGE_INITIALIZER_SIGNATURE, &calldata_values)?;
@@ -954,6 +1038,35 @@ async fn initialize_contracts(
Ok(())
}
+async fn register_chain(
+ eth_client: &EthClient,
+ contract_addresses: ContractAddresses,
+ chain_id: u64,
+ deployer: &Signer,
+) -> Result<(), DeployerError> {
+ let params = vec![
+ Value::Uint(U256::from(chain_id)),
+ Value::Address(contract_addresses.bridge_address),
+ ];
+
+ ethrex_l2_sdk::call_contract(
+ eth_client,
+ deployer,
+ contract_addresses
+ .router
+ .ok_or(DeployerError::InternalError(
+ "Router address is None. This is a bug.".to_string(),
+ ))?,
+ ROUTER_REGISTER_SIGNATURE,
+ params,
+ )
+ .await?;
+
+ info!(chain_id, "Chain registered");
+
+ Ok(())
+}
+
async fn make_deposits(
bridge: Address,
eth_client: &EthClient,
@@ -1199,6 +1312,11 @@ fn write_contract_addresses_to_env(
"ETHREX_NATIVE_TOKEN_L1_ADDRESS={:#x}",
native_token_l1_address
)?;
+ writeln!(
+ writer,
+ "ETHREX_SHARED_BRIDGE_ROUTER_ADDRESS={:#x}",
+ contract_addresses.router.unwrap_or_default()
+ )?;
trace!(?env_file_path, "Contract addresses written to .env");
Ok(())
}
diff --git a/crates/common/types/l2.rs b/crates/common/types/l2.rs
index d1764247099..47258176fda 100644
--- a/crates/common/types/l2.rs
+++ b/crates/common/types/l2.rs
@@ -1,2 +1,3 @@
pub mod batch;
pub mod fee_config;
+pub mod l2_to_l2_message;
diff --git a/crates/common/types/l2/batch.rs b/crates/common/types/l2/batch.rs
index 379d6ce814e..78de4b808c5 100644
--- a/crates/common/types/l2/batch.rs
+++ b/crates/common/types/l2/batch.rs
@@ -1,4 +1,7 @@
-use crate::{H256, types::BlobsBundle};
+use crate::{
+ H256,
+ types::{BlobsBundle, l2_to_l2_message::L2toL2Message},
+};
use serde::{Deserialize, Serialize};
#[derive(Clone, Serialize, Deserialize, Debug, Default)]
@@ -8,7 +11,8 @@ pub struct Batch {
pub last_block: u64,
pub state_root: H256,
pub privileged_transactions_hash: H256,
- pub message_hashes: Vec,
+ pub l1_message_hashes: Vec,
+ pub l2_to_l2_messages: Vec,
pub blobs_bundle: BlobsBundle,
pub commit_tx: Option,
pub verify_tx: Option,
diff --git a/crates/common/types/l2/l2_to_l2_message.rs b/crates/common/types/l2/l2_to_l2_message.rs
new file mode 100644
index 00000000000..1ad4965b41a
--- /dev/null
+++ b/crates/common/types/l2/l2_to_l2_message.rs
@@ -0,0 +1,46 @@
+use bytes::Bytes;
+use ethereum_types::{Address, U256};
+use ethrex_rlp::{decode::RLPDecode, structs::Decoder};
+use serde::{Deserialize, Serialize};
+
+#[derive(Debug, Clone, Default, Serialize, Deserialize)]
+/// Represents a message from the L2 to another L2
+pub struct L2toL2Message {
+ /// Chain id of the destination chain
+ pub chain_id: U256,
+ /// Address that originated the transaction
+ pub from: Address,
+ /// Address of the recipient in the destination chain
+ pub to: Address,
+ /// Amount of ETH to send to the recipient
+ pub value: U256,
+ /// Gas limit for the transaction execution in the destination chain
+ pub gas_limit: U256,
+ /// Calldata for the transaction in the destination chain
+ pub data: Bytes,
+}
+
+impl RLPDecode for L2toL2Message {
+ fn decode_unfinished(rlp: &[u8]) -> Result<(Self, &[u8]), ethrex_rlp::error::RLPDecodeError> {
+ let decoder = Decoder::new(rlp)?;
+
+ let (chain_id, decoder) = decoder.decode_field("chain_id")?;
+ let (from, decoder) = decoder.decode_field("from")?;
+ let (to, decoder) = decoder.decode_field("to")?;
+ let (value, decoder) = decoder.decode_field("value")?;
+ let (gas_limit, decoder) = decoder.decode_field("gas_limit")?;
+ let (data, decoder) = decoder.decode_field("data")?;
+
+ Ok((
+ L2toL2Message {
+ chain_id,
+ from,
+ to,
+ value,
+ gas_limit,
+ data,
+ },
+ decoder.finish()?,
+ ))
+ }
+}
diff --git a/crates/l2/based/block_fetcher.rs b/crates/l2/based/block_fetcher.rs
index 50235ac9b60..6bc542d2393 100644
--- a/crates/l2/based/block_fetcher.rs
+++ b/crates/l2/based/block_fetcher.rs
@@ -1,6 +1,7 @@
use std::{cmp::min, collections::HashMap, sync::Arc, time::Duration};
use ethrex_blockchain::{Blockchain, fork_choice::apply_fork_choice, vm::StoreVmDatabase};
+use ethrex_common::types::l2_to_l2_message::L2toL2Message;
use ethrex_common::utils::keccak;
use ethrex_common::{
Address, H160, H256, U256,
@@ -8,6 +9,7 @@ use ethrex_common::{
AccountUpdate, Block, BlockNumber, PrivilegedL2Transaction, Transaction, batch::Batch,
},
};
+use ethrex_l2_common::l1_messages::get_l2_to_l2_messages;
use ethrex_l2_common::{
l1_messages::{L1Message, get_block_l1_messages, get_l1_message_hash},
privileged_transactions::compute_privileged_transactions_hash,
@@ -332,7 +334,7 @@ impl BlockFetcher {
.collect();
let mut messages = Vec::new();
for block in batch {
- let block_messages = self.extract_block_messages(block.header.number).await?;
+ let (block_messages, _) = self.extract_block_messages(block.header.number).await?;
messages.extend(block_messages);
}
let privileged_transactions_hash =
@@ -391,40 +393,46 @@ impl BlockFetcher {
let (blobs_bundle, _) =
generate_blobs_bundle(&state_diff).map_err(|_| BlockFetcherError::BlobBundleError)?;
+ let (l1_message_hashes, l2_to_l2_messages) = self.get_batch_messages(batch).await?;
+
Ok(Batch {
number: batch_number.as_u64(),
first_block: first_block.header.number,
last_block: last_block.header.number,
state_root: new_state_root,
privileged_transactions_hash,
- message_hashes: self.get_batch_message_hashes(batch).await?,
+ l1_message_hashes,
+ l2_to_l2_messages,
blobs_bundle,
commit_tx: Some(commit_tx),
verify_tx: None,
})
}
- async fn get_batch_message_hashes(
+ async fn get_batch_messages(
&mut self,
batch: &[Block],
- ) -> Result, BlockFetcherError> {
+ ) -> Result<(Vec, Vec), BlockFetcherError> {
let mut message_hashes = Vec::new();
+ let mut l2_to_l2_messages = Vec::new();
for block in batch {
- let block_messages = self.extract_block_messages(block.header.number).await?;
+ let (block_messages, block_l2_to_l2_messages) =
+ self.extract_block_messages(block.header.number).await?;
+ l2_to_l2_messages.extend(block_l2_to_l2_messages);
for msg in &block_messages {
message_hashes.push(get_l1_message_hash(msg));
}
}
- Ok(message_hashes)
+ Ok((message_hashes, l2_to_l2_messages))
}
async fn extract_block_messages(
&mut self,
block_number: BlockNumber,
- ) -> Result, BlockFetcherError> {
+ ) -> Result<(Vec, Vec), BlockFetcherError> {
let Some(block_body) = self.store.get_block_body(block_number).await? else {
return Err(BlockFetcherError::InconsistentStorage(format!(
"Block {block_number} is supposed to be in store at this point"
@@ -451,7 +459,10 @@ impl BlockFetcher {
txs.push(tx.clone());
receipts.push(receipt);
}
- Ok(get_block_l1_messages(&receipts))
+ Ok((
+ get_block_l1_messages(&receipts),
+ get_l2_to_l2_messages(&receipts),
+ ))
}
/// Process the logs from the event `BatchVerified`.
diff --git a/crates/l2/common/src/l1_messages.rs b/crates/l2/common/src/l1_messages.rs
index 1724dab3267..2dc105008c0 100644
--- a/crates/l2/common/src/l1_messages.rs
+++ b/crates/l2/common/src/l1_messages.rs
@@ -1,16 +1,29 @@
-use std::sync::LazyLock;
-
+use bytes::Bytes;
use ethereum_types::{Address, H256};
+use ethrex_common::types::l2_to_l2_message::L2toL2Message;
use ethrex_common::utils::keccak;
use ethrex_common::{H160, U256, types::Receipt};
use serde::{Deserialize, Serialize};
+use crate::calldata::Value;
+
pub const L1MESSENGER_ADDRESS: Address = H160([
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xff, 0xfe,
]);
+// keccak256("L1Message(address,bytes32,uint256)")
+static L1MESSAGE_EVENT_SELECTOR: H256 = H256([
+ 0x18, 0xd7, 0xb7, 0x05, 0x34, 0x4d, 0x61, 0x6d, 0x1b, 0x61, 0xda, 0xa6, 0xa8, 0xcc, 0xfc, 0xf9,
+ 0xf1, 0x0c, 0x27, 0xad, 0xe0, 0x07, 0xcc, 0x45, 0xcf, 0x87, 0x0d, 0x1e, 0x12, 0x1f, 0x1a, 0x9d,
+]);
+// keccak256("L2ToL2Message(uint256,address,address,uint256,uint256,bytes)")
+static L2_MESSAGE_SELECTOR: H256 = H256([
+ 0x09, 0xdb, 0x04, 0xf0, 0x10, 0xf1, 0x0e, 0xf2, 0x0f, 0xce, 0xf9, 0xca, 0xe9, 0xf6, 0x4a, 0xbb,
+ 0xde, 0x92, 0xfe, 0xe1, 0x2c, 0x68, 0xf6, 0x92, 0xc2, 0x3a, 0x72, 0xcc, 0x54, 0xb2, 0x96, 0x9e,
+]);
+
#[derive(Serialize, Deserialize, Debug)]
pub struct L1MessageProof {
pub batch_number: u64,
@@ -52,9 +65,6 @@ pub fn get_block_l1_message_hashes(receipts: &[Receipt]) -> Vec {
}
pub fn get_block_l1_messages(receipts: &[Receipt]) -> Vec {
- static L1MESSAGE_EVENT_SELECTOR: LazyLock =
- LazyLock::new(|| keccak("L1Message(address,bytes32,uint256)".as_bytes()));
-
receipts
.iter()
.flat_map(|receipt| {
@@ -75,3 +85,59 @@ pub fn get_block_l1_messages(receipts: &[Receipt]) -> Vec {
})
.collect()
}
+
+pub fn get_l2_to_l2_messages(receipts: &[Receipt]) -> Vec {
+ receipts
+ .iter()
+ .flat_map(|receipt| {
+ receipt
+ .logs
+ .iter()
+ .filter(|log| {
+ log.address == L1MESSENGER_ADDRESS && log.topics.contains(&L2_MESSAGE_SELECTOR)
+ })
+ .flat_map(|log| l2_message_from_log_data(&log.data))
+ })
+ .collect()
+}
+
+fn l2_message_from_log_data(log_data: &[u8]) -> Option {
+ let mut offset = 0;
+
+ let chain_id = U256::from_big_endian(log_data.get(offset..offset + 32)?);
+ offset += 32;
+
+ let from = Address::from_slice(log_data.get(offset + 12..offset + 32)?);
+ offset += 32;
+
+ let to = Address::from_slice(log_data.get(offset + 12..offset + 32)?);
+ offset += 32;
+
+ let value = U256::from_big_endian(log_data.get(offset..offset + 32)?);
+ offset += 32;
+
+ let gas_limit = U256::from_big_endian(log_data.get(offset..offset + 32)?);
+ offset += 64; // 32 from gas_limit + 32 from data offset
+
+ let data_len: usize = U256::from_big_endian(log_data.get(offset..offset + 32)?).as_usize();
+ let data = Bytes::copy_from_slice(log_data.get(offset + 32..offset + 32 + data_len)?);
+
+ Some(L2toL2Message {
+ chain_id,
+ from,
+ to,
+ value,
+ gas_limit,
+ data,
+ })
+}
+
+pub fn value_from_l2_to_l2_message(msg: &L2toL2Message) -> Value {
+ Value::Tuple(vec![
+ Value::Uint(msg.chain_id),
+ Value::Address(msg.to),
+ Value::Uint(msg.value),
+ Value::Uint(msg.gas_limit),
+ Value::Bytes(msg.data.clone()),
+ ])
+}
diff --git a/crates/l2/contracts/src/l1/CommonBridge.sol b/crates/l2/contracts/src/l1/CommonBridge.sol
index 0026ebfeb9c..7f8c1fb366b 100644
--- a/crates/l2/contracts/src/l1/CommonBridge.sol
+++ b/crates/l2/contracts/src/l1/CommonBridge.sol
@@ -14,6 +14,7 @@ import {MerkleProof} from "@openzeppelin/contracts/utils/cryptography/MerkleProo
import "./interfaces/ICommonBridge.sol";
import "./interfaces/IOnChainProposer.sol";
import "../l2/interfaces/ICommonBridgeL2.sol";
+import {IRouter} from "./interfaces/IRouter.sol";
/// @title CommonBridge contract.
/// @author LambdaClass
@@ -40,7 +41,7 @@ contract CommonBridge is
/// @dev The value is the merkle root of the logs.
/// @dev If there exist a merkle root for a given batch number it means
/// that the logs were published on L1, and that that batch was committed.
- mapping(uint256 => bytes32) public batchWithdrawalLogsMerkleRoots;
+ mapping(uint256 batchNumber => bytes32) public batchWithdrawalLogsMerkleRoots;
/// @notice Array of hashed pending privileged transactions
bytes32[] public pendingTxHashes;
@@ -92,6 +93,8 @@ contract CommonBridge is
/// Otherwise, this address is used for native token deposits and withdrawals.
address public NATIVE_TOKEN_L1;
+ address public SHARED_BRIDGE_ROUTER = address(0);
+
modifier onlyOnChainProposer() {
require(
msg.sender == ON_CHAIN_PROPOSER,
@@ -111,7 +114,8 @@ contract CommonBridge is
address owner,
address onChainProposer,
uint256 inclusionMaxWait,
- address _nativeToken
+ address _nativeToken,
+ address _sharedBridgeRouter
) public initializer {
require(
onChainProposer != address(0),
@@ -126,6 +130,8 @@ contract CommonBridge is
NATIVE_TOKEN_L1 = _nativeToken;
+ SHARED_BRIDGE_ROUTER = _sharedBridgeRouter;
+
OwnableUpgradeable.__Ownable_init(owner);
ReentrancyGuardUpgradeable.__ReentrancyGuard_init();
}
@@ -175,8 +181,6 @@ contract CommonBridge is
}
function _sendToL2(address from, SendValues memory sendValues) private {
- _burnGas(sendValues.gasLimit);
-
bytes32 l2MintTxHash = keccak256(
bytes.concat(
bytes20(from),
@@ -209,6 +213,7 @@ contract CommonBridge is
function sendToL2(
SendValues calldata sendValues
) public override whenNotPaused {
+ _burnGas(sendValues.gasLimit);
_sendToL2(_getSenderAlias(), sendValues);
}
@@ -217,6 +222,11 @@ contract CommonBridge is
uint256 _amount,
address l2Recipient
) public payable override whenNotPaused {
+ _burnGas(21000 * 5);
+ _deposit(_amount, l2Recipient);
+ }
+
+ function _deposit(uint256 _amount, address l2Recipient) private {
uint256 value;
// Here we define value depending on whether the native token is ETH or an ERC20
@@ -298,6 +308,7 @@ contract CommonBridge is
value: 0,
data: callData
});
+ _burnGas(sendValues.gasLimit);
_sendToL2(L2_BRIDGE_ADDRESS, sendValues);
}
@@ -349,9 +360,9 @@ contract CommonBridge is
/// @inheritdoc ICommonBridge
function getWithdrawalLogsMerkleRoot(
- uint256 blockNumber
+ uint256 batchNumber
) public view returns (bytes32) {
- return batchWithdrawalLogsMerkleRoots[blockNumber];
+ return batchWithdrawalLogsMerkleRoots[batchNumber];
}
/// @inheritdoc ICommonBridge
@@ -364,9 +375,7 @@ contract CommonBridge is
bytes32(0),
"CommonBridge: withdrawal logs already published"
);
- batchWithdrawalLogsMerkleRoots[
- withdrawalLogsBatchNumber
- ] = withdrawalsLogsMerkleRoot;
+ batchWithdrawalLogsMerkleRoots[withdrawalLogsBatchNumber] = withdrawalsLogsMerkleRoot;
emit WithdrawalsPublished(
withdrawalLogsBatchNumber,
withdrawalsLogsMerkleRoot
@@ -490,6 +499,33 @@ contract CommonBridge is
);
}
+ /// @inheritdoc ICommonBridge
+ function sendMessage(uint256 dstChainId, SendValues memory message) public override onlyOnChainProposer {
+ IRouter(SHARED_BRIDGE_ROUTER).sendMessage{value: message.value}(dstChainId, message);
+ }
+
+ /// @inheritdoc ICommonBridge
+ function receiveMessage(SendValues calldata message) public override payable {
+ require(
+ msg.sender == SHARED_BRIDGE_ROUTER,
+ "CommonBridge: caller is not the shared bridge router"
+ );
+
+ if (message.data.length == 0) {
+ require(
+ message.value == msg.value,
+ "CommonBridge: message.value does not match msg.value"
+ );
+ _deposit(0, message.to);
+ } else {
+ if (message.value != 0) {
+ _deposit(0, _getSenderAlias());
+ }
+ _sendToL2(_getSenderAlias(), message);
+ }
+
+ }
+
function upgradeL2Contract(
address l2Contract,
address newImplementation,
@@ -506,6 +542,7 @@ contract CommonBridge is
value: 0,
data: callData
});
+ _burnGas(sendValues.gasLimit);
_sendToL2(L2_PROXY_ADMIN, sendValues);
}
diff --git a/crates/l2/contracts/src/l1/OnChainProposer.sol b/crates/l2/contracts/src/l1/OnChainProposer.sol
index 4a2d8ef711e..ae80abd5584 100644
--- a/crates/l2/contracts/src/l1/OnChainProposer.sol
+++ b/crates/l2/contracts/src/l1/OnChainProposer.sol
@@ -11,6 +11,7 @@ import {ICommonBridge} from "./interfaces/ICommonBridge.sol";
import {IRiscZeroVerifier} from "./interfaces/IRiscZeroVerifier.sol";
import {ISP1Verifier} from "./interfaces/ISP1Verifier.sol";
import {ITDXVerifier} from "./interfaces/ITDXVerifier.sol";
+import {IRouter} from "./interfaces/IRouter.sol";
/// @title OnChainProposer contract.
/// @author LambdaClass
@@ -196,6 +197,7 @@ contract OnChainProposer is
uint256 batchNumber,
bytes32 newStateRoot,
bytes32 withdrawalsLogsMerkleRoot,
+ L2toL2Message[] calldata l2CrossMessages,
bytes32 processedPrivilegedTransactionsRollingHash,
bytes32 lastBlockHash
) external override onlySequencer whenNotPaused {
@@ -224,6 +226,7 @@ contract OnChainProposer is
"OnChainProposer: invalid privileged transaction logs"
);
}
+
if (withdrawalsLogsMerkleRoot != bytes32(0)) {
ICommonBridge(BRIDGE).publishWithdrawals(
batchNumber,
@@ -231,6 +234,17 @@ contract OnChainProposer is
);
}
+ for (uint256 i = 0; i < l2CrossMessages.length; i++) {
+ L2toL2Message calldata message = l2CrossMessages[i];
+ ICommonBridge.SendValues memory sendValues = ICommonBridge.SendValues(
+ message.to,
+ message.gasLimit,
+ message.value,
+ message.data
+ );
+ ICommonBridge(BRIDGE).sendMessage(message.chainId, sendValues);
+ }
+
// Blob is published in the (EIP-4844) transaction that calls this function.
bytes32 blobVersionedHash = blobhash(0);
if (VALIDIUM) {
diff --git a/crates/l2/contracts/src/l1/Router.sol b/crates/l2/contracts/src/l1/Router.sol
new file mode 100644
index 00000000000..459e9bbc394
--- /dev/null
+++ b/crates/l2/contracts/src/l1/Router.sol
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: MIT
+pragma solidity =0.8.29;
+
+import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
+import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
+import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
+import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
+import { IRouter } from "./interfaces/IRouter.sol";
+import { ICommonBridge } from "./interfaces/ICommonBridge.sol";
+
+/// @title Router contract.
+/// @author LambdaClass
+contract Router is
+ IRouter,
+ Initializable,
+ UUPSUpgradeable,
+ Ownable2StepUpgradeable,
+ PausableUpgradeable
+{
+ mapping(uint256 chainId => address bridge) public bridges;
+
+ function initialize(address owner) public initializer {
+ OwnableUpgradeable.__Ownable_init(owner);
+ }
+
+ /// @inheritdoc IRouter
+ function register(uint256 chainId, address _commonBridge) onlyOwner whenNotPaused public {
+ if (_commonBridge == address(0)) {
+ revert InvalidAddress(address(0));
+ }
+
+ if (bridges[chainId] != address(0)) {
+ revert ChainAlreadyRegistered(chainId);
+ }
+
+ bridges[chainId] = _commonBridge;
+
+ emit ChainRegistered(chainId, _commonBridge);
+ }
+
+ /// @inheritdoc IRouter
+ function deregister(uint256 chainId) onlyOwner whenNotPaused public {
+ if (bridges[chainId] == address(0)) {
+ revert ChainNotRegistered(chainId);
+ }
+
+ delete bridges[chainId];
+
+ emit ChainDeregistered(chainId);
+ }
+
+ /// @inheritdoc IRouter
+ function sendMessage(uint256 chainId, ICommonBridge.SendValues calldata message) public override payable {
+ if (bridges[chainId] == address(0)) {
+ revert ChainNotRegistered(chainId);
+ }
+
+ ICommonBridge(bridges[chainId]).receiveMessage{value: msg.value}(message);
+ }
+
+ /// @notice Allow owner to upgrade the contract.
+ /// @param newImplementation the address of the new implementation
+ function _authorizeUpgrade(
+ address newImplementation
+ ) internal virtual override onlyOwner {}
+
+ function pause() external onlyOwner {
+ _pause();
+ }
+
+ function unpause() external onlyOwner {
+ _unpause();
+ }
+}
diff --git a/crates/l2/contracts/src/l1/interfaces/ICommonBridge.sol b/crates/l2/contracts/src/l1/interfaces/ICommonBridge.sol
index db1ec114c8e..f90e7bcbc18 100644
--- a/crates/l2/contracts/src/l1/interfaces/ICommonBridge.sol
+++ b/crates/l2/contracts/src/l1/interfaces/ICommonBridge.sol
@@ -87,11 +87,11 @@ interface ICommonBridge {
/// @notice Method to retrieve the merkle root of the withdrawal logs of a
/// given block.
/// @dev This method is used by the L2 OnChainOperator at the verify stage.
- /// @param blockNumber the block number in L2 where the withdrawal logs were
+ /// @param batchNumber the batch number in L2 where the withdrawal logs were
/// emitted.
/// @return the merkle root of the withdrawal logs of the given block.
function getWithdrawalLogsMerkleRoot(
- uint256 blockNumber
+ uint256 batchNumber
) external view returns (bytes32);
/// @notice Publishes the L2 withdrawals on L1.
@@ -121,11 +121,11 @@ interface ICommonBridge {
/// @param withdrawalProof the merkle path to the withdrawal log.
/// @param withdrawalLogIndex the index of the message log in the block.
/// This is the index of the withdraw transaction relative to the block's messages.
- /// @param l2WithdrawalBatchNumber the batch number where the withdrawal log
+ /// @param withdrawalBatchNumber the batch number where the withdrawal log
/// was emitted.
function claimWithdrawal(
uint256 claimedAmount,
- uint256 l2WithdrawalBatchNumber,
+ uint256 withdrawalBatchNumber,
uint256 withdrawalLogIndex,
bytes32[] calldata withdrawalProof
) external;
@@ -136,17 +136,29 @@ interface ICommonBridge {
/// @param claimedAmount the amount that will be claimed.
/// @param withdrawalProof the merkle path to the withdrawal log.
/// @param withdrawalLogIndex the index of the message log in the batch.
- /// @param l2WithdrawalBatchNumber the batch number where the withdrawal log
+ /// @param withdrawalBatchNumber the batch number where the withdrawal log
/// was emitted.
function claimWithdrawalERC20(
address tokenL1,
address tokenL2,
uint256 claimedAmount,
- uint256 l2WithdrawalBatchNumber,
+ uint256 withdrawalBatchNumber,
uint256 withdrawalLogIndex,
bytes32[] calldata withdrawalProof
) external;
+ /// @notice Sends a message to another chain via shared bridge router.
+ /// @dev This method should only be called by the OnChainProposer.
+ /// @param dstChainId The ID of the destination chain.
+ /// @param message The message details to send.
+ function sendMessage(uint256 dstChainId, SendValues memory message) external;
+
+ /// @notice Receives a message from another chain via shared bridge router.
+ /// @dev This method should only be called by the shared bridge router, as this
+ /// method will not burn the L2 gas.
+ /// @param message The message details to receive.
+ function receiveMessage(SendValues memory message) external payable;
+
/// @notice Checks if the sequencer has exceeded it's processing deadlines
function hasExpiredPrivilegedTransactions() external view returns (bool);
diff --git a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol
index be6696c86da..e798cf67433 100644
--- a/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol
+++ b/crates/l2/contracts/src/l1/interfaces/IOnChainProposer.sol
@@ -14,6 +14,14 @@ interface IOnChainProposer {
/// @return The latest verified batch number as a uint256.
function lastVerifiedBatch() external view returns (uint256);
+ struct L2toL2Message {
+ uint256 chainId;
+ address to;
+ uint256 value;
+ uint256 gasLimit;
+ bytes data;
+ }
+
/// @notice A batch has been committed.
/// @dev Event emitted when a batch is committed.
/// @param newStateRoot The new state root of the batch that was committed.
@@ -62,6 +70,7 @@ interface IOnChainProposer {
uint256 batchNumber,
bytes32 newStateRoot,
bytes32 withdrawalsLogsMerkleRoot,
+ L2toL2Message[] calldata l2CrossMessages,
bytes32 processedPrivilegedTransactionsRollingHash,
bytes32 lastBlockHash
) external;
diff --git a/crates/l2/contracts/src/l1/interfaces/IRouter.sol b/crates/l2/contracts/src/l1/interfaces/IRouter.sol
new file mode 100644
index 00000000000..dafeb0dbe02
--- /dev/null
+++ b/crates/l2/contracts/src/l1/interfaces/IRouter.sol
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.29;
+
+import { ICommonBridge } from "./ICommonBridge.sol";
+
+interface IRouter {
+ /// @notice Registers a new chain with its OnChainProposer and CommonBridge addresses.
+ /// @param chainId The ID of the chain to register.
+ /// @param commonBridge The address of the CommonBridge for the chain.
+ function register(uint256 chainId, address commonBridge) external;
+
+ /// @notice Deregisters a chain
+ /// @param chainId The ID of the chain to deregister.
+ function deregister(uint256 chainId) external;
+
+ /// @notice Sends a message to a specified chain via its CommonBridge.
+ /// @param chainId The ID of the destination chain.
+ /// @param message The message details to send.
+ function sendMessage(uint256 chainId, ICommonBridge.SendValues calldata message) external payable;
+
+ /// @notice Emitted when a new chain is registered.
+ /// @param chainId The ID of the registered chain.
+ /// @param commonBridge The address of the CommonBridge for the registered chain.
+ event ChainRegistered(uint256 indexed chainId, address commonBridge);
+
+ /// @notice Emitted when a chain is deregistered.
+ /// @param chainId The ID of the deregistered chain.
+ event ChainDeregistered(uint256 indexed chainId);
+
+ /// @notice Error indicating an invalid address was provided.
+ /// @param addr The invalid address.
+ error InvalidAddress(address addr);
+
+ /// @notice Error indicating a chain is already registered.
+ /// @param chainId The ID of the already registered chain.
+ error ChainAlreadyRegistered(uint256 chainId);
+
+ /// @notice Error indicating a chain is not registered.
+ /// @param chainId The ID of the not registered chain.
+ error ChainNotRegistered(uint256 chainId);
+}
diff --git a/crates/l2/contracts/src/l2/CommonBridgeL2.sol b/crates/l2/contracts/src/l2/CommonBridgeL2.sol
index 57f52c5fe06..7dd79766759 100644
--- a/crates/l2/contracts/src/l2/CommonBridgeL2.sol
+++ b/crates/l2/contracts/src/l2/CommonBridgeL2.sol
@@ -104,4 +104,16 @@ contract CommonBridgeL2 is ICommonBridgeL2 {
keccak256(abi.encodePacked(tokenL1, tokenL2, destination, amount))
);
}
+
+ /// @inheritdoc ICommonBridgeL2
+ function sendToL2(uint256 chainId, address to, uint256 destGasLimit, bytes calldata data) external override payable {
+ _burnGas(destGasLimit);
+ IL2ToL1Messenger(L1_MESSENGER).sendMessageToL2{value: msg.value}(chainId, msg.sender, to, destGasLimit, data);
+ }
+
+ /// Burns at least {amount} gas
+ function _burnGas(uint256 amount) private view {
+ uint256 startingGas = gasleft();
+ while (startingGas - gasleft() < amount) {}
+ }
}
diff --git a/crates/l2/contracts/src/l2/L2ToL1Messenger.sol b/crates/l2/contracts/src/l2/L2ToL1Messenger.sol
index 5e7f32dfae3..1bc4e502365 100644
--- a/crates/l2/contracts/src/l2/L2ToL1Messenger.sol
+++ b/crates/l2/contracts/src/l2/L2ToL1Messenger.sol
@@ -10,10 +10,16 @@ contract L2ToL1Messenger is IL2ToL1Messenger {
/// @dev Message Id that should be incremented before a message is sent
uint256 public lastMessageId;
+ /// @inheritdoc IL2ToL1Messenger
function sendMessageToL1(bytes32 data) external {
// This event gets pushed to L1, the sequencer monitors
// them on every block.
lastMessageId += 1;
emit L1Message(msg.sender, data, lastMessageId);
}
+
+ /// @inheritdoc IL2ToL1Messenger
+ function sendMessageToL2(uint256 chainId, address from, address to, uint256 gasLimit, bytes calldata data) external payable {
+ emit L2ToL2Message(chainId, from, to, msg.value, gasLimit, data);
+ }
}
diff --git a/crates/l2/contracts/src/l2/interfaces/ICommonBridgeL2.sol b/crates/l2/contracts/src/l2/interfaces/ICommonBridgeL2.sol
index 1330098acb4..455e8b0c056 100644
--- a/crates/l2/contracts/src/l2/interfaces/ICommonBridgeL2.sol
+++ b/crates/l2/contracts/src/l2/interfaces/ICommonBridgeL2.sol
@@ -82,4 +82,13 @@ interface ICommonBridgeL2 {
/// @param destination Address on L1 that should receive the tokens
/// @param amount Amount of tokens to withdraw
function withdrawERC20(address tokenL1, address tokenL2, address destination, uint256 amount) external;
+
+ /// @notice Sends an arbitrary message to the another chain.
+ /// @dev This can be used to call functions on contracts on, or transfer ETH to,
+ /// the destination chain.
+ /// @param chainId The chain ID of the destination chain.
+ /// @param to The address of the contract on the destination chain.
+ /// @param destGasLimit The gas limit for the destination chain execution.
+ /// @param data The calldata to send to the destination contract.
+ function sendToL2(uint256 chainId, address to, uint256 destGasLimit, bytes calldata data) external payable;
}
diff --git a/crates/l2/contracts/src/l2/interfaces/IL2ToL1Messenger.sol b/crates/l2/contracts/src/l2/interfaces/IL2ToL1Messenger.sol
index 4d2d7015434..4c7294b0e88 100644
--- a/crates/l2/contracts/src/l2/interfaces/IL2ToL1Messenger.sol
+++ b/crates/l2/contracts/src/l2/interfaces/IL2ToL1Messenger.sol
@@ -16,7 +16,30 @@ interface IL2ToL1Messenger {
uint256 indexed messageId
);
+ event L2ToL2Message(
+ uint256 chainId,
+ address from,
+ address to,
+ uint256 value,
+ uint256 gasLimit,
+ bytes data
+ );
+
/// @notice Sends the given data to the L1
/// @param data data to be sent to L1
function sendMessageToL1(bytes32 data) external;
+
+ /// @notice Sends a message to another L2 chain
+ /// @param chainId the destination chain id
+ /// @param from the sender address on the source chain
+ /// @param to the recipient address on the destination chain
+ /// @param gasLimit the gas limit for the message execution on the destination chain
+ /// @param data the calldata to be sent to the recipient on the destination chain
+ function sendMessageToL2(
+ uint256 chainId,
+ address from,
+ address to,
+ uint256 gasLimit,
+ bytes calldata data
+ ) external payable;
}
diff --git a/crates/l2/monitor/widget/batches.rs b/crates/l2/monitor/widget/batches.rs
index 855298b1610..47d1cccf398 100644
--- a/crates/l2/monitor/widget/batches.rs
+++ b/crates/l2/monitor/widget/batches.rs
@@ -149,7 +149,7 @@ impl BatchesTable {
BatchLine {
number: batch.number,
block_count: batch.last_block - batch.first_block + 1,
- message_count: batch.message_hashes.len(),
+ message_count: batch.l1_message_hashes.len(),
commit_tx: batch.commit_tx,
verify_tx: batch.verify_tx,
}
diff --git a/crates/l2/sdk/src/sdk.rs b/crates/l2/sdk/src/sdk.rs
index 5625b922439..f41b8562f29 100644
--- a/crates/l2/sdk/src/sdk.rs
+++ b/crates/l2/sdk/src/sdk.rs
@@ -36,10 +36,10 @@ pub use ethrex_sdk_contract_utils::*;
use calldata::from_hex_string_to_h256_array;
-// 0x36664d7c5031bd965bbb405b55495a90dd780740
+// 0xa92c24809e706ba464ca0028017229ec37367a3e
pub const DEFAULT_BRIDGE_ADDRESS: Address = H160([
- 0x36, 0x66, 0x4d, 0x7c, 0x50, 0x31, 0xbd, 0x96, 0x5b, 0xbb, 0x40, 0x5b, 0x55, 0x49, 0x5a, 0x90,
- 0xdd, 0x78, 0x07, 0x40,
+ 0xa9, 0x2c, 0x24, 0x80, 0x9e, 0x70, 0x6b, 0xa4, 0x64, 0xca, 0x00, 0x28, 0x01, 0x72, 0x29, 0xec,
+ 0x37, 0x36, 0x7a, 0x3e,
]);
// 0x000000000000000000000000000000000000ffff
diff --git a/crates/l2/sequencer/l1_committer.rs b/crates/l2/sequencer/l1_committer.rs
index 93e3cbddc95..a4f7ff8e5af 100644
--- a/crates/l2/sequencer/l1_committer.rs
+++ b/crates/l2/sequencer/l1_committer.rs
@@ -9,18 +9,21 @@ use crate::{
},
};
-use bytes::Bytes;
use ethrex_blockchain::{Blockchain, vm::StoreVmDatabase};
use ethrex_common::{
Address, H256, U256,
types::{
AccountUpdate, BLOB_BASE_FEE_UPDATE_FRACTION, BlobsBundle, Block, BlockNumber,
MIN_BASE_FEE_PER_BLOB_GAS, TxType, batch::Batch, blobs_bundle, fake_exponential_checked,
+ l2_to_l2_message::L2toL2Message,
},
};
use ethrex_l2_common::{
calldata::Value,
- l1_messages::{get_block_l1_messages, get_l1_message_hash},
+ l1_messages::{
+ get_block_l1_messages, get_l1_message_hash, get_l2_to_l2_messages,
+ value_from_l2_to_l2_message,
+ },
merkle_tree::compute_merkle_root,
privileged_transactions::{
PRIVILEGED_TX_BUDGET, compute_privileged_transactions_hash,
@@ -59,7 +62,7 @@ use spawned_concurrency::tasks::{
const COMMIT_FUNCTION_SIGNATURE_BASED: &str =
"commitBatch(uint256,bytes32,bytes32,bytes32,bytes32,bytes[])";
-const COMMIT_FUNCTION_SIGNATURE: &str = "commitBatch(uint256,bytes32,bytes32,bytes32,bytes32)";
+const COMMIT_FUNCTION_SIGNATURE: &str = "commitBatch(uint256,bytes32,bytes32,(uint256,address,uint256,uint256,bytes)[],bytes32,bytes32)";
/// Default wake up time for the committer to check if it should send a commit tx
const COMMITTER_DEFAULT_WAKE_TIME_MS: u64 = 60_000;
@@ -238,7 +241,8 @@ impl L1Committer {
let (
blobs_bundle,
new_state_root,
- message_hashes,
+ l1_message_hashes,
+ l2_to_l2_messages,
privileged_transactions_hash,
last_block_of_batch,
) = self
@@ -256,7 +260,8 @@ impl L1Committer {
last_block: last_block_of_batch,
state_root: new_state_root,
privileged_transactions_hash,
- message_hashes,
+ l1_message_hashes,
+ l2_to_l2_messages,
blobs_bundle,
commit_tx: None,
verify_tx: None,
@@ -328,11 +333,22 @@ impl L1Committer {
&mut self,
mut last_added_block_number: BlockNumber,
batch_number: u64,
- ) -> Result<(BlobsBundle, H256, Vec, H256, BlockNumber), CommitterError> {
+ ) -> Result<
+ (
+ BlobsBundle,
+ H256,
+ Vec,
+ Vec,
+ H256,
+ BlockNumber,
+ ),
+ CommitterError,
+ > {
let first_block_of_batch = last_added_block_number + 1;
let mut blobs_bundle = BlobsBundle::default();
let mut acc_messages = vec![];
+ let mut acc_l2_to_l2_messages = vec![];
let mut acc_privileged_txs = vec![];
let mut acc_account_updates: HashMap = HashMap::new();
let mut message_hashes = vec![];
@@ -406,6 +422,7 @@ impl L1Committer {
);
// Get block messages and privileged transactions
let messages = get_block_l1_messages(&receipts);
+ let l2_to_l2_messages = get_l2_to_l2_messages(&receipts);
let privileged_transactions = get_block_privileged_transactions(&txs);
// Get block account updates.
@@ -431,6 +448,7 @@ impl L1Committer {
// Accumulate block data with the rest of the batch.
acc_messages.extend(messages.clone());
+ acc_l2_to_l2_messages.extend(l2_to_l2_messages);
acc_privileged_txs.extend(privileged_transactions.clone());
for account in account_updates {
let address = account.address;
@@ -551,6 +569,7 @@ impl L1Committer {
blobs_bundle,
new_state_root,
message_hashes,
+ acc_l2_to_l2_messages,
privileged_transactions_hash,
last_added_block_number,
))
@@ -619,20 +638,10 @@ impl L1Committer {
}
async fn send_commitment(&mut self, batch: &Batch) -> Result {
- let messages_merkle_root = compute_merkle_root(&batch.message_hashes);
+ let messages_merkle_root = compute_merkle_root(&batch.l1_message_hashes);
let last_block_hash = get_last_block_hash(&self.store, batch.last_block)?;
- let mut calldata_values = vec![
- Value::Uint(U256::from(batch.number)),
- Value::FixedBytes(batch.state_root.0.to_vec().into()),
- Value::FixedBytes(messages_merkle_root.0.to_vec().into()),
- Value::FixedBytes(batch.privileged_transactions_hash.0.to_vec().into()),
- Value::FixedBytes(last_block_hash.0.to_vec().into()),
- ];
-
- let (commit_function_signature, values) = if self.based {
- let mut encoded_blocks: Vec = Vec::new();
-
+ let calldata = if self.based {
let (blocks, _) = fetch_blocks_with_respective_fee_configs::(
batch.number,
&self.store,
@@ -640,21 +649,40 @@ impl L1Committer {
)
.await?;
- for block in blocks {
- encoded_blocks.push(block.encode_to_vec().into());
- }
-
- calldata_values.push(Value::Array(
- encoded_blocks.into_iter().map(Value::Bytes).collect(),
- ));
-
- (COMMIT_FUNCTION_SIGNATURE_BASED, calldata_values)
+ let encoded_blocks = blocks
+ .into_iter()
+ .map(|block| Value::Bytes(block.encode_to_vec().into()))
+ .collect::>();
+
+ let calldata_values = vec![
+ Value::Uint(U256::from(batch.number)),
+ Value::FixedBytes(batch.state_root.0.to_vec().into()),
+ Value::FixedBytes(messages_merkle_root.0.to_vec().into()),
+ Value::FixedBytes(batch.privileged_transactions_hash.0.to_vec().into()),
+ Value::FixedBytes(last_block_hash.0.to_vec().into()),
+ Value::Array(encoded_blocks),
+ ];
+
+ encode_calldata(COMMIT_FUNCTION_SIGNATURE_BASED, &calldata_values)?
} else {
- (COMMIT_FUNCTION_SIGNATURE, calldata_values)
+ let calldata_values = vec![
+ Value::Uint(U256::from(batch.number)),
+ Value::FixedBytes(batch.state_root.0.to_vec().into()),
+ Value::FixedBytes(messages_merkle_root.0.to_vec().into()),
+ Value::Array(
+ batch
+ .l2_to_l2_messages
+ .iter()
+ .map(value_from_l2_to_l2_message)
+ .collect(),
+ ),
+ Value::FixedBytes(batch.privileged_transactions_hash.0.to_vec().into()),
+ Value::FixedBytes(last_block_hash.0.to_vec().into()),
+ ];
+
+ encode_calldata(COMMIT_FUNCTION_SIGNATURE, &calldata_values)?
};
- let calldata = encode_calldata(commit_function_signature, &values)?;
-
let gas_price = self
.eth_client
.get_gas_price_with_extra(20)
diff --git a/crates/l2/storage/src/api.rs b/crates/l2/storage/src/api.rs
index 2f7a12023d1..fe860f56ee7 100644
--- a/crates/l2/storage/src/api.rs
+++ b/crates/l2/storage/src/api.rs
@@ -4,7 +4,10 @@ use std::fmt::Debug;
use ethrex_common::{
H256,
- types::{AccountUpdate, Blob, BlockNumber, batch::Batch, fee_config::FeeConfig},
+ types::{
+ AccountUpdate, Blob, BlockNumber, batch::Batch, fee_config::FeeConfig,
+ l2_to_l2_message::L2toL2Message,
+ },
};
use ethrex_l2_common::prover::{BatchProof, ProverInputData, ProverType};
@@ -148,6 +151,11 @@ pub trait StoreEngineRollup: Debug + Send + Sync {
async fn revert_to_batch(&self, batch_number: u64) -> Result<(), RollupStoreError>;
+ async fn get_l2_to_l2_messages(
+ &self,
+ batch_number: u64,
+ ) -> Result