anvil_zksync_l1_sidecar/contracts/
mod.rs

1// Hide ugly auto-generated alloy structs outside of this module.
2mod private {
3    use zksync_types::commitment::L1BatchWithMetadata;
4
5    // Macros that hide non-trivial implementations are not great. One considered alternative was to
6    // use `alloy_sol_macro_expander` directly from `build.rs`, prettify generated code with
7    // `prettyplease` and then output into a VCS-tracked directory. Although this works, unfortunately
8    // the generated code is still very ugly, so I decided to not go forward with this for now.
9    //
10    // Once https://github.com/alloy-rs/core/issues/261 is resolved hopefully it will become much more
11    // human-readable.
12    //
13    // Additionally, https://github.com/alloy-rs/core/issues/601 tracks proper support for output into
14    // a file.
15    alloy::sol!("src/contracts/sol/IExecutor.sol");
16    // Copied from `PriorityTree.sol` as the entire file has imports that are unprocessable by `alloy::sol!`
17    alloy::sol! {
18        struct PriorityOpsBatchInfo {
19            bytes32[] leftPath;
20            bytes32[] rightPath;
21            bytes32[] itemHashes;
22        }
23    }
24    alloy::sol!(IZKChain, "src/contracts/artifacts/IZKChain.json");
25
26    impl From<&L1BatchWithMetadata> for IExecutor::StoredBatchInfo {
27        fn from(value: &L1BatchWithMetadata) -> Self {
28            Self::from((
29                value.header.number.0 as u64,
30                alloy::primitives::FixedBytes::<32>::from(value.metadata.root_hash.0),
31                value.metadata.rollup_last_leaf_index,
32                alloy::primitives::U256::from(value.header.l1_tx_count),
33                alloy::primitives::FixedBytes::<32>::from(
34                    value.header.priority_ops_onchain_data_hash().0,
35                ),
36                alloy::primitives::FixedBytes::<32>::from(value.metadata.l2_l1_merkle_root.0),
37                alloy::primitives::U256::from(value.header.timestamp),
38                alloy::primitives::FixedBytes::<32>::from(value.metadata.commitment.0),
39            ))
40        }
41    }
42}
43
44pub use self::private::IZKChain::NewPriorityRequest;
45use alloy::primitives::TxHash;
46
47use self::private::{IExecutor, PriorityOpsBatchInfo};
48use alloy::sol_types::{SolCall, SolValue};
49use zksync_mini_merkle_tree::MiniMerkleTree;
50use zksync_types::commitment::{serialize_commitments, L1BatchWithMetadata};
51use zksync_types::l1::L1Tx;
52use zksync_types::web3::keccak256;
53use zksync_types::{L2ChainId, H256};
54
55/// Current commitment encoding version as per protocol.
56pub const SUPPORTED_ENCODING_VERSION: u8 = 0;
57
58/// Builds a Solidity function call to `commitBatchesSharedBridge` as expected by `IExecutor.sol`.
59///
60/// Assumes system log verification and DA input verification are disabled.
61pub fn commit_batches_shared_bridge_call(
62    l2_chain_id: L2ChainId,
63    last_committed_l1_batch: &L1BatchWithMetadata,
64    batch: &L1BatchWithMetadata,
65) -> impl SolCall {
66    IExecutor::commitBatchesSharedBridgeCall::new((
67        alloy::primitives::U256::from(l2_chain_id.as_u64()),
68        alloy::primitives::U256::from(last_committed_l1_batch.header.number.0 + 1),
69        alloy::primitives::U256::from(last_committed_l1_batch.header.number.0 + 1),
70        commit_calldata(last_committed_l1_batch, batch).into(),
71    ))
72}
73
74/// `commitBatchesSharedBridge` expects the rest of calldata to be of very specific form. This
75/// function makes sure last committed batch and new batch are encoded correctly (assumes post gateway).
76fn commit_calldata(
77    last_committed_l1_batch: &L1BatchWithMetadata,
78    batch: &L1BatchWithMetadata,
79) -> Vec<u8> {
80    let stored_batch_info = IExecutor::StoredBatchInfo::from(last_committed_l1_batch);
81    let last_batch_hash = H256(keccak256(stored_batch_info.abi_encode_params().as_slice()));
82    tracing::info!(?last_batch_hash, "preparing commit calldata");
83
84    let commit_batch_info = IExecutor::CommitBatchInfo::from((
85        batch.header.number.0 as u64,
86        batch.header.timestamp,
87        batch.metadata.rollup_last_leaf_index,
88        alloy::primitives::FixedBytes::<32>::from(batch.metadata.root_hash.0),
89        alloy::primitives::U256::from(batch.header.l1_tx_count),
90        alloy::primitives::FixedBytes::<32>::from(batch.header.priority_ops_onchain_data_hash().0),
91        alloy::primitives::FixedBytes::<32>::from(
92            batch
93                .metadata
94                .bootloader_initial_content_commitment
95                .unwrap()
96                .0,
97        ),
98        alloy::primitives::FixedBytes::<32>::from(
99            batch.metadata.events_queue_commitment.unwrap().0,
100        ),
101        alloy::primitives::Bytes::from(serialize_commitments(&batch.header.system_logs)),
102        // Our DA input consists only of state diff hash. Executor is patched to not use anything else.
103        alloy::primitives::Bytes::from(
104            batch
105                .metadata
106                .state_diff_hash
107                .expect("Failed to get state_diff_hash from metadata")
108                .0,
109        ),
110    ));
111    let encoded_data = (stored_batch_info, vec![commit_batch_info]).abi_encode_params();
112
113    // Prefixed by current encoding version as expected by protocol
114    [[SUPPORTED_ENCODING_VERSION].to_vec(), encoded_data]
115        .concat()
116        .to_vec()
117}
118
119/// Builds a Solidity function call to `proveBatchesSharedBridge` as expected by `IExecutor.sol`.
120///
121/// Assumes `TestnetVerifier` was deployed (thus verification for empty proofs is disabled).
122pub fn prove_batches_shared_bridge_call(
123    l2_chain_id: L2ChainId,
124    last_proved_l1_batch: &L1BatchWithMetadata,
125    batch: &L1BatchWithMetadata,
126) -> impl SolCall {
127    IExecutor::proveBatchesSharedBridgeCall::new((
128        alloy::primitives::U256::from(l2_chain_id.as_u64()),
129        alloy::primitives::U256::from(last_proved_l1_batch.header.number.0 + 1),
130        alloy::primitives::U256::from(last_proved_l1_batch.header.number.0 + 1),
131        prove_calldata(last_proved_l1_batch, batch).into(),
132    ))
133}
134
135/// `proveBatchesSharedBridge` expects the rest of calldata to be of very specific form. This
136/// function makes sure last proved batch and new batch are encoded correctly (assumes post gateway).
137fn prove_calldata(
138    last_proved_l1_batch: &L1BatchWithMetadata,
139    batch: &L1BatchWithMetadata,
140) -> Vec<u8> {
141    let prev_l1_batch_info = IExecutor::StoredBatchInfo::from(last_proved_l1_batch);
142    let batches_arg = vec![IExecutor::StoredBatchInfo::from(batch)];
143    let proof_input = Vec::<alloy::primitives::U256>::new();
144    let encoded_data = (prev_l1_batch_info, batches_arg, proof_input).abi_encode_params();
145
146    // Prefixed by current encoding version as expected by protocol
147    [[SUPPORTED_ENCODING_VERSION].to_vec(), encoded_data]
148        .concat()
149        .to_vec()
150}
151
152/// Builds a Solidity function call to `executeBatchesSharedBridge` as expected by `IExecutor.sol`.
153pub fn execute_batches_shared_bridge_call(
154    l2_chain_id: L2ChainId,
155    batch: &L1BatchWithMetadata,
156    l1_tx_merkle_tree: &MiniMerkleTree<L1Tx>,
157) -> impl SolCall {
158    IExecutor::executeBatchesSharedBridgeCall::new((
159        alloy::primitives::U256::from(l2_chain_id.as_u64()),
160        alloy::primitives::U256::from(batch.header.number.0),
161        alloy::primitives::U256::from(batch.header.number.0),
162        execute_calldata(batch, l1_tx_merkle_tree).into(),
163    ))
164}
165
166/// `executeBatchesSharedBridge` expects the rest of calldata to be of very specific form. This
167/// function makes sure batch and its priority operations are encoded correctly (assumes post gateway).
168fn execute_calldata(
169    batch: &L1BatchWithMetadata,
170    l1_tx_merkle_tree: &MiniMerkleTree<L1Tx>,
171) -> Vec<u8> {
172    let count = batch.header.l1_tx_count as usize;
173    let priority_ops_proofs = if count > 0 {
174        let (_, left, right) = l1_tx_merkle_tree.merkle_root_and_paths_for_range(..count);
175        let hashes = l1_tx_merkle_tree.hashes_prefix(count);
176        vec![PriorityOpsBatchInfo {
177            leftPath: left
178                .into_iter()
179                .map(Option::unwrap_or_default)
180                .map(|hash| TxHash::from(hash.0))
181                .collect(),
182            rightPath: right
183                .into_iter()
184                .map(Option::unwrap_or_default)
185                .map(|hash| TxHash::from(hash.0))
186                .collect(),
187            itemHashes: hashes
188                .into_iter()
189                .map(|hash| TxHash::from(hash.0))
190                .collect(),
191        }]
192    } else {
193        vec![PriorityOpsBatchInfo {
194            leftPath: vec![],
195            rightPath: vec![],
196            itemHashes: vec![],
197        }]
198    };
199    let batches_arg = vec![IExecutor::StoredBatchInfo::from(batch)];
200    let encoded_data = (batches_arg, priority_ops_proofs).abi_encode_params();
201
202    // Prefixed by current encoding version as expected by protocol
203    [[SUPPORTED_ENCODING_VERSION].to_vec(), encoded_data]
204        .concat()
205        .to_vec()
206}