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}