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