1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) system call implementation.
use alloc::{boxed::Box, format, string::ToString, vec::Vec};
use core::fmt::Display;

use crate::ConfigureEvm;
use alloy_eips::eip7251::{ConsolidationRequest, CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS};
use reth_execution_errors::{BlockExecutionError, BlockValidationError};
use reth_primitives::{Buf, Header, Request};
use revm::{interpreter::Host, Database, DatabaseCommit, Evm};
use revm_primitives::{
    Address, BlockEnv, Bytes, CfgEnvWithHandlerCfg, EnvWithHandlerCfg, ExecutionResult, FixedBytes,
    ResultAndState,
};

/// Apply the [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) post block contract call.
///
/// This constructs a new [Evm] with the given DB, and environment
/// ([`CfgEnvWithHandlerCfg`] and [`BlockEnv`]) to execute the post block contract call.
///
/// This uses [`apply_consolidation_requests_contract_call`] to ultimately calculate the
/// [requests](Request).
pub fn post_block_consolidation_requests_contract_call<EvmConfig, DB>(
    evm_config: &EvmConfig,
    db: &mut DB,
    initialized_cfg: &CfgEnvWithHandlerCfg,
    initialized_block_env: &BlockEnv,
) -> Result<Vec<Request>, BlockExecutionError>
where
    DB: Database + DatabaseCommit,
    DB::Error: Display,
    EvmConfig: ConfigureEvm<Header = Header>,
{
    // apply post-block EIP-7251 contract call
    let mut evm_post_block = Evm::builder()
        .with_db(db)
        .with_env_with_handler_cfg(EnvWithHandlerCfg::new_with_cfg_env(
            initialized_cfg.clone(),
            initialized_block_env.clone(),
            Default::default(),
        ))
        .build();

    // initialize a block from the env, because the post block call needs the block itself
    apply_consolidation_requests_contract_call(evm_config, &mut evm_post_block)
}

/// Applies the post-block call to the EIP-7251 consolidation requests contract.
///
/// If Prague is not active at the given timestamp, then this is a no-op, and an empty vector is
/// returned. Otherwise, the consolidation requests are returned.
#[inline]
pub fn apply_consolidation_requests_contract_call<EvmConfig, EXT, DB>(
    evm_config: &EvmConfig,
    evm: &mut Evm<'_, EXT, DB>,
) -> Result<Vec<Request>, BlockExecutionError>
where
    DB: Database + DatabaseCommit,
    DB::Error: core::fmt::Display,
    EvmConfig: ConfigureEvm<Header = Header>,
{
    // get previous env
    let previous_env = Box::new(evm.context.env().clone());

    // Fill transaction environment with the EIP-7251 consolidation requests contract message data.
    //
    // This requirement for the consolidation requests contract call defined by
    // [EIP-7251](https://eips.ethereum.org/EIPS/eip-7251) is:
    //
    // At the end of processing any execution block where block.timestamp >= FORK_TIMESTAMP (i.e.
    // after processing all transactions and after performing the block body requests validations)
    // clienst software MUST [..] call the contract as `SYSTEM_ADDRESS` and empty input data to
    // trigger the system subroutine execute.
    evm_config.fill_tx_env_system_contract_call(
        &mut evm.context.evm.env,
        alloy_eips::eip7002::SYSTEM_ADDRESS,
        CONSOLIDATION_REQUEST_PREDEPLOY_ADDRESS,
        Bytes::new(),
    );

    let ResultAndState { result, mut state } = match evm.transact() {
        Ok(res) => res,
        Err(e) => {
            evm.context.evm.env = previous_env;
            return Err(BlockValidationError::ConsolidationRequestsContractCall {
                message: format!("execution failed: {e}"),
            }
            .into())
        }
    };

    // cleanup the state
    state.remove(&alloy_eips::eip7002::SYSTEM_ADDRESS);
    state.remove(&evm.block().coinbase);
    evm.context.evm.db.commit(state);

    // re-set the previous env
    evm.context.evm.env = previous_env;

    let mut data = match result {
        ExecutionResult::Success { output, .. } => Ok(output.into_data()),
        ExecutionResult::Revert { output, .. } => {
            Err(BlockValidationError::ConsolidationRequestsContractCall {
                message: format!("execution reverted: {output}"),
            })
        }
        ExecutionResult::Halt { reason, .. } => {
            Err(BlockValidationError::ConsolidationRequestsContractCall {
                message: format!("execution halted: {reason:?}"),
            })
        }
    }?;

    // Consolidations are encoded as a series of consolidation requests, each with the following
    // format:
    //
    // +------+--------+---------------+
    // | addr | pubkey | target pubkey |
    // +------+--------+---------------+
    //    20      48        48

    const CONSOLIDATION_REQUEST_SIZE: usize = 20 + 48 + 48;
    let mut consolidation_requests = Vec::with_capacity(data.len() / CONSOLIDATION_REQUEST_SIZE);
    while data.has_remaining() {
        if data.remaining() < CONSOLIDATION_REQUEST_SIZE {
            return Err(BlockValidationError::ConsolidationRequestsContractCall {
                message: "invalid consolidation request length".to_string(),
            }
            .into())
        }

        let mut source_address = Address::ZERO;
        data.copy_to_slice(source_address.as_mut_slice());

        let mut source_pubkey = FixedBytes::<48>::ZERO;
        data.copy_to_slice(source_pubkey.as_mut_slice());

        let mut target_pubkey = FixedBytes::<48>::ZERO;
        data.copy_to_slice(target_pubkey.as_mut_slice());

        consolidation_requests.push(Request::ConsolidationRequest(ConsolidationRequest {
            source_address,
            source_pubkey,
            target_pubkey,
        }));
    }

    Ok(consolidation_requests)
}