Skip to main content

cast/cmd/safe/
deploy.rs

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/// CLI arguments for `cast safe create`.
19#[derive(Args, Debug)]
20pub struct CreateArgs {
21    /// Addresses that own the Safe.
22    #[arg(required = true, num_args = 1..)]
23    owners: Vec<Address>,
24
25    /// Number of owner signatures required. Defaults to all owners.
26    #[arg(long)]
27    threshold: Option<usize>,
28
29    /// CREATE2 salt nonce. Defaults to Safe Protocol Kit's chain-specific nonce.
30    #[arg(long)]
31    salt_nonce: Option<U256>,
32
33    /// Safe singleton address. Defaults to the canonical v1.4.1 deployment.
34    #[arg(long, conflicts_with = "l1")]
35    singleton: Option<Address>,
36
37    /// Use the L1 Safe singleton instead of SafeL2.
38    #[arg(long)]
39    l1: bool,
40
41    /// SafeProxyFactory address.
42    #[arg(long, default_value_t = SAFE_PROXY_FACTORY_V1_4_1)]
43    factory: Address,
44
45    /// CompatibilityFallbackHandler address. Pass the zero address to disable it.
46    #[arg(long, default_value_t = COMPATIBILITY_FALLBACK_HANDLER_V1_4_1)]
47    fallback_handler: Address,
48
49    /// Number of confirmations to wait for.
50    #[arg(long, default_value = "1")]
51    confirmations: u64,
52
53    /// Timeout for deployment confirmation, in seconds.
54    #[arg(long, env = "ETH_TIMEOUT")]
55    timeout: Option<u64>,
56
57    /// Polling interval for the deployment receipt, in seconds.
58    #[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}