forked from foundry-rs/foundry
-
Notifications
You must be signed in to change notification settings - Fork 0
/
run.rs
214 lines (184 loc) · 7.4 KB
/
run.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
use crate::{cmd::Cmd, utils, utils::consume_config_rpc_url};
use cast::trace::CallTraceDecoder;
use clap::Parser;
use ethers::{
abi::Address,
prelude::{Middleware, Provider},
solc::utils::RuntimeOrHandle,
types::H256,
};
use forge::{
debug::DebugArena,
executor::{builder::Backend, opts::EvmOpts, DeployResult, ExecutorBuilder, RawCallResult},
trace::{identifier::EtherscanIdentifier, CallTraceArena, CallTraceDecoderBuilder, TraceKind},
};
use foundry_config::Config;
use std::{
collections::{BTreeMap, HashMap},
str::FromStr,
time::Duration,
};
use ui::{TUIExitReason, Tui, Ui};
use yansi::Paint;
#[derive(Debug, Clone, Parser)]
pub struct RunArgs {
#[clap(help = "The transaction hash.", value_name = "TXHASH")]
tx: String,
#[clap(short, long, env = "ETH_RPC_URL", value_name = "URL")]
rpc_url: Option<String>,
#[clap(long, short = 'd', help = "Debugs the transaction.")]
debug: bool,
#[clap(
long,
short = 'q',
help = "Executes the transaction only with the state from the previous block. May result in different results than the live execution!"
)]
quick: bool,
#[clap(
long,
help = "Labels address in the trace. 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045:vitalik.eth",
value_name = "LABEL"
)]
label: Vec<String>,
}
impl Cmd for RunArgs {
type Output = ();
fn run(self) -> eyre::Result<Self::Output> {
RuntimeOrHandle::new().block_on(self.run_tx())
}
}
impl RunArgs {
async fn run_tx(self) -> eyre::Result<()> {
let figment = Config::figment();
let mut evm_opts = figment.extract::<EvmOpts>()?;
let config = Config::from_provider(figment).sanitized();
let rpc_url = consume_config_rpc_url(self.rpc_url);
let provider =
Provider::try_from(rpc_url.as_str()).expect("could not instantiate provider");
if let Some(tx) =
provider.get_transaction(H256::from_str(&self.tx).expect("invalid tx hash")).await?
{
let tx_block_number = tx.block_number.expect("no block number").as_u64();
let tx_hash = tx.hash();
evm_opts.fork_url = Some(rpc_url);
evm_opts.fork_block_number = Some(tx_block_number - 1);
// Set up the execution environment
let env = evm_opts.evm_env().await;
let db =
Backend::new(utils::get_fork(&evm_opts, &config.rpc_storage_caching), &env).await;
let builder = ExecutorBuilder::new()
.with_config(env)
.with_spec(crate::utils::evm_spec(&config.evm_version));
let mut executor = builder.build(db);
// Set the state to the moment right before the transaction
if !self.quick {
println!("Executing previous transactions from the block.");
let block_txes = provider.get_block_with_txs(tx_block_number).await?;
for past_tx in block_txes.unwrap().transactions.into_iter() {
if past_tx.hash().eq(&tx_hash) {
break
}
executor.set_gas_limit(past_tx.gas);
if let Some(to) = past_tx.to {
executor
.call_raw_committing(past_tx.from, to, past_tx.input.0, past_tx.value)
.unwrap();
} else {
executor
.deploy(past_tx.from, past_tx.input.0, past_tx.value, None)
.unwrap();
}
}
}
// Execute our transaction
let mut result = {
executor.set_tracing(true).set_gas_limit(tx.gas);
if self.debug {
executor.set_debugger(true);
}
if let Some(to) = tx.to {
let RawCallResult { reverted, gas, traces, debug: run_debug, .. } =
executor.call_raw_committing(tx.from, to, tx.input.0, tx.value)?;
RunResult {
success: !reverted,
traces: vec![(TraceKind::Execution, traces.unwrap_or_default())],
debug: run_debug.unwrap_or_default(),
gas,
}
} else {
let DeployResult { gas, traces, debug: run_debug, .. }: DeployResult =
executor.deploy(tx.from, tx.input.0, tx.value, None).unwrap();
RunResult {
success: true,
traces: vec![(TraceKind::Execution, traces.unwrap_or_default())],
debug: run_debug.unwrap_or_default(),
gas,
}
}
};
let etherscan_identifier = EtherscanIdentifier::new(
evm_opts.get_remote_chain_id(),
config.etherscan_api_key,
Config::foundry_etherscan_chain_cache_dir(evm_opts.get_chain_id()),
Duration::from_secs(24 * 60 * 60),
);
let labeled_addresses: BTreeMap<Address, String> = self
.label
.iter()
.filter_map(|label_str| {
let mut iter = label_str.split(':');
if let Some(addr) = iter.next() {
if let (Ok(address), Some(label)) = (Address::from_str(addr), iter.next()) {
return Some((address, label.to_string()))
}
}
None
})
.collect();
let mut decoder = CallTraceDecoderBuilder::new().with_labels(labeled_addresses).build();
for (_, trace) in &mut result.traces {
decoder.identify(trace, ðerscan_identifier);
}
if self.debug {
run_debugger(result, decoder)?;
} else {
print_traces(&mut result, decoder)?;
}
}
Ok(())
}
}
fn run_debugger(result: RunResult, decoder: CallTraceDecoder) -> eyre::Result<()> {
// TODO Get source from etherscan
let source_code: BTreeMap<u32, String> = BTreeMap::new();
let calls: Vec<DebugArena> = vec![result.debug];
let flattened = calls.last().expect("we should have collected debug info").flatten(0);
let tui = Tui::new(flattened, 0, decoder.contracts, HashMap::new(), source_code)?;
match tui.start().expect("Failed to start tui") {
TUIExitReason::CharExit => Ok(()),
}
}
fn print_traces(result: &mut RunResult, decoder: CallTraceDecoder) -> eyre::Result<()> {
if result.traces.is_empty() {
eyre::bail!("Unexpected error: No traces. Please report this as a bug: https://github.com/foundry-rs/foundry/issues/new?assignees=&labels=T-bug&template=BUG-FORM.yml");
}
println!("Traces:");
for (_, trace) in &mut result.traces {
decoder.decode(trace);
println!("{trace}");
}
println!();
if result.success {
println!("{}", Paint::green("Script ran successfully."));
} else {
println!("{}", Paint::red("Script failed."));
}
println!("Gas used: {}", result.gas);
Ok(())
}
struct RunResult {
pub success: bool,
pub traces: Vec<(TraceKind, CallTraceArena)>,
pub debug: DebugArena,
pub gas: u64,
}