soroban_sdk/
deploy.rs

1//! Deploy contains types for deploying contracts.
2//!
3//! Contracts are assigned an ID that is derived from a set of arguments. A
4//! contract may choose which set of arguments to use to deploy with:
5//!
6//! - [Deployer::with_current_contract] – A contract deployed by the currently
7//! executing contract will have an ID derived from the currently executing
8//! contract's ID.
9//!
10//! The deployer can be created using [Env::deployer].
11//!
12//! ### Examples
13//!
14//! #### Deploy a contract without constructor (or 0-argument constructor)
15//!
16//! ```
17//! use soroban_sdk::{contract, contractimpl, BytesN, Env, Symbol};
18//!
19//! const DEPLOYED_WASM: &[u8] = include_bytes!("../doctest_fixtures/contract.wasm");
20//!
21//! #[contract]
22//! pub struct Contract;
23//!
24//! #[contractimpl]
25//! impl Contract {
26//!     pub fn deploy(env: Env, wasm_hash: BytesN<32>) {
27//!         let salt = [0u8; 32];
28//!         let deployer = env.deployer().with_current_contract(salt);
29//!         let contract_address = deployer.deploy_v2(wasm_hash, ());
30//!         // ...
31//!     }
32//! }
33//!
34//! #[test]
35//! fn test() {
36//! # }
37//! # #[cfg(feature = "testutils")]
38//! # fn main() {
39//!     let env = Env::default();
40//!     let contract_address = env.register(Contract, ());
41//!     let contract = ContractClient::new(&env, &contract_address);
42//!     // Upload the contract code before deploying its instance.
43//!     let wasm_hash = env.deployer().upload_contract_wasm(DEPLOYED_WASM);
44//!     contract.deploy(&wasm_hash);
45//! }
46//! # #[cfg(not(feature = "testutils"))]
47//! # fn main() { }
48//! ```
49//!
50//! #### Deploy a contract with a multi-argument constructor
51//!
52//! ```
53//! use soroban_sdk::{contract, contractimpl, BytesN, Env, Symbol, IntoVal};
54//!
55//! const DEPLOYED_WASM_WITH_CTOR: &[u8] = include_bytes!("../doctest_fixtures/contract_with_constructor.wasm");
56//!
57//! #[contract]
58//! pub struct Contract;
59//!
60//! #[contractimpl]
61//! impl Contract {
62//!     pub fn deploy_with_constructor(env: Env, wasm_hash: BytesN<32>) {
63//!         let salt = [1u8; 32];
64//!         let deployer = env.deployer().with_current_contract(salt);
65//!         let contract_address = deployer.deploy_v2(
66//!              wasm_hash,
67//!              (1_u32, 2_i64),
68//!         );
69//!         // ...
70//!     }
71//! }
72//!
73//! #[test]
74//! fn test() {
75//! # }
76//! # #[cfg(feature = "testutils")]
77//! # fn main() {
78//!     let env = Env::default();
79//!     let contract_address = env.register(Contract, ());
80//!     let contract = ContractClient::new(&env, &contract_address);
81//!     // Upload the contract code before deploying its instance.
82//!     let wasm_hash = env.deployer().upload_contract_wasm(DEPLOYED_WASM_WITH_CTOR);
83//!     contract.deploy_with_constructor(&wasm_hash);
84//! }
85//! # #[cfg(not(feature = "testutils"))]
86//! # fn main() { }
87//! ```
88//!
89//! #### Derive before deployment what the address of a contract will be
90//!
91//! ```
92//! use soroban_sdk::{contract, contractimpl, Address, BytesN, Env, Symbol, IntoVal};
93//!
94//! #[contract]
95//! pub struct Contract;
96//!
97//! #[contractimpl]
98//! impl Contract {
99//!     pub fn deploy_contract_address(env: Env) -> Address {
100//!         let salt = [1u8; 32];
101//!         let deployer = env.deployer().with_current_contract(salt);
102//!         // Deployed contract address is deterministic and can be accessed
103//!         // before deploying the contract. It is derived from the deployer
104//!         // (the current contract's address) and the salt passed in above.
105//!         deployer.deployed_address()
106//!     }
107//! }
108//!
109//! #[test]
110//! fn test() {
111//! # }
112//! # #[cfg(feature = "testutils")]
113//! # fn main() {
114//!     let env = Env::default();
115//!     let contract_address = env.register(Contract, ());
116//!     let contract = ContractClient::new(&env, &contract_address);
117//!     assert_eq!(
118//!         contract.deploy_contract_address(),
119//!         Address::from_str(&env, "CBESJIMX7J53SWJGJ7WQ6QTLJI4S5LPPJNC2BNVD63GIKAYCDTDOO322"),
120//!     );
121//! }
122//! # #[cfg(not(feature = "testutils"))]
123//! # fn main() { }
124//! ```
125
126use crate::{
127    env::internal::Env as _, unwrap::UnwrapInfallible, Address, Bytes, BytesN, ConstructorArgs,
128    Env, IntoVal,
129};
130
131/// Deployer provides access to deploying contracts.
132pub struct Deployer {
133    env: Env,
134}
135
136impl Deployer {
137    pub(crate) fn new(env: &Env) -> Deployer {
138        Deployer { env: env.clone() }
139    }
140
141    pub fn env(&self) -> &Env {
142        &self.env
143    }
144
145    /// Get a deployer that deploys contract that derive the contract IDs
146    /// from the current contract and provided salt.
147    pub fn with_current_contract(
148        &self,
149        salt: impl IntoVal<Env, BytesN<32>>,
150    ) -> DeployerWithAddress {
151        DeployerWithAddress {
152            env: self.env.clone(),
153            address: self.env.current_contract_address(),
154            salt: salt.into_val(&self.env),
155        }
156    }
157
158    /// Get a deployer that deploys contracts that derive the contract ID
159    /// from the provided address and salt.
160    ///
161    /// The deployer address must authorize all the deployments.
162    pub fn with_address(
163        &self,
164        address: Address,
165        salt: impl IntoVal<Env, BytesN<32>>,
166    ) -> DeployerWithAddress {
167        DeployerWithAddress {
168            env: self.env.clone(),
169            address,
170            salt: salt.into_val(&self.env),
171        }
172    }
173
174    /// Get a deployer that deploys an instance of Stellar Asset Contract
175    /// corresponding to the provided serialized asset.
176    ///
177    /// `serialized_asset` is the Stellar `Asset` XDR serialized to bytes. Refer
178    /// to `[soroban_sdk::xdr::Asset]`
179    pub fn with_stellar_asset(
180        &self,
181        serialized_asset: impl IntoVal<Env, Bytes>,
182    ) -> DeployerWithAsset {
183        DeployerWithAsset {
184            env: self.env.clone(),
185            serialized_asset: serialized_asset.into_val(&self.env),
186        }
187    }
188
189    /// Upload the contract Wasm code to the network.
190    ///
191    /// Returns the hash of the uploaded Wasm that can be then used for
192    /// the contract deployment.
193    /// ### Examples
194    /// ```
195    /// use soroban_sdk::{BytesN, Env};
196    ///
197    /// const WASM: &[u8] = include_bytes!("../doctest_fixtures/contract.wasm");
198    ///
199    /// #[test]
200    /// fn test() {
201    /// # }
202    /// # fn main() {
203    ///     let env = Env::default();
204    ///     env.deployer().upload_contract_wasm(WASM);
205    /// }
206    /// ```
207    pub fn upload_contract_wasm(&self, contract_wasm: impl IntoVal<Env, Bytes>) -> BytesN<32> {
208        self.env
209            .upload_wasm(contract_wasm.into_val(&self.env).to_object())
210            .unwrap_infallible()
211            .into_val(&self.env)
212    }
213
214    /// Replaces the executable of the current contract with the provided Wasm.
215    ///
216    /// The Wasm blob identified by the `wasm_hash` has to be already present
217    /// in the ledger (uploaded via `[Deployer::upload_contract_wasm]`).
218    ///
219    /// The function won't do anything immediately. The contract executable
220    /// will only be updated after the invocation has successfully finished.
221    pub fn update_current_contract_wasm(&self, wasm_hash: impl IntoVal<Env, BytesN<32>>) {
222        self.env
223            .update_current_contract_wasm(wasm_hash.into_val(&self.env).to_object())
224            .unwrap_infallible();
225    }
226
227    /// Extend the TTL of the contract instance and code.
228    ///
229    /// Extends the TTL of the instance and code only if the TTL for the provided contract is below `threshold` ledgers.
230    /// The TTL will then become `extend_to`. Note that the `threshold` check and TTL extensions are done for both the
231    /// contract code and contract instance, so it's possible that one is bumped but not the other depending on what the
232    /// current TTL's are.
233    ///
234    /// The TTL is the number of ledgers between the current ledger and the final ledger the data can still be accessed.
235    pub fn extend_ttl(&self, contract_address: Address, threshold: u32, extend_to: u32) {
236        self.env
237            .extend_contract_instance_and_code_ttl(
238                contract_address.to_object(),
239                threshold.into(),
240                extend_to.into(),
241            )
242            .unwrap_infallible();
243    }
244
245    /// Extend the TTL of the contract instance.
246    ///
247    /// Same as [`extend_ttl`](Self::extend_ttl) but only for contract instance.
248    pub fn extend_ttl_for_contract_instance(
249        &self,
250        contract_address: Address,
251        threshold: u32,
252        extend_to: u32,
253    ) {
254        self.env
255            .extend_contract_instance_ttl(
256                contract_address.to_object(),
257                threshold.into(),
258                extend_to.into(),
259            )
260            .unwrap_infallible();
261    }
262
263    /// Extend the TTL of the contract code.
264    ///
265    /// Same as [`extend_ttl`](Self::extend_ttl) but only for contract code.
266    pub fn extend_ttl_for_code(&self, contract_address: Address, threshold: u32, extend_to: u32) {
267        self.env
268            .extend_contract_code_ttl(
269                contract_address.to_object(),
270                threshold.into(),
271                extend_to.into(),
272            )
273            .unwrap_infallible();
274    }
275}
276
277/// A deployer that deploys a contract that has its ID derived from the provided
278/// address and salt.
279pub struct DeployerWithAddress {
280    env: Env,
281    address: Address,
282    salt: BytesN<32>,
283}
284
285impl DeployerWithAddress {
286    /// Return the address of the contract defined by the deployer.
287    ///
288    /// This function can be called at anytime, before or after the contract is
289    /// deployed, because contract addresses are deterministic.
290    pub fn deployed_address(&self) -> Address {
291        self.env
292            .get_contract_id(self.address.to_object(), self.salt.to_object())
293            .unwrap_infallible()
294            .into_val(&self.env)
295    }
296
297    /// Deploy a contract that uses Wasm executable with provided hash.
298    ///
299    /// The address of the deployed contract is defined by the deployer address
300    /// and provided salt.
301    ///
302    /// Returns the deployed contract's address.
303    #[deprecated(note = "use deploy_v2")]
304    pub fn deploy(&self, wasm_hash: impl IntoVal<Env, BytesN<32>>) -> Address {
305        let env = &self.env;
306        let address_obj = env
307            .create_contract(
308                self.address.to_object(),
309                wasm_hash.into_val(env).to_object(),
310                self.salt.to_object(),
311            )
312            .unwrap_infallible();
313        unsafe { Address::unchecked_new(env.clone(), address_obj) }
314    }
315
316    /// Deploy a contract that uses Wasm executable with provided hash.
317    ///
318    /// The constructor args will be passed to the contract's constructor. Pass
319    /// `()` for contract's with no constructor or a constructor with zero
320    /// arguments.
321    ///
322    /// The address of the deployed contract is defined by the deployer address
323    /// and provided salt.
324    ///
325    /// Returns the deployed contract's address.
326    pub fn deploy_v2<A>(
327        &self,
328        wasm_hash: impl IntoVal<Env, BytesN<32>>,
329        constructor_args: A,
330    ) -> Address
331    where
332        A: ConstructorArgs,
333    {
334        let env = &self.env;
335        let address_obj = env
336            .create_contract_with_constructor(
337                self.address.to_object(),
338                wasm_hash.into_val(env).to_object(),
339                self.salt.to_object(),
340                constructor_args.into_val(env).to_object(),
341            )
342            .unwrap_infallible();
343        unsafe { Address::unchecked_new(env.clone(), address_obj) }
344    }
345}
346
347pub struct DeployerWithAsset {
348    env: Env,
349    serialized_asset: Bytes,
350}
351
352impl DeployerWithAsset {
353    /// Return the address of the contract defined by the deployer.
354    ///
355    /// This function can be called at anytime, before or after the contract is
356    /// deployed, because contract addresses are deterministic.
357    pub fn deployed_address(&self) -> Address {
358        self.env
359            .get_asset_contract_id(self.serialized_asset.to_object())
360            .unwrap_infallible()
361            .into_val(&self.env)
362    }
363
364    pub fn deploy(&self) -> Address {
365        self.env
366            .create_asset_contract(self.serialized_asset.to_object())
367            .unwrap_infallible()
368            .into_val(&self.env)
369    }
370}
371
372#[cfg(any(test, feature = "testutils"))]
373#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
374mod testutils {
375    use crate::deploy::Deployer;
376    use crate::Address;
377
378    impl crate::testutils::Deployer for Deployer {
379        fn get_contract_instance_ttl(&self, contract: &Address) -> u32 {
380            self.env
381                .host()
382                .get_contract_instance_live_until_ledger(contract.to_object())
383                .unwrap()
384                .checked_sub(self.env.ledger().sequence())
385                .unwrap()
386        }
387
388        fn get_contract_code_ttl(&self, contract: &Address) -> u32 {
389            self.env
390                .host()
391                .get_contract_code_live_until_ledger(contract.to_object())
392                .unwrap()
393                .checked_sub(self.env.ledger().sequence())
394                .unwrap()
395        }
396    }
397}