forge_script_sequence/
reader.rs
1use crate::{ScriptSequence, TransactionWithMetadata};
2use alloy_network::AnyTransactionReceipt;
3use eyre::{bail, Result};
4use foundry_common::fs;
5use revm_inspectors::tracing::types::CallKind;
6use std::path::{Component, Path, PathBuf};
7
8#[derive(Debug, Clone)]
18pub struct BroadcastReader {
19 contract_name: String,
20 chain_id: u64,
21 tx_type: Vec<CallKind>,
22 broadcast_path: PathBuf,
23}
24
25impl BroadcastReader {
26 pub fn new(contract_name: String, chain_id: u64, broadcast_path: &Path) -> Result<Self> {
28 if !broadcast_path.exists() && !broadcast_path.is_dir() {
29 bail!("broadcast dir does not exist");
30 }
31
32 Ok(Self {
33 contract_name,
34 chain_id,
35 tx_type: Default::default(),
36 broadcast_path: broadcast_path.to_path_buf(),
37 })
38 }
39
40 pub fn with_tx_type(mut self, tx_type: CallKind) -> Self {
42 self.tx_type.push(tx_type);
43 self
44 }
45
46 pub fn read(&self) -> eyre::Result<Vec<ScriptSequence>> {
53 let mut broadcasts = vec![];
55 for entry in walkdir::WalkDir::new(&self.broadcast_path).into_iter() {
56 let entry = entry?;
57 let path = entry.path();
58
59 if path.is_file() && path.extension().is_some_and(|ext| ext == "json") {
60 if path.components().any(|c| c.as_os_str().to_string_lossy().contains("-latest")) {
62 continue;
63 }
64
65 if path.components().any(|c| c == Component::Normal("multi".as_ref())) {
67 let broadcast = fs::read_json_file::<serde_json::Value>(path)?;
70 let multichain_deployments = broadcast
71 .get("deployments")
72 .and_then(|deployments| {
73 serde_json::from_value::<Vec<ScriptSequence>>(deployments.clone()).ok()
74 })
75 .unwrap_or_default();
76
77 broadcasts.extend(multichain_deployments);
78 continue;
79 }
80
81 let broadcast = fs::read_json_file::<ScriptSequence>(path)?;
82 broadcasts.push(broadcast);
83 }
84 }
85
86 let broadcasts = self.filter_and_sort(broadcasts);
87
88 Ok(broadcasts)
89 }
90
91 pub fn read_latest(&self) -> eyre::Result<ScriptSequence> {
95 let broadcasts = self.read()?;
96
97 let target = broadcasts
99 .into_iter()
100 .max_by_key(|broadcast| broadcast.timestamp)
101 .ok_or_else(|| eyre::eyre!("No broadcasts found"))?;
102
103 Ok(target)
104 }
105
106 pub fn filter_and_sort(&self, broadcasts: Vec<ScriptSequence>) -> Vec<ScriptSequence> {
108 let mut seqs = broadcasts
110 .into_iter()
111 .filter(|broadcast| {
112 if broadcast.chain != self.chain_id {
113 return false;
114 }
115
116 broadcast.transactions.iter().any(move |tx| {
117 let name_filter =
118 tx.contract_name.clone().is_some_and(|cn| cn == self.contract_name);
119
120 let type_filter = self.tx_type.is_empty() || self.tx_type.contains(&tx.opcode);
121
122 name_filter && type_filter
123 })
124 })
125 .collect::<Vec<_>>();
126
127 seqs.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));
129
130 seqs
131 }
132
133 pub fn into_tx_receipts(
143 &self,
144 broadcast: ScriptSequence,
145 ) -> Vec<(TransactionWithMetadata, AnyTransactionReceipt)> {
146 let transactions = broadcast.transactions.clone();
147
148 let txs = transactions
149 .into_iter()
150 .filter(|tx| {
151 let name_filter =
152 tx.contract_name.clone().is_some_and(|cn| cn == self.contract_name);
153
154 let type_filter = self.tx_type.is_empty() || self.tx_type.contains(&tx.opcode);
155
156 name_filter && type_filter
157 })
158 .collect::<Vec<_>>();
159
160 let mut targets = Vec::new();
161 for tx in txs.into_iter() {
162 let maybe_receipt = broadcast
163 .receipts
164 .iter()
165 .find(|receipt| tx.hash.is_some_and(|hash| hash == receipt.transaction_hash));
166
167 if let Some(receipt) = maybe_receipt {
168 targets.push((tx, receipt.clone()));
169 }
170 }
171
172 targets.sort_by(|a, b| b.1.block_number.cmp(&a.1.block_number));
174
175 targets
176 }
177}