Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: separate initial checks #486

Merged
merged 9 commits into from
May 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions bins/revm-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,6 @@ name = "analysis"

[[bin]]
name = "snailtracer"

[[bin]]
name = "transfer"
1 change: 0 additions & 1 deletion bins/revm-test/src/bin/analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ fn main() {
);
//evm.env.tx.data = Bytes::from(hex::decode("30627b7c").unwrap());
evm.env.tx.data = Bytes::from(hex::decode("8035F0CE").unwrap());
evm.env.cfg.perf_all_precompiles_have_balance = true;

let bytecode_raw = Bytecode::new_raw(contract_data.clone());
let bytecode_checked = Bytecode::new_raw(contract_data.clone()).to_checked();
Expand Down
39 changes: 39 additions & 0 deletions bins/revm-test/src/bin/transfer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
use revm::{
db::BenchmarkDB,
primitives::{Bytecode, TransactTo, U256},
};
use std::time::{Duration, Instant};
extern crate alloc;

fn main() {
// BenchmarkDB is dummy state that implements Database trait.
let mut evm = revm::new();

// execution globals block hash/gas_limit/coinbase/timestamp..
evm.env.tx.caller = "0x0000000000000000000000000000000000000001"
.parse()
.unwrap();
evm.env.tx.value = U256::from(10);
evm.env.tx.transact_to = TransactTo::Call(
"0x0000000000000000000000000000000000000000"
.parse()
.unwrap(),
);
//evm.env.tx.data = Bytes::from(hex::decode("30627b7c").unwrap());

evm.database(BenchmarkDB::new_bytecode(Bytecode::new()));

// Microbenchmark
let bench_options = microbench::Options::default().time(Duration::from_secs(1));

microbench::bench(&bench_options, "Simple value transfer", || {
let _ = evm.transact().unwrap();
});

let time = Instant::now();
for _ in 0..10000 {
let _ = evm.transact().unwrap();
}
let elapsed = time.elapsed();
println!("10k runs in {:?}", elapsed.as_nanos() / 10_000);
}
48 changes: 48 additions & 0 deletions crates/interpreter/src/gas/calc.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use super::constants::*;
use crate::alloc::vec::Vec;
use crate::{
inner_models::SelfDestructResult,
primitives::Spec,
primitives::{SpecId::*, U256},
};
use revm_primitives::{Bytes, B160};

#[allow(clippy::collapsible_else_if)]
pub fn sstore_refund<SPEC: Spec>(original: U256, current: U256, new: U256) -> i64 {
Expand Down Expand Up @@ -325,3 +327,49 @@ pub fn memory_gas(a: usize) -> u64 {
.saturating_mul(a)
.saturating_add(a.saturating_mul(a) / 512)
}

/// Initial gas that is deducted for transaction to be included.
/// Initial gas contains initial stipend gas, gas for access list and input data.
pub fn initial_tx_gas<SPEC: Spec>(
input: &Bytes,
is_create: bool,
access_list: &[(B160, Vec<U256>)],
) -> u64 {
let mut initial_gas = 0;
let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
let non_zero_data_len = input.len() as u64 - zero_data_len;

// initdate stipend
initial_gas += zero_data_len * TRANSACTION_ZERO_DATA;
// EIP-2028: Transaction data gas cost reduction
initial_gas += non_zero_data_len * if SPEC::enabled(ISTANBUL) { 16 } else { 68 };

// get number of access list account and storages.
if SPEC::enabled(BERLIN) {
let accessed_slots = access_list
.iter()
.fold(0, |slot_count, (_, slots)| slot_count + slots.len() as u64);
initial_gas += access_list.len() as u64 * ACCESS_LIST_ADDRESS;
initial_gas += accessed_slots * ACCESS_LIST_STORAGE_KEY;
}

// base stipend
initial_gas += if is_create {
if SPEC::enabled(HOMESTEAD) {
// EIP-2: Homestead Hard-fork Changes
53000
} else {
21000
}
} else {
21000
};

// EIP-3860: Limit and meter initcode
// Initcode stipend for bytecode analysis
if SPEC::enabled(SHANGHAI) && is_create {
initial_gas += initcode_cost(input.len() as u64)
}

initial_gas
}
13 changes: 13 additions & 0 deletions crates/primitives/src/constants.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/// Interpreter stack limit
pub const STACK_LIMIT: u64 = 1024;
/// EVM call stack limit
pub const CALL_STACK_LIMIT: u64 = 1024;

/// EIP-170: Contract code size limit
/// By default limit is 0x6000 (~25kb)
pub const MAX_CODE_SIZE: usize = 0x6000;

/// EIP-3860: Limit and meter initcode
///
/// Limit of maximum initcode size is 2 * MAX_CODE_SIZE
pub const MAX_INITCODE_SIZE: usize = 2 * MAX_CODE_SIZE;
167 changes: 160 additions & 7 deletions crates/primitives/src/env.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use crate::{alloc::vec::Vec, SpecId, B160, B256, U256};
use crate::{
alloc::vec::Vec, Account, EVMError, InvalidTransaction, Spec, SpecId, B160, B256, KECCAK_EMPTY,
MAX_INITCODE_SIZE, U256,
};
use bytes::Bytes;
use core::cmp::min;
use core::cmp::{min, Ordering};

#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand Down Expand Up @@ -78,10 +81,6 @@ pub enum CreateScheme {
pub struct CfgEnv {
pub chain_id: U256,
pub spec_id: SpecId,
/// If all precompiles have some balance we can skip initially fetching them from the database.
/// This is is not really needed on mainnet, and defaults to false, but in most cases it is
/// safe to be set to `true`, depending on the chain.
pub perf_all_precompiles_have_balance: bool,
/// Bytecode that is created with CREATE/CREATE2 is by default analysed and jumptable is created.
/// This is very benefitial for testing and speeds up execution of that bytecode if called multiple times.
///
Expand Down Expand Up @@ -121,6 +120,58 @@ pub struct CfgEnv {
pub disable_base_fee: bool,
}

impl CfgEnv {
#[cfg(feature = "optional_eip3607")]
pub fn is_eip3607_disabled(&self) -> bool {
self.disable_eip3607
}

#[cfg(not(feature = "optional_eip3607"))]
pub fn is_eip3607_disabled(&self) -> bool {
false
}

#[cfg(feature = "optional_balance_check")]
pub fn is_balance_check_disabled(&self) -> bool {
self.disable_balance_check
}

#[cfg(not(feature = "optional_balance_check"))]
pub fn is_balance_check_disabled(&self) -> bool {
false
}

#[cfg(feature = "optional_gas_refund")]
pub fn is_gas_refund_disabled(&self) -> bool {
self.disable_gas_refund
}

#[cfg(not(feature = "optional_gas_refund"))]
pub fn is_gas_refund_disabled(&self) -> bool {
false
}

#[cfg(feature = "optional_no_base_fee")]
pub fn is_base_fee_check_disabled(&self) -> bool {
self.disable_base_fee
}

#[cfg(not(feature = "optional_no_base_fee"))]
pub fn is_base_fee_check_disabled(&self) -> bool {
false
}

#[cfg(feature = "optional_block_gas_limit")]
pub fn is_block_gas_limit_disabled(&self) -> bool {
self.disable_block_gas_limit
}

#[cfg(not(feature = "optional_block_gas_limit"))]
pub fn is_block_gas_limit_disabled(&self) -> bool {
false
}
}

#[derive(Clone, Default, Debug, Eq, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum AnalysisKind {
Expand All @@ -135,7 +186,6 @@ impl Default for CfgEnv {
CfgEnv {
chain_id: U256::from(1),
spec_id: SpecId::LATEST,
perf_all_precompiles_have_balance: false,
perf_analyse_created_bytecodes: Default::default(),
limit_contract_code_size: None,
#[cfg(feature = "memory_limit")]
Expand Down Expand Up @@ -196,4 +246,107 @@ impl Env {
)
}
}

/// Validate ENV data of the block.
///
/// It can be skip if you are sure that PREVRANDAO is set.
#[inline]
pub fn validate_block_env<SPEC: Spec, T>(&self) -> Result<(), EVMError<T>> {
// Prevrandao is required for merge
if SPEC::enabled(SpecId::MERGE) && self.block.prevrandao.is_none() {
return Err(EVMError::PrevrandaoNotSet);
}
Ok(())
}

/// Validate transaction data that is set inside ENV and return error if something is wrong.
///
/// Return inital spend gas (Gas needed to execute transaction).
#[inline]
pub fn validate_tx<SPEC: Spec>(&self) -> Result<(), InvalidTransaction> {
let gas_limit = self.tx.gas_limit;
let effective_gas_price = self.effective_gas_price();
let is_create = self.tx.transact_to.is_create();

// BASEFEE tx check
if SPEC::enabled(SpecId::LONDON) {
if let Some(priority_fee) = self.tx.gas_priority_fee {
if priority_fee > self.tx.gas_price {
// or gas_max_fee for eip1559
return Err(InvalidTransaction::GasMaxFeeGreaterThanPriorityFee);
}
}
let basefee = self.block.basefee;

// check minimal cost against basefee
if !self.cfg.is_base_fee_check_disabled() && effective_gas_price < basefee {
return Err(InvalidTransaction::GasPriceLessThanBasefee);
}
}

// Check if gas_limit is more than block_gas_limit
if !self.cfg.is_block_gas_limit_disabled() && U256::from(gas_limit) > self.block.gas_limit {
return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
}

// EIP-3860: Limit and meter initcode
if SPEC::enabled(SpecId::SHANGHAI) && is_create && self.tx.data.len() > MAX_INITCODE_SIZE {
return Err(InvalidTransaction::CreateInitcodeSizeLimit);
}

// Check if the transaction's chain id is correct
if let Some(tx_chain_id) = self.tx.chain_id {
if U256::from(tx_chain_id) != self.cfg.chain_id {
return Err(InvalidTransaction::InvalidChainId);
}
}

// Check if the transaction's chain id is correct
if !SPEC::enabled(SpecId::BERLIN) && !self.tx.access_list.is_empty() {
return Err(InvalidTransaction::AccessListNotSupported);
}

Ok(())
}

/// Validate transaction agains state.
#[inline]
pub fn validate_tx_agains_state(&self, account: &Account) -> Result<(), InvalidTransaction> {
// EIP-3607: Reject transactions from senders with deployed code
// This EIP is introduced after london but there was no collision in past
// so we can leave it enabled always
if !self.cfg.is_eip3607_disabled() && account.info.code_hash != KECCAK_EMPTY {
return Err(InvalidTransaction::RejectCallerWithCode);
}

// Check that the transaction's nonce is correct
if let Some(tx) = self.tx.nonce {
let state = account.info.nonce;
match tx.cmp(&state) {
Ordering::Greater => {
return Err(InvalidTransaction::NonceTooHigh { tx, state });
}
Ordering::Less => {
return Err(InvalidTransaction::NonceTooLow { tx, state });
}
_ => {}
}
}

let balance_check = U256::from(self.tx.gas_limit)
.checked_mul(self.tx.gas_price)
.and_then(|gas_cost| gas_cost.checked_add(self.tx.value))
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;

// Check if account has enough balance for gas_limit*gas_price and value transfer.
// Transfer will be done inside `*_inner` functions.
if !self.cfg.is_balance_check_disabled() && balance_check > account.info.balance {
return Err(InvalidTransaction::LackOfFundForMaxFee {
fee: self.tx.gas_limit,
balance: account.info.balance,
});
}

Ok(())
}
}
4 changes: 3 additions & 1 deletion crates/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

pub mod bits;
pub mod bytecode;
pub mod constants;
pub mod db;
pub mod env;
pub mod log;
Expand All @@ -26,8 +27,9 @@ pub type Hash = B256;

pub use bitvec;
pub use bytecode::*;
pub use constants::*;
pub use env::*;
pub use hashbrown::{hash_map, HashMap};
pub use hashbrown::{hash_map, hash_set, HashMap, HashSet};
pub use log::Log;
pub use precompile::*;
pub use result::*;
Expand Down
15 changes: 9 additions & 6 deletions crates/primitives/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use alloc::vec::Vec;
use bytes::Bytes;
use ruint::aliases::U256;

pub type EVMResult<DB> = core::result::Result<ResultAndState, EVMError<DB>>;
pub type EVMResult<DBError> = core::result::Result<ResultAndState, EVMError<DBError>>;

#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
Expand Down Expand Up @@ -91,14 +91,14 @@ impl Output {

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum EVMError<DB> {
pub enum EVMError<DBError> {
Transaction(InvalidTransaction),
/// REVM specific and related to environment.
PrevrandaoNotSet,
Database(DB),
Database(DBError),
}

impl<DB> From<InvalidTransaction> for EVMError<DB> {
impl<DBError> From<InvalidTransaction> for EVMError<DBError> {
fn from(invalid: InvalidTransaction) -> Self {
EVMError::Transaction(invalid)
}
Expand All @@ -114,8 +114,8 @@ pub enum InvalidTransaction {
/// EIP-3607 Reject transactions from senders with deployed code
RejectCallerWithCode,
/// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
LackOfFundForGasLimit {
gas_limit: U256,
LackOfFundForMaxFee {
fee: u64,
balance: U256,
},
/// Overflow payment in transaction.
Expand All @@ -133,6 +133,9 @@ pub enum InvalidTransaction {
/// EIP-3860: Limit and meter initcode
CreateInitcodeSizeLimit,
InvalidChainId,
/// Access list is not supported is not supported
/// for blocks before Berlin hardfork.
AccessListNotSupported,
}

/// When transaction return successfully without halts.
Expand Down
Loading