use auto_impl::auto_impl;
use context::{Cfg, LocalContextTr};
use context_interface::{ContextTr, JournalTr};
use interpreter::{CallInputs, Gas, InstructionResult, InterpreterResult};
use precompile::{PrecompileOutput, PrecompileSpecId, PrecompileStatus, Precompiles};
use primitives::{hardfork::SpecId, Address, Bytes};
use std::{
boxed::Box,
string::{String, ToString},
};
#[auto_impl(&mut, Box)]
pub trait PrecompileProvider<CTX: ContextTr> {
type Output;
fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool;
fn run(
&mut self,
context: &mut CTX,
inputs: &CallInputs,
) -> Result<Option<Self::Output>, String>;
fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>>;
fn contains(&self, address: &Address) -> bool;
}
#[derive(Debug)]
pub struct EthPrecompiles {
pub precompiles: &'static Precompiles,
pub spec: SpecId,
}
impl EthPrecompiles {
pub fn new(spec: SpecId) -> Self {
Self {
precompiles: Precompiles::new(PrecompileSpecId::from_spec_id(spec)),
spec,
}
}
pub fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
Box::new(self.precompiles.addresses().cloned())
}
pub fn contains(&self, address: &Address) -> bool {
self.precompiles.contains(address)
}
}
impl Clone for EthPrecompiles {
fn clone(&self) -> Self {
Self {
precompiles: self.precompiles,
spec: self.spec,
}
}
}
pub fn precompile_output_to_interpreter_result(
output: PrecompileOutput,
gas_limit: u64,
) -> InterpreterResult {
let bytes = if output.status.is_success_or_revert() {
output.bytes
} else {
Bytes::new()
};
let mut result = InterpreterResult {
result: InstructionResult::Return,
gas: Gas::new_with_regular_gas_and_reservoir(gas_limit, output.reservoir),
output: bytes,
};
result.gas.set_state_gas_spent(output.state_gas_used);
result.gas.record_refund(output.gas_refunded);
if output.status.is_success_or_revert() {
let _ = result.gas.record_regular_cost(output.gas_used);
} else {
result.gas.spend_all();
}
result.result = match output.status {
PrecompileStatus::Success => InstructionResult::Return,
PrecompileStatus::Revert => InstructionResult::Revert,
PrecompileStatus::Halt(halt_reason) => {
if halt_reason.is_oog() {
InstructionResult::PrecompileOOG
} else {
InstructionResult::PrecompileError
}
}
};
result
}
impl<CTX: ContextTr> PrecompileProvider<CTX> for EthPrecompiles {
type Output = InterpreterResult;
fn set_spec(&mut self, spec: <CTX::Cfg as Cfg>::Spec) -> bool {
let spec = spec.into();
if spec == self.spec {
return false;
}
self.precompiles = Precompiles::new(PrecompileSpecId::from_spec_id(spec));
self.spec = spec;
true
}
fn run(
&mut self,
context: &mut CTX,
inputs: &CallInputs,
) -> Result<Option<InterpreterResult>, String> {
let Some(precompile) = self.precompiles.get(&inputs.bytecode_address) else {
return Ok(None);
};
let output = precompile
.execute(
&inputs.input.as_bytes(context),
inputs.gas_limit,
inputs.reservoir,
)
.map_err(|e| e.to_string())?;
if let Some(halt_reason) = output.halt_reason() {
if !halt_reason.is_oog() && context.journal().depth() == 1 {
context
.local_mut()
.set_precompile_error_context(halt_reason.to_string());
}
}
let result = precompile_output_to_interpreter_result(output, inputs.gas_limit);
Ok(Some(result))
}
fn warm_addresses(&self) -> Box<impl Iterator<Item = Address>> {
Self::warm_addresses(self)
}
fn contains(&self, address: &Address) -> bool {
Self::contains(self, address)
}
}