Skip to content

Commit

Permalink
precompile addresses refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
rakita committed Oct 19, 2023
1 parent cd67913 commit 253b72a
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 107 deletions.
46 changes: 29 additions & 17 deletions crates/precompile/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,9 @@ use once_cell::race::OnceBox;
pub use revm_primitives as primitives;
pub use revm_primitives::{
precompile::{PrecompileError as Error, *},
Bytes, HashMap,
Address, Bytes, HashMap, B256,
};

pub type Address = [u8; 20];
pub type B256 = [u8; 32];

pub fn calc_linear_cost_u32(len: usize, base: u64, word: u64) -> u64 {
(len as u64 + 32 - 1) / 32 * word + base
}
Expand Down Expand Up @@ -121,17 +118,14 @@ impl SpecId {
BEDROCK | REGOLITH => Self::BERLIN,
}
}

pub const fn enabled(self, spec_id: u8) -> bool {
spec_id >= self as u8
}
}

impl Precompiles {
/// Returns precompiles for Homestead spec.
pub fn homestead() -> &'static Self {
static INSTANCE: OnceBox<Precompiles> = OnceBox::new();
INSTANCE.get_or_init(|| {
let inner = vec![
let mut inner = vec![
secp256k1::ECRECOVER,
hash::SHA256,
hash::RIPEMD160,
Expand All @@ -142,6 +136,7 @@ impl Precompiles {
})
}

/// Returns precompiles for Byzantium spec.
pub fn byzantium() -> &'static Self {
static INSTANCE: OnceBox<Precompiles> = OnceBox::new();
INSTANCE.get_or_init(|| {
Expand All @@ -160,6 +155,7 @@ impl Precompiles {
})
}

/// Returns precompiles for Istanbul spec.
pub fn istanbul() -> &'static Self {
static INSTANCE: OnceBox<Precompiles> = OnceBox::new();
INSTANCE.get_or_init(|| {
Expand All @@ -177,6 +173,7 @@ impl Precompiles {
})
}

/// Returns precompiles for Berlin spec.
pub fn berlin() -> &'static Self {
static INSTANCE: OnceBox<Precompiles> = OnceBox::new();
INSTANCE.get_or_init(|| {
Expand All @@ -190,6 +187,8 @@ impl Precompiles {
})
}

/// Returns precompiles for Cancun spec.
///
/// If `std` feature is not enabled KZG Point Evaluation precompile will not be included.
pub fn cancun() -> &'static Self {
static INSTANCE: OnceBox<Precompiles> = OnceBox::new();
Expand All @@ -215,10 +214,12 @@ impl Precompiles {
})
}

/// Returns the precompiles for the latest spec.
pub fn latest() -> &'static Self {
Self::berlin()
Self::cancun()
}

/// Returns the precompiles for the given spec.
pub fn new(spec: SpecId) -> &'static Self {
match spec {
SpecId::HOMESTEAD => Self::homestead(),
Expand All @@ -230,25 +231,36 @@ impl Precompiles {
}
}

/// Returns an iterator over the precompiles addresses.
#[inline]
pub fn addresses(&self) -> impl IntoIterator<Item = &Address> {
self.inner.iter().map(|i| &i.0)
}

/// Is the given address a precompile.
#[inline]
pub fn contains(&self, address: &Address) -> bool {
self.inner.binary_search_by_key(address, |i| i.0)
self.get(address).is_some()
}

/// Returns the precompile for the given address.
#[inline]
pub fn get(&self, address: &Address) -> Option<Precompile> {
//return None;
self.fun.get(address).cloned()
self.inner
.binary_search_by_key(address, |i| i.0)
.ok()
.map(|i| self.inner[i].1.clone())
}

/// Is the precompiles list empty.
pub fn is_empty(&self) -> bool {
self.fun.len() == 0
self.inner.len() == 0
}

/// Returns the number of precompiles.
pub fn len(&self) -> usize {
self.fun.len()
self.inner.len()
}
}

Expand All @@ -259,7 +271,7 @@ impl Precompiles {
#[inline]
const fn u64_to_address(x: u64) -> Address {
let x = x.to_be_bytes();
[
Address::new([
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, x[0], x[1], x[2], x[3], x[4], x[5], x[6], x[7],
]
}
])
}
19 changes: 9 additions & 10 deletions crates/precompile/src/secp256k1.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{Error, Precompile, PrecompileWithAddress, PrecompileResult, StandardPrecompileFn};
use crate::{Error, Precompile, PrecompileResult, PrecompileWithAddress, StandardPrecompileFn};
use alloc::vec::Vec;
use core::cmp::min;
use revm_primitives::B256;

pub const ECRECOVER: PrecompileWithAddress = PrecompileWithAddress(
crate::u64_to_address(1),
Expand Down Expand Up @@ -56,7 +57,7 @@ mod secp256k1 {
let secp = Secp256k1::new();
let public = secp.recover_ecdsa(&Message::from_digest_slice(&msg[..32])?, &sig)?;

let mut hash = keccak256(&public.serialize_uncompressed()[1..]).0;
let mut hash = keccak256(&public.serialize_uncompressed()[1..]);
hash[..12].fill(0);
Ok(hash)
}
Expand All @@ -71,22 +72,20 @@ fn ec_recover_run(i: &[u8], target_gas: u64) -> PrecompileResult {
let mut input = [0u8; 128];
input[..min(i.len(), 128)].copy_from_slice(&i[..min(i.len(), 128)]);

let mut msg = [0u8; 32];
//let mut msg = [0u8; 32];
let mut sig = [0u8; 65];

msg[0..32].copy_from_slice(&input[0..32]);
sig[0..32].copy_from_slice(&input[64..96]);
sig[32..64].copy_from_slice(&input[96..128]);
let msg = B256::from_slice(&input[0..32]);
//msg[0..32].copy_from_slice(&input[0..32]);
sig[0..64].copy_from_slice(&input[64..128]);

if input[32..63] != [0u8; 31] || !matches!(input[63], 27 | 28) {
return Ok((ECRECOVER_BASE, Vec::new()));
}

sig[64] = input[63] - 27;

let out = secp256k1::ecrecover(&sig, &msg)
.map(Vec::from)
.unwrap_or_default();
let out = secp256k1::ecrecover(&sig, &msg).unwrap_or_default();

Ok((ECRECOVER_BASE, out))
Ok((ECRECOVER_BASE, out.to_vec()))
}
19 changes: 10 additions & 9 deletions crates/revm/src/evm_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::interpreter::{
analysis::to_analysed, gas, return_ok, CallContext, CallInputs, CallScheme, Contract,
CreateInputs, Gas, Host, InstructionResult, Interpreter, SelfDestructResult, Transfer,
};
use crate::journaled_state::{is_precompile, JournalCheckpoint, JournaledState};
use crate::journaled_state::{JournalCheckpoint, JournaledState};
use crate::primitives::{
keccak256, Address, AnalysisKind, Bytecode, Bytes, EVMError, EVMResult, Env,
InvalidTransaction, Log, Output, Spec, SpecId::*, TransactTo, B256, U256,
Expand Down Expand Up @@ -649,19 +649,20 @@ impl<'a, GSPEC: Spec + 'static, DB: Database> EVMImpl<'a, GSPEC, DB> {
}

/// Call precompile contract
fn call_precompile(&mut self, inputs: &CallInputs, mut gas: Gas) -> CallResult {
fn call_precompile(
&mut self,
precompile: Precompile,
inputs: &CallInputs,
mut gas: Gas,
) -> CallResult {
let input_data = &inputs.input;
let contract = inputs.contract;

let precompile = self
.data
.precompiles
.get(&contract)
.expect("Check for precompile should be already done");
let out = match precompile {
Precompile::Standard(fun) => fun(input_data, gas.limit()),
Precompile::Env(fun) => fun(input_data, gas.limit(), self.env()),
};

match out {
Ok((gas_used, data)) => {
if !crate::USE_GAS || gas.record_cost(gas_used) {
Expand Down Expand Up @@ -769,8 +770,8 @@ impl<'a, GSPEC: Spec + 'static, DB: Database> EVMImpl<'a, GSPEC, DB> {
Err(e) => return e,
};

let ret = if is_precompile(&inputs.contract, self.data.precompiles.len()) {
self.call_precompile(inputs, prepared_call.gas)
let ret = if let Some(precompile) = self.data.precompiles.get(&inputs.contract) {
self.call_precompile(precompile, inputs, prepared_call.gas)
} else if !prepared_call.contract.bytecode.is_empty() {
// Create interpreter and execute subcall
let (exit_reason, bytes, gas) = self.run_interpreter(
Expand Down
94 changes: 24 additions & 70 deletions crates/revm/src/journaled_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::primitives::{
use alloc::vec::Vec;
use core::mem;
use revm_interpreter::primitives::SpecId;
use revm_precompile::Precompiles;

#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand All @@ -24,31 +25,42 @@ pub struct JournaledState {
/// Spec is needed for two things SpuriousDragon's `EIP-161 State clear`,
/// and for Cancun's `EIP-6780: SELFDESTRUCT in same transaction`
pub spec: SpecId,
/// It is assumed that precompiles start from 0x1 address and span next N addresses.
/// we are using that assumption here
pub num_of_precompiles: usize,
/// Precompiles addresses are used to check if loaded address
/// should be considered cold or hot loaded. It is cloned from
/// EVMData to be directly accessed from JournaledState.
///
/// Note that addresses are sorted.
pub precompile_addresses: Vec<Address>,
}

impl JournaledState {
/// Create new JournaledState.
///
/// num_of_precompiles is used to determine how many precompiles are there.
/// Assumption is that number of N first addresses are precompiles (excluding 0x00..00)
/// precompile_addresses is used to determine if address is precompile or not.
///
/// Note: This function will journal state after Spurious Dragon fork.
/// And will not take into account if account is not existing or empty.
pub fn new(num_of_precompiles: usize, spec: SpecId) -> JournaledState {
pub fn new(
num_of_precompiles: usize,
spec: SpecId,
precompile_addresses: Vec<Address>,
) -> JournaledState {
Self {
state: HashMap::new(),
transient_storage: TransientStorage::default(),
logs: Vec::new(),
journal: vec![vec![]],
depth: 0,
spec,
num_of_precompiles,
precompile_addresses,
}
}

/// Is address precompile
pub fn is_precompile(&self, address: &Address) -> bool {
self.precompile_addresses.binary_search(address).is_ok()
}

/// Return reference to state.
pub fn state(&mut self) -> &mut State {
&mut self.state
Expand Down Expand Up @@ -199,7 +211,8 @@ impl JournaledState {
let last_journal = self.journal.last_mut().unwrap();

// check if it is possible to create this account.
if Self::check_account_collision(address, account, self.num_of_precompiles) {

if Self::check_account_collision(address, account, self.is_precompile(&address)) {
self.checkpoint_revert(checkpoint);
return Err(InstructionResult::CreateCollision);
}
Expand Down Expand Up @@ -256,7 +269,7 @@ impl JournaledState {
pub fn check_account_collision(
address: Address,
account: &Account,
num_of_precompiles: usize,
is_precompile: bool,
) -> bool {
// Check collision. Bytecode needs to be empty.
if account.info.code_hash != KECCAK_EMPTY {
Expand All @@ -268,7 +281,7 @@ impl JournaledState {
}

// Check collision. New account address is precompile.
if is_precompile(&address, num_of_precompiles) {
if is_precompile {
return true;
}

Expand Down Expand Up @@ -540,7 +553,7 @@ impl JournaledState {
.push(JournalEntry::AccountLoaded { address });

// precompiles are warm loaded so we need to take that into account
let is_cold = !is_precompile(&address, self.num_of_precompiles);
let is_cold = self.is_precompile(&address);

(vac.insert(account), is_cold)
}
Expand Down Expand Up @@ -780,62 +793,3 @@ pub struct JournalCheckpoint {
log_i: usize,
journal_i: usize,
}

/// Check if address is precompile by having assumption
/// that precompiles are in range of 1 to N.
#[inline]
pub fn is_precompile(address: &Address, num_of_precompiles: usize) -> bool {
if !address[..18].iter().all(|i| *i == 0) {
return false;
}
let num = u16::from_be_bytes([address[18], address[19]]);
num.wrapping_sub(1) < num_of_precompiles as u16
}

#[cfg(test)]
mod test {
use super::*;

#[test]
fn test_is_precompile() {
assert!(
!is_precompile(
&Address::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]),
3
),
"Zero is not precompile"
);

assert!(
!is_precompile(
&Address::new([1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9]),
3
),
"0x100..0 is not precompile"
);

assert!(
!is_precompile(
&Address::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4]),
3
),
"0x000..4 is not precompile"
);

assert!(
is_precompile(
&Address::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]),
3
),
"0x00..01 is precompile"
);

assert!(
is_precompile(
&Address::new([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3]),
3
),
"0x000..3 is precompile"
);
}
}
2 changes: 1 addition & 1 deletion crates/revm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub use db::{Database, DatabaseCommit, DatabaseRef, InMemoryDB};
pub use evm::{evm_inner, new, EVM};
pub use evm_context::EVMData;
pub use evm_impl::{EVMImpl, Transact, CALL_STACK_LIMIT};
pub use journaled_state::{is_precompile, JournalCheckpoint, JournalEntry, JournaledState};
pub use journaled_state::{JournalCheckpoint, JournalEntry, JournaledState};

// reexport `revm_precompiles`
#[doc(inline)]
Expand Down

0 comments on commit 253b72a

Please sign in to comment.