use alloy_evm::Database;
use alloy_primitives::Bytes;
use alloy_sol_types::{SolCall, SolError};
use revm::{
handler::FrameResult,
interpreter::{CallInputs, CallOutcome, Gas, InstructionResult, InterpreterResult},
};
use crate::{
sandbox::execute_keyless_deploy_call, ExternalEnvTypes, IKeylessDeploy, IMegaAccessControl,
IMegaLimitControl, IOracle, MegaContext, MegaSpecId, OracleEnv, ACCESS_CONTROL_ADDRESS,
DISABLED_BY_PARENT_REVERT_DATA, KEYLESS_DEPLOY_ADDRESS, LIMIT_CONTROL_ADDRESS,
ORACLE_CONTRACT_ADDRESS,
};
pub type InterceptResult = Option<FrameResult>;
alloy_sol_types::sol! {
error NonZeroTransfer();
}
pub trait SystemContractInterceptor<DB: Database, ExtEnvs: ExternalEnvTypes> {
fn intercept(
ctx: &mut MegaContext<DB, ExtEnvs>,
call_inputs: &CallInputs,
depth: usize,
) -> InterceptResult;
}
pub fn dispatch_system_contract_interceptors<DB: Database, ExtEnvs: ExternalEnvTypes>(
ctx: &mut MegaContext<DB, ExtEnvs>,
call_inputs: &CallInputs,
depth: usize,
) -> InterceptResult {
let spec = ctx.spec;
if spec.is_enabled(OracleHintInterceptor::ACTIVATION_SPEC) {
OracleHintInterceptor::intercept(ctx, call_inputs, depth);
}
if spec.is_enabled(KeylessDeployInterceptor::ACTIVATION_SPEC) {
if let Some(result) = KeylessDeployInterceptor::intercept(ctx, call_inputs, depth) {
return Some(result);
}
}
if spec.is_enabled(AccessControlInterceptor::ACTIVATION_SPEC) {
if let Some(result) = AccessControlInterceptor::intercept(ctx, call_inputs, depth) {
return Some(result);
}
}
if spec.is_enabled(LimitControlInterceptor::ACTIVATION_SPEC) {
if let Some(result) = LimitControlInterceptor::intercept(ctx, call_inputs, depth) {
return Some(result);
}
}
None
}
fn reject_non_zero_transfer(call_inputs: &CallInputs) -> InterceptResult {
if call_inputs.transfer_value().is_some_and(|value| !value.is_zero()) {
return Some(FrameResult::Call(CallOutcome::new(
InterpreterResult::new(
InstructionResult::Revert,
Bytes::copy_from_slice(&NonZeroTransfer::SELECTOR),
Gas::new(call_inputs.gas_limit),
),
call_inputs.return_memory_offset.clone(),
)));
}
None
}
#[derive(Debug)]
pub struct OracleHintInterceptor;
impl OracleHintInterceptor {
pub const ACTIVATION_SPEC: MegaSpecId = MegaSpecId::REX2;
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> SystemContractInterceptor<DB, ExtEnvs>
for OracleHintInterceptor
{
fn intercept(
ctx: &mut MegaContext<DB, ExtEnvs>,
call_inputs: &CallInputs,
_depth: usize,
) -> InterceptResult {
if call_inputs.target_address != ORACLE_CONTRACT_ADDRESS {
return None;
}
let input_bytes = call_inputs.input.bytes(ctx);
if let Ok(call) = IOracle::sendHintCall::abi_decode(&input_bytes) {
ctx.oracle_env.borrow().on_hint(call_inputs.caller, call.topic, call.data);
}
None
}
}
#[derive(Debug)]
pub struct KeylessDeployInterceptor;
impl KeylessDeployInterceptor {
pub const ACTIVATION_SPEC: MegaSpecId = MegaSpecId::REX2;
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> SystemContractInterceptor<DB, ExtEnvs>
for KeylessDeployInterceptor
{
fn intercept(
ctx: &mut MegaContext<DB, ExtEnvs>,
call_inputs: &CallInputs,
depth: usize,
) -> InterceptResult {
if ctx.is_sandbox_disabled() || depth != 0 {
return None;
}
if call_inputs.target_address != KEYLESS_DEPLOY_ADDRESS {
return None;
}
let input_bytes = call_inputs.input.bytes(ctx);
let call = IKeylessDeploy::keylessDeployCall::abi_decode(&input_bytes).ok()?;
Some(execute_keyless_deploy_call(
ctx,
call_inputs,
&call.keylessDeploymentTransaction,
call.gasLimitOverride,
))
}
}
#[derive(Debug)]
pub struct AccessControlInterceptor;
impl AccessControlInterceptor {
pub const ACTIVATION_SPEC: MegaSpecId = MegaSpecId::REX4;
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> SystemContractInterceptor<DB, ExtEnvs>
for AccessControlInterceptor
{
fn intercept(
ctx: &mut MegaContext<DB, ExtEnvs>,
call_inputs: &CallInputs,
depth: usize,
) -> InterceptResult {
if call_inputs.target_address != ACCESS_CONTROL_ADDRESS {
return None;
}
let input_bytes = call_inputs.input.bytes(ctx);
let caller_journal_depth = depth;
if IMegaAccessControl::disableVolatileDataAccessCall::abi_decode(&input_bytes).is_ok() {
if let Some(result) = reject_non_zero_transfer(call_inputs) {
return Some(result);
}
ctx.volatile_data_tracker.borrow_mut().disable_access(caller_journal_depth);
return Some(FrameResult::Call(CallOutcome::new(
InterpreterResult::new(
InstructionResult::Return,
Bytes::new(),
Gas::new(call_inputs.gas_limit),
),
call_inputs.return_memory_offset.clone(),
)));
}
if IMegaAccessControl::enableVolatileDataAccessCall::abi_decode(&input_bytes).is_ok() {
if let Some(result) = reject_non_zero_transfer(call_inputs) {
return Some(result);
}
let success =
ctx.volatile_data_tracker.borrow_mut().enable_access(caller_journal_depth);
let result = if success {
FrameResult::Call(CallOutcome::new(
InterpreterResult::new(
InstructionResult::Return,
Bytes::new(),
Gas::new(call_inputs.gas_limit),
),
call_inputs.return_memory_offset.clone(),
))
} else {
FrameResult::Call(CallOutcome::new(
InterpreterResult::new(
InstructionResult::Revert,
Bytes::copy_from_slice(&DISABLED_BY_PARENT_REVERT_DATA),
Gas::new(call_inputs.gas_limit),
),
call_inputs.return_memory_offset.clone(),
))
};
return Some(result);
}
if IMegaAccessControl::isVolatileDataAccessDisabledCall::abi_decode(&input_bytes).is_ok() {
if let Some(result) = reject_non_zero_transfer(call_inputs) {
return Some(result);
}
let disabled =
ctx.volatile_data_tracker.borrow().volatile_access_disabled(caller_journal_depth);
let output =
IMegaAccessControl::isVolatileDataAccessDisabledCall::abi_encode_returns(&disabled);
return Some(FrameResult::Call(CallOutcome::new(
InterpreterResult::new(
InstructionResult::Return,
Bytes::from(output),
Gas::new(call_inputs.gas_limit),
),
call_inputs.return_memory_offset.clone(),
)));
}
None
}
}
#[derive(Debug)]
pub struct LimitControlInterceptor;
impl LimitControlInterceptor {
pub const ACTIVATION_SPEC: MegaSpecId = MegaSpecId::REX4;
}
impl<DB: Database, ExtEnvs: ExternalEnvTypes> SystemContractInterceptor<DB, ExtEnvs>
for LimitControlInterceptor
{
fn intercept(
ctx: &mut MegaContext<DB, ExtEnvs>,
call_inputs: &CallInputs,
_depth: usize,
) -> InterceptResult {
if call_inputs.target_address != LIMIT_CONTROL_ADDRESS {
return None;
}
let input_bytes = call_inputs.input.bytes(ctx);
if IMegaLimitControl::remainingComputeGasCall::abi_decode(&input_bytes).is_ok() {
if let Some(result) = reject_non_zero_transfer(call_inputs) {
return Some(result);
}
let remaining = ctx.additional_limit.borrow().current_call_remaining_compute_gas();
let output = IMegaLimitControl::remainingComputeGasCall::abi_encode_returns(&remaining);
return Some(FrameResult::Call(CallOutcome::new(
InterpreterResult::new(
InstructionResult::Return,
Bytes::from(output),
Gas::new(call_inputs.gas_limit),
),
call_inputs.return_memory_offset.clone(),
)));
}
None
}
}