use std::path::PathBuf;
use alloy_primitives::{Address, Bytes, TxHash};
use edb_common::{
fork_and_prepare, relax_evm_constraints, Cache, CachePath, EdbCache, EdbCachePath, EdbContext,
ForkResult,
};
use eyre::Result;
use foundry_block_explorers::{contract::ContractCreationData, Client};
use revm::{
context::{Cfg, ContextTr},
database::CacheDB,
primitives::KECCAK_EMPTY,
state::Bytecode,
Database, DatabaseCommit, DatabaseRef, InspectEvm, MainBuilder,
};
use tracing::{debug, error};
use crate::{next_etherscan_api_key, Artifact, TweakInspector};
pub struct CodeTweaker<'a, DB>
where
DB: Database + DatabaseCommit + DatabaseRef + Clone,
<CacheDB<DB> as Database>::Error: Clone,
<DB as Database>::Error: Clone,
{
ctx: &'a mut EdbContext<DB>,
rpc_url: String,
etherscan_api_key: Option<String>,
}
impl<'a, DB> CodeTweaker<'a, DB>
where
DB: Database + DatabaseCommit + DatabaseRef + Clone,
<CacheDB<DB> as Database>::Error: Clone,
<DB as Database>::Error: Clone,
{
pub fn new(
ctx: &'a mut EdbContext<DB>,
rpc_url: String,
etherscan_api_key: Option<String>,
) -> Self {
Self { ctx, rpc_url, etherscan_api_key }
}
pub async fn tweak(
&mut self,
addr: &Address,
artifact: &Artifact,
recompiled_artifact: &Artifact,
quick: bool,
) -> Result<()> {
let tweaked_code =
self.get_tweaked_code(addr, artifact, recompiled_artifact, quick).await?;
if tweaked_code.is_empty() {
error!(addr=?addr, quick=?quick, "Tweaked code is empty");
}
let db = self.ctx.db_mut();
let mut info = db
.basic(*addr)
.map_err(|e| eyre::eyre!("Failed to get account info for {}: {}", addr, e))?
.unwrap_or_default();
info.code_hash = KECCAK_EMPTY;
info.code = Some(Bytecode::new_raw(tweaked_code));
db.insert_account_info(*addr, info);
Ok(())
}
async fn get_tweaked_code(
&self,
addr: &Address,
artifact: &Artifact,
recompiled_artifact: &Artifact,
quick: bool,
) -> Result<Bytes> {
let creation_tx_hash = self.get_creation_tx(addr).await?;
debug!("Creation tx: {} -> {}", creation_tx_hash, addr);
let ForkResult { context: mut replay_ctx, target_tx_env: mut creation_tx_env, .. } =
fork_and_prepare(&self.rpc_url, creation_tx_hash, quick).await?;
relax_evm_constraints(&mut replay_ctx, &mut creation_tx_env);
let contract = artifact.contract().ok_or(eyre::eyre!("Failed to get contract"))?;
let recompiled_contract =
recompiled_artifact.contract().ok_or(eyre::eyre!("Failed to get contract"))?;
let constructor_args = recompiled_artifact.constructor_arguments();
let mut inspector =
TweakInspector::new(*addr, contract, recompiled_contract, constructor_args);
let mut evm = replay_ctx.build_mainnet_with_inspector(&mut inspector);
evm.inspect_one_tx(creation_tx_env)
.map_err(|e| eyre::eyre!("Failed to inspect the target transaction: {:?}", e))?;
inspector.into_deployed_code()
}
pub async fn get_creation_tx(&self, addr: &Address) -> Result<TxHash> {
let chain_id = self.ctx.cfg().chain_id();
let etherscan_cache_dir =
EdbCachePath::new(None as Option<PathBuf>).etherscan_chain_cache_dir(chain_id);
let cache = EdbCache::<ContractCreationData>::new(etherscan_cache_dir, None)?;
let label = format!("contract_creation_{addr}");
if let Some(creation_data) = cache.load_cache(&label) {
Ok(creation_data.transaction_hash)
} else {
let etherscan_api_key =
self.etherscan_api_key.clone().unwrap_or(next_etherscan_api_key());
let etherscan = Client::builder()
.with_api_key(etherscan_api_key)
.chain(chain_id.into())?
.build()?;
let creation_data = etherscan.contract_creation_data(*addr).await?;
cache.save_cache(&label, &creation_data)?;
Ok(creation_data.transaction_hash)
}
}
}