1use alloc::vec::Vec;
4use alloy_consensus::{Transaction, TxType, Typed2718};
5use alloy_primitives::B256;
6use alloy_rlp::{Buf, Header};
7use kona_genesis::{RollupConfig, SystemConfig};
8use op_alloy_consensus::OpBlock;
9
10use crate::{
11 L1BlockInfoBedrock, L1BlockInfoEcotone, L1BlockInfoInterop, L1BlockInfoIsthmus, L1BlockInfoTx,
12 OpBlockConversionError, SpanBatchError, SpanDecodingError,
13};
14
15pub fn starts_with_2718_deposit<B>(value: &B) -> bool
17where
18 B: AsRef<[u8]>,
19{
20 value.as_ref().first() == Some(&0x7E)
21}
22
23pub fn starts_with_7702_tx<B>(value: &B) -> bool
25where
26 B: AsRef<[u8]>,
27{
28 value.as_ref().first() == Some(&(TxType::Eip7702 as u8))
29}
30
31pub fn to_system_config(
33 block: &OpBlock,
34 rollup_config: &RollupConfig,
35) -> Result<SystemConfig, OpBlockConversionError> {
36 if block.header.number == rollup_config.genesis.l2.number {
37 if block.header.hash_slow() != rollup_config.genesis.l2.hash {
38 return Err(OpBlockConversionError::InvalidGenesisHash(
39 rollup_config.genesis.l2.hash,
40 block.header.hash_slow(),
41 ));
42 }
43 return rollup_config
44 .genesis
45 .system_config
46 .ok_or(OpBlockConversionError::MissingSystemConfigGenesis);
47 }
48
49 if block.body.transactions.is_empty() {
50 return Err(OpBlockConversionError::EmptyTransactions(block.header.hash_slow()));
51 }
52 let Some(tx) = block.body.transactions[0].as_deposit() else {
53 return Err(OpBlockConversionError::InvalidTxType(block.body.transactions[0].ty()));
54 };
55
56 let l1_info = L1BlockInfoTx::decode_calldata(tx.input().as_ref())?;
57 let l1_fee_scalar = match l1_info {
58 L1BlockInfoTx::Bedrock(L1BlockInfoBedrock { l1_fee_scalar, .. }) => l1_fee_scalar,
59 L1BlockInfoTx::Ecotone(L1BlockInfoEcotone {
60 base_fee_scalar,
61 blob_base_fee_scalar,
62 ..
63 }) |
64 L1BlockInfoTx::Interop(L1BlockInfoInterop {
65 base_fee_scalar,
66 blob_base_fee_scalar,
67 ..
68 }) |
69 L1BlockInfoTx::Isthmus(L1BlockInfoIsthmus {
70 base_fee_scalar,
71 blob_base_fee_scalar,
72 ..
73 }) => {
74 let mut buf = B256::ZERO;
78 buf[0] = 0x01;
79 buf[24..28].copy_from_slice(blob_base_fee_scalar.to_be_bytes().as_ref());
80 buf[28..32].copy_from_slice(base_fee_scalar.to_be_bytes().as_ref());
81 buf.into()
82 }
83 };
84
85 let mut cfg = SystemConfig {
86 batcher_address: l1_info.batcher_address(),
87 overhead: l1_info.l1_fee_overhead(),
88 scalar: l1_fee_scalar,
89 gas_limit: block.header.gas_limit,
90 ..Default::default()
91 };
92
93 if rollup_config.is_holocene_active(block.header.timestamp) {
95 let eip1559_params = &block.header.extra_data;
96
97 if eip1559_params.len() != 9 {
98 return Err(OpBlockConversionError::Eip1559DecodeError);
99 }
100 if eip1559_params[0] != 0 {
101 return Err(OpBlockConversionError::Eip1559DecodeError);
102 }
103
104 cfg.eip1559_denominator = Some(u32::from_be_bytes(
105 eip1559_params[1..5]
106 .try_into()
107 .map_err(|_| OpBlockConversionError::Eip1559DecodeError)?,
108 ));
109 cfg.eip1559_elasticity = Some(u32::from_be_bytes(
110 eip1559_params[5..9]
111 .try_into()
112 .map_err(|_| OpBlockConversionError::Eip1559DecodeError)?,
113 ));
114 }
115
116 if rollup_config.is_isthmus_active(block.header.timestamp) {
117 cfg.operator_fee_scalar = Some(l1_info.operator_fee_scalar());
118 cfg.operator_fee_constant = Some(l1_info.operator_fee_constant());
119 }
120
121 Ok(cfg)
122}
123
124pub fn read_tx_data(r: &mut &[u8]) -> Result<(Vec<u8>, TxType), SpanBatchError> {
126 let mut tx_data = Vec::new();
127 let first_byte =
128 *r.first().ok_or(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?;
129 let mut tx_type = 0;
130 if first_byte <= 0x7F {
131 tx_type = first_byte;
133 tx_data.push(tx_type);
134 r.advance(1);
135 }
136
137 let rlp_header = Header::decode(&mut (**r).as_ref())
140 .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))?;
141
142 let tx_payload = if rlp_header.list {
143 let payload_length_with_header = rlp_header.payload_length + rlp_header.length();
145 let payload = r[0..payload_length_with_header].to_vec();
146 r.advance(payload_length_with_header);
147 Ok(payload)
148 } else {
149 Err(SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionData))
150 }?;
151 tx_data.extend_from_slice(&tx_payload);
152
153 Ok((
154 tx_data,
155 tx_type
156 .try_into()
157 .map_err(|_| SpanBatchError::Decoding(SpanDecodingError::InvalidTransactionType))?,
158 ))
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164 use crate::test_utils::{RAW_BEDROCK_INFO_TX, RAW_ECOTONE_INFO_TX, RAW_ISTHMUS_INFO_TX};
165 use alloc::vec;
166 use alloy_eips::eip1898::BlockNumHash;
167 use alloy_primitives::{U256, address, bytes, uint};
168 use kona_genesis::{ChainGenesis, HardForkConfig};
169
170 #[test]
171 fn test_to_system_config_invalid_genesis_hash() {
172 let block = OpBlock::default();
173 let rollup_config = RollupConfig::default();
174 let err = to_system_config(&block, &rollup_config).unwrap_err();
175 assert_eq!(
176 err,
177 OpBlockConversionError::InvalidGenesisHash(
178 rollup_config.genesis.l2.hash,
179 block.header.hash_slow(),
180 )
181 );
182 }
183
184 #[test]
185 fn test_to_system_config_missing_system_config_genesis() {
186 let block = OpBlock::default();
187 let block_hash = block.header.hash_slow();
188 let rollup_config = RollupConfig {
189 genesis: ChainGenesis {
190 l2: BlockNumHash { hash: block_hash, ..Default::default() },
191 ..Default::default()
192 },
193 ..Default::default()
194 };
195 let err = to_system_config(&block, &rollup_config).unwrap_err();
196 assert_eq!(err, OpBlockConversionError::MissingSystemConfigGenesis);
197 }
198
199 #[test]
200 fn test_to_system_config_from_genesis() {
201 let block = OpBlock::default();
202 let block_hash = block.header.hash_slow();
203 let rollup_config = RollupConfig {
204 genesis: ChainGenesis {
205 l2: BlockNumHash { hash: block_hash, ..Default::default() },
206 system_config: Some(SystemConfig::default()),
207 ..Default::default()
208 },
209 ..Default::default()
210 };
211 let config = to_system_config(&block, &rollup_config).unwrap();
212 assert_eq!(config, SystemConfig::default());
213 }
214
215 #[test]
216 fn test_to_system_config_empty_txs() {
217 let block = OpBlock {
218 header: alloy_consensus::Header { number: 1, ..Default::default() },
219 ..Default::default()
220 };
221 let block_hash = block.header.hash_slow();
222 let rollup_config = RollupConfig {
223 genesis: ChainGenesis {
224 l2: BlockNumHash { hash: block_hash, ..Default::default() },
225 ..Default::default()
226 },
227 ..Default::default()
228 };
229 let err = to_system_config(&block, &rollup_config).unwrap_err();
230 assert_eq!(err, OpBlockConversionError::EmptyTransactions(block_hash));
231 }
232
233 #[test]
234 fn test_to_system_config_non_deposit() {
235 let block = OpBlock {
236 header: alloy_consensus::Header { number: 1, ..Default::default() },
237 body: alloy_consensus::BlockBody {
238 transactions: vec![op_alloy_consensus::OpTxEnvelope::Legacy(
239 alloy_consensus::Signed::new_unchecked(
240 alloy_consensus::TxLegacy {
241 chain_id: Some(1),
242 nonce: 1,
243 gas_price: 1,
244 gas_limit: 1,
245 to: alloy_primitives::TxKind::Create,
246 value: U256::ZERO,
247 input: alloy_primitives::Bytes::new(),
248 },
249 alloy_primitives::PrimitiveSignature::new(U256::ZERO, U256::ZERO, false),
250 Default::default(),
251 ),
252 )],
253 ..Default::default()
254 },
255 };
256 let block_hash = block.header.hash_slow();
257 let rollup_config = RollupConfig {
258 genesis: ChainGenesis {
259 l2: BlockNumHash { hash: block_hash, ..Default::default() },
260 ..Default::default()
261 },
262 ..Default::default()
263 };
264 let err = to_system_config(&block, &rollup_config).unwrap_err();
265 assert_eq!(err, OpBlockConversionError::InvalidTxType(0));
266 }
267
268 #[test]
269 fn test_constructs_bedrock_system_config() {
270 let block = OpBlock {
271 header: alloy_consensus::Header { number: 1, ..Default::default() },
272 body: alloy_consensus::BlockBody {
273 transactions: vec![op_alloy_consensus::OpTxEnvelope::Deposit(
274 alloy_primitives::Sealed::new(op_alloy_consensus::TxDeposit {
275 input: alloy_primitives::Bytes::from(&RAW_BEDROCK_INFO_TX),
276 ..Default::default()
277 }),
278 )],
279 ..Default::default()
280 },
281 };
282 let block_hash = block.header.hash_slow();
283 let rollup_config = RollupConfig {
284 genesis: ChainGenesis {
285 l2: BlockNumHash { hash: block_hash, ..Default::default() },
286 ..Default::default()
287 },
288 ..Default::default()
289 };
290 let config = to_system_config(&block, &rollup_config).unwrap();
291 let expected = SystemConfig {
292 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
293 overhead: uint!(188_U256),
294 scalar: uint!(684000_U256),
295 gas_limit: 0,
296 base_fee_scalar: None,
297 blob_base_fee_scalar: None,
298 eip1559_denominator: None,
299 eip1559_elasticity: None,
300 operator_fee_scalar: None,
301 operator_fee_constant: None,
302 };
303 assert_eq!(config, expected);
304 }
305
306 #[test]
307 fn test_constructs_ecotone_system_config() {
308 let block = OpBlock {
309 header: alloy_consensus::Header {
310 number: 1,
311 extra_data: bytes!("000000beef0000babe"),
313 ..Default::default()
314 },
315 body: alloy_consensus::BlockBody {
316 transactions: vec![op_alloy_consensus::OpTxEnvelope::Deposit(
317 alloy_primitives::Sealed::new(op_alloy_consensus::TxDeposit {
318 input: alloy_primitives::Bytes::from(&RAW_ECOTONE_INFO_TX),
319 ..Default::default()
320 }),
321 )],
322 ..Default::default()
323 },
324 };
325 let block_hash = block.header.hash_slow();
326 let rollup_config = RollupConfig {
327 genesis: ChainGenesis {
328 l2: BlockNumHash { hash: block_hash, ..Default::default() },
329 ..Default::default()
330 },
331 hardforks: HardForkConfig { holocene_time: Some(0), ..Default::default() },
332 ..Default::default()
333 };
334 assert!(rollup_config.is_holocene_active(block.header.timestamp));
335 let config = to_system_config(&block, &rollup_config).unwrap();
336 let expected = SystemConfig {
337 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
338 overhead: U256::ZERO,
339 scalar: uint!(
340 452312848583266388373324160190187140051835877600158453279134670530344387928_U256
341 ),
342 gas_limit: 0,
343 base_fee_scalar: None,
344 blob_base_fee_scalar: None,
345 eip1559_denominator: Some(0xbeef),
346 eip1559_elasticity: Some(0xbabe),
347 operator_fee_scalar: None,
348 operator_fee_constant: None,
349 };
350 assert_eq!(config, expected);
351 }
352
353 #[test]
354 fn test_constructs_isthmus_system_config() {
355 let block = OpBlock {
356 header: alloy_consensus::Header {
357 number: 1,
358 extra_data: bytes!("000000beef0000babe"),
360 ..Default::default()
361 },
362 body: alloy_consensus::BlockBody {
363 transactions: vec![op_alloy_consensus::OpTxEnvelope::Deposit(
364 alloy_primitives::Sealed::new(op_alloy_consensus::TxDeposit {
365 input: alloy_primitives::Bytes::from(&RAW_ISTHMUS_INFO_TX),
366 ..Default::default()
367 }),
368 )],
369 ..Default::default()
370 },
371 };
372 let block_hash = block.header.hash_slow();
373 let rollup_config = RollupConfig {
374 genesis: ChainGenesis {
375 l2: BlockNumHash { hash: block_hash, ..Default::default() },
376 ..Default::default()
377 },
378 hardforks: HardForkConfig {
379 holocene_time: Some(0),
380 isthmus_time: Some(0),
381 ..Default::default()
382 },
383 ..Default::default()
384 };
385 assert!(rollup_config.is_holocene_active(block.header.timestamp));
386 let config = to_system_config(&block, &rollup_config).unwrap();
387 let expected = SystemConfig {
388 batcher_address: address!("6887246668a3b87f54deb3b94ba47a6f63f32985"),
389 overhead: U256::ZERO,
390 scalar: uint!(
391 452312848583266388373324160190187140051835877600158453279134670530344387928_U256
392 ),
393 gas_limit: 0,
394 base_fee_scalar: None,
395 blob_base_fee_scalar: None,
396 eip1559_denominator: Some(0xbeef),
397 eip1559_elasticity: Some(0xbabe),
398 operator_fee_scalar: Some(0xabcd),
399 operator_fee_constant: Some(0xdcba),
400 };
401 assert_eq!(config, expected);
402 }
403}