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