1use crate::evm_env::{block_env_from_block_header, tx_evm_env_from_tx};
2use alloy_eips::BlockNumHash;
3use alloy_evm::EvmEnv;
4use alloy_primitives::{address, TxHash, TxKind};
5use alloy_primitives::{Address, Bytes, B256};
6use alloy_rpc_types::Log;
7use alloy_rpc_types::{AccessList, AccessListItem, Header, Transaction};
8use alloy_rpc_types_trace::geth::AccountState;
9use eyre::eyre;
10use kabu_types_blockchain::GethStateUpdate;
11use revm::context::result::{EVMError, ExecutionResult, HaltReason, Output, ResultAndState};
12use revm::context::{ContextTr, TxEnv};
13use revm::database::CacheDB;
14use revm::handler::EvmTr;
15use revm::state::{Account, EvmState};
16use revm::ExecuteEvm;
17use revm::{Context, Database, DatabaseCommit, DatabaseRef, MainBuilder, MainContext};
18use std::collections::BTreeMap;
19use thiserror::Error;
20use tracing::{debug, error};
21
22pub static COINBASE: Address = address!("1f9090aaE28b8a3dCeaDf281B0F12828e676c326");
23
24#[derive(Debug, Error)]
25pub enum EvmError {
26 #[error("Evm transact error with err={0}")]
27 TransactError(String),
28 #[error("Evm transact commit error with err={0}")]
29 TransactCommitError(String),
30 #[error("Reverted with reason={0}, gas_used={1}")]
31 Reverted(String, u64),
32 #[error("Halted with halt_reason={0:?}, gas_used={1}")]
33 Halted(HaltReason, u64),
34}
35
36fn parse_execution_result(execution_result: ResultAndState) -> eyre::Result<(Vec<u8>, u64, EvmState)> {
37 let ResultAndState { result, state } = execution_result;
38 let gas_used = result.gas_used();
39
40 match result {
41 ExecutionResult::Success { output: Output::Call(value), .. } => Ok((value.to_vec(), gas_used, state)),
42 ExecutionResult::Success { output: Output::Create(_bytes, _address), .. } => Ok((vec![], gas_used, state)),
43 ExecutionResult::Revert { output, gas_used } => Err(eyre!(EvmError::Reverted(revert_bytes_to_string(&output), gas_used))),
44 ExecutionResult::Halt { reason, gas_used } => Err(eyre!(EvmError::Halted(reason, gas_used))),
45 }
46}
47
48pub fn evm_call<DB>(state_db: DB, env: EvmEnv, transact_to: Address, call_data_vec: Vec<u8>) -> eyre::Result<(Vec<u8>, u64, EvmState)>
50where
51 DB: DatabaseRef,
52 <DB as DatabaseRef>::Error: std::fmt::Debug,
53{
54 let mut env = env;
55 env.cfg_env.disable_base_fee = true;
56 env.cfg_env.disable_balance_check = true;
57 env.cfg_env.disable_block_gas_limit = true;
58 env.cfg_env.disable_nonce_check = true;
59
60 let mut evm = Context::mainnet().with_db(CacheDB::new(state_db)).with_block(env.block_env).with_cfg(env.cfg_env).build_mainnet();
61
62 let tx_env = TxEnv { kind: TxKind::Call(transact_to), data: Bytes::from(call_data_vec), ..TxEnv::default() };
63
64 let result_and_state = evm.transact(tx_env).map_err(|e| EvmError::TransactError(format!("{e:?}")))?;
65
66 parse_execution_result(result_and_state)
67}
68
69pub fn evm_transact<DB, EVM>(evm: &mut EVM, tx_env: TxEnv) -> eyre::Result<(Vec<u8>, u64, EvmState)>
70where
71 DB: Database + DatabaseCommit,
72 <DB as Database>::Error: std::fmt::Debug,
73 EVM: EvmTr<Context: ContextTr<Db = DB>>
74 + ExecuteEvm<Tx = TxEnv, ExecutionResult = ExecutionResult, State = EvmState, Error = EVMError<<DB as Database>::Error>>,
75 <EVM as ExecuteEvm>::Error: std::fmt::Debug,
76 <EVM as ExecuteEvm>::State: Clone,
77{
78 let result = evm.transact(tx_env).map_err(|e| EvmError::TransactError(format!("{e:?}")))?;
79
80 evm.ctx_mut().db_mut().commit(result.state.clone());
81 parse_execution_result(ResultAndState { result: result.result, state: result.state })
82}
83
84pub fn evm_call_tx<DB>(state_db: DB, env: &EvmEnv, tx_env: TxEnv) -> eyre::Result<(Vec<u8>, u64, EvmState)>
86where
87 DB: DatabaseRef,
88 <DB as DatabaseRef>::Error: std::fmt::Debug,
89{
90 let mut evm = Context::mainnet().with_db(CacheDB::new(state_db)).with_block(env.block_env.clone()).build_mainnet();
91
92 let result_and_state = evm.transact(tx_env).map_err(|e| EvmError::TransactError(format!("{e:?}")))?;
93
94 parse_execution_result(result_and_state)
95}
96
97pub fn evm_access_list<DB>(state_db: DB, env: &EvmEnv, tx_env: TxEnv) -> eyre::Result<(u64, AccessList)>
98where
99 DB: DatabaseRef,
100 <DB as DatabaseRef>::Error: std::fmt::Debug,
101{
102 let mut env = env.clone();
103 env.block_env.beneficiary = COINBASE;
104
105 let mut evm = Context::mainnet().with_db(CacheDB::new(state_db)).with_block(env.block_env).with_cfg(env.cfg_env).build_mainnet();
106
107 let ref_tx = evm.transact(tx_env).map_err(|e| EvmError::TransactError(format!("{e:?}")))?;
108 let execution_result = ref_tx.result;
109 match execution_result {
110 ExecutionResult::Success { output, gas_used, reason, .. } => {
111 debug!(gas_used, ?reason, ?output, "AccessList");
112 let mut acl = AccessList::default();
113
114 for (addr, acc) in ref_tx.state {
115 let storage_keys: Vec<B256> = acc.storage.keys().map(|x| (*x).into()).collect();
116 acl.0.push(AccessListItem { address: addr, storage_keys });
117 }
118
119 Ok((gas_used, acl))
120 }
121 ExecutionResult::Revert { output, gas_used } => Err(eyre!(EvmError::Reverted(revert_bytes_to_string(&output), gas_used))),
122 ExecutionResult::Halt { reason, gas_used } => Err(eyre!(EvmError::Halted(reason, gas_used))),
123 }
124}
125
126pub fn evm_call_tx_in_block<DB, T: Into<Transaction>>(tx: T, state_db: DB, header: &Header) -> eyre::Result<ResultAndState>
127where
128 DB: DatabaseRef,
129{
130 let mut evm = Context::mainnet().with_db(CacheDB::new(state_db)).with_block(block_env_from_block_header(header)).build_mainnet();
131
132 let tx_env = tx_evm_env_from_tx(tx);
133 evm.transact(tx_env).map_err(|_| eyre!("TRANSACT_ERROR"))
134}
135
136pub fn convert_evm_result_to_rpc(
137 result: ResultAndState,
138 tx_hash: TxHash,
139 block_num_hash: BlockNumHash,
140 block_timestamp: u64,
141) -> eyre::Result<(Vec<Log>, GethStateUpdate)> {
142 let logs = match result.result {
143 ExecutionResult::Success { logs, .. } => logs
144 .into_iter()
145 .enumerate()
146 .map(|(log_index, l)| Log {
147 inner: l.clone(),
148 block_hash: Some(block_num_hash.hash),
149 block_number: Some(block_num_hash.number),
150 transaction_hash: Some(tx_hash),
151 transaction_index: Some(0u64),
152 log_index: Some(log_index as u64),
153 removed: false,
154 block_timestamp: Some(block_timestamp),
155 })
156 .collect(),
157 _ => return Err(eyre!("EXECUTION_REVERTED")),
158 };
159
160 let mut state_update: GethStateUpdate = GethStateUpdate::default();
161
162 for (address, account) in result.state.into_iter() {
163 let (address, account): (Address, Account) = (address, account);
164 let storage: BTreeMap<B256, B256> = account.storage.into_iter().map(|(k, v)| (k.into(), v.present_value.into())).collect();
165
166 let account_state = AccountState {
167 balance: Some(account.info.balance),
168 code: account.info.code.map(|x| x.bytes()),
169 nonce: Some(account.info.nonce),
170 storage,
171 };
172 state_update.insert(address, account_state);
173 }
174
175 Ok((logs, state_update))
176}
177
178pub fn revert_bytes_to_string(bytes: &Bytes) -> String {
179 if bytes.len() < 4 {
180 return format!("{bytes:?}");
181 }
182 let error_data = &bytes[4..];
183
184 match String::from_utf8(error_data.to_vec()) {
185 Ok(s) => s.replace(char::from(0), "").trim().to_string(),
186 Err(_) => format!("{bytes:?}"),
187 }
188}