use crate::{
solidity::{
codec::Reader,
modifier::FunctionModifier,
revert::{MayRevert, RevertReason},
},
EvmResult,
};
use fp_evm::{Log, PrecompileHandle};
pub trait PrecompileHandleExt: PrecompileHandle {
fn record_db_read<Runtime: pallet_evm::Config>(
&mut self,
data_max_encoded_len: usize,
) -> Result<(), evm::ExitError>;
fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult;
fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult;
fn check_function_modifier(&self, modifier: FunctionModifier) -> MayRevert;
fn read_u32_selector(&self) -> MayRevert<u32>;
fn read_after_selector(&self) -> MayRevert<Reader>;
}
impl<T: PrecompileHandle> PrecompileHandleExt for T {
fn record_db_read<Runtime: pallet_evm::Config>(
&mut self,
data_max_encoded_len: usize,
) -> Result<(), evm::ExitError> {
self.record_cost(crate::prelude::RuntimeHelper::<Runtime>::db_read_gas_cost())?;
self.record_external_cost(None, Some(data_max_encoded_len as u64), None)
}
fn record_log_costs_manual(&mut self, topics: usize, data_len: usize) -> EvmResult {
self.record_cost(crate::evm::costs::log_costs(topics, data_len)?)?;
Ok(())
}
fn record_log_costs(&mut self, logs: &[&Log]) -> EvmResult {
for log in logs {
self.record_log_costs_manual(log.topics.len(), log.data.len())?;
}
Ok(())
}
fn check_function_modifier(&self, modifier: FunctionModifier) -> MayRevert {
crate::solidity::modifier::check_function_modifier(
self.context(),
self.is_static(),
modifier,
)
}
fn read_u32_selector(&self) -> MayRevert<u32> {
crate::solidity::codec::selector(self.input())
.ok_or(RevertReason::read_out_of_bounds("selector").into())
}
fn read_after_selector(&self) -> MayRevert<Reader> {
Reader::new_skip_selector(self.input())
}
}
environmental::environmental!(EVM_CONTEXT: trait PrecompileHandle);
pub fn using_precompile_handle<'a, R, F: FnOnce() -> R>(
precompile_handle: &'a mut dyn PrecompileHandle,
mutator: F,
) -> R {
unsafe {
EVM_CONTEXT::using(
core::mem::transmute::<&'a mut dyn PrecompileHandle, &'static mut dyn PrecompileHandle>(
precompile_handle,
),
mutator,
)
}
}
pub fn with_precompile_handle<R, F: FnOnce(&mut dyn PrecompileHandle) -> R>(f: F) -> Option<R> {
EVM_CONTEXT::with(|precompile_handle| f(precompile_handle))
}
#[cfg(test)]
mod tests {
use super::*;
struct MockPrecompileHandle;
impl PrecompileHandle for MockPrecompileHandle {
fn call(
&mut self,
_: sp_core::H160,
_: Option<evm::Transfer>,
_: Vec<u8>,
_: Option<u64>,
_: bool,
_: &evm::Context,
) -> (evm::ExitReason, Vec<u8>) {
unimplemented!()
}
fn record_cost(&mut self, _: u64) -> Result<(), evm::ExitError> {
unimplemented!()
}
fn remaining_gas(&self) -> u64 {
unimplemented!()
}
fn log(
&mut self,
_: sp_core::H160,
_: Vec<sp_core::H256>,
_: Vec<u8>,
) -> Result<(), evm::ExitError> {
unimplemented!()
}
fn code_address(&self) -> sp_core::H160 {
unimplemented!()
}
fn input(&self) -> &[u8] {
unimplemented!()
}
fn context(&self) -> &evm::Context {
unimplemented!()
}
fn is_static(&self) -> bool {
true
}
fn gas_limit(&self) -> Option<u64> {
unimplemented!()
}
fn record_external_cost(
&mut self,
_ref_time: Option<u64>,
_proof_size: Option<u64>,
_storage_growth: Option<u64>,
) -> Result<(), fp_evm::ExitError> {
Ok(())
}
fn refund_external_cost(&mut self, _ref_time: Option<u64>, _proof_size: Option<u64>) {}
}
#[test]
fn with_precompile_handle_without_context() {
assert_eq!(with_precompile_handle(|_| {}), None);
}
#[test]
fn with_precompile_handle_with_context() {
let mut precompile_handle = MockPrecompileHandle;
assert_eq!(
using_precompile_handle(&mut precompile_handle, || with_precompile_handle(
|handle| handle.is_static()
)),
Some(true)
);
}
}