starknet_rust_contract/
factory.rs

1use starknet_rust_accounts::{Account, AccountError, ConnectedAccount, ExecutionV3};
2use starknet_rust_core::{
3    types::{Call, FeeEstimate, Felt, InvokeTransactionResult, SimulatedTransaction},
4    utils::{get_udc_deployed_address, UdcUniqueSettings, UdcUniqueness},
5};
6
7/// The Cairo 0 UDC address: `0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf`.
8const LEGACY_UDC_ADDRESS: Felt = Felt::from_raw([
9    121672436446604875,
10    9333317513348225193,
11    15685625669053253235,
12    15144800532519055890,
13]);
14
15/// The Cairo 1 UDC address: `0x02ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125`.
16const NEW_UDC_ADDRESS: Felt = Felt::from_raw([
17    505287751652144584,
18    6849092491656713429,
19    14735209673864872887,
20    4208494925911946768,
21]);
22
23/// Selector for entrypoint `deployContract`.
24const SELECTOR_DEPLOYCONTRACT: Felt = Felt::from_raw([
25    469988280392664069,
26    1439621915307882061,
27    1265649739554438882,
28    18249998464715511309,
29]);
30
31/// A contract factory that acts as a blueprint for deploying Starknet smart contracts using the
32/// Universal Deployer Contract.
33#[derive(Debug)]
34pub struct ContractFactory<A> {
35    class_hash: Felt,
36    udc_address: Felt,
37    account: A,
38}
39
40/// Abstraction over contract deployment via the UDC. This type uses `INVOKE` v3 transactions under
41/// the hood, and hence pays transaction fees in STRK.
42#[must_use]
43#[derive(Debug)]
44pub struct DeploymentV3<'f, A> {
45    factory: &'f ContractFactory<A>,
46    constructor_calldata: Vec<Felt>,
47    salt: Felt,
48    unique: bool,
49    // The following fields allow us to mimic an `Execution` API.
50    nonce: Option<Felt>,
51    l1_gas: Option<u64>,
52    l1_gas_price: Option<u128>,
53    l2_gas: Option<u64>,
54    l2_gas_price: Option<u128>,
55    l1_data_gas: Option<u64>,
56    l1_data_gas_price: Option<u128>,
57    gas_estimate_multiplier: f64,
58    gas_price_estimate_multiplier: f64,
59    tip: Option<u64>,
60}
61
62/// Specifies the Universal Deployer Contract to be used.
63#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
64pub enum UdcSelector {
65    /// The original, now deprecated, deployer contract deployed at
66    /// `0x041a78e741e5af2fec34b695679bc6891742439f7afb8484ecd7766661ad02bf`.
67    Legacy,
68    /// The latest deployer contract deployed at
69    /// `0x02ceed65a4bd731034c01113685c831b01c15d7d432f71afb1cf1634b53a2125`.
70    #[default]
71    New,
72    /// Custom compatible deployer contract instance.
73    Custom(Felt),
74}
75
76impl<A> ContractFactory<A> {
77    /// Constructs a new [`ContractFactory`] from a class hash and an account.
78    ///
79    /// The [`ContractFactory`] created uses the default address for the Universal Deployer
80    /// Contract. To use a custom UDC deployment, use [`new_with_udc`](fn.new_with_udc) instead.
81    #[deprecated = "this method uses the legacy UDC; use `new_with_udc` instead"]
82    pub const fn new(class_hash: Felt, account: A) -> Self {
83        Self::new_with_udc(class_hash, account, UdcSelector::Legacy)
84    }
85
86    /// Constructs a new [`ContractFactory`] with a custom Universal Deployer Contract instance.
87    pub const fn new_with_udc(class_hash: Felt, account: A, udc: UdcSelector) -> Self {
88        Self {
89            class_hash,
90            udc_address: udc.address(),
91            account,
92        }
93    }
94}
95
96impl<A> ContractFactory<A>
97where
98    A: Account,
99{
100    /// Generates an instance of [`DeploymentV3`] for sending `INVOKE` v3 transactions for the
101    /// contract deployment. Pays transaction fees in `STRK`.
102    pub const fn deploy_v3(
103        &self,
104        constructor_calldata: Vec<Felt>,
105        salt: Felt,
106        unique: bool,
107    ) -> DeploymentV3<'_, A> {
108        DeploymentV3 {
109            factory: self,
110            constructor_calldata,
111            salt,
112            unique,
113            nonce: None,
114            l1_gas: None,
115            l1_gas_price: None,
116            l2_gas: None,
117            l2_gas_price: None,
118            l1_data_gas: None,
119            l1_data_gas_price: None,
120            gas_estimate_multiplier: 1.5,
121            gas_price_estimate_multiplier: 1.5,
122            tip: None,
123        }
124    }
125
126    /// Generates an instance of [`DeploymentV3`] for sending `INVOKE` v3 transactions for the
127    /// contract deployment. Pays transaction fees in `STRK`.
128    #[deprecated = "transaction version used might change unexpectedly; use `deploy_v3` instead"]
129    pub const fn deploy(
130        &self,
131        constructor_calldata: Vec<Felt>,
132        salt: Felt,
133        unique: bool,
134    ) -> DeploymentV3<'_, A> {
135        self.deploy_v3(constructor_calldata, salt, unique)
136    }
137}
138
139impl<A> DeploymentV3<'_, A> {
140    /// Returns a new [`DeploymentV3`] with the `nonce`.
141    pub fn nonce(self, nonce: Felt) -> Self {
142        Self {
143            nonce: Some(nonce),
144            ..self
145        }
146    }
147
148    /// Returns a new [`DeploymentV3`] with the `l1_gas`.
149    pub fn l1_gas(self, l1_gas: u64) -> Self {
150        Self {
151            l1_gas: Some(l1_gas),
152            ..self
153        }
154    }
155
156    /// Returns a new [`DeploymentV3`] with the `l1_gas_price`.
157    pub fn l1_gas_price(self, l1_gas_price: u128) -> Self {
158        Self {
159            l1_gas_price: Some(l1_gas_price),
160            ..self
161        }
162    }
163
164    /// Returns a new [`DeploymentV3`] with the `l2_gas`.
165    pub fn l2_gas(self, l2_gas: u64) -> Self {
166        Self {
167            l2_gas: Some(l2_gas),
168            ..self
169        }
170    }
171
172    /// Returns a new [`DeploymentV3`] with the `l2_gas_price`.
173    pub fn l2_gas_price(self, l2_gas_price: u128) -> Self {
174        Self {
175            l2_gas_price: Some(l2_gas_price),
176            ..self
177        }
178    }
179
180    /// Returns a new [`DeploymentV3`] with the `l1_data_gas`.
181    pub fn l1_data_gas(self, l1_data_gas: u64) -> Self {
182        Self {
183            l1_data_gas: Some(l1_data_gas),
184            ..self
185        }
186    }
187
188    /// Returns a new [`DeploymentV3`] with the `l1_data_gas_price`.
189    pub fn l1_data_gas_price(self, l1_data_gas_price: u128) -> Self {
190        Self {
191            l1_data_gas_price: Some(l1_data_gas_price),
192            ..self
193        }
194    }
195
196    /// Returns a new [`DeploymentV3`] with the gas amount estimate multiplier.  The multiplier is
197    /// used when the gas amount is not manually specified and must be fetched from a
198    /// [`Provider`](starknet_rust_providers::Provider) instead.
199    pub fn gas_estimate_multiplier(self, gas_estimate_multiplier: f64) -> Self {
200        Self {
201            gas_estimate_multiplier,
202            ..self
203        }
204    }
205
206    /// Returns a new [`DeploymentV3`] with the gas price estimate multiplier.  The multiplier is
207    /// used when the gas price is not manually specified and must be fetched from a
208    /// [`Provider`](starknet_rust_providers::Provider) instead.
209    pub fn gas_price_estimate_multiplier(self, gas_price_estimate_multiplier: f64) -> Self {
210        Self {
211            gas_price_estimate_multiplier,
212            ..self
213        }
214    }
215
216    /// Returns a new [`DeploymentV3`] with the `tip`.
217    pub fn tip(self, tip: u64) -> Self {
218        Self {
219            tip: Some(tip),
220            ..self
221        }
222    }
223}
224
225impl<A> DeploymentV3<'_, A>
226where
227    A: Account,
228{
229    /// Calculate the resulting contract address without sending a transaction.
230    pub fn deployed_address(&self) -> Felt {
231        get_udc_deployed_address(
232            self.salt,
233            self.factory.class_hash,
234            &if self.unique {
235                UdcUniqueness::Unique(UdcUniqueSettings {
236                    deployer_address: self.factory.account.address(),
237                    udc_contract_address: self.factory.udc_address,
238                })
239            } else {
240                UdcUniqueness::NotUnique
241            },
242            &self.constructor_calldata,
243        )
244    }
245}
246
247impl<A> DeploymentV3<'_, A>
248where
249    A: ConnectedAccount + Sync,
250{
251    /// Estimates transaction fees from a [`Provider`](starknet_rust_providers::Provider).
252    pub async fn estimate_fee(&self) -> Result<FeeEstimate, AccountError<A::SignError>> {
253        let execution: ExecutionV3<'_, A> = self.into();
254        execution.estimate_fee().await
255    }
256
257    /// Simulates the transaction from a [`Provider`](starknet_rust_providers::Provider). Transaction
258    /// validation and fee transfer can be skipped.
259    pub async fn simulate(
260        &self,
261        skip_validate: bool,
262        skip_fee_charge: bool,
263    ) -> Result<SimulatedTransaction, AccountError<A::SignError>> {
264        let execution: ExecutionV3<'_, A> = self.into();
265        execution.simulate(skip_validate, skip_fee_charge).await
266    }
267
268    /// Signs and broadcasts the transaction to the network.
269    pub async fn send(&self) -> Result<InvokeTransactionResult, AccountError<A::SignError>> {
270        let execution: ExecutionV3<'_, A> = self.into();
271        execution.send().await
272    }
273}
274
275impl<'f, A> From<&DeploymentV3<'f, A>> for ExecutionV3<'f, A> {
276    fn from(value: &DeploymentV3<'f, A>) -> Self {
277        let mut calldata = vec![
278            value.factory.class_hash,
279            value.salt,
280            if value.unique { Felt::ONE } else { Felt::ZERO },
281            value.constructor_calldata.len().into(),
282        ];
283        calldata.extend_from_slice(&value.constructor_calldata);
284
285        let execution = Self::new(
286            vec![Call {
287                to: value.factory.udc_address,
288                selector: SELECTOR_DEPLOYCONTRACT,
289                calldata,
290            }],
291            &value.factory.account,
292        );
293
294        let execution = if let Some(nonce) = value.nonce {
295            execution.nonce(nonce)
296        } else {
297            execution
298        };
299
300        let execution = if let Some(l1_gas) = value.l1_gas {
301            execution.l1_gas(l1_gas)
302        } else {
303            execution
304        };
305
306        let execution = if let Some(l1_gas_price) = value.l1_gas_price {
307            execution.l1_gas_price(l1_gas_price)
308        } else {
309            execution
310        };
311
312        let execution = if let Some(l2_gas) = value.l2_gas {
313            execution.l2_gas(l2_gas)
314        } else {
315            execution
316        };
317
318        let execution = if let Some(l2_gas_price) = value.l2_gas_price {
319            execution.l2_gas_price(l2_gas_price)
320        } else {
321            execution
322        };
323
324        let execution = if let Some(l1_data_gas) = value.l1_data_gas {
325            execution.l1_data_gas(l1_data_gas)
326        } else {
327            execution
328        };
329
330        let execution = if let Some(l1_data_gas_price) = value.l1_data_gas_price {
331            execution.l1_data_gas_price(l1_data_gas_price)
332        } else {
333            execution
334        };
335
336        let execution = if let Some(tip) = value.tip {
337            execution.tip(tip)
338        } else {
339            execution
340        };
341
342        let execution = execution.gas_estimate_multiplier(value.gas_estimate_multiplier);
343
344        execution.gas_price_estimate_multiplier(value.gas_price_estimate_multiplier)
345    }
346}
347
348impl UdcSelector {
349    /// Gets the contract address of the selected Universal Deployer Contract instance.
350    pub const fn address(&self) -> Felt {
351        match self {
352            Self::Legacy => LEGACY_UDC_ADDRESS,
353            Self::New => NEW_UDC_ADDRESS,
354            Self::Custom(address) => *address,
355        }
356    }
357}
358
359impl From<UdcSelector> for Felt {
360    fn from(value: UdcSelector) -> Self {
361        value.address()
362    }
363}
364
365#[cfg(test)]
366mod tests {
367    use starknet_rust_accounts::{ExecutionEncoding, SingleOwnerAccount};
368    use starknet_rust_core::chain_id;
369    use starknet_rust_providers::SequencerGatewayProvider;
370    use starknet_rust_signers::{LocalWallet, SigningKey};
371
372    use super::*;
373
374    #[allow(deprecated)]
375    #[test]
376    #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
377    fn test_deployed_address_unique() {
378        let factory = ContractFactory::new(
379            Felt::from_hex("0x2bfd9564754d9b4a326da62b2f22b8fea7bbeffd62da4fcaea986c323b7aeb")
380                .unwrap(),
381            SingleOwnerAccount::new(
382                SequencerGatewayProvider::starknet_alpha_sepolia(),
383                LocalWallet::from_signing_key(SigningKey::from_random()),
384                Felt::from_hex("0xb1461de04c6a1aa3375bdf9b7723a8779c082ffe21311d683a0b15c078b5dc")
385                    .unwrap(),
386                chain_id::SEPOLIA,
387                ExecutionEncoding::Legacy,
388            ),
389        );
390
391        let unique_address_v3 = factory
392            .deploy_v3(
393                vec![Felt::from_hex("0x1234").unwrap()],
394                Felt::from_hex("0x3456").unwrap(),
395                true,
396            )
397            .deployed_address();
398
399        let not_unique_address_v3 = factory
400            .deploy_v3(
401                vec![Felt::from_hex("0x1234").unwrap()],
402                Felt::from_hex("0x3456").unwrap(),
403                false,
404            )
405            .deployed_address();
406
407        assert_eq!(
408            unique_address_v3,
409            Felt::from_hex("0x36e05bcd41191387bc2f04ed9cad4776a75df3b748b0246a5d217a988474181")
410                .unwrap()
411        );
412
413        assert_eq!(
414            not_unique_address_v3,
415            Felt::from_hex("0x3a320b6aa0b451b22fba90b5d75b943932649137c09a86a5cf4853031be70c1")
416                .unwrap()
417        );
418    }
419}