1use super::{
2 contracts::{
3 ISafe, ISafeProxyFactory, PREDETERMINED_SALT_NONCE, SAFE_L2_V1_4_1, SAFE_V1_4_1,
4 SENTINEL_OWNER,
5 },
6 transaction::send_safe_call,
7};
8use alloy_network::Ethereum;
9use alloy_primitives::{Address, Bytes, U256, keccak256, map::AddressHashSet};
10use alloy_provider::Provider;
11use alloy_sol_types::{SolCall, SolEvent};
12use eyre::{Context, Result, ensure};
13use foundry_cli::{
14 json::print_scalar,
15 opts::{RpcOpts, TransactionOpts},
16 utils::LoadConfig,
17};
18use foundry_common::{provider::ProviderBuilder, sh_status};
19use foundry_wallets::WalletOpts;
20
21#[allow(clippy::too_many_arguments)]
22pub(super) async fn run(
23 owners: Vec<Address>,
24 threshold: Option<usize>,
25 salt_nonce: Option<U256>,
26 singleton: Option<Address>,
27 l1: bool,
28 factory: Address,
29 fallback_handler: Address,
30 confirmations: u64,
31 timeout: Option<u64>,
32 poll_interval: Option<u64>,
33 rpc: RpcOpts,
34 wallet: WalletOpts,
35 tx: TransactionOpts,
36) -> Result<()> {
37 let threshold = validate_owners(&owners, threshold)?;
38 let config = rpc.load_config()?;
39 let provider = ProviderBuilder::<Ethereum>::from_config(&config)?.build()?;
40 let chain_id = provider.get_chain_id().await?;
41 let singleton =
42 singleton.unwrap_or(if l1 || chain_id == 1 { SAFE_V1_4_1 } else { SAFE_L2_V1_4_1 });
43 ensure_contract(&provider, singleton, "Safe singleton", "--singleton").await?;
44 ensure_contract(&provider, factory, "SafeProxyFactory", "--factory").await?;
45 if fallback_handler != Address::ZERO {
46 ensure_contract(
47 &provider,
48 fallback_handler,
49 "CompatibilityFallbackHandler",
50 "--fallback-handler",
51 )
52 .await?;
53 }
54
55 let salt_nonce = salt_nonce.unwrap_or_else(|| default_salt_nonce(chain_id));
56 let initializer: Bytes = ISafe::setupCall {
57 owners,
58 threshold: U256::from(threshold),
59 to: Address::ZERO,
60 data: Bytes::new(),
61 fallbackHandler: fallback_handler,
62 paymentToken: Address::ZERO,
63 payment: U256::ZERO,
64 paymentReceiver: Address::ZERO,
65 }
66 .abi_encode()
67 .into();
68
69 sh_status!("Deploying Safe with singleton {singleton}")?;
70 let calldata: Bytes = ISafeProxyFactory::createProxyWithNonceCall {
71 singleton,
72 initializer,
73 saltNonce: salt_nonce,
74 }
75 .abi_encode()
76 .into();
77 let result =
78 send_safe_call(factory, calldata, confirmations, timeout, poll_interval, rpc, wallet, tx)
79 .await
80 .wrap_err("failed to submit Safe deployment")?;
81 let deployed = result
82 .logs
83 .iter()
84 .filter(|log| log.address() == factory)
85 .find_map(|log| ISafeProxyFactory::ProxyCreation::decode_log(&log.inner).ok())
86 .ok_or_else(|| eyre::eyre!("Safe deployment receipt did not emit ProxyCreation"))?;
87 sh_status!("Transaction hash: {}", result.tx_hash)?;
88 print_scalar(deployed.proxy.to_checksum(None))?;
89 Ok(())
90}
91
92fn validate_owners(owners: &[Address], threshold: Option<usize>) -> Result<usize> {
93 ensure!(!owners.is_empty(), "at least one Safe owner is required");
94 let threshold = threshold.unwrap_or(owners.len());
95 ensure!(threshold > 0, "Safe threshold must be greater than zero");
96 ensure!(
97 threshold <= owners.len(),
98 "Safe threshold ({threshold}) exceeds owner count ({})",
99 owners.len()
100 );
101 let mut unique = AddressHashSet::default();
102 for owner in owners {
103 ensure!(*owner != Address::ZERO, "Safe owner cannot be the zero address");
104 ensure!(*owner != SENTINEL_OWNER, "Safe owner cannot be the sentinel address");
105 ensure!(unique.insert(*owner), "duplicate Safe owner: {owner}");
106 }
107 Ok(threshold)
108}
109
110fn default_salt_nonce(chain_id: u64) -> U256 {
111 U256::from_be_slice(keccak256(format!("{PREDETERMINED_SALT_NONCE}{chain_id}")).as_slice())
112}
113
114pub(super) async fn ensure_contract<P>(
115 provider: &P,
116 address: Address,
117 name: &str,
118 flag: &str,
119) -> Result<()>
120where
121 P: Provider<Ethereum>,
122{
123 ensure!(
124 !provider.get_code_at(address).await?.is_empty(),
125 "{name} is not deployed at {address}; provide the network deployment with {flag}"
126 );
127 Ok(())
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use std::str::FromStr;
134
135 #[test]
136 fn validates_safe_owner_configuration() {
137 let owners = [Address::repeat_byte(1), Address::repeat_byte(2)];
138 assert_eq!(validate_owners(&owners, None).unwrap(), 2);
139 assert_eq!(validate_owners(&owners, Some(1)).unwrap(), 1);
140 assert!(validate_owners(&owners, Some(3)).is_err());
141 assert!(validate_owners(&[owners[0], owners[0]], None).is_err());
142 assert!(validate_owners(&[Address::ZERO], None).is_err());
143 }
144
145 #[test]
146 fn matches_protocol_kit_default_salt_nonce() {
147 assert_eq!(
148 default_salt_nonce(1),
149 U256::from_str("0x69b348339eea4ed93f9d11931c3b894c8f9d8c7663a053024b11cb7eb4e5a1f6")
150 .unwrap()
151 );
152 }
153}