1use alloy_eips::{Decodable2718, eip1559::BaseFeeParams};
4use alloy_network::TransactionResponse;
5use alloy_primitives::{Address, B256, Bytes};
6use alloy_rpc_types_eth::{Block, BlockTransactions, Withdrawals};
7use kona_genesis::RollupConfig;
8use kona_protocol::OpAttributesWithParent;
9use op_alloy_consensus::{EIP1559ParamError, OpTxEnvelope, decode_holocene_extra_data};
10use op_alloy_rpc_types::Transaction;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum AttributesMatch {
15 Match,
17 Mismatch(AttributesMismatch),
19}
20
21impl AttributesMatch {
22 pub const fn is_match(&self) -> bool {
24 matches!(self, Self::Match)
25 }
26
27 pub const fn is_mismatch(&self) -> bool {
29 matches!(self, Self::Mismatch(_))
30 }
31
32 pub fn check_withdrawals(
34 config: &RollupConfig,
35 attributes: &OpAttributesWithParent,
36 block: &Block<Transaction>,
37 ) -> Self {
38 let attr_withdrawals = attributes.inner().payload_attributes.withdrawals.as_ref();
39 let attr_withdrawals = attr_withdrawals.map(|w| Withdrawals::new(w.to_vec()));
40 let block_withdrawals = block.withdrawals.as_ref();
41
42 if config.is_canyon_active(block.header.timestamp) {
43 if attr_withdrawals.is_none_or(|w| !w.is_empty()) {
45 return Self::Mismatch(AttributesMismatch::CanyonWithdrawalsNotEmpty);
46 }
47 if block_withdrawals.is_none_or(|w| !w.is_empty()) {
48 return Self::Mismatch(AttributesMismatch::CanyonWithdrawalsNotEmpty);
49 }
50 if !config.is_isthmus_active(block.header.timestamp) {
51 let empty_hash = alloy_consensus::EMPTY_ROOT_HASH;
53 if block.header.inner.withdrawals_root != Some(empty_hash) {
54 return Self::Mismatch(AttributesMismatch::CanyonNotEmptyHash);
55 }
56 }
57 } else {
58 if attr_withdrawals.is_some() {
60 return Self::Mismatch(AttributesMismatch::BedrockWithdrawals);
61 }
62 }
63
64 if config.is_isthmus_active(block.header.timestamp) {
65 if block.header.inner.withdrawals_root.is_none() {
67 return Self::Mismatch(AttributesMismatch::IsthmusMissingWithdrawalsRoot);
68 }
69 }
70
71 Self::Match
72 }
73
74 fn check_transactions(attributes_txs: &[Bytes], block: &Block<Transaction>) -> Self {
78 let block_txs = match block.transactions {
82 BlockTransactions::Hashes(_) | BlockTransactions::Full(_)
83 if attributes_txs.is_empty() && block.transactions.is_empty() =>
84 {
85 return Self::Match;
92 }
93 BlockTransactions::Uncle => {
94 error!(
96 "Invalid format for the block transactions. The `Uncle` transaction format is not relevant in that context and should not get used here. This is a bug"
97 );
98
99 return AttributesMismatch::MalformedBlockTransactions.into();
100 }
101 BlockTransactions::Hashes(_) => {
102 error!(
104 "Invalid format for the block transactions. The `Hash` transaction format is not relevant in that context and should not get used here. This is a bug."
105 );
106
107 return AttributesMismatch::MalformedBlockTransactions.into();
108 }
109 BlockTransactions::Full(ref block_txs) => block_txs,
110 };
111
112 let attributes_txs_len = attributes_txs.len();
113 let block_txs_len = block_txs.len();
114
115 if attributes_txs_len != block_txs_len {
116 return AttributesMismatch::TransactionLen(attributes_txs_len, block_txs_len).into();
117 }
118
119 for (attr_tx_bytes, block_tx) in attributes_txs.iter().zip(block_txs) {
123 trace!(
124 target: "engine",
125 ?attr_tx_bytes,
126 block_tx_hash = %block_tx.tx_hash(),
127 "Checking attributes transaction against block transaction",
128 );
129 let Ok(attr_tx) = OpTxEnvelope::decode_2718(&mut &attr_tx_bytes[..]) else {
131 error!(
132 "Impossible to deserialize transaction from attributes. If we have stored these attributes it means the transactions where well formatted. This is a bug"
133 );
134
135 return AttributesMismatch::MalformedAttributesTransaction.into();
136 };
137
138 if &attr_tx != block_tx.inner.inner.inner() {
139 warn!(target: "engine", ?attr_tx, ?block_tx, "Transaction mismatch in derived attributes");
140 return AttributesMismatch::TransactionContent(attr_tx.tx_hash(), block_tx.tx_hash())
141 .into()
142 }
143 }
144
145 Self::Match
146 }
147
148 fn check_eip1559(
150 config: &RollupConfig,
151 attributes: &OpAttributesWithParent,
152 block: &Block<Transaction>,
153 ) -> Self {
154 let (ae, ad): (u128, u128) = match attributes.inner().decode_eip_1559_params() {
157 None => {
158 if config.is_holocene_active(block.header.timestamp) {
162 error!(
163 "EIP1559 parameters for attributes not set while holocene is active. This is a bug"
164 );
165 return AttributesMismatch::MissingAttributesEIP1559.into();
166 }
167
168 return Self::Match;
170 }
171 Some((0, e)) if e != 0 => {
172 error!(
173 "Holocene EIP1559 params cannot have a 0 denominator unless elasticity is also 0. This is a bug"
174 );
175 return AttributesMismatch::InvalidEIP1559ParamsCombination.into();
176 }
177 Some((0, 0)) => {
181 let BaseFeeParams { max_change_denominator, elasticity_multiplier } =
182 config.chain_op_config.as_canyon_base_fee_params();
183
184 (elasticity_multiplier, max_change_denominator)
185 }
186 Some((ae, ad)) => (ae.into(), ad.into()),
187 };
188
189 let (be, bd): (u128, u128) = match decode_holocene_extra_data(&block.header.extra_data) {
191 Ok((be, bd)) => (be.into(), bd.into()),
192 Err(EIP1559ParamError::NoEIP1559Params) => {
193 error!(
194 "EIP1559 parameters for the block not set while holocene is active. This is a bug"
195 );
196 return AttributesMismatch::MissingBlockEIP1559.into();
197 }
198 Err(EIP1559ParamError::InvalidVersion(v)) => {
199 error!(
200 version = v,
201 "The version in the extra data EIP1559 payload is incorrect. Should be 0. This is a bug",
202 );
203 return AttributesMismatch::InvalidExtraDataVersion.into();
204 }
205 Err(e) => {
206 error!(err = ?e, "An unknown extra data decoding error occurred. This is a bug",);
207
208 return AttributesMismatch::UnknownExtraDataDecodingError(e).into();
209 }
210 };
211
212 if ae != be || ad != bd {
214 return AttributesMismatch::EIP1559Parameters(
215 BaseFeeParams { max_change_denominator: ad, elasticity_multiplier: ae },
216 BaseFeeParams { max_change_denominator: bd, elasticity_multiplier: be },
217 )
218 .into()
219 }
220
221 Self::Match
222 }
223
224 pub fn check(
228 config: &RollupConfig,
229 attributes: &OpAttributesWithParent,
230 block: &Block<Transaction>,
231 ) -> Self {
232 if attributes.parent.block_info.hash != block.header.inner.parent_hash {
233 return AttributesMismatch::ParentHash(
234 attributes.parent.block_info.hash,
235 block.header.inner.parent_hash,
236 )
237 .into();
238 }
239
240 if attributes.inner().payload_attributes.timestamp != block.header.inner.timestamp {
241 return AttributesMismatch::Timestamp(
242 attributes.inner().payload_attributes.timestamp,
243 block.header.inner.timestamp,
244 )
245 .into();
246 }
247
248 let mix_hash = block.header.inner.mix_hash;
249 if attributes.inner().payload_attributes.prev_randao != mix_hash {
250 return AttributesMismatch::PrevRandao(
251 attributes.inner().payload_attributes.prev_randao,
252 mix_hash,
253 )
254 .into();
255 }
256
257 let default_vec = vec![];
259 let attributes_txs =
260 attributes.inner().transactions.as_ref().map_or_else(|| &default_vec, |attrs| attrs);
261
262 if let mismatch @ Self::Mismatch(_) = Self::check_transactions(attributes_txs, block) {
264 return mismatch
265 }
266
267 let Some(gas_limit) = attributes.inner().gas_limit else {
268 return AttributesMismatch::MissingAttributesGasLimit.into();
269 };
270
271 if gas_limit != block.header.inner.gas_limit {
272 return AttributesMismatch::GasLimit(gas_limit, block.header.inner.gas_limit).into();
273 }
274
275 if let Self::Mismatch(m) = Self::check_withdrawals(config, attributes, block) {
276 return m.into();
277 }
278
279 if attributes.inner().payload_attributes.parent_beacon_block_root !=
280 block.header.inner.parent_beacon_block_root
281 {
282 return AttributesMismatch::ParentBeaconBlockRoot(
283 attributes.inner().payload_attributes.parent_beacon_block_root,
284 block.header.inner.parent_beacon_block_root,
285 )
286 .into();
287 }
288
289 if attributes.inner().payload_attributes.suggested_fee_recipient !=
290 block.header.inner.beneficiary
291 {
292 return AttributesMismatch::FeeRecipient(
293 attributes.inner().payload_attributes.suggested_fee_recipient,
294 block.header.inner.beneficiary,
295 )
296 .into();
297 }
298
299 if let m @ Self::Mismatch(_) = Self::check_eip1559(config, attributes, block) {
301 return m;
302 }
303
304 Self::Match
305 }
306}
307
308#[derive(Debug, Clone, Copy, PartialEq, Eq)]
311pub enum AttributesMismatch {
312 ParentHash(B256, B256),
314 Timestamp(u64, u64),
316 PrevRandao(B256, B256),
318 MalformedBlockTransactions,
321 MalformedAttributesTransaction,
324 TransactionLen(usize, usize),
326 TransactionContent(B256, B256),
328 MissingAttributesEIP1559,
330 MissingBlockEIP1559,
332 InvalidExtraDataVersion,
334 UnknownExtraDataDecodingError(EIP1559ParamError),
336 InvalidEIP1559ParamsCombination,
338 EIP1559Parameters(BaseFeeParams, BaseFeeParams),
340 Transactions(u64, u64),
342 GasLimit(u64, u64),
344 MissingAttributesGasLimit,
346 FeeRecipient(Address, Address),
348 ParentBeaconBlockRoot(Option<B256>, Option<B256>),
350 CanyonWithdrawalsNotEmpty,
352 CanyonNotEmptyHash,
354 BedrockWithdrawals,
356 IsthmusMissingWithdrawalsRoot,
358}
359
360impl From<AttributesMismatch> for AttributesMatch {
361 fn from(mismatch: AttributesMismatch) -> Self {
362 Self::Mismatch(mismatch)
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369 use crate::AttributesMismatch::EIP1559Parameters;
370 use alloy_consensus::EMPTY_ROOT_HASH;
371 use alloy_primitives::{Bytes, FixedBytes, address, b256};
372 use alloy_rpc_types_eth::BlockTransactions;
373 use arbitrary::{Arbitrary, Unstructured};
374 use kona_protocol::{BlockInfo, L2BlockInfo};
375 use kona_registry::ROLLUP_CONFIGS;
376 use op_alloy_consensus::encode_holocene_extra_data;
377 use op_alloy_rpc_types_engine::OpPayloadAttributes;
378
379 fn default_attributes() -> OpAttributesWithParent {
380 OpAttributesWithParent {
381 inner: OpPayloadAttributes::default(),
382 parent: L2BlockInfo::default(),
383 l1_origin: BlockInfo::default(),
384 is_last_in_span: true,
385 }
386 }
387
388 fn default_rollup_config() -> &'static RollupConfig {
389 let opm = 10;
390 ROLLUP_CONFIGS.get(&opm).expect("default rollup config should exist")
391 }
392
393 #[test]
394 fn test_attributes_match_parent_hash_mismatch() {
395 let cfg = default_rollup_config();
396 let attributes = default_attributes();
397 let mut block = Block::<Transaction>::default();
398 block.header.inner.parent_hash =
399 b256!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
400 let check = AttributesMatch::check(cfg, &attributes, &block);
401 let expected: AttributesMatch = AttributesMismatch::ParentHash(
402 attributes.parent.block_info.hash,
403 block.header.inner.parent_hash,
404 )
405 .into();
406 assert_eq!(check, expected);
407 assert!(check.is_mismatch());
408 }
409
410 #[test]
411 fn test_attributes_match_check_timestamp() {
412 let cfg = default_rollup_config();
413 let attributes = default_attributes();
414 let mut block = Block::<Transaction>::default();
415 block.header.inner.timestamp = 1234567890;
416 let check = AttributesMatch::check(cfg, &attributes, &block);
417 let expected: AttributesMatch = AttributesMismatch::Timestamp(
418 attributes.inner().payload_attributes.timestamp,
419 block.header.inner.timestamp,
420 )
421 .into();
422 assert_eq!(check, expected);
423 assert!(check.is_mismatch());
424 }
425
426 #[test]
427 fn test_attributes_match_check_prev_randao() {
428 let cfg = default_rollup_config();
429 let attributes = default_attributes();
430 let mut block = Block::<Transaction>::default();
431 block.header.inner.mix_hash =
432 b256!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef");
433 let check = AttributesMatch::check(cfg, &attributes, &block);
434 let expected: AttributesMatch = AttributesMismatch::PrevRandao(
435 attributes.inner().payload_attributes.prev_randao,
436 block.header.inner.mix_hash,
437 )
438 .into();
439 assert_eq!(check, expected);
440 assert!(check.is_mismatch());
441 }
442
443 #[test]
444 fn test_attributes_match_missing_gas_limit() {
445 let cfg = default_rollup_config();
446 let attributes = default_attributes();
447 let mut block = Block::<Transaction>::default();
448 block.header.inner.gas_limit = 123456;
449 let check = AttributesMatch::check(cfg, &attributes, &block);
450 let expected: AttributesMatch = AttributesMismatch::MissingAttributesGasLimit.into();
451 assert_eq!(check, expected);
452 assert!(check.is_mismatch());
453 }
454
455 #[test]
456 fn test_attributes_match_check_gas_limit() {
457 let cfg = default_rollup_config();
458 let mut attributes = default_attributes();
459 attributes.inner.gas_limit = Some(123457);
460 let mut block = Block::<Transaction>::default();
461 block.header.inner.gas_limit = 123456;
462 let check = AttributesMatch::check(cfg, &attributes, &block);
463 let expected: AttributesMatch = AttributesMismatch::GasLimit(
464 attributes.inner().gas_limit.unwrap_or_default(),
465 block.header.inner.gas_limit,
466 )
467 .into();
468 assert_eq!(check, expected);
469 assert!(check.is_mismatch());
470 }
471
472 #[test]
473 fn test_attributes_match_check_parent_beacon_block_root() {
474 let cfg = default_rollup_config();
475 let mut attributes = default_attributes();
476 attributes.inner.gas_limit = Some(0);
477 attributes.inner.payload_attributes.parent_beacon_block_root =
478 Some(b256!("1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"));
479 let block = Block::<Transaction>::default();
480 let check = AttributesMatch::check(cfg, &attributes, &block);
481 let expected: AttributesMatch = AttributesMismatch::ParentBeaconBlockRoot(
482 attributes.inner().payload_attributes.parent_beacon_block_root,
483 block.header.inner.parent_beacon_block_root,
484 )
485 .into();
486 assert_eq!(check, expected);
487 assert!(check.is_mismatch());
488 }
489
490 #[test]
491 fn test_attributes_match_check_fee_recipient() {
492 let cfg = default_rollup_config();
493 let mut attributes = default_attributes();
494 attributes.inner.gas_limit = Some(0);
495 let mut block = Block::<Transaction>::default();
496 block.header.inner.beneficiary = address!("1234567890abcdef1234567890abcdef12345678");
497 let check = AttributesMatch::check(cfg, &attributes, &block);
498 let expected: AttributesMatch = AttributesMismatch::FeeRecipient(
499 attributes.inner().payload_attributes.suggested_fee_recipient,
500 block.header.inner.beneficiary,
501 )
502 .into();
503 assert_eq!(check, expected);
504 assert!(check.is_mismatch());
505 }
506
507 fn generate_txs(num_txs: usize) -> Vec<Transaction> {
508 let mut data = vec![0; 1024];
510 let mut rng = rand::rng();
511
512 (0..num_txs)
513 .map(|_| {
514 rand::Rng::fill(&mut rng, &mut data[..]);
515
516 let u = Unstructured::new(&data);
518
519 Transaction::arbitrary_take_rest(u).expect("Impossible to generate arbitrary tx")
521 })
522 .collect()
523 }
524
525 fn test_transactions_match_helper() -> (OpAttributesWithParent, Block<Transaction>) {
526 const NUM_TXS: usize = 10;
527
528 let transactions = generate_txs(NUM_TXS);
529 let mut attributes = default_attributes();
530 attributes.inner.gas_limit = Some(0);
531 attributes.inner.transactions = Some(
532 transactions
533 .iter()
534 .map(|tx| {
535 use alloy_eips::Encodable2718;
536 let mut buf = vec![];
537 tx.inner.inner.inner().encode_2718(&mut buf);
538 Bytes::from(buf)
539 })
540 .collect::<Vec<_>>(),
541 );
542
543 let block = Block::<Transaction> {
544 transactions: BlockTransactions::Full(transactions),
545 ..Default::default()
546 };
547
548 (attributes, block)
549 }
550
551 #[test]
552 fn test_attributes_match_check_transactions() {
553 let cfg = default_rollup_config();
554 let (attributes, block) = test_transactions_match_helper();
555 let check = AttributesMatch::check(cfg, &attributes, &block);
556 assert_eq!(check, AttributesMatch::Match);
557 }
558
559 #[test]
560 fn test_attributes_mismatch_check_transactions_len() {
561 let cfg = default_rollup_config();
562 let (mut attributes, block) = test_transactions_match_helper();
563 attributes.inner = OpPayloadAttributes {
564 transactions: attributes.inner.transactions.map(|mut txs| {
565 txs.pop();
566 txs
567 }),
568 ..attributes.inner
569 };
570
571 let block_txs_len = block.transactions.len();
572
573 let expected: AttributesMatch =
574 AttributesMismatch::TransactionLen(block_txs_len - 1, block_txs_len).into();
575
576 let check = AttributesMatch::check(cfg, &attributes, &block);
577 assert_eq!(check, expected);
578 assert!(check.is_mismatch());
579 }
580
581 #[test]
582 fn test_attributes_mismatch_check_transaction_content() {
583 let cfg = default_rollup_config();
584 let (attributes, mut block) = test_transactions_match_helper();
585 let BlockTransactions::Full(block_txs) = &mut block.transactions else {
586 unreachable!("The helper should build a full list of transactions")
587 };
588
589 let first_tx = block_txs.last().unwrap().clone();
590 let first_tx_hash = first_tx.tx_hash();
591
592 let last_tx = block_txs.first_mut().unwrap();
596 let last_tx_hash = last_tx.tx_hash();
597 *last_tx = first_tx;
598
599 let expected: AttributesMatch =
600 AttributesMismatch::TransactionContent(last_tx_hash, first_tx_hash).into();
601
602 let check = AttributesMatch::check(cfg, &attributes, &block);
603 assert_eq!(check, expected);
604 assert!(check.is_mismatch());
605 }
606
607 #[test]
609 fn test_attributes_mismatch_empty_tx_attributes() {
610 let cfg = default_rollup_config();
611 let (mut attributes, block) = test_transactions_match_helper();
612 attributes.inner = OpPayloadAttributes { transactions: None, ..attributes.inner };
613
614 let block_txs_len = block.transactions.len();
615
616 let expected: AttributesMatch = AttributesMismatch::TransactionLen(0, block_txs_len).into();
617
618 let check = AttributesMatch::check(cfg, &attributes, &block);
619 assert_eq!(check, expected);
620 assert!(check.is_mismatch());
621 }
622
623 #[test]
626 fn test_block_transactions_wrong_format() {
627 let cfg = default_rollup_config();
628 let (attributes, mut block) = test_transactions_match_helper();
629 block.transactions = BlockTransactions::Uncle;
630
631 let expected: AttributesMatch = AttributesMismatch::MalformedBlockTransactions.into();
632
633 let check = AttributesMatch::check(cfg, &attributes, &block);
634 assert_eq!(check, expected);
635 assert!(check.is_mismatch());
636 }
637
638 #[test]
641 fn test_attributes_transactions_wrong_format() {
642 let cfg = default_rollup_config();
643 let (mut attributes, block) = test_transactions_match_helper();
644 let txs = attributes.inner.transactions.as_mut().unwrap();
645 let first_tx_bytes = txs.first_mut().unwrap();
646 *first_tx_bytes = Bytes::copy_from_slice(&[0, 1, 2]);
647
648 let expected: AttributesMatch = AttributesMismatch::MalformedAttributesTransaction.into();
649
650 let check = AttributesMatch::check(cfg, &attributes, &block);
651 assert_eq!(check, expected);
652 assert!(check.is_mismatch());
653 }
654
655 #[test]
658 fn test_attributes_and_block_transactions_empty() {
659 let cfg = default_rollup_config();
660 let (mut attributes, mut block) = test_transactions_match_helper();
661
662 attributes.inner = OpPayloadAttributes { transactions: Some(vec![]), ..attributes.inner };
663
664 block.transactions = BlockTransactions::Full(vec![]);
665
666 let check = AttributesMatch::check(cfg, &attributes, &block);
667 assert_eq!(check, AttributesMatch::Match);
668
669 attributes.inner = OpPayloadAttributes { transactions: None, ..attributes.inner };
672 block.transactions = BlockTransactions::Hashes(vec![]);
673
674 let check = AttributesMatch::check(cfg, &attributes, &block);
675 assert_eq!(check, AttributesMatch::Match);
676 }
677
678 #[test]
681 fn test_attributes_and_block_transactions_empty_hash_format() {
682 let cfg = default_rollup_config();
683 let (mut attributes, mut block) = test_transactions_match_helper();
684
685 attributes.inner = OpPayloadAttributes { transactions: Some(vec![]), ..attributes.inner };
686
687 block.transactions = BlockTransactions::Hashes(vec![]);
688
689 let check = AttributesMatch::check(cfg, &attributes, &block);
690 assert_eq!(check, AttributesMatch::Match);
691 }
692
693 #[test]
695 fn test_attributes_empty_and_block_uncle() {
696 let cfg = default_rollup_config();
697 let (mut attributes, mut block) = test_transactions_match_helper();
698
699 attributes.inner = OpPayloadAttributes { transactions: Some(vec![]), ..attributes.inner };
700
701 block.transactions = BlockTransactions::Uncle;
702
703 let expected: AttributesMatch = AttributesMismatch::MalformedBlockTransactions.into();
704
705 let check = AttributesMatch::check(cfg, &attributes, &block);
706 assert_eq!(check, expected);
707 }
708
709 fn eip1559_test_setup() -> (RollupConfig, OpAttributesWithParent, Block<Transaction>) {
710 let mut cfg = default_rollup_config().clone();
711
712 cfg.hardforks.holocene_time = Some(0);
715
716 let mut attributes = default_attributes();
717 attributes.inner.gas_limit = Some(0);
718 attributes.inner.payload_attributes.withdrawals = Some(vec![]);
720
721 let block = Block {
723 withdrawals: Some(Withdrawals(vec![])),
724 header: alloy_rpc_types_eth::Header {
725 inner: alloy_consensus::Header {
726 withdrawals_root: Some(EMPTY_ROOT_HASH),
727 ..Default::default()
728 },
729 ..Default::default()
730 },
731 ..Default::default()
732 };
733
734 (cfg, attributes, block)
735 }
736
737 #[test]
739 fn test_eip1559_parameters_not_specified_holocene() {
740 let (cfg, attributes, block) = eip1559_test_setup();
741
742 let check = AttributesMatch::check(&cfg, &attributes, &block);
743 assert_eq!(check, AttributesMatch::Mismatch(AttributesMismatch::MissingAttributesEIP1559));
744 assert!(check.is_mismatch());
745 }
746
747 #[test]
749 fn test_eip1559_parameters_specified_attributes_but_not_block() {
750 let (cfg, mut attributes, block) = eip1559_test_setup();
751
752 attributes.inner.eip_1559_params = Some(Default::default());
753
754 let check = AttributesMatch::check(&cfg, &attributes, &block);
755 assert_eq!(check, AttributesMatch::Mismatch(AttributesMismatch::MissingBlockEIP1559));
756 assert!(check.is_mismatch());
757 }
758
759 #[test]
762 fn test_eip1559_parameters_specified_both_and_empty() {
763 let (cfg, mut attributes, mut block) = eip1559_test_setup();
764
765 attributes.inner.eip_1559_params = Some(Default::default());
766 block.header.extra_data = vec![0; 9].into();
767
768 let check = AttributesMatch::check(&cfg, &attributes, &block);
769 assert_eq!(
770 check,
771 AttributesMatch::Mismatch(EIP1559Parameters(
772 BaseFeeParams { max_change_denominator: 250, elasticity_multiplier: 6 },
773 BaseFeeParams { max_change_denominator: 0, elasticity_multiplier: 0 }
774 ))
775 );
776 assert!(check.is_mismatch());
777 }
778
779 #[test]
780 fn test_eip1559_parameters_empty_for_attr_only() {
781 let (cfg, mut attributes, mut block) = eip1559_test_setup();
782
783 attributes.inner.eip_1559_params = Some(Default::default());
784 block.header.extra_data = encode_holocene_extra_data(
785 Default::default(),
786 BaseFeeParams { max_change_denominator: 250, elasticity_multiplier: 6 },
787 )
788 .unwrap();
789
790 let check = AttributesMatch::check(&cfg, &attributes, &block);
791 assert_eq!(check, AttributesMatch::Match);
792 assert!(check.is_match());
793 }
794
795 #[test]
796 fn test_eip1559_parameters_custom_values_match() {
797 let (cfg, mut attributes, mut block) = eip1559_test_setup();
798
799 let eip1559_extra_params = encode_holocene_extra_data(
800 Default::default(),
801 BaseFeeParams { max_change_denominator: 100, elasticity_multiplier: 2 },
802 )
803 .unwrap();
804 let eip1559_params: FixedBytes<8> =
805 eip1559_extra_params.clone().split_off(1).as_ref().try_into().unwrap();
806
807 attributes.inner.eip_1559_params = Some(eip1559_params);
808 block.header.extra_data = eip1559_extra_params;
809
810 let check = AttributesMatch::check(&cfg, &attributes, &block);
811 assert_eq!(check, AttributesMatch::Match);
812 assert!(check.is_match());
813 }
814
815 #[test]
816 fn test_eip1559_parameters_custom_values_mismatch() {
817 let (cfg, mut attributes, mut block) = eip1559_test_setup();
818
819 let eip1559_extra_params = encode_holocene_extra_data(
820 Default::default(),
821 BaseFeeParams { max_change_denominator: 100, elasticity_multiplier: 2 },
822 )
823 .unwrap();
824
825 let eip1559_params: FixedBytes<8> = encode_holocene_extra_data(
826 Default::default(),
827 BaseFeeParams { max_change_denominator: 99, elasticity_multiplier: 2 },
828 )
829 .unwrap()
830 .split_off(1)
831 .as_ref()
832 .try_into()
833 .unwrap();
834
835 attributes.inner.eip_1559_params = Some(eip1559_params);
836 block.header.extra_data = eip1559_extra_params;
837
838 let check = AttributesMatch::check(&cfg, &attributes, &block);
839 assert_eq!(
840 check,
841 AttributesMatch::Mismatch(AttributesMismatch::EIP1559Parameters(
842 BaseFeeParams { max_change_denominator: 99, elasticity_multiplier: 2 },
843 BaseFeeParams { max_change_denominator: 100, elasticity_multiplier: 2 }
844 ))
845 );
846 assert!(check.is_mismatch());
847 }
848
849 #[test]
851 fn test_eip1559_parameters_combination_mismatch() {
852 let (cfg, mut attributes, mut block) = eip1559_test_setup();
853
854 let eip1559_extra_params = encode_holocene_extra_data(
855 Default::default(),
856 BaseFeeParams { max_change_denominator: 5, elasticity_multiplier: 0 },
857 )
858 .unwrap();
859 let eip1559_params: FixedBytes<8> =
860 eip1559_extra_params.clone().split_off(1).as_ref().try_into().unwrap();
861
862 attributes.inner.eip_1559_params = Some(eip1559_params);
863 block.header.extra_data = eip1559_extra_params;
864
865 let check = AttributesMatch::check(&cfg, &attributes, &block);
866 assert_eq!(
867 check,
868 AttributesMatch::Mismatch(AttributesMismatch::InvalidEIP1559ParamsCombination)
869 );
870 assert!(check.is_mismatch());
871 }
872
873 #[test]
875 fn test_eip1559_parameters_invalid_version() {
876 let (cfg, mut attributes, mut block) = eip1559_test_setup();
877
878 let eip1559_extra_params = encode_holocene_extra_data(
879 Default::default(),
880 BaseFeeParams { max_change_denominator: 100, elasticity_multiplier: 2 },
881 )
882 .unwrap();
883 let eip1559_params: FixedBytes<8> =
884 eip1559_extra_params.clone().split_off(1).as_ref().try_into().unwrap();
885
886 let mut raw_extra_params_bytes = eip1559_extra_params.to_vec();
887 raw_extra_params_bytes[0] = 10;
888
889 attributes.inner.eip_1559_params = Some(eip1559_params);
890 block.header.extra_data = raw_extra_params_bytes.into();
891
892 let check = AttributesMatch::check(&cfg, &attributes, &block);
893 assert_eq!(check, AttributesMatch::Mismatch(AttributesMismatch::InvalidExtraDataVersion));
894 assert!(check.is_mismatch());
895 }
896
897 #[test]
899 fn test_eip1559_default_param_cant_overflow() {
900 let (mut cfg, mut attributes, mut block) = eip1559_test_setup();
901 cfg.chain_op_config.eip1559_denominator_canyon = u64::MAX;
902 cfg.chain_op_config.eip1559_elasticity = u64::MAX;
903
904 attributes.inner.eip_1559_params = Some(Default::default());
905 block.header.extra_data = vec![0; 9].into();
906
907 let check = AttributesMatch::check(&cfg, &attributes, &block);
908
909 assert_eq!(
912 check,
913 AttributesMatch::Mismatch(EIP1559Parameters(
914 BaseFeeParams {
915 max_change_denominator: u64::MAX as u128,
916 elasticity_multiplier: u64::MAX as u128
917 },
918 BaseFeeParams { max_change_denominator: 0, elasticity_multiplier: 0 }
919 ))
920 );
921 assert!(check.is_mismatch());
922 }
923
924 #[test]
925 fn test_attributes_match() {
926 let cfg = default_rollup_config();
927 let mut attributes = default_attributes();
928 attributes.inner.gas_limit = Some(0);
929 let block = Block::<Transaction>::default();
930 let check = AttributesMatch::check(cfg, &attributes, &block);
931 assert_eq!(check, AttributesMatch::Match);
932 assert!(check.is_match());
933 }
934}