Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cmd/ethrex_l2/src/commands/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ impl Command {
.await?;
let rollup_store = StoreRollup::new(
store_path
.join("../rollup_store")
.join("./rollup_store")
.to_str()
.expect("Invalid store path"),
EngineTypeRollup::Libmdbx,
Expand All @@ -224,11 +224,12 @@ impl Command {

// Iterate over each blob
let files: Vec<std::fs::DirEntry> = read_dir(blobs_dir)?.try_collect()?;
for (batch_number, file) in files
for (file_number, file) in files
.into_iter()
.sorted_by_key(|f| f.file_name())
.enumerate()
{
let batch_number = file_number as u64 + 1;
let blob = std::fs::read(file.path())?;

if blob.len() != BYTES_PER_BLOB {
Expand Down Expand Up @@ -294,7 +295,7 @@ impl Command {
// Store batch info in L2 storage
rollup_store
.store_batch(
batch_number as u64,
batch_number,
first_block_number,
last_block_number,
withdrawal_hashes,
Expand Down
2 changes: 1 addition & 1 deletion crates/l2/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ ethrex_L2_CONTRACTS_PATH=./contracts
L1_RPC_URL=http://localhost:8545
L1_PRIVATE_KEY=0x385c546456b6a603a1cfcaa9ec9494ba4832da08dd6bcf4de9a71e4a01b74924

ethrex_L2_DEV_LIBMDBX=dev_ethrex_l2
ethrex_L2_DEV_LIBMDBX?=dev_ethrex_l2
ethrex_L1_DEV_LIBMDBX=dev_ethrex_l1
L1_PORT=8545
L2_PORT=1729
Expand Down
31 changes: 10 additions & 21 deletions crates/l2/contracts/src/l1/CommonBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,30 +76,19 @@ contract CommonBridge is ICommonBridge, Ownable, ReentrancyGuard {
require(msg.value > 0, "CommonBridge: amount to deposit is zero");

bytes32 l2MintTxHash = keccak256(
abi.encodePacked(
msg.sender,
depositValues.to,
depositValues.recipient,
msg.value,
depositValues.gasLimit,
depositId,
depositValues.data
bytes.concat(
bytes20(depositValues.to),
bytes32(msg.value),
bytes32(depositId),
bytes20(depositValues.recipient),
bytes20(msg.sender),
bytes32(depositValues.gasLimit),
bytes32(keccak256(depositValues.data))
)
);

pendingDepositLogs.push(
keccak256(
bytes.concat(
bytes20(depositValues.to),
bytes32(msg.value),
bytes32(depositId),
bytes20(depositValues.recipient),
bytes20(msg.sender),
bytes32(depositValues.gasLimit),
bytes32(keccak256(depositValues.data))
)
)
);
pendingDepositLogs.push(l2MintTxHash);

emit DepositInitiated(
msg.value,
depositValues.to,
Expand Down
72 changes: 44 additions & 28 deletions crates/l2/sequencer/l1_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,33 +150,28 @@ impl L1Watcher {
) -> Result<Vec<H256>, L1WatcherError> {
let mut deposit_txs = Vec::new();

for log in logs {
let (mint_value, to_address, deposit_id, recipient, from, gas_limit, calldata) =
parse_values_log(log.log)?;

let value_bytes = mint_value.to_big_endian();
let id_bytes = deposit_id.to_big_endian();
let gas_limit_bytes = gas_limit.to_big_endian();
let deposit_hash = keccak(
[
to_address.as_bytes(),
&value_bytes,
&id_bytes,
recipient.as_bytes(),
from.as_bytes(),
&gas_limit_bytes,
keccak(&calldata).as_bytes(),
]
.concat(),
);

let deposit_already_processed = store
.get_transaction_by_hash(deposit_hash)
.await
.map_err(L1WatcherError::FailedAccessingStore)?
.is_some();
// Get the pending deposits from the contract.
let pending_deposits = self
.eth_client
.get_pending_deposit_logs(self.address)
.await?;

if deposit_already_processed {
for log in logs {
let (
mint_value,
to_address,
deposit_id,
recipient,
from,
gas_limit,
calldata,
deposit_hash,
) = parse_values_log(log.log)?;

if self
.deposit_already_processed(deposit_hash, &pending_deposits, store)
.await?
{
warn!("Deposit already processed (to: {recipient:#x}, value: {mint_value}, depositId: {deposit_id}), skipping.");
continue;
}
Expand Down Expand Up @@ -239,12 +234,32 @@ impl L1Watcher {

Ok(deposit_txs)
}

async fn deposit_already_processed(
&self,
deposit_hash: H256,
pending_deposits: &[H256],
store: &Store,
) -> Result<bool, L1WatcherError> {
if store
.get_transaction_by_hash(deposit_hash)
.await
.map_err(L1WatcherError::FailedAccessingStore)?
.is_some()
{
return Ok(true);
}

// If we have a reconstructed state, we don't have the transaction in our store.
// Check if the deposit is marked as pending in the contract.
Ok(!pending_deposits.contains(&deposit_hash))
}
}

#[allow(clippy::type_complexity)]
fn parse_values_log(
log: RpcLogInfo,
) -> Result<(U256, H160, U256, H160, H160, U256, Vec<u8>), L1WatcherError> {
) -> Result<(U256, H160, U256, H160, H160, U256, Vec<u8>, H256), L1WatcherError> {
let mint_value = format!(
"{:#x}",
log.topics
Expand Down Expand Up @@ -307,7 +322,7 @@ fn parse_values_log(
),
)?);

let _deposit_tx_hash = H256::from_slice(log.data.get(128..160).ok_or(
let deposit_tx_hash = H256::from_slice(log.data.get(128..160).ok_or(
L1WatcherError::FailedToDeserializeLog(
"Failed to parse deposit_tx_hash from log: log.data[64..96] out of bounds".to_owned(),
),
Expand All @@ -334,5 +349,6 @@ fn parse_values_log(
from,
gas_limit,
calldata.to_vec(),
deposit_tx_hash,
))
}
59 changes: 42 additions & 17 deletions crates/l2/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ const L2_GAS_COST_MAX_DELTA: U256 = U256([100_000_000_000_000, 0, 0, 0]);
///
/// 1. Check balances on L1 and L2
/// 2. Deposit from L1 to L2
/// 3. Check balances on L1 and L2
/// 4. Transfer funds on L2
/// 5. Check balances on L2
/// 6. Withdraw funds from L2 to L1
/// 7. Check balances on L1 and L2
/// 8. Claim funds on L1
/// 9. Check balances on L1 and L2
/// 3. Check deposit receipt in L2
/// 4. Check balances on L1 and L2
/// 5. Transfer funds on L2
/// 6. Check balances on L2
/// 7. Withdraw funds from L2 to L1
/// 8. Check balances on L1 and L2
/// 9. Claim funds on L1
/// 10. Check balances on L1 and L2
#[tokio::test]
async fn l2_integration_test() -> Result<(), Box<dyn std::error::Error>> {
let eth_client = eth_client();
Expand Down Expand Up @@ -100,7 +101,28 @@ async fn l2_integration_test() -> Result<(), Box<dyn std::error::Error>> {
recoverable_fees_vault_balance
);

// 3. Check balances on L1 and L2
// 3. Check deposit receipt on L2

let topic =
keccak(b"DepositInitiated(uint256,address,uint256,address,address,uint256,bytes,bytes32)");
let logs = eth_client
.get_logs(
U256::from(deposit_tx_receipt.block_info.block_number),
U256::from(deposit_tx_receipt.block_info.block_number),
common_bridge_address(),
topic,
)
.await?;

let l2_deposit_tx_hash =
H256::from_slice(logs.first().unwrap().log.data.get(128..160).unwrap());

println!("Waiting for deposit transaction receipt on L2");

let _ = ethrex_l2_sdk::wait_for_transaction_receipt(l2_deposit_tx_hash, &proposer_client, 1000)
.await?;

// 4. Check balances on L1 and L2

println!("Checking balances on L1 and L2 after deposit");

Expand Down Expand Up @@ -159,7 +181,7 @@ async fn l2_integration_test() -> Result<(), Box<dyn std::error::Error>> {
"Recoverable Fees Balance: {}, This amount is given because of the L2 Privileged Transaction, a deposit shouldn't give a tip to the coinbase address if the gas sent as tip doesn't come from the L1.",
first_deposit_recoverable_fees_vault_balance
);
// 4. Transfer funds on L2
// 5. Transfer funds on L2

println!("Transferring funds on L2");

Expand Down Expand Up @@ -188,7 +210,7 @@ async fn l2_integration_test() -> Result<(), Box<dyn std::error::Error>> {
recoverable_fees_vault_balance
);

// 5. Check balances on L2
// 6. Check balances on L2

println!("Checking balances on L2 after transfer");

Expand All @@ -214,7 +236,7 @@ async fn l2_integration_test() -> Result<(), Box<dyn std::error::Error>> {
"Random account balance should increase with transfer value"
);

// 6. Withdraw funds from L2 to L1
// 7. Withdraw funds from L2 to L1

println!("Withdrawing funds from L2 to L1");
let withdraw_value = U256::from(100000000000000000000u128);
Expand All @@ -230,7 +252,7 @@ async fn l2_integration_test() -> Result<(), Box<dyn std::error::Error>> {
.await
.expect("Withdraw tx receipt not found");

// 7. Check balances on L1 and L2
// 8. Check balances on L1 and L2

println!("Checking balances on L1 and L2 after withdrawal");

Expand All @@ -254,7 +276,7 @@ async fn l2_integration_test() -> Result<(), Box<dyn std::error::Error>> {
"L2 balance should decrease with withdraw value + gas costs"
);

// 8. Claim funds on L1
// 9. Claim funds on L1

println!("Claiming funds on L1");

Expand Down Expand Up @@ -301,7 +323,7 @@ async fn l2_integration_test() -> Result<(), Box<dyn std::error::Error>> {
let claim_tx_receipt =
ethrex_l2_sdk::wait_for_transaction_receipt(claim_tx, &eth_client, 15).await?;

// 9. Check balances on L1 and L2
// 10. Check balances on L1 and L2

println!("Checking balances on L1 and L2 after claim");

Expand Down Expand Up @@ -496,6 +518,9 @@ async fn l2_deposit_with_contract_call() -> Result<(), Box<dyn std::error::Error
)
.await?;

// Gets the current block_number to search logs later.
let first_block = proposer_client.get_block_number().await?;

let deposit_tx_hash = eth_client
.send_eip1559_transaction(&deposit_tx, &l1_rich_wallet_private_key())
.await?;
Expand All @@ -519,10 +544,10 @@ async fn l2_deposit_with_contract_call() -> Result<(), Box<dyn std::error::Error
.await?;

// Wait for the event to be emitted
let mut blk_number = U256::zero();
let mut blk_number = first_block;
let topic = keccak(b"NumberSet(uint256)");
while proposer_client
.get_logs(U256::from(0), blk_number, contract_address, topic)
.get_logs(first_block, blk_number, contract_address, topic)
.await
.is_ok_and(|logs| logs.is_empty())
{
Expand All @@ -532,7 +557,7 @@ async fn l2_deposit_with_contract_call() -> Result<(), Box<dyn std::error::Error
}

let logs = proposer_client
.get_logs(U256::from(0), blk_number, contract_address, topic)
.get_logs(first_block, blk_number, contract_address, topic)
.await?;
println!("Logs: {logs:?}");

Expand Down
51 changes: 45 additions & 6 deletions crates/networking/rpc/clients/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -950,10 +950,53 @@ impl EthClient {
.await
}

pub async fn get_pending_deposit_logs(
&self,
common_bridge_address: Address,
) -> Result<Vec<H256>, EthClientError> {
let response = self
._generic_call(b"getPendingDepositLogs()", common_bridge_address)
.await?;
Self::from_hex_string_to_h256_array(&response)
}

pub fn from_hex_string_to_h256_array(hex_string: &str) -> Result<Vec<H256>, EthClientError> {
let bytes = hex::decode(hex_string.strip_prefix("0x").unwrap_or(hex_string))
.map_err(|_| EthClientError::Custom("Invalid hex string".to_owned()))?;

// The ABI encoding for dynamic arrays is:
// 1. Offset to data (32 bytes)
// 2. Length of array (32 bytes)
// 3. Array elements (each 32 bytes)
if bytes.len() < 64 {
return Err(EthClientError::Custom("Response too short".to_owned()));
}

// Get the offset (should be 0x20 for simple arrays)
let offset = U256::from_big_endian(&bytes[0..32]).as_usize();

// Get the length of the array
let length = U256::from_big_endian(&bytes[offset..offset + 32]).as_usize();

// Calculate the start of the array data
let data_start = offset + 32;
let data_end = data_start + (length * 32);

if data_end > bytes.len() {
return Err(EthClientError::Custom("Invalid array length".to_owned()));
}

// Convert the slice directly to H256 array
bytes[data_start..data_end]
.chunks_exact(32)
.map(|chunk| Ok(H256::from_slice(chunk)))
.collect()
}

async fn _generic_call(
&self,
selector: &[u8],
on_chain_proposer_address: Address,
contract_address: Address,
) -> Result<String, EthClientError> {
let selector = keccak(selector)
.as_bytes()
Expand All @@ -968,11 +1011,7 @@ impl EthClient {
calldata.extend(vec![0; leading_zeros]);

let hex_string = self
.call(
on_chain_proposer_address,
calldata.into(),
Overrides::default(),
)
.call(contract_address, calldata.into(), Overrides::default())
.await?;

Ok(hex_string)
Expand Down
Loading