Skip to content

Commit f8ff6b3

Browse files
authored
feat: separate initial checks (#486)
* wip separate initial checks * tests passing, consolidate some checks * test * feat: add transfer test * temp * Update crates/interpreter/src/gas/calc.rs * / Initial gas that is deducted from the transaction gas limit. * fmt
1 parent c3b0312 commit f8ff6b3

File tree

12 files changed

+493
-367
lines changed

12 files changed

+493
-367
lines changed

bins/revm-test/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ name = "analysis"
1515

1616
[[bin]]
1717
name = "snailtracer"
18+
19+
[[bin]]
20+
name = "transfer"

bins/revm-test/src/bin/analysis.rs

-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ fn main() {
2525
);
2626
//evm.env.tx.data = Bytes::from(hex::decode("30627b7c").unwrap());
2727
evm.env.tx.data = Bytes::from(hex::decode("8035F0CE").unwrap());
28-
evm.env.cfg.perf_all_precompiles_have_balance = true;
2928

3029
let bytecode_raw = Bytecode::new_raw(contract_data.clone());
3130
let bytecode_checked = Bytecode::new_raw(contract_data.clone()).to_checked();

bins/revm-test/src/bin/transfer.rs

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use revm::{
2+
db::BenchmarkDB,
3+
primitives::{Bytecode, TransactTo, U256},
4+
};
5+
use std::time::{Duration, Instant};
6+
extern crate alloc;
7+
8+
fn main() {
9+
// BenchmarkDB is dummy state that implements Database trait.
10+
let mut evm = revm::new();
11+
12+
// execution globals block hash/gas_limit/coinbase/timestamp..
13+
evm.env.tx.caller = "0x0000000000000000000000000000000000000001"
14+
.parse()
15+
.unwrap();
16+
evm.env.tx.value = U256::from(10);
17+
evm.env.tx.transact_to = TransactTo::Call(
18+
"0x0000000000000000000000000000000000000000"
19+
.parse()
20+
.unwrap(),
21+
);
22+
//evm.env.tx.data = Bytes::from(hex::decode("30627b7c").unwrap());
23+
24+
evm.database(BenchmarkDB::new_bytecode(Bytecode::new()));
25+
26+
// Microbenchmark
27+
let bench_options = microbench::Options::default().time(Duration::from_secs(1));
28+
29+
microbench::bench(&bench_options, "Simple value transfer", || {
30+
let _ = evm.transact().unwrap();
31+
});
32+
33+
let time = Instant::now();
34+
for _ in 0..10000 {
35+
let _ = evm.transact().unwrap();
36+
}
37+
let elapsed = time.elapsed();
38+
println!("10k runs in {:?}", elapsed.as_nanos() / 10_000);
39+
}

crates/interpreter/src/gas/calc.rs

+48
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
use super::constants::*;
2+
use crate::alloc::vec::Vec;
23
use crate::{
34
inner_models::SelfDestructResult,
45
primitives::Spec,
56
primitives::{SpecId::*, U256},
67
};
8+
use revm_primitives::{Bytes, B160};
79

810
#[allow(clippy::collapsible_else_if)]
911
pub fn sstore_refund<SPEC: Spec>(original: U256, current: U256, new: U256) -> i64 {
@@ -325,3 +327,49 @@ pub fn memory_gas(a: usize) -> u64 {
325327
.saturating_mul(a)
326328
.saturating_add(a.saturating_mul(a) / 512)
327329
}
330+
331+
/// Initial gas that is deducted for transaction to be included.
332+
/// Initial gas contains initial stipend gas, gas for access list and input data.
333+
pub fn initial_tx_gas<SPEC: Spec>(
334+
input: &Bytes,
335+
is_create: bool,
336+
access_list: &[(B160, Vec<U256>)],
337+
) -> u64 {
338+
let mut initial_gas = 0;
339+
let zero_data_len = input.iter().filter(|v| **v == 0).count() as u64;
340+
let non_zero_data_len = input.len() as u64 - zero_data_len;
341+
342+
// initdate stipend
343+
initial_gas += zero_data_len * TRANSACTION_ZERO_DATA;
344+
// EIP-2028: Transaction data gas cost reduction
345+
initial_gas += non_zero_data_len * if SPEC::enabled(ISTANBUL) { 16 } else { 68 };
346+
347+
// get number of access list account and storages.
348+
if SPEC::enabled(BERLIN) {
349+
let accessed_slots = access_list
350+
.iter()
351+
.fold(0, |slot_count, (_, slots)| slot_count + slots.len() as u64);
352+
initial_gas += access_list.len() as u64 * ACCESS_LIST_ADDRESS;
353+
initial_gas += accessed_slots * ACCESS_LIST_STORAGE_KEY;
354+
}
355+
356+
// base stipend
357+
initial_gas += if is_create {
358+
if SPEC::enabled(HOMESTEAD) {
359+
// EIP-2: Homestead Hard-fork Changes
360+
53000
361+
} else {
362+
21000
363+
}
364+
} else {
365+
21000
366+
};
367+
368+
// EIP-3860: Limit and meter initcode
369+
// Initcode stipend for bytecode analysis
370+
if SPEC::enabled(SHANGHAI) && is_create {
371+
initial_gas += initcode_cost(input.len() as u64)
372+
}
373+
374+
initial_gas
375+
}

crates/primitives/src/constants.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/// Interpreter stack limit
2+
pub const STACK_LIMIT: u64 = 1024;
3+
/// EVM call stack limit
4+
pub const CALL_STACK_LIMIT: u64 = 1024;
5+
6+
/// EIP-170: Contract code size limit
7+
/// By default limit is 0x6000 (~25kb)
8+
pub const MAX_CODE_SIZE: usize = 0x6000;
9+
10+
/// EIP-3860: Limit and meter initcode
11+
///
12+
/// Limit of maximum initcode size is 2 * MAX_CODE_SIZE
13+
pub const MAX_INITCODE_SIZE: usize = 2 * MAX_CODE_SIZE;

crates/primitives/src/env.rs

+160-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
use crate::{alloc::vec::Vec, SpecId, B160, B256, U256};
1+
use crate::{
2+
alloc::vec::Vec, Account, EVMError, InvalidTransaction, Spec, SpecId, B160, B256, KECCAK_EMPTY,
3+
MAX_INITCODE_SIZE, U256,
4+
};
25
use bytes::Bytes;
3-
use core::cmp::min;
6+
use core::cmp::{min, Ordering};
47

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

123+
impl CfgEnv {
124+
#[cfg(feature = "optional_eip3607")]
125+
pub fn is_eip3607_disabled(&self) -> bool {
126+
self.disable_eip3607
127+
}
128+
129+
#[cfg(not(feature = "optional_eip3607"))]
130+
pub fn is_eip3607_disabled(&self) -> bool {
131+
false
132+
}
133+
134+
#[cfg(feature = "optional_balance_check")]
135+
pub fn is_balance_check_disabled(&self) -> bool {
136+
self.disable_balance_check
137+
}
138+
139+
#[cfg(not(feature = "optional_balance_check"))]
140+
pub fn is_balance_check_disabled(&self) -> bool {
141+
false
142+
}
143+
144+
#[cfg(feature = "optional_gas_refund")]
145+
pub fn is_gas_refund_disabled(&self) -> bool {
146+
self.disable_gas_refund
147+
}
148+
149+
#[cfg(not(feature = "optional_gas_refund"))]
150+
pub fn is_gas_refund_disabled(&self) -> bool {
151+
false
152+
}
153+
154+
#[cfg(feature = "optional_no_base_fee")]
155+
pub fn is_base_fee_check_disabled(&self) -> bool {
156+
self.disable_base_fee
157+
}
158+
159+
#[cfg(not(feature = "optional_no_base_fee"))]
160+
pub fn is_base_fee_check_disabled(&self) -> bool {
161+
false
162+
}
163+
164+
#[cfg(feature = "optional_block_gas_limit")]
165+
pub fn is_block_gas_limit_disabled(&self) -> bool {
166+
self.disable_block_gas_limit
167+
}
168+
169+
#[cfg(not(feature = "optional_block_gas_limit"))]
170+
pub fn is_block_gas_limit_disabled(&self) -> bool {
171+
false
172+
}
173+
}
174+
124175
#[derive(Clone, Default, Debug, Eq, PartialEq)]
125176
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
126177
pub enum AnalysisKind {
@@ -135,7 +186,6 @@ impl Default for CfgEnv {
135186
CfgEnv {
136187
chain_id: U256::from(1),
137188
spec_id: SpecId::LATEST,
138-
perf_all_precompiles_have_balance: false,
139189
perf_analyse_created_bytecodes: Default::default(),
140190
limit_contract_code_size: None,
141191
#[cfg(feature = "memory_limit")]
@@ -196,4 +246,107 @@ impl Env {
196246
)
197247
}
198248
}
249+
250+
/// Validate ENV data of the block.
251+
///
252+
/// It can be skip if you are sure that PREVRANDAO is set.
253+
#[inline]
254+
pub fn validate_block_env<SPEC: Spec, T>(&self) -> Result<(), EVMError<T>> {
255+
// Prevrandao is required for merge
256+
if SPEC::enabled(SpecId::MERGE) && self.block.prevrandao.is_none() {
257+
return Err(EVMError::PrevrandaoNotSet);
258+
}
259+
Ok(())
260+
}
261+
262+
/// Validate transaction data that is set inside ENV and return error if something is wrong.
263+
///
264+
/// Return inital spend gas (Gas needed to execute transaction).
265+
#[inline]
266+
pub fn validate_tx<SPEC: Spec>(&self) -> Result<(), InvalidTransaction> {
267+
let gas_limit = self.tx.gas_limit;
268+
let effective_gas_price = self.effective_gas_price();
269+
let is_create = self.tx.transact_to.is_create();
270+
271+
// BASEFEE tx check
272+
if SPEC::enabled(SpecId::LONDON) {
273+
if let Some(priority_fee) = self.tx.gas_priority_fee {
274+
if priority_fee > self.tx.gas_price {
275+
// or gas_max_fee for eip1559
276+
return Err(InvalidTransaction::GasMaxFeeGreaterThanPriorityFee);
277+
}
278+
}
279+
let basefee = self.block.basefee;
280+
281+
// check minimal cost against basefee
282+
if !self.cfg.is_base_fee_check_disabled() && effective_gas_price < basefee {
283+
return Err(InvalidTransaction::GasPriceLessThanBasefee);
284+
}
285+
}
286+
287+
// Check if gas_limit is more than block_gas_limit
288+
if !self.cfg.is_block_gas_limit_disabled() && U256::from(gas_limit) > self.block.gas_limit {
289+
return Err(InvalidTransaction::CallerGasLimitMoreThanBlock);
290+
}
291+
292+
// EIP-3860: Limit and meter initcode
293+
if SPEC::enabled(SpecId::SHANGHAI) && is_create && self.tx.data.len() > MAX_INITCODE_SIZE {
294+
return Err(InvalidTransaction::CreateInitcodeSizeLimit);
295+
}
296+
297+
// Check if the transaction's chain id is correct
298+
if let Some(tx_chain_id) = self.tx.chain_id {
299+
if U256::from(tx_chain_id) != self.cfg.chain_id {
300+
return Err(InvalidTransaction::InvalidChainId);
301+
}
302+
}
303+
304+
// Check if the transaction's chain id is correct
305+
if !SPEC::enabled(SpecId::BERLIN) && !self.tx.access_list.is_empty() {
306+
return Err(InvalidTransaction::AccessListNotSupported);
307+
}
308+
309+
Ok(())
310+
}
311+
312+
/// Validate transaction agains state.
313+
#[inline]
314+
pub fn validate_tx_agains_state(&self, account: &Account) -> Result<(), InvalidTransaction> {
315+
// EIP-3607: Reject transactions from senders with deployed code
316+
// This EIP is introduced after london but there was no collision in past
317+
// so we can leave it enabled always
318+
if !self.cfg.is_eip3607_disabled() && account.info.code_hash != KECCAK_EMPTY {
319+
return Err(InvalidTransaction::RejectCallerWithCode);
320+
}
321+
322+
// Check that the transaction's nonce is correct
323+
if let Some(tx) = self.tx.nonce {
324+
let state = account.info.nonce;
325+
match tx.cmp(&state) {
326+
Ordering::Greater => {
327+
return Err(InvalidTransaction::NonceTooHigh { tx, state });
328+
}
329+
Ordering::Less => {
330+
return Err(InvalidTransaction::NonceTooLow { tx, state });
331+
}
332+
_ => {}
333+
}
334+
}
335+
336+
let balance_check = U256::from(self.tx.gas_limit)
337+
.checked_mul(self.tx.gas_price)
338+
.and_then(|gas_cost| gas_cost.checked_add(self.tx.value))
339+
.ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
340+
341+
// Check if account has enough balance for gas_limit*gas_price and value transfer.
342+
// Transfer will be done inside `*_inner` functions.
343+
if !self.cfg.is_balance_check_disabled() && balance_check > account.info.balance {
344+
return Err(InvalidTransaction::LackOfFundForMaxFee {
345+
fee: self.tx.gas_limit,
346+
balance: account.info.balance,
347+
});
348+
}
349+
350+
Ok(())
351+
}
199352
}

crates/primitives/src/lib.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
pub mod bits;
44
pub mod bytecode;
5+
pub mod constants;
56
pub mod db;
67
pub mod env;
78
pub mod log;
@@ -26,8 +27,9 @@ pub type Hash = B256;
2627

2728
pub use bitvec;
2829
pub use bytecode::*;
30+
pub use constants::*;
2931
pub use env::*;
30-
pub use hashbrown::{hash_map, HashMap};
32+
pub use hashbrown::{hash_map, hash_set, HashMap, HashSet};
3133
pub use log::Log;
3234
pub use precompile::*;
3335
pub use result::*;

crates/primitives/src/result.rs

+9-6
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use alloc::vec::Vec;
33
use bytes::Bytes;
44
use ruint::aliases::U256;
55

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

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

9292
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
9393
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
94-
pub enum EVMError<DB> {
94+
pub enum EVMError<DBError> {
9595
Transaction(InvalidTransaction),
9696
/// REVM specific and related to environment.
9797
PrevrandaoNotSet,
98-
Database(DB),
98+
Database(DBError),
9999
}
100100

101-
impl<DB> From<InvalidTransaction> for EVMError<DB> {
101+
impl<DBError> From<InvalidTransaction> for EVMError<DBError> {
102102
fn from(invalid: InvalidTransaction) -> Self {
103103
EVMError::Transaction(invalid)
104104
}
@@ -114,8 +114,8 @@ pub enum InvalidTransaction {
114114
/// EIP-3607 Reject transactions from senders with deployed code
115115
RejectCallerWithCode,
116116
/// Transaction account does not have enough amount of ether to cover transferred value and gas_limit*gas_price.
117-
LackOfFundForGasLimit {
118-
gas_limit: U256,
117+
LackOfFundForMaxFee {
118+
fee: u64,
119119
balance: U256,
120120
},
121121
/// Overflow payment in transaction.
@@ -133,6 +133,9 @@ pub enum InvalidTransaction {
133133
/// EIP-3860: Limit and meter initcode
134134
CreateInitcodeSizeLimit,
135135
InvalidChainId,
136+
/// Access list is not supported is not supported
137+
/// for blocks before Berlin hardfork.
138+
AccessListNotSupported,
136139
}
137140

138141
/// When transaction return successfully without halts.

0 commit comments

Comments
 (0)