1use super::{
2 contracts::{
3 COMPATIBILITY_FALLBACK_HANDLER_V1_4_1, ISafe, ISafeProxyFactory, PREDETERMINED_SALT_NONCE,
4 SAFE_L2_V1_4_1, SAFE_PROXY_FACTORY_V1_4_1, SAFE_V1_4_1, SENTINEL_OWNER,
5 },
6 rpc_provider,
7 transaction::SafeSendOpts,
8};
9use alloy_network::Ethereum;
10use alloy_primitives::{Address, Bytes, U256, keccak256, map::AddressHashSet};
11use alloy_provider::Provider;
12use alloy_sol_types::{SolCall, SolEvent};
13use clap::Args;
14use eyre::{Context, Result, ensure};
15use foundry_cli::json::print_scalar;
16use foundry_common::sh_status;
17
18#[derive(Args, Debug)]
20pub struct CreateArgs {
21 #[arg(required = true, num_args = 1..)]
23 owners: Vec<Address>,
24
25 #[arg(long)]
27 threshold: Option<usize>,
28
29 #[arg(long)]
31 salt_nonce: Option<U256>,
32
33 #[arg(long, conflicts_with = "l1")]
35 singleton: Option<Address>,
36
37 #[arg(long)]
39 l1: bool,
40
41 #[arg(long, default_value_t = SAFE_PROXY_FACTORY_V1_4_1)]
43 factory: Address,
44
45 #[arg(long, default_value_t = COMPATIBILITY_FALLBACK_HANDLER_V1_4_1)]
47 fallback_handler: Address,
48
49 #[arg(long, default_value = "1")]
51 confirmations: u64,
52
53 #[arg(long, env = "ETH_TIMEOUT")]
55 timeout: Option<u64>,
56
57 #[arg(long, env = "ETH_POLL_INTERVAL")]
59 poll_interval: Option<u64>,
60
61 #[command(flatten)]
62 send: SafeSendOpts,
63}
64
65impl CreateArgs {
66 pub(super) async fn run(self) -> Result<()> {
67 let Self {
68 owners,
69 threshold,
70 salt_nonce,
71 singleton,
72 l1,
73 factory,
74 fallback_handler,
75 confirmations,
76 timeout,
77 poll_interval,
78 send,
79 } = self;
80 let threshold = validate_owners(&owners, threshold)?;
81 let (provider, chain_id) = rpc_provider(&send.rpc).await?;
82 let singleton =
83 singleton.unwrap_or(if l1 || chain_id == 1 { SAFE_V1_4_1 } else { SAFE_L2_V1_4_1 });
84 ensure_contract(&provider, singleton, "Safe singleton", "--singleton").await?;
85 ensure_contract(&provider, factory, "SafeProxyFactory", "--factory").await?;
86 if fallback_handler != Address::ZERO {
87 ensure_contract(
88 &provider,
89 fallback_handler,
90 "CompatibilityFallbackHandler",
91 "--fallback-handler",
92 )
93 .await?;
94 }
95
96 let initializer = ISafe::setupCall {
97 owners,
98 threshold: U256::from(threshold),
99 to: Address::ZERO,
100 data: Bytes::new(),
101 fallbackHandler: fallback_handler,
102 paymentToken: Address::ZERO,
103 payment: U256::ZERO,
104 paymentReceiver: Address::ZERO,
105 }
106 .abi_encode()
107 .into();
108
109 sh_status!("Deploying Safe with singleton {singleton}")?;
110 let calldata = ISafeProxyFactory::createProxyWithNonceCall {
111 singleton,
112 initializer,
113 saltNonce: salt_nonce.unwrap_or_else(|| default_salt_nonce(chain_id)),
114 }
115 .abi_encode()
116 .into();
117 let result = send
118 .send(factory, calldata, confirmations, timeout, poll_interval)
119 .await
120 .wrap_err("failed to submit Safe deployment")?;
121 let deployed = result
122 .logs
123 .iter()
124 .filter(|log| log.address() == factory)
125 .find_map(|log| ISafeProxyFactory::ProxyCreation::decode_log(&log.inner).ok())
126 .ok_or_else(|| eyre::eyre!("Safe deployment receipt did not emit ProxyCreation"))?;
127 sh_status!("Transaction hash: {}", result.tx_hash)?;
128 print_scalar(deployed.proxy.to_checksum(None))
129 }
130}
131
132fn validate_owners(owners: &[Address], threshold: Option<usize>) -> Result<usize> {
133 ensure!(!owners.is_empty(), "at least one Safe owner is required");
134 let threshold = threshold.unwrap_or(owners.len());
135 ensure!(threshold > 0, "Safe threshold must be greater than zero");
136 ensure!(
137 threshold <= owners.len(),
138 "Safe threshold ({threshold}) exceeds owner count ({})",
139 owners.len()
140 );
141 let mut unique = AddressHashSet::default();
142 for owner in owners {
143 ensure!(*owner != Address::ZERO, "Safe owner cannot be the zero address");
144 ensure!(*owner != SENTINEL_OWNER, "Safe owner cannot be the sentinel address");
145 ensure!(unique.insert(*owner), "duplicate Safe owner: {owner}");
146 }
147 Ok(threshold)
148}
149
150fn default_salt_nonce(chain_id: u64) -> U256 {
151 U256::from_be_slice(keccak256(format!("{PREDETERMINED_SALT_NONCE}{chain_id}")).as_slice())
152}
153
154pub(super) async fn ensure_contract<P: Provider<Ethereum>>(
155 provider: &P,
156 address: Address,
157 name: &str,
158 flag: &str,
159) -> Result<()> {
160 ensure!(
161 !provider.get_code_at(address).await?.is_empty(),
162 "{name} is not deployed at {address}; provide the network deployment with {flag}"
163 );
164 Ok(())
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use std::str::FromStr;
171
172 #[test]
173 fn validates_safe_owner_configuration() {
174 let owners = [Address::repeat_byte(1), Address::repeat_byte(2)];
175 assert_eq!(validate_owners(&owners, None).unwrap(), 2);
176 assert_eq!(validate_owners(&owners, Some(1)).unwrap(), 1);
177 assert!(validate_owners(&owners, Some(3)).is_err());
178 assert!(validate_owners(&[owners[0], owners[0]], None).is_err());
179 assert!(validate_owners(&[Address::ZERO], None).is_err());
180 }
181
182 #[test]
183 fn matches_protocol_kit_default_salt_nonce() {
184 assert_eq!(
185 default_salt_nonce(1),
186 U256::from_str("0x69b348339eea4ed93f9d11931c3b894c8f9d8c7663a053024b11cb7eb4e5a1f6")
187 .unwrap()
188 );
189 }
190}