use std::collections::BTreeMap;
use alloy::{
hex,
network::TransactionBuilder,
primitives::{
keccak256,
Address,
Bytes,
B256,
},
providers::Provider,
rpc::types::TransactionRequest,
sol_types::SolCall,
};
use crate::{
artifacts::Artifacts,
bindings::proxy::IUUPSUpgradeable,
error::{
Error,
Result,
},
factory::{
ensure_create2_deployer,
CREATE2_DEPLOYER,
},
};
#[macro_export]
macro_rules! send_with_nonce_retry {
($call_expr:expr, $label:expr, $provider:expr, $sender:expr) => {{
const MAX_RETRIES: u32 = 3;
let mut result: $crate::Result<alloy::rpc::types::TransactionReceipt> =
Err($crate::Error::Rpc {
detail: "unreachable".into(),
});
for attempt in 0..MAX_RETRIES {
let nonce =
alloy::providers::Provider::get_transaction_count($provider, $sender)
.await
.map_err(|e| $crate::Error::Rpc {
detail: format!("{} failed to fetch nonce: {e}", $label),
})?;
match ($call_expr).nonce(nonce).send().await {
Ok(pending) => {
result =
pending.get_receipt().await.map_err(|e| $crate::Error::Rpc {
detail: format!("{} confirmation failed: {e}", $label),
});
break;
}
Err(e) => {
let msg = e.to_string();
let next_attempt = attempt.saturating_add(1);
if msg.contains("nonce too low") && next_attempt < MAX_RETRIES {
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
continue;
}
result = Err($crate::Error::Rpc {
detail: format!("{} send failed: {e}", $label),
});
break;
}
}
}
result
}};
}
pub async fn deploy_contract<P: Provider>(
provider: &P,
bytecode: Bytes,
label: &str,
) -> Result<Address> {
deploy_contract_from(provider, bytecode, label, None).await
}
pub async fn deploy_contract_from<P: Provider>(
provider: &P,
bytecode: Bytes,
label: &str,
sender: Option<Address>,
) -> Result<Address> {
let mut tx = TransactionRequest::default().with_deploy_code(bytecode);
if let Some(addr) = sender {
let nonce =
provider
.get_transaction_count(addr)
.await
.map_err(|e| Error::Rpc {
detail: format!("failed to fetch nonce for {label}: {e}"),
})?;
tx = tx.with_nonce(nonce);
}
let pending = provider
.send_transaction(tx)
.await
.map_err(|e| Error::Rpc {
detail: format!("failed to send {label} deploy tx: {e}"),
})?;
let receipt = pending.get_receipt().await.map_err(|e| Error::Rpc {
detail: format!("failed to get {label} deploy receipt: {e}"),
})?;
receipt.contract_address.ok_or_else(|| Error::Rpc {
detail: format!("{label} deploy did not return contract address"),
})
}
pub async fn deploy_with_ctor<P: Provider>(
provider: &P,
bytecode: &Bytes,
constructor_args: &[u8],
label: &str,
sender: Option<Address>,
) -> Result<Address> {
let mut deploy_bytecode = bytecode.to_vec();
deploy_bytecode.extend_from_slice(constructor_args);
deploy_contract_from(provider, Bytes::from(deploy_bytecode), label, sender).await
}
pub async fn deploy_proxy<P: Provider>(
provider: &P,
proxy_bytecode: &Bytes,
implementation: Address,
init_data: Bytes,
label: &str,
sender: Option<Address>,
) -> Result<Address> {
let constructor_args =
alloy::sol_types::SolValue::abi_encode_params(&(implementation, init_data));
deploy_with_ctor(provider, proxy_bytecode, &constructor_args, label, sender).await
}
pub async fn deploy_behind_proxy<P: Provider, C: SolCall>(
provider: &P,
artifacts: &Artifacts,
contract: &str,
init_call: &C,
sender: Option<Address>,
) -> Result<Address> {
let implementation = deploy_contract_from(
provider,
artifacts.bytecode(contract)?,
&format!("{contract} (impl)"),
sender,
)
.await?;
let proxy_bytecode = artifacts.bytecode("ERC1967Proxy")?;
deploy_proxy(
provider,
&proxy_bytecode,
implementation,
init_call.abi_encode().into(),
&format!("{contract} (proxy)"),
sender,
)
.await
}
pub async fn upgrade_uups<P: Provider>(
provider: &P,
artifacts: &Artifacts,
proxy: Address,
contract: &str,
data: Bytes,
sender: Option<Address>,
) -> Result<Address> {
let new_impl = deploy_contract_from(
provider,
artifacts.bytecode(contract)?,
&format!("{contract} (new impl)"),
sender,
)
.await?;
let proxied = IUUPSUpgradeable::new(proxy, provider);
let call = proxied.upgradeToAndCall(new_impl, data);
let pending =
match sender {
Some(addr) => {
let nonce = provider.get_transaction_count(addr).await.map_err(|e| {
Error::Rpc {
detail: format!("{contract} upgrade failed to fetch nonce: {e}"),
}
})?;
call.nonce(nonce).send().await
}
None => call.send().await,
}
.map_err(|e| Error::Rpc {
detail: format!("{contract} upgradeToAndCall send failed: {e}"),
})?;
pending.get_receipt().await.map_err(|e| Error::Rpc {
detail: format!("{contract} upgradeToAndCall confirmation failed: {e}"),
})?;
Ok(new_impl)
}
pub(crate) async fn deploy_via_create2<P: Provider>(
provider: &P,
salt: B256,
init_code: &[u8],
predicted: Address,
label: &str,
sender: Option<Address>,
) -> Result<()> {
let mut input = salt.to_vec();
input.extend_from_slice(init_code);
let mut tx = TransactionRequest::default()
.with_to(CREATE2_DEPLOYER)
.with_input(Bytes::from(input));
if let Some(addr) = sender {
let nonce =
provider
.get_transaction_count(addr)
.await
.map_err(|e| Error::Rpc {
detail: format!("{label}: failed to fetch nonce: {e}"),
})?;
tx = tx.with_nonce(nonce);
}
let pending = provider
.send_transaction(tx)
.await
.map_err(|e| Error::Rpc {
detail: format!("{label}: CREATE2 deploy send failed: {e}"),
})?;
pending.get_receipt().await.map_err(|e| Error::Rpc {
detail: format!("{label}: CREATE2 deploy confirmation failed: {e}"),
})?;
if !code_present(provider, predicted, label).await? {
return Err(Error::Rpc {
detail: format!("{label}: no code at the predicted address {predicted}"),
});
}
Ok(())
}
async fn code_present<P: Provider>(
provider: &P,
address: Address,
label: &str,
) -> Result<bool> {
let code = provider
.get_code_at(address)
.await
.map_err(|e| Error::Rpc {
detail: format!("{label}: failed to read code at {address}: {e}"),
})?;
Ok(!code.is_empty())
}
pub const LIBRARY_SALT: B256 = B256::ZERO;
pub fn library_address(creation_code: &[u8]) -> Address {
CREATE2_DEPLOYER.create2(LIBRARY_SALT, keccak256(creation_code))
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Libraries {
linked: BTreeMap<(String, String), Address>,
distinct: BTreeMap<B256, (Address, bool)>,
}
impl Libraries {
pub async fn deploy<P: Provider>(
provider: &P,
artifacts: &Artifacts,
contracts: &[(&str, &str)],
sender: Option<Address>,
) -> Result<Self> {
let mut libraries = Self::default();
for (file, contract) in contracts {
libraries
.resolve(provider, artifacts, file, contract, sender)
.await?;
}
Ok(libraries)
}
pub fn address(&self, file: &str, library: &str) -> Option<Address> {
self.linked
.get(&(file.to_owned(), library.to_owned()))
.copied()
}
pub fn distinct(&self) -> impl Iterator<Item = (B256, Address)> + '_ {
self.distinct
.iter()
.map(|(hash, (address, _))| (*hash, *address))
}
pub fn deployed(&self) -> impl Iterator<Item = Address> + '_ {
self.distinct
.values()
.filter(|(_, deployed)| *deployed)
.map(|(address, _)| *address)
}
pub fn link(
&self,
artifacts: &Artifacts,
file: &str,
contract: &str,
) -> Result<Bytes> {
let mut hex_str = artifacts.bytecode_hex(file, contract)?;
for (lib_path, libs) in artifacts.link_references(file, contract)? {
let lib_file = file_stem(&lib_path)?;
for (lib_name, refs) in libs.as_object().into_iter().flatten() {
let address = self.address(lib_file, lib_name).ok_or_else(|| {
Error::Artifact {
detail: format!(
"{file}.sol:{contract} links {lib_file}.sol:{lib_name}, which \
is not among the deployed libraries"
),
}
})?;
substitute(&mut hex_str, refs, address, lib_name)?;
}
}
if hex_str.contains("__$") {
return Err(Error::Artifact {
detail: format!(
"{file}.sol:{contract} still has a link placeholder after linking"
),
});
}
let bytes = hex::decode(&hex_str).map_err(|e| Error::Artifact {
detail: format!(
"invalid bytecode hex after linking {file}.sol:{contract}: {e}"
),
})?;
Ok(Bytes::from(bytes))
}
async fn resolve<P: Provider>(
&mut self,
provider: &P,
artifacts: &Artifacts,
file: &str,
contract: &str,
sender: Option<Address>,
) -> Result<()> {
for (lib_path, libs) in artifacts.link_references(file, contract)? {
let lib_file = file_stem(&lib_path)?;
for lib_name in libs.as_object().into_iter().flatten().map(|(name, _)| name) {
let key = (lib_file.to_owned(), lib_name.clone());
if self.linked.contains_key(&key) {
continue;
}
Box::pin(self.resolve(provider, artifacts, lib_file, lib_name, sender))
.await?;
let code = self.link(artifacts, lib_file, lib_name)?;
let hash = keccak256(&code);
let address = match self.distinct.get(&hash) {
Some((address, _)) => *address,
None => {
let label = format!("{lib_file}.sol:{lib_name} (library)");
let address = library_address(&code);
let found = code_present(provider, address, &label).await?;
if !found {
ensure_create2_deployer(provider).await?;
deploy_via_create2(
provider,
LIBRARY_SALT,
&code,
address,
&label,
sender,
)
.await?;
}
self.distinct.insert(hash, (address, !found));
address
}
};
self.linked.insert(key, address);
}
}
Ok(())
}
}
fn file_stem(path: &str) -> Result<&str> {
std::path::Path::new(path)
.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| Error::Artifact {
detail: format!("bad library file path {path}"),
})
}
fn substitute(
hex_str: &mut String,
refs: &serde_json::Value,
address: Address,
library: &str,
) -> Result<()> {
let addr_hex = hex::encode(address.as_slice()); for r in refs.as_array().into_iter().flatten() {
let start = r["start"]
.as_u64()
.and_then(|v| usize::try_from(v).ok())
.ok_or_else(|| Error::Artifact {
detail: format!("bad linkReference start for {library}"),
})?;
let length = r["length"]
.as_u64()
.and_then(|v| usize::try_from(v).ok())
.ok_or_else(|| Error::Artifact {
detail: format!("bad linkReference length for {library}"),
})?;
if length != Address::len_bytes() {
return Err(Error::Artifact {
detail: format!(
"linkReference for {library} is {length} bytes, not an address"
),
});
}
let begin = start.checked_mul(2);
let end = start.checked_add(length).and_then(|v| v.checked_mul(2));
let (begin, end) = begin.zip(end).ok_or_else(|| Error::Artifact {
detail: format!("linkReference offset overflow for {library}"),
})?;
if end > hex_str.len() {
return Err(Error::Artifact {
detail: format!("linkReference for {library} runs past the bytecode"),
});
}
hex_str.replace_range(begin..end, &addr_hex);
}
Ok(())
}
pub async fn load_linked_bytecode<P: Provider>(
provider: &P,
artifacts: &Artifacts,
file: &str,
contract: &str,
sender: Option<Address>,
) -> Result<Bytes> {
Libraries::deploy(provider, artifacts, &[(file, contract)], sender)
.await?
.link(artifacts, file, contract)
}