mega-evm 1.5.1

The evm tailored for the MegaETH
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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
//! Keyless deploy sandbox execution.
//!
//! This module executes keyless deployment in an isolated sandbox environment
//! to implement Nick's Method for deterministic contract deployment.
//!
//! # Spam Protection
//!
//! This module guarantees that once sandbox execution starts and completes, the signer
//! will always be charged for gas consumed. This is achieved through:
//!
//! - **Top-level restriction**: Only intercepted at `depth == 0` (see `evm/execution.rs`)
//! - **Execution errors return success**: `ExecutionReverted`, `ExecutionHalted`, and
//!   `EmptyCodeDeployed` return `InstructionResult::Return` with error data encoded in the return
//!   value, not `InstructionResult::Revert`
//! - **Atomic state application**: `apply_sandbox_state` always succeeds after sandbox execution
//!   completes
//!
//! This design ensures there is no way for an attacker to trigger sandbox execution
//! and then have the gas charges reverted.

#[cfg(not(feature = "std"))]
use alloc as std;
use std::{rc::Rc, string::ToString, vec::Vec};

use alloy_consensus::Transaction as AlloyTransaction;
use alloy_evm::Evm;
use alloy_primitives::{Address, Bytes, Log, TxKind, U256};
use alloy_sol_types::SolCall;
use mega_system_contracts::keyless_deploy::IKeylessDeploy;
use revm::{
    context::{
        result::{ExecutionResult, ResultAndState},
        ContextTr, TxEnv,
    },
    context_interface::Transaction,
    handler::FrameResult,
    interpreter::{CallOutcome, Gas, Host, InstructionResult, InterpreterResult},
    primitives::KECCAK_EMPTY,
    state::EvmState,
    Database,
};

use crate::{
    constants, mark_frame_result_as_exceeding_limit, merge_evm_state_optional_status,
    ExternalEnvTypes, MegaContext, MegaEvm, MegaHaltReason, MegaSpecId, MegaTransaction,
    TxRuntimeLimit,
};

use super::tx::{calculate_keyless_deploy_address, decode_keyless_tx, recover_signer};

use super::{
    error::{encode_error_result, KeylessDeployError},
    state::SandboxDb,
};

/// Executes a keyless deploy call and returns the frame result.
///
/// Implements Nick's Method contract deployment:
/// 1. Validates the call (no ether transfer)
/// 2. Decodes the pre-EIP-155 transaction from calldata
/// 3. Validates gas limit override against transaction gas limit
/// 4. Recovers the signer and calculates the deploy address
/// 5. Executes contract creation in a sandbox environment
/// 6. Applies only allowed state changes (deployAddress + deploySigner balance)
///
/// # Spam Protection Guarantee
///
/// This function is designed so that once sandbox execution starts and completes
/// (producing either `SandboxOutcome::Success` or `SandboxOutcome::Failure`), the
/// outer transaction **cannot revert**. Execution errors return success with error
/// data encoded in the return value, ensuring the signer is always charged for gas.
///
/// This guarantee depends on this function only being called at `depth == 0`
/// (enforced in `evm/execution.rs`), preventing contracts from wrapping and reverting.
pub fn execute_keyless_deploy_call<DB: alloy_evm::Database, ExtEnvs: ExternalEnvTypes>(
    ctx: &mut MegaContext<DB, ExtEnvs>,
    call_inputs: &revm::interpreter::CallInputs,
    tx_bytes: &Bytes,
    gas_limit_override: U256,
) -> FrameResult {
    // Gas tracking for this call
    let mut gas = Gas::new(call_inputs.gas_limit);
    let return_memory_offset = call_inputs.return_memory_offset.clone();

    // Macros to construct frame results, avoiding closure borrow issues
    macro_rules! make_error {
        ($error:expr) => {
            FrameResult::Call(CallOutcome::new(
                InterpreterResult::new(InstructionResult::Revert, encode_error_result($error), gas),
                return_memory_offset,
            ))
        };
    }

    macro_rules! make_halt {
        () => {
            FrameResult::Call(CallOutcome::new(
                InterpreterResult::new(
                    InstructionResult::OutOfGas,
                    Bytes::new(),
                    Gas::new_spent(gas.limit()),
                ),
                return_memory_offset,
            ))
        };
    }

    macro_rules! make_success {
        ($gas_used:expr, $deployed_address:expr) => {
            FrameResult::Call(CallOutcome::new(
                InterpreterResult::new(
                    InstructionResult::Return,
                    IKeylessDeploy::keylessDeployCall::abi_encode_returns(
                        &IKeylessDeploy::keylessDeployReturn {
                            gasUsed: $gas_used,
                            deployedAddress: $deployed_address,
                            errorData: Bytes::new(),
                        },
                    )
                    .into(),
                    gas,
                ),
                return_memory_offset,
            ))
        };
    }

    // Macro for execution failures (ExecutionReverted, ExecutionHalted, EmptyCodeDeployed).
    // These return success (not revert) so state changes persist and the signer is charged.
    macro_rules! make_execution_failure {
        ($gas_used:expr, $error:expr) => {
            FrameResult::Call(CallOutcome::new(
                InterpreterResult::new(
                    InstructionResult::Return, // Success, not Revert
                    IKeylessDeploy::keylessDeployCall::abi_encode_returns(
                        &IKeylessDeploy::keylessDeployReturn {
                            gasUsed: $gas_used,
                            deployedAddress: Address::ZERO,
                            errorData: encode_error_result($error).to_vec().into(),
                        },
                    )
                    .into(),
                    gas,
                ),
                return_memory_offset,
            ))
        };
    }

    // Step 1: Charge overhead gas
    let cost = constants::rex2::KEYLESS_DEPLOY_OVERHEAD_GAS;
    let has_sufficient_gas = gas.record_cost(cost);
    if !has_sufficient_gas {
        return make_halt!();
    }

    // Rex3+: Record keyless deploy overhead gas as compute gas.
    // The fixed overhead (100K) covers RLP decoding, signature recovery, and state filtering.
    // Sandbox execution gas is tracked separately within the sandbox.
    if ctx.spec.is_enabled(MegaSpecId::REX3) {
        let mut limit = ctx.additional_limit.borrow_mut();
        if !limit.record_compute_gas(cost) {
            let crate::LimitCheck::ExceedsLimit { limit, used, frame_local, .. } =
                limit.compute_gas.check_limit()
            else {
                unreachable!()
            };
            return if frame_local {
                // revert if the limit exceeding is local to the frame
                make_error!(KeylessDeployError::InsufficientComputeGas { limit, used })
            } else {
                // halt if the limit exceeding is global to the transaction
                let mut result = make_halt!();
                mark_frame_result_as_exceeding_limit(
                    &mut result,
                    crate::AdditionalLimit::EXCEEDING_LIMIT_INSTRUCTION_RESULT,
                    Default::default(),
                );
                result
            };
        }
    }

    // Step 2: Check no ether transfer
    if !call_inputs.value.get().is_zero() {
        return make_error!(KeylessDeployError::NoEtherTransfer);
    }

    // Step 3: Decode the keyless transaction
    let keyless_tx = match decode_keyless_tx(tx_bytes) {
        Ok(tx) => tx,
        Err(e) => return make_error!(e),
    };

    // Step 3b: Validate transaction nonce is zero (required for Nick's Method)
    if keyless_tx.nonce() != 0 {
        return make_error!(KeylessDeployError::NonZeroTxNonce { tx_nonce: keyless_tx.nonce() });
    }

    // Step 4: Validate gas limit override
    let tx_gas_limit = keyless_tx.gas_limit();
    let gas_limit_override_u64: u64 = gas_limit_override.try_into().unwrap_or(u64::MAX);
    if gas_limit_override_u64 < tx_gas_limit {
        return make_error!(KeylessDeployError::GasLimitTooLow {
            tx_gas_limit,
            provided_gas_limit: gas_limit_override_u64,
        });
    }

    // Step 5: Recover signer and calculate deploy address
    let deploy_signer = match recover_signer(&keyless_tx) {
        Ok(addr) => addr,
        Err(e) => return make_error!(e),
    };
    let deploy_address = calculate_keyless_deploy_address(deploy_signer);

    // Restrict keyless deploys to signers with nonce <= 1 in parent state.
    let signer_nonce = match get_account_nonce(ctx, deploy_signer) {
        Ok(nonce) => nonce,
        Err(e) => return make_error!(e),
    };
    if signer_nonce > 1 {
        return make_error!(KeylessDeployError::SignerNonceTooHigh { signer_nonce });
    }

    // Step 6: Build the sandbox transaction.
    // The gas limit is set to the gas limit override.
    // The nonce is set to 0.
    // The enveloped_tx is set to the original raw keyless deploy transaction bytes
    let sandbox_tx = {
        let tx = TxEnv {
            caller: deploy_signer,
            kind: TxKind::Create,
            data: keyless_tx.input().clone(),
            value: keyless_tx.value(),
            gas_limit: gas_limit_override_u64,
            gas_price: keyless_tx.effective_gas_price(None),
            nonce: 0,
            ..Default::default()
        };
        let mut mega_tx = MegaTransaction::new(tx);
        mega_tx.enveloped_tx = Some(tx_bytes.clone());
        mega_tx
    };

    // Step 7: Check deploy address doesn't already have code
    {
        let deploy_account = ctx
            .journal_mut()
            .database
            .basic(deploy_address)
            .map_err(|e| KeylessDeployError::InternalError(e.to_string()));
        match deploy_account {
            Ok(Some(info)) if info.code_hash != KECCAK_EMPTY => {
                return make_error!(KeylessDeployError::ContractAlreadyExists);
            }
            Err(e) => return make_error!(e),
            _ => {}
        }
    }

    // Step 8: Execute sandbox and apply state changes
    match execute_keyless_deploy_sandbox(ctx, sandbox_tx) {
        Ok(SandboxOutcome::Success { state, result }) => {
            if let Err(e) = apply_sandbox_state(ctx, state, deploy_signer) {
                return make_error!(e);
            }

            // Verify the deployed address matches the expected address
            if result.deploy_address != deploy_address {
                return make_error!(KeylessDeployError::AddressMismatch);
            }

            // Emit logs from sandbox in parent context
            for log in result.logs {
                ctx.log(log);
            }

            make_success!(result.gas_used, result.deploy_address)
        }
        Ok(SandboxOutcome::Failure { state, error }) => {
            if let Err(e) = apply_sandbox_state(ctx, state, deploy_signer) {
                return make_error!(e);
            }
            // Extract gas_used from the execution error
            let gas_used = match &error {
                KeylessDeployError::ExecutionReverted { gas_used, .. } |
                KeylessDeployError::ExecutionHalted { gas_used, .. } |
                KeylessDeployError::EmptyCodeDeployed { gas_used } => *gas_used,
                _ => 0, // Shouldn't happen for execution errors
            };
            // Return success (not revert) so state changes persist and signer is charged
            make_execution_failure!(gas_used, error)
        }
        Err(e) => make_error!(e),
    }
}

/// Result of sandbox execution.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SandboxResult {
    gas_used: u64,
    deploy_address: Address,
    logs: Vec<Log>,
}

/// Executes the contract creation in a sandbox environment.
///
/// Uses a type-erased `SandboxDb` to prevent infinite type instantiation.
///
/// # Arguments
///
/// * `ctx` - The parent context to execute in
/// * `sandbox_tx` - The transaction to execute, with `enveloped_tx` set to the original raw keyless
///   deploy transaction bytes
pub fn execute_keyless_deploy_sandbox<DB: alloy_evm::Database, ExtEnvs: ExternalEnvTypes>(
    ctx: &mut MegaContext<DB, ExtEnvs>,
    sandbox_tx: MegaTransaction,
) -> Result<SandboxOutcome, KeylessDeployError> {
    let deploy_signer = sandbox_tx.caller();
    let gas_limit = sandbox_tx.gas_limit();
    let gas_price = sandbox_tx.gas_price();
    let value = sandbox_tx.value();

    // Extract values we need BEFORE borrowing the journal
    let mega_spec = ctx.mega_spec();
    let block = ctx.block().clone();
    let chain = ctx.chain().clone();

    // REX4+: Clone Rc references to parent's external envs before `journal_mut()` mutably
    // borrows `ctx`, after which its fields are no longer accessible.
    let shared_external_envs = mega_spec
        .is_enabled(MegaSpecId::REX4)
        .then(|| (Rc::clone(&ctx.salt_env), Rc::clone(&ctx.oracle_env)));

    let journal = ctx.journal_mut();

    // Create type-erased sandbox database with split borrows:
    // - Immutable reference to journal state (for cached accounts)
    // - Mutable reference to underlying database (for cache misses)
    // Override the signer's nonce to 0 for keyless deploy (Nick's Method requires nonce=0)
    let mut sandbox_db = SandboxDb::new(&journal.inner.state, &mut journal.database)
        .with_nonce_override(deploy_signer);

    // Check signer balance
    let signer_account = sandbox_db
        .basic(deploy_signer)
        .map_err(|e| KeylessDeployError::InternalError(e.to_string()))?
        .unwrap_or_default();

    // Ensure signer has enough balance to cover gas cost and value
    let gas_cost = U256::from(gas_limit) * U256::from(gas_price);
    let total_cost = gas_cost.checked_add(value).ok_or(KeylessDeployError::InsufficientBalance)?;
    if signer_account.balance < total_cost {
        return Err(KeylessDeployError::InsufficientBalance);
    }

    // Execute sandbox - using type-erased SandboxDb prevents infinite type instantiation
    if let Some((salt_env, oracle_env)) = shared_external_envs {
        // REX4+: Share parent's salt env and oracle env with the sandbox
        // while keeping a fresh dynamic gas cost cache local to the sandbox.
        let sandbox_ctx = MegaContext::<_, ExtEnvs>::new_with_shared_ext_envs(
            sandbox_db, mega_spec, salt_env, oracle_env,
        )
        .with_block(block)
        .with_chain(chain)
        .with_sandbox_disabled(true);
        let mut sandbox_evm = MegaEvm::new(sandbox_ctx);
        process_sandbox_transact_result(sandbox_evm.transact_raw(sandbox_tx))
    } else {
        // Pre-REX4: Use EmptyExternalEnv for backward compatibility.
        let sandbox_ctx = MegaContext::new(sandbox_db, mega_spec)
            .with_block(block)
            .with_chain(chain)
            .with_sandbox_disabled(true);
        let mut sandbox_evm = MegaEvm::new(sandbox_ctx);
        process_sandbox_transact_result(sandbox_evm.transact_raw(sandbox_tx))
    }
}

/// Outcome of sandbox execution, including state for merging on failure.
#[derive(Debug)]
pub enum SandboxOutcome {
    /// Successful execution with the resulting state and return data.
    Success {
        /// Sandbox state to merge into the parent context.
        state: EvmState,
        /// Execution result details.
        result: SandboxResult,
    },
    /// Failed execution with the resulting state and error.
    Failure {
        /// Sandbox state to merge into the parent context.
        state: EvmState,
        /// Error returned by sandbox execution.
        error: KeylessDeployError,
    },
}

/// Processes the result of sandbox EVM execution into a [`SandboxOutcome`].
///
/// Handles all execution result variants (success, revert, halt) and transact errors.
fn process_sandbox_transact_result(
    result: Result<ResultAndState<MegaHaltReason>, impl core::fmt::Display>,
) -> Result<SandboxOutcome, KeylessDeployError> {
    match result {
        Ok(ResultAndState { result: exec_result, state: sandbox_state }) => match exec_result {
            ExecutionResult::Success { gas_used, output, logs, .. } => {
                if let revm::context::result::Output::Create(bytecode, Some(created_addr)) = output
                {
                    // Empty bytecode is treated as failure to prevent replay attacks.
                    // Without this check, a keyless deploy tx that returns empty code could
                    // be submitted multiple times, draining the signer's funds.
                    if bytecode.is_empty() {
                        return Ok(SandboxOutcome::Failure {
                            state: sandbox_state,
                            error: KeylessDeployError::EmptyCodeDeployed { gas_used },
                        });
                    }
                    Ok(SandboxOutcome::Success {
                        state: sandbox_state,
                        result: SandboxResult { deploy_address: created_addr, gas_used, logs },
                    })
                } else {
                    // Contract creation didn't return an address - should never happen
                    // but we return an error instead of panicking to avoid crashing the node
                    Err(KeylessDeployError::NoContractCreated)
                }
            }
            ExecutionResult::Revert { gas_used, output } => Ok(SandboxOutcome::Failure {
                state: sandbox_state,
                error: KeylessDeployError::ExecutionReverted { gas_used, output },
            }),
            ExecutionResult::Halt { gas_used, reason } => Ok(SandboxOutcome::Failure {
                state: sandbox_state,
                error: KeylessDeployError::ExecutionHalted { gas_used, reason },
            }),
        },
        Err(e) => Err(KeylessDeployError::InternalError(e.to_string())),
    }
}

/// Applies all state changes from sandbox execution to the parent journal.
///
/// Note that we need to merge all accounts into the parent journal, even if they are not
/// touched or created. This is because we need to know which accounts are read but
/// not written to obtain `ReadSet` to facilitate stateless witness generation.
///
/// We do not merge any account status into the parent journal and the coldness of accounts and
/// storage slots are preserved. This is because the changes in the sandbox are treated as a silent
/// change in the database and should not affect the behavior of the current transaction (e.g., gas
/// cost due to coldness) execept that the state itself are different.
///
/// The sandbox execution uses nonce 0 for the signer (Nick's Method), and after the CREATE
/// transaction the nonce becomes 1. This nonce change is intentionally preserved in the parent
/// state to reflect that the signer has been used for a keyless deploy.
fn apply_sandbox_state<DB: alloy_evm::Database, ExtEnvs: ExternalEnvTypes>(
    ctx: &mut MegaContext<DB, ExtEnvs>,
    sandbox_state: EvmState,
    _deploy_signer: Address,
) -> Result<(), KeylessDeployError> {
    let journal = ctx.journal_mut();

    // Merge the sandbox state into the parent journal
    merge_evm_state_optional_status(&mut journal.state, &sandbox_state, false);

    Ok(())
}

/// Reads an account nonce from the journal cache first, then falls back to the backing database.
fn get_account_nonce<DB: alloy_evm::Database, ExtEnvs: ExternalEnvTypes>(
    ctx: &mut MegaContext<DB, ExtEnvs>,
    address: Address,
) -> Result<u64, KeylessDeployError> {
    let journal = ctx.journal_mut();
    if let Some(acc) = journal.state.get(&address) {
        return Ok(acc.info.nonce);
    }
    Ok(journal
        .database
        .basic(address)
        .map_err(|e| KeylessDeployError::InternalError(e.to_string()))?
        .map(|info| info.nonce)
        .unwrap_or(0))
}