forge_script/
transaction.rs1use super::ScriptResult;
2use crate::build::LinkedBuildData;
3use alloy_dyn_abi::JsonAbiExt;
4use alloy_primitives::{Address, B256, TxKind, hex};
5use eyre::Result;
6use forge_script_sequence::TransactionWithMetadata;
7use foundry_common::{ContractData, SELECTOR_LEN, TransactionMaybeSigned, fmt::format_token_raw};
8use foundry_evm::traces::CallTraceDecoder;
9use itertools::Itertools;
10use revm_inspectors::tracing::types::CallKind;
11use std::collections::BTreeMap;
12
13#[derive(Debug)]
14pub struct ScriptTransactionBuilder {
15 transaction: TransactionWithMetadata,
16}
17
18impl ScriptTransactionBuilder {
19 pub fn new(transaction: TransactionMaybeSigned, rpc: String) -> Self {
20 let mut transaction = TransactionWithMetadata::from_tx_request(transaction);
21 transaction.rpc = rpc;
22 transaction.is_fixed_gas_limit = transaction.tx().gas().is_some();
24
25 Self { transaction }
26 }
27
28 pub fn set_call(
30 &mut self,
31 local_contracts: &BTreeMap<Address, &ContractData>,
32 decoder: &CallTraceDecoder,
33 create2_deployer: Address,
34 ) -> Result<()> {
35 if let Some(TxKind::Call(to)) = self.transaction.transaction.to() {
36 if to == create2_deployer {
37 if let Some(input) = self.transaction.transaction.input() {
38 let (salt, init_code) = input.split_at(32);
39
40 self.set_create(
41 true,
42 create2_deployer.create2_from_code(B256::from_slice(salt), init_code),
43 local_contracts,
44 )?;
45 }
46 } else {
47 self.transaction.opcode = CallKind::Call;
48 self.transaction.contract_address = Some(to);
49
50 let Some(data) = self.transaction.transaction.input() else { return Ok(()) };
51
52 if data.len() < SELECTOR_LEN {
53 return Ok(());
54 }
55
56 let (selector, data) = data.split_at(SELECTOR_LEN);
57
58 let function = if let Some(info) = local_contracts.get(&to) {
59 self.transaction.contract_name = Some(info.name.clone());
61 info.abi.functions().find(|function| function.selector() == selector)
62 } else {
63 decoder.functions.get(selector).and_then(|v| v.first())
66 };
67
68 if let Some(function) = function {
69 self.transaction.function = Some(function.signature());
70
71 let values = function.abi_decode_input(data).inspect_err(|_| {
72 error!(
73 contract=?self.transaction.contract_name,
74 signature=?function,
75 data=hex::encode(data),
76 "Failed to decode function arguments",
77 );
78 })?;
79 self.transaction.arguments =
80 Some(values.iter().map(format_token_raw).collect());
81 }
82 }
83 }
84
85 Ok(())
86 }
87
88 pub fn set_create(
93 &mut self,
94 is_create2: bool,
95 address: Address,
96 contracts: &BTreeMap<Address, &ContractData>,
97 ) -> Result<()> {
98 if is_create2 {
99 self.transaction.opcode = CallKind::Create2;
100 } else {
101 self.transaction.opcode = CallKind::Create;
102 }
103
104 let info = contracts.get(&address);
105 self.transaction.contract_name = info.map(|info| info.name.clone());
106 self.transaction.contract_address = Some(address);
107
108 let Some(data) = self.transaction.transaction.input() else { return Ok(()) };
109 let Some(info) = info else { return Ok(()) };
110 let Some(bytecode) = info.bytecode() else { return Ok(()) };
111
112 let creation_code = if is_create2 {
114 if data.len() < 32 {
115 return Ok(());
116 }
117 &data[32..]
118 } else {
119 data
120 };
121
122 let contains_constructor_args = creation_code.len() > bytecode.len();
124 if !contains_constructor_args {
125 return Ok(());
126 }
127 let constructor_args = &creation_code[bytecode.len()..];
128
129 let Some(constructor) = info.abi.constructor() else { return Ok(()) };
130 let values = constructor.abi_decode_input(constructor_args).inspect_err(|_| {
131 error!(
132 contract=?self.transaction.contract_name,
133 signature=%format!("constructor({})", constructor.inputs.iter().map(|p| &p.ty).format(",")),
134 is_create2,
135 constructor_args=%hex::encode(constructor_args),
136 "Failed to decode constructor arguments",
137 );
138 debug!(full_data=%hex::encode(data), bytecode=%hex::encode(creation_code));
139 })?;
140 self.transaction.arguments = Some(values.iter().map(format_token_raw).collect());
141
142 Ok(())
143 }
144
145 pub fn with_execution_result(
147 mut self,
148 result: &ScriptResult,
149 gas_estimate_multiplier: u64,
150 linked_build_data: &LinkedBuildData,
151 ) -> Self {
152 let mut created_contracts =
153 result.get_created_contracts(&linked_build_data.known_contracts);
154
155 created_contracts.retain(|contract| {
157 self.transaction.contract_address != Some(contract.address)
159 });
160
161 self.transaction.additional_contracts = created_contracts;
162
163 if !self.transaction.is_fixed_gas_limit
164 && let Some(unsigned) = self.transaction.transaction.as_unsigned_mut()
165 {
166 unsigned.gas = Some(result.gas_used * gas_estimate_multiplier / 100);
168 }
169
170 self
171 }
172
173 pub fn build(self) -> TransactionWithMetadata {
174 self.transaction
175 }
176}
177
178impl From<TransactionWithMetadata> for ScriptTransactionBuilder {
179 fn from(transaction: TransactionWithMetadata) -> Self {
180 Self { transaction }
181 }
182}