1use std::collections::BTreeMap;
6
7use alloy::{
8 hex,
9 network::TransactionBuilder,
10 primitives::{
11 keccak256,
12 Address,
13 Bytes,
14 B256,
15 },
16 providers::Provider,
17 rpc::types::TransactionRequest,
18 sol_types::SolCall,
19};
20
21use crate::{
22 artifacts::Artifacts,
23 bindings::proxy::IUUPSUpgradeable,
24 error::{
25 Error,
26 Result,
27 },
28 factory::{
29 ensure_create2_deployer,
30 CREATE2_DEPLOYER,
31 },
32};
33
34#[macro_export]
44macro_rules! send_with_nonce_retry {
45 ($call_expr:expr, $label:expr, $provider:expr, $sender:expr) => {{
46 const MAX_RETRIES: u32 = 3;
47 let mut result: $crate::Result<alloy::rpc::types::TransactionReceipt> =
48 Err($crate::Error::Rpc {
49 detail: "unreachable".into(),
50 });
51 for attempt in 0..MAX_RETRIES {
52 let nonce =
53 alloy::providers::Provider::get_transaction_count($provider, $sender)
54 .await
55 .map_err(|e| $crate::Error::Rpc {
56 detail: format!("{} failed to fetch nonce: {e}", $label),
57 })?;
58 match ($call_expr).nonce(nonce).send().await {
59 Ok(pending) => {
60 result =
61 pending.get_receipt().await.map_err(|e| $crate::Error::Rpc {
62 detail: format!("{} confirmation failed: {e}", $label),
63 });
64 break;
65 }
66 Err(e) => {
67 let msg = e.to_string();
68 let next_attempt = attempt.saturating_add(1);
69 if msg.contains("nonce too low") && next_attempt < MAX_RETRIES {
70 tokio::time::sleep(std::time::Duration::from_secs(2)).await;
71 continue;
72 }
73 result = Err($crate::Error::Rpc {
74 detail: format!("{} send failed: {e}", $label),
75 });
76 break;
77 }
78 }
79 }
80 result
81 }};
82}
83
84pub async fn deploy_contract<P: Provider>(
86 provider: &P,
87 bytecode: Bytes,
88 label: &str,
89) -> Result<Address> {
90 deploy_contract_from(provider, bytecode, label, None).await
91}
92
93pub async fn deploy_contract_from<P: Provider>(
98 provider: &P,
99 bytecode: Bytes,
100 label: &str,
101 sender: Option<Address>,
102) -> Result<Address> {
103 let mut tx = TransactionRequest::default().with_deploy_code(bytecode);
104
105 if let Some(addr) = sender {
106 let nonce =
107 provider
108 .get_transaction_count(addr)
109 .await
110 .map_err(|e| Error::Rpc {
111 detail: format!("failed to fetch nonce for {label}: {e}"),
112 })?;
113 tx = tx.with_nonce(nonce);
114 }
115
116 let pending = provider
117 .send_transaction(tx)
118 .await
119 .map_err(|e| Error::Rpc {
120 detail: format!("failed to send {label} deploy tx: {e}"),
121 })?;
122
123 let receipt = pending.get_receipt().await.map_err(|e| Error::Rpc {
124 detail: format!("failed to get {label} deploy receipt: {e}"),
125 })?;
126
127 receipt.contract_address.ok_or_else(|| Error::Rpc {
128 detail: format!("{label} deploy did not return contract address"),
129 })
130}
131
132pub async fn deploy_with_ctor<P: Provider>(
134 provider: &P,
135 bytecode: &Bytes,
136 constructor_args: &[u8],
137 label: &str,
138 sender: Option<Address>,
139) -> Result<Address> {
140 let mut deploy_bytecode = bytecode.to_vec();
141 deploy_bytecode.extend_from_slice(constructor_args);
142 deploy_contract_from(provider, Bytes::from(deploy_bytecode), label, sender).await
143}
144
145pub async fn deploy_proxy<P: Provider>(
148 provider: &P,
149 proxy_bytecode: &Bytes,
150 implementation: Address,
151 init_data: Bytes,
152 label: &str,
153 sender: Option<Address>,
154) -> Result<Address> {
155 let constructor_args =
157 alloy::sol_types::SolValue::abi_encode_params(&(implementation, init_data));
158 deploy_with_ctor(provider, proxy_bytecode, &constructor_args, label, sender).await
159}
160
161pub async fn deploy_behind_proxy<P: Provider, C: SolCall>(
165 provider: &P,
166 artifacts: &Artifacts,
167 contract: &str,
168 init_call: &C,
169 sender: Option<Address>,
170) -> Result<Address> {
171 let implementation = deploy_contract_from(
172 provider,
173 artifacts.bytecode(contract)?,
174 &format!("{contract} (impl)"),
175 sender,
176 )
177 .await?;
178 let proxy_bytecode = artifacts.bytecode("ERC1967Proxy")?;
179 deploy_proxy(
180 provider,
181 &proxy_bytecode,
182 implementation,
183 init_call.abi_encode().into(),
184 &format!("{contract} (proxy)"),
185 sender,
186 )
187 .await
188}
189
190pub async fn upgrade_uups<P: Provider>(
196 provider: &P,
197 artifacts: &Artifacts,
198 proxy: Address,
199 contract: &str,
200 data: Bytes,
201 sender: Option<Address>,
202) -> Result<Address> {
203 let new_impl = deploy_contract_from(
204 provider,
205 artifacts.bytecode(contract)?,
206 &format!("{contract} (new impl)"),
207 sender,
208 )
209 .await?;
210 let proxied = IUUPSUpgradeable::new(proxy, provider);
211 let call = proxied.upgradeToAndCall(new_impl, data);
212 let pending =
213 match sender {
214 Some(addr) => {
215 let nonce = provider.get_transaction_count(addr).await.map_err(|e| {
216 Error::Rpc {
217 detail: format!("{contract} upgrade failed to fetch nonce: {e}"),
218 }
219 })?;
220 call.nonce(nonce).send().await
221 }
222 None => call.send().await,
223 }
224 .map_err(|e| Error::Rpc {
225 detail: format!("{contract} upgradeToAndCall send failed: {e}"),
226 })?;
227 pending.get_receipt().await.map_err(|e| Error::Rpc {
228 detail: format!("{contract} upgradeToAndCall confirmation failed: {e}"),
229 })?;
230 Ok(new_impl)
231}
232
233pub(crate) async fn deploy_via_create2<P: Provider>(
239 provider: &P,
240 salt: B256,
241 init_code: &[u8],
242 predicted: Address,
243 label: &str,
244 sender: Option<Address>,
245) -> Result<()> {
246 let mut input = salt.to_vec();
247 input.extend_from_slice(init_code);
248 let mut tx = TransactionRequest::default()
249 .with_to(CREATE2_DEPLOYER)
250 .with_input(Bytes::from(input));
251 if let Some(addr) = sender {
252 let nonce =
253 provider
254 .get_transaction_count(addr)
255 .await
256 .map_err(|e| Error::Rpc {
257 detail: format!("{label}: failed to fetch nonce: {e}"),
258 })?;
259 tx = tx.with_nonce(nonce);
260 }
261 let pending = provider
262 .send_transaction(tx)
263 .await
264 .map_err(|e| Error::Rpc {
265 detail: format!("{label}: CREATE2 deploy send failed: {e}"),
266 })?;
267 pending.get_receipt().await.map_err(|e| Error::Rpc {
268 detail: format!("{label}: CREATE2 deploy confirmation failed: {e}"),
269 })?;
270 if !code_present(provider, predicted, label).await? {
271 return Err(Error::Rpc {
272 detail: format!("{label}: no code at the predicted address {predicted}"),
273 });
274 }
275 Ok(())
276}
277
278async fn code_present<P: Provider>(
279 provider: &P,
280 address: Address,
281 label: &str,
282) -> Result<bool> {
283 let code = provider
284 .get_code_at(address)
285 .await
286 .map_err(|e| Error::Rpc {
287 detail: format!("{label}: failed to read code at {address}: {e}"),
288 })?;
289 Ok(!code.is_empty())
290}
291
292pub const LIBRARY_SALT: B256 = B256::ZERO;
297
298pub fn library_address(creation_code: &[u8]) -> Address {
302 CREATE2_DEPLOYER.create2(LIBRARY_SALT, keccak256(creation_code))
303}
304
305#[derive(Clone, Debug, Default, PartialEq, Eq)]
333pub struct Libraries {
334 linked: BTreeMap<(String, String), Address>,
336 distinct: BTreeMap<B256, (Address, bool)>,
339}
340
341impl Libraries {
342 pub async fn deploy<P: Provider>(
350 provider: &P,
351 artifacts: &Artifacts,
352 contracts: &[(&str, &str)],
353 sender: Option<Address>,
354 ) -> Result<Self> {
355 let mut libraries = Self::default();
356 for (file, contract) in contracts {
357 libraries
358 .resolve(provider, artifacts, file, contract, sender)
359 .await?;
360 }
361 Ok(libraries)
362 }
363
364 pub fn address(&self, file: &str, library: &str) -> Option<Address> {
367 self.linked
368 .get(&(file.to_owned(), library.to_owned()))
369 .copied()
370 }
371
372 pub fn distinct(&self) -> impl Iterator<Item = (B256, Address)> + '_ {
375 self.distinct
376 .iter()
377 .map(|(hash, (address, _))| (*hash, *address))
378 }
379
380 pub fn deployed(&self) -> impl Iterator<Item = Address> + '_ {
382 self.distinct
383 .values()
384 .filter(|(_, deployed)| *deployed)
385 .map(|(address, _)| *address)
386 }
387
388 pub fn link(
392 &self,
393 artifacts: &Artifacts,
394 file: &str,
395 contract: &str,
396 ) -> Result<Bytes> {
397 let mut hex_str = artifacts.bytecode_hex(file, contract)?;
398 for (lib_path, libs) in artifacts.link_references(file, contract)? {
399 let lib_file = file_stem(&lib_path)?;
400 for (lib_name, refs) in libs.as_object().into_iter().flatten() {
401 let address = self.address(lib_file, lib_name).ok_or_else(|| {
402 Error::Artifact {
403 detail: format!(
404 "{file}.sol:{contract} links {lib_file}.sol:{lib_name}, which \
405 is not among the deployed libraries"
406 ),
407 }
408 })?;
409 substitute(&mut hex_str, refs, address, lib_name)?;
410 }
411 }
412 if hex_str.contains("__$") {
413 return Err(Error::Artifact {
414 detail: format!(
415 "{file}.sol:{contract} still has a link placeholder after linking"
416 ),
417 });
418 }
419 let bytes = hex::decode(&hex_str).map_err(|e| Error::Artifact {
420 detail: format!(
421 "invalid bytecode hex after linking {file}.sol:{contract}: {e}"
422 ),
423 })?;
424 Ok(Bytes::from(bytes))
425 }
426
427 async fn resolve<P: Provider>(
429 &mut self,
430 provider: &P,
431 artifacts: &Artifacts,
432 file: &str,
433 contract: &str,
434 sender: Option<Address>,
435 ) -> Result<()> {
436 for (lib_path, libs) in artifacts.link_references(file, contract)? {
437 let lib_file = file_stem(&lib_path)?;
438 for lib_name in libs.as_object().into_iter().flatten().map(|(name, _)| name) {
439 let key = (lib_file.to_owned(), lib_name.clone());
440 if self.linked.contains_key(&key) {
441 continue;
442 }
443 Box::pin(self.resolve(provider, artifacts, lib_file, lib_name, sender))
446 .await?;
447 let code = self.link(artifacts, lib_file, lib_name)?;
448 let hash = keccak256(&code);
449 let address = match self.distinct.get(&hash) {
450 Some((address, _)) => *address,
451 None => {
452 let label = format!("{lib_file}.sol:{lib_name} (library)");
453 let address = library_address(&code);
454 let found = code_present(provider, address, &label).await?;
455 if !found {
456 ensure_create2_deployer(provider).await?;
457 deploy_via_create2(
458 provider,
459 LIBRARY_SALT,
460 &code,
461 address,
462 &label,
463 sender,
464 )
465 .await?;
466 }
467 self.distinct.insert(hash, (address, !found));
468 address
469 }
470 };
471 self.linked.insert(key, address);
472 }
473 }
474 Ok(())
475 }
476}
477
478fn file_stem(path: &str) -> Result<&str> {
480 std::path::Path::new(path)
481 .file_stem()
482 .and_then(|s| s.to_str())
483 .ok_or_else(|| Error::Artifact {
484 detail: format!("bad library file path {path}"),
485 })
486}
487
488fn substitute(
490 hex_str: &mut String,
491 refs: &serde_json::Value,
492 address: Address,
493 library: &str,
494) -> Result<()> {
495 let addr_hex = hex::encode(address.as_slice()); for r in refs.as_array().into_iter().flatten() {
497 let start = r["start"]
498 .as_u64()
499 .and_then(|v| usize::try_from(v).ok())
500 .ok_or_else(|| Error::Artifact {
501 detail: format!("bad linkReference start for {library}"),
502 })?;
503 let length = r["length"]
504 .as_u64()
505 .and_then(|v| usize::try_from(v).ok())
506 .ok_or_else(|| Error::Artifact {
507 detail: format!("bad linkReference length for {library}"),
508 })?;
509 if length != Address::len_bytes() {
510 return Err(Error::Artifact {
511 detail: format!(
512 "linkReference for {library} is {length} bytes, not an address"
513 ),
514 });
515 }
516 let begin = start.checked_mul(2);
518 let end = start.checked_add(length).and_then(|v| v.checked_mul(2));
519 let (begin, end) = begin.zip(end).ok_or_else(|| Error::Artifact {
520 detail: format!("linkReference offset overflow for {library}"),
521 })?;
522 if end > hex_str.len() {
523 return Err(Error::Artifact {
524 detail: format!("linkReference for {library} runs past the bytecode"),
525 });
526 }
527 hex_str.replace_range(begin..end, &addr_hex);
528 }
529 Ok(())
530}
531
532pub async fn load_linked_bytecode<P: Provider>(
544 provider: &P,
545 artifacts: &Artifacts,
546 file: &str,
547 contract: &str,
548 sender: Option<Address>,
549) -> Result<Bytes> {
550 Libraries::deploy(provider, artifacts, &[(file, contract)], sender)
551 .await?
552 .link(artifacts, file, contract)
553}