#[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,
};
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 {
let mut gas = Gas::new(call_inputs.gas_limit);
let return_memory_offset = call_inputs.return_memory_offset.clone();
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_rules! make_execution_failure {
($gas_used:expr, $error:expr) => {
FrameResult::Call(CallOutcome::new(
InterpreterResult::new(
InstructionResult::Return, 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,
))
};
}
let cost = constants::rex2::KEYLESS_DEPLOY_OVERHEAD_GAS;
let has_sufficient_gas = gas.record_cost(cost);
if !has_sufficient_gas {
return make_halt!();
}
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 {
make_error!(KeylessDeployError::InsufficientComputeGas { limit, used })
} else {
let mut result = make_halt!();
mark_frame_result_as_exceeding_limit(
&mut result,
crate::AdditionalLimit::EXCEEDING_LIMIT_INSTRUCTION_RESULT,
Default::default(),
);
result
};
}
}
if !call_inputs.value.get().is_zero() {
return make_error!(KeylessDeployError::NoEtherTransfer);
}
let keyless_tx = match decode_keyless_tx(tx_bytes) {
Ok(tx) => tx,
Err(e) => return make_error!(e),
};
if keyless_tx.nonce() != 0 {
return make_error!(KeylessDeployError::NonZeroTxNonce { tx_nonce: keyless_tx.nonce() });
}
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,
});
}
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);
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 });
}
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
};
{
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),
_ => {}
}
}
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);
}
if result.deploy_address != deploy_address {
return make_error!(KeylessDeployError::AddressMismatch);
}
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);
}
let gas_used = match &error {
KeylessDeployError::ExecutionReverted { gas_used, .. } |
KeylessDeployError::ExecutionHalted { gas_used, .. } |
KeylessDeployError::EmptyCodeDeployed { gas_used } => *gas_used,
_ => 0, };
make_execution_failure!(gas_used, error)
}
Err(e) => make_error!(e),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SandboxResult {
gas_used: u64,
deploy_address: Address,
logs: Vec<Log>,
}
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();
let mega_spec = ctx.mega_spec();
let block = ctx.block().clone();
let chain = ctx.chain().clone();
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();
let mut sandbox_db = SandboxDb::new(&journal.inner.state, &mut journal.database)
.with_nonce_override(deploy_signer);
let signer_account = sandbox_db
.basic(deploy_signer)
.map_err(|e| KeylessDeployError::InternalError(e.to_string()))?
.unwrap_or_default();
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);
}
if let Some((salt_env, oracle_env)) = shared_external_envs {
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 {
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))
}
}
#[derive(Debug)]
pub enum SandboxOutcome {
Success {
state: EvmState,
result: SandboxResult,
},
Failure {
state: EvmState,
error: KeylessDeployError,
},
}
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
{
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 {
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())),
}
}
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_evm_state_optional_status(&mut journal.state, &sandbox_state, false);
Ok(())
}
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))
}