|
13 | 13 | use bytes::Bytes; |
14 | 14 | use ethrex_blockchain::vm::StoreVmDatabase; |
15 | 15 | use ethrex_common::types::{ |
16 | | - Account, BlockHeader, Code, FRAME_RECEIPT_STATUS_SUCCESS, Fork, Frame, FrameMode, |
17 | | - FrameTransaction, Transaction, |
| 16 | + Account, BlockHeader, Code, FRAME_RECEIPT_STATUS_SKIPPED, FRAME_RECEIPT_STATUS_SUCCESS, Fork, |
| 17 | + Frame, FrameMode, FrameTransaction, Transaction, |
18 | 18 | }; |
19 | 19 | use ethrex_common::{Address, H256, U256, constants::EMPTY_TRIE_HASH}; |
20 | 20 | use ethrex_crypto::NativeCrypto; |
@@ -557,6 +557,102 @@ fn frameparam_reads_frame_index_from_stack_top() { |
557 | 557 | ); |
558 | 558 | } |
559 | 559 |
|
| 560 | +/// FRAMEPARAM(param=0x05, frameIndex=2) -> status of frame[2], then SSTORE at slot 0. |
| 561 | +/// Bytecode: PUSH1 0x05 (param), PUSH1 0x02 (frameIndex - top), FRAMEPARAM (0xB3), |
| 562 | +/// PUSH1 0x00 (slot key), SSTORE (0x55), STOP (0x00). |
| 563 | +const FRAMEPARAM_READ_FRAME2_STATUS: &[u8] = |
| 564 | + &[0x60, 0x05, 0x60, 0x02, 0xB3, 0x60, 0x00, 0x55, 0x00]; |
| 565 | + |
| 566 | +#[test] |
| 567 | +fn frameparam_status_of_skipped_frame_is_two() { |
| 568 | + // EIP-8141: a frame skipped by a failed atomic batch reports status 0x2 |
| 569 | + // (not 0x3). Layout: |
| 570 | + // frame0: VERIFY on the sender -> APPROVE(3) -> payer + sender_approved. |
| 571 | + // frame1: DEFAULT, atomic-batch flag, reverts -> unrolls the batch. |
| 572 | + // frame2: DEFAULT, atomic-batch flag -> SKIPPED. |
| 573 | + // frame3: DEFAULT, no flag -> batch terminator, also SKIPPED. |
| 574 | + // frame4: DEFAULT, no flag -> reads FRAMEPARAM(0x05, 2) and stores it. |
| 575 | + let reverter = Address::from_low_u64_be(0xD1); |
| 576 | + let stop_ct = Address::from_low_u64_be(0xD2); |
| 577 | + let reader = Address::from_low_u64_be(0xD3); |
| 578 | + let tx = frame_tx_with_frames(vec![ |
| 579 | + verify_frame(FUNDED_SENDER), |
| 580 | + Frame { |
| 581 | + mode: u8::from(FrameMode::Default), |
| 582 | + flags: 0x04, |
| 583 | + target: Some(reverter), |
| 584 | + gas_limit: 60_000, |
| 585 | + value: U256::zero(), |
| 586 | + data: Bytes::new(), |
| 587 | + }, |
| 588 | + Frame { |
| 589 | + mode: u8::from(FrameMode::Default), |
| 590 | + flags: 0x04, |
| 591 | + target: Some(stop_ct), |
| 592 | + gas_limit: 30_000, |
| 593 | + value: U256::zero(), |
| 594 | + data: Bytes::new(), |
| 595 | + }, |
| 596 | + Frame { |
| 597 | + mode: u8::from(FrameMode::Default), |
| 598 | + flags: 0x00, |
| 599 | + target: Some(stop_ct), |
| 600 | + gas_limit: 30_000, |
| 601 | + value: U256::zero(), |
| 602 | + data: Bytes::new(), |
| 603 | + }, |
| 604 | + Frame { |
| 605 | + mode: u8::from(FrameMode::Default), |
| 606 | + flags: 0x00, |
| 607 | + target: Some(reader), |
| 608 | + // EIP-8037 (active at Hegota): the new-slot SSTORE spills |
| 609 | + // STATE_BYTES_PER_STORAGE_SET * cost_per_state_byte (~98k) into |
| 610 | + // the frame's regular gas, so the budget must cover it. |
| 611 | + gas_limit: 300_000, |
| 612 | + value: U256::zero(), |
| 613 | + data: Bytes::new(), |
| 614 | + }, |
| 615 | + ]); |
| 616 | + let accounts = [ |
| 617 | + ( |
| 618 | + FUNDED_SENDER, |
| 619 | + AUTO_SEED_SENDER_BALANCE, |
| 620 | + 0, |
| 621 | + Bytes::from(APPROVE_BOTH_CODE.to_vec()), |
| 622 | + ), |
| 623 | + ( |
| 624 | + reverter, |
| 625 | + U256::zero(), |
| 626 | + 0, |
| 627 | + Bytes::from(PURE_REVERT_CODE.to_vec()), |
| 628 | + ), |
| 629 | + (stop_ct, U256::zero(), 0, Bytes::from(vec![0x00u8])), // STOP |
| 630 | + ( |
| 631 | + reader, |
| 632 | + U256::zero(), |
| 633 | + 0, |
| 634 | + Bytes::from(FRAMEPARAM_READ_FRAME2_STATUS.to_vec()), |
| 635 | + ), |
| 636 | + ]; |
| 637 | + let (result, db) = run_frame_tx(&accounts, tx); |
| 638 | + let report = result.expect("a DEFAULT-frame batch revert must not invalidate the tx"); |
| 639 | + |
| 640 | + let frame_results = report |
| 641 | + .frame_results |
| 642 | + .expect("frame tx report must carry per-frame results"); |
| 643 | + assert_eq!( |
| 644 | + frame_results[2].0, FRAME_RECEIPT_STATUS_SKIPPED, |
| 645 | + "frame[2] must be recorded as skipped" |
| 646 | + ); |
| 647 | + |
| 648 | + // FRAMEPARAM(0x05) must surface the same code, and it must be 2. |
| 649 | + assert_eq!( |
| 650 | + storage_slot(&db, reader, H256::zero()), |
| 651 | + U256::from(2u64), |
| 652 | + "FRAMEPARAM param 0x05 must return 2 for a frame skipped by a failed atomic batch" |
| 653 | + ); |
| 654 | +} |
| 655 | + |
560 | 656 | // ==================== APPROVE scope-0 bypass ==================== |
561 | 657 |
|
562 | 658 | #[test] |
|
0 commit comments