use revm::{
context::result::{ExecutionResult, HaltReason, Output},
primitives::Bytes,
};
use std::ops::Range;
pub(crate) struct SearchRange(Range<u64>);
impl core::fmt::Display for SearchRange {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}..{}", self.0.start, self.0.end)
}
}
impl From<Range<u64>> for SearchRange {
fn from(value: Range<u64>) -> Self {
Self(value)
}
}
impl From<SearchRange> for Range<u64> {
fn from(value: SearchRange) -> Self {
value.0
}
}
impl SearchRange {
pub(crate) const fn new(start: u64, end: u64) -> Self {
Self(start..end)
}
pub(crate) const fn midpoint(&self) -> u64 {
(self.max() + self.min()) / 2
}
pub(crate) const fn min(&self) -> u64 {
self.0.start
}
pub(crate) const fn set_min(&mut self, min: u64) {
self.0.start = min;
}
pub(crate) const fn maybe_raise_min(&mut self, candidate: u64) {
if candidate > self.min() {
self.set_min(candidate);
}
}
pub(crate) const fn max(&self) -> u64 {
self.0.end
}
pub(crate) const fn set_max(&mut self, max: u64) {
self.0.end = max;
}
pub(crate) const fn maybe_lower_max(&mut self, candidate: u64) {
if candidate < self.max() {
self.set_max(candidate);
}
}
pub(crate) const fn ratio(&self) -> f64 {
(self.max() - self.min()) as f64 / self.max() as f64
}
pub(crate) fn contains(&self, value: u64) -> bool {
self.0.contains(&value)
}
pub(crate) const fn size(&self) -> u64 {
self.0.end - self.0.start
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EstimationResult {
Success {
limit: u64,
refund: u64,
gas_used: u64,
output: Output,
},
Revert {
limit: u64,
reason: Bytes,
gas_used: u64,
},
Halt {
limit: u64,
reason: HaltReason,
gas_used: u64,
},
}
impl core::fmt::Display for EstimationResult {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Success { limit, refund, gas_used, .. } => {
write!(
f,
"Success {{ gas_limit: {limit}, refund: {refund}, gas_used: {gas_used}, .. }}",
)
}
Self::Revert { limit, gas_used, .. } => {
write!(f, "Revert {{ gas_limit: {limit}, gas_used: {gas_used}, .. }}")
}
Self::Halt { limit, reason, gas_used } => {
write!(f, "Halt {{ gas_limit: {limit}, reason: {reason:?}, gas_used: {gas_used} }}")
}
}
}
}
impl EstimationResult {
pub fn from_limit_and_execution_result(limit: u64, value: &ExecutionResult) -> Self {
match value {
ExecutionResult::Success { gas_used, output, gas_refunded, .. } => Self::Success {
limit,
refund: *gas_refunded,
gas_used: *gas_used,
output: output.clone(),
},
ExecutionResult::Revert { output, gas_used } => {
Self::Revert { limit, reason: output.clone(), gas_used: *gas_used }
}
ExecutionResult::Halt { reason, gas_used } => {
Self::Halt { limit, reason: reason.clone(), gas_used: *gas_used }
}
}
}
pub const fn basic_transfer_success(estimation: u64) -> Self {
Self::Success {
limit: estimation,
refund: 0,
gas_used: estimation,
output: Output::Call(Bytes::new()),
}
}
pub const fn is_success(&self) -> bool {
matches!(self, Self::Success { .. })
}
pub const fn is_failure(&self) -> bool {
!self.is_success()
}
pub const fn limit(&self) -> u64 {
match self {
Self::Success { limit, .. } => *limit,
Self::Revert { limit, .. } => *limit,
Self::Halt { limit, .. } => *limit,
}
}
pub const fn gas_refunded(&self) -> Option<u64> {
match self {
Self::Success { refund, .. } => Some(*refund),
_ => None,
}
}
pub const fn output(&self) -> Option<&Output> {
match self {
Self::Success { output, .. } => Some(output),
_ => None,
}
}
pub const fn gas_used(&self) -> u64 {
match self {
Self::Success { gas_used, .. } => *gas_used,
Self::Revert { gas_used, .. } => *gas_used,
Self::Halt { gas_used, .. } => *gas_used,
}
}
pub const fn is_revert(&self) -> bool {
matches!(self, Self::Revert { .. })
}
pub const fn revert_reason(&self) -> Option<&Bytes> {
match self {
Self::Revert { reason, .. } => Some(reason),
_ => None,
}
}
pub const fn is_halt(&self) -> bool {
matches!(self, Self::Halt { .. })
}
pub const fn halt_reason(&self) -> Option<&HaltReason> {
match self {
Self::Halt { reason, .. } => Some(reason),
_ => None,
}
}
pub(crate) fn adjust_binary_search_range(&self, range: &mut SearchRange) -> Result<(), Self> {
match self {
Self::Success { limit, .. } => range.set_max(*limit),
Self::Revert { limit, .. } => range.set_min(*limit),
Self::Halt { limit, reason, .. } => {
if matches!(reason, HaltReason::OutOfGas(_) | HaltReason::InvalidFEOpcode) {
range.set_min(*limit);
} else {
return Err(self.clone());
}
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_search_range() {
let mut range = SearchRange::new(100, 200);
assert_eq!(range.min(), 100);
assert_eq!(range.max(), 200);
assert_eq!(range.size(), 100);
assert_eq!(range.ratio(), 0.5);
assert_eq!(range.midpoint(), 150);
assert!(range.contains(150));
range.maybe_raise_min(100);
assert_eq!(range.min(), 100);
range.maybe_raise_min(125);
assert_eq!(range.min(), 125);
assert_eq!(range.midpoint(), 162);
range.maybe_lower_max(180);
assert_eq!(range.max(), 180);
assert_eq!(range.midpoint(), 152);
range.maybe_raise_min(100);
assert_eq!(range.min(), 125);
range.maybe_lower_max(200);
assert_eq!(range.max(), 180);
}
}