1use crate::{
2 crypto, err,
3 host::{
4 metered_clone::{MeteredAlloc, MeteredClone},
5 metered_write_xdr, ContractReentryMode,
6 },
7 vm::Vm,
8 xdr::{
9 Asset, ContractCodeEntry, ContractDataDurability, ContractExecutable, ContractIdPreimage,
10 ContractIdPreimageFromAddress, CreateContractArgsV2, ExtensionPoint, Hash, LedgerKey,
11 LedgerKeyContractCode, ScAddress, ScErrorCode, ScErrorType,
12 },
13 AddressObject, BytesObject, Host, HostError, Symbol, TryFromVal, TryIntoVal, Val,
14};
15use std::rc::Rc;
16
17const CONSTRUCTOR_FUNCTION_NAME: &str = "__constructor";
18const CONSTRUCTOR_SUPPORT_PROTOCOL: u32 = 22;
19
20impl Host {
21 fn create_contract_with_id(
23 &self,
24 contract_id: Hash,
25 contract_executable: ContractExecutable,
26 ) -> Result<(), HostError> {
27 let storage_key = self.contract_instance_ledger_key(&contract_id)?;
28 if self
29 .try_borrow_storage_mut()?
30 .has_with_host(&storage_key, self, None)?
31 {
32 return Err(self.err(
33 ScErrorType::Storage,
34 ScErrorCode::ExistingValue,
35 "contract already exists",
36 &[self
37 .add_host_object(self.scbytes_from_hash(&contract_id)?)?
38 .into()],
39 ));
40 }
41 if let ContractExecutable::Wasm(wasm_hash) = &contract_executable {
45 if !self.wasm_exists(wasm_hash)? {
46 return Err(err!(
47 self,
48 (ScErrorType::Storage, ScErrorCode::MissingValue),
49 "Wasm does not exist",
50 *wasm_hash
51 ));
52 }
53 }
54 self.store_contract_instance(Some(contract_executable), None, contract_id, &storage_key)?;
55 Ok(())
56 }
57
58 fn call_constructor(
59 &self,
60 contract_id: &Hash,
61 constructor_args: Vec<Val>,
62 ) -> Result<(), HostError> {
63 let contract_protocol = self.get_contract_protocol_version(&contract_id)?;
67 if contract_protocol < CONSTRUCTOR_SUPPORT_PROTOCOL {
68 if constructor_args.is_empty() {
69 return Ok(());
70 }
71 return Err(self.err(
72 ScErrorType::Context,
73 ScErrorCode::InvalidAction,
74 "trying to call non-default constructor on a contract that doesn't support constructors (built prior to protocol 22)",
75 &[],
76 ));
77 }
78 let res = self
79 .call_n_internal(
80 contract_id,
81 CONSTRUCTOR_FUNCTION_NAME.try_into_val(self)?,
82 constructor_args.as_slice(),
83 CallParams {
84 reentry_mode: ContractReentryMode::Prohibited,
85 internal_host_call: true,
86 treat_missing_function_as_noop: constructor_args.is_empty(),
89 },
90 )
91 .map_err(|err| {
92 if err.is_recoverable() {
96 self.err(
98 ScErrorType::Context,
99 ScErrorCode::InvalidAction,
100 "constructor invocation has failed with error",
101 &[err.error.to_val()],
102 )
103 } else {
104 err
105 }
106 })?;
107 if !res.is_void() {
108 return Err(self.err(
109 ScErrorType::Value,
110 ScErrorCode::UnexpectedType,
111 "constructor returned non-void value",
112 &[res],
113 ));
114 }
115 Ok(())
116 }
117
118 fn maybe_initialize_stellar_asset_contract(
119 &self,
120 contract_id: &Hash,
121 id_preimage: &ContractIdPreimage,
122 ) -> Result<(), HostError> {
123 if let ContractIdPreimage::Asset(asset) = id_preimage {
124 let mut asset_bytes: Vec<u8> = Default::default();
125 metered_write_xdr(self.budget_ref(), asset, &mut asset_bytes)?;
126 self.call_n_internal(
127 contract_id,
128 Symbol::try_from_val(self, &"init_asset")?,
129 &[self
130 .add_host_object(self.scbytes_from_vec(asset_bytes)?)?
131 .into()],
132 CallParams::default_external_call(),
133 )?;
134 Ok(())
135 } else {
136 Ok(())
137 }
138 }
139
140 pub(crate) fn create_contract_internal(
141 &self,
142 deployer: Option<AddressObject>,
143 args: CreateContractArgsV2,
144 constructor_args: Vec<Val>,
145 ) -> Result<AddressObject, HostError> {
146 let has_deployer = deployer.is_some();
147 if has_deployer {
148 self.try_borrow_authorization_manager()?
149 .push_create_contract_host_fn_frame(self, args.metered_clone(self)?)?;
150 }
151 let res = self.create_contract_with_optional_auth(deployer, args, constructor_args);
158 if has_deployer {
159 self.try_borrow_authorization_manager()?
160 .pop_frame(self, None)?;
161 }
162 res
163 }
164
165 fn create_contract_with_optional_auth(
166 &self,
167 deployer: Option<AddressObject>,
168 args: CreateContractArgsV2,
169 constructor_args: Vec<Val>,
170 ) -> Result<AddressObject, HostError> {
171 if let Some(deployer_address) = deployer {
172 self.try_borrow_authorization_manager()?.require_auth(
173 self,
174 deployer_address,
175 Default::default(),
176 )?;
177 }
178
179 let id_preimage =
180 self.get_full_contract_id_preimage(args.contract_id_preimage.metered_clone(self)?)?;
181 let contract_id = Hash(self.metered_hash_xdr(&id_preimage)?);
182 self.create_contract_with_id(contract_id.metered_clone(self)?, args.executable.clone())?;
183 self.maybe_initialize_stellar_asset_contract(&contract_id, &args.contract_id_preimage)?;
184 if matches!(args.executable, ContractExecutable::Wasm(_)) {
185 self.call_constructor(&contract_id, constructor_args)?;
186 }
187 self.add_host_object(ScAddress::Contract(contract_id))
188 }
189
190 pub(crate) fn get_contract_id_hash(
191 &self,
192 deployer: AddressObject,
193 salt: BytesObject,
194 ) -> Result<Hash, HostError> {
195 let contract_id_preimage = ContractIdPreimage::Address(ContractIdPreimageFromAddress {
196 address: self.visit_obj(deployer, |addr: &ScAddress| addr.metered_clone(self))?,
197 salt: self.u256_from_bytesobj_input("contract_id_salt", salt)?,
198 });
199
200 let id_preimage =
201 self.get_full_contract_id_preimage(contract_id_preimage.metered_clone(self)?)?;
202 Ok(Hash(self.metered_hash_xdr(&id_preimage)?))
203 }
204
205 pub(crate) fn get_asset_contract_id_hash(&self, asset: Asset) -> Result<Hash, HostError> {
206 let id_preimage = self.get_full_contract_id_preimage(ContractIdPreimage::Asset(asset))?;
207 let id_arr: [u8; 32] = self.metered_hash_xdr(&id_preimage)?;
208 Ok(Hash(id_arr))
209 }
210
211 pub(crate) fn upload_contract_wasm(&self, wasm: Vec<u8>) -> Result<BytesObject, HostError> {
212 let hash_bytes: [u8; 32] = crypto::sha256_hash_from_bytes(wasm.as_slice(), self)?
213 .try_into()
214 .map_err(|_| {
215 self.err(
216 ScErrorType::Value,
217 ScErrorCode::InternalError,
218 "unexpected hash length",
219 &[],
220 )
221 })?;
222
223 let wasm_bytes_m: crate::xdr::BytesM = wasm.try_into().map_err(|_| {
225 self.err(
226 ScErrorType::Value,
227 ScErrorCode::ExceededLimit,
228 "Wasm code is too large",
229 &[],
230 )
231 })?;
232
233 let mut ext = crate::xdr::ContractCodeEntryExt::V0;
234
235 if cfg!(any(test, feature = "testutils")) && wasm_bytes_m.as_slice().is_empty() {
244 } else {
248 let _check_vm = Vm::new(
249 self,
250 Hash(hash_bytes.metered_clone(self)?),
251 wasm_bytes_m.as_slice(),
252 )?;
253 _check_vm.module.cost_inputs.charge_for_parsing(self)?;
257 ext = crate::xdr::ContractCodeEntryExt::V1(crate::xdr::ContractCodeEntryV1 {
258 ext: ExtensionPoint::V0,
259 cost_inputs: crate::vm::ParsedModule::extract_refined_contract_cost_inputs(
260 self,
261 wasm_bytes_m.as_slice(),
262 )?,
263 });
264 }
265
266 let hash_obj = self.add_host_object(self.scbytes_from_slice(hash_bytes.as_slice())?)?;
267 let code_key = Rc::metered_new(
268 LedgerKey::ContractCode(LedgerKeyContractCode {
269 hash: Hash(hash_bytes.metered_clone(self)?),
270 }),
271 self,
272 )?;
273
274 let mut storage = self.try_borrow_storage_mut()?;
275
276 #[allow(unused_mut)]
278 let mut should_put_contract = !storage.has_with_host(&code_key, self, None)?;
279
280 if !should_put_contract {
282 let entry = storage.get_with_host(&code_key, self, None)?;
283 if let crate::xdr::LedgerEntryData::ContractCode(ContractCodeEntry {
284 ext: old_ext,
285 ..
286 }) = &entry.data
287 {
288 should_put_contract = *old_ext != ext;
289 }
290 }
291
292 if should_put_contract {
293 let data = ContractCodeEntry {
294 hash: Hash(hash_bytes),
295 ext,
296 code: wasm_bytes_m,
297 };
298 storage.put_with_host(
299 &code_key,
300 &Host::new_contract_code(self, data)?,
301 Some(self.get_min_live_until_ledger(ContractDataDurability::Persistent)?),
302 self,
303 None,
304 )?;
305 }
306 Ok(hash_obj)
307 }
308}
309
310use super::frame::CallParams;
311#[cfg(any(test, feature = "testutils"))]
312use super::ContractFunctionSet;
313
314#[cfg(any(test, feature = "testutils"))]
316impl Host {
317 pub fn register_test_contract(
318 &self,
319 contract_address: AddressObject,
320 contract_fns: Rc<dyn ContractFunctionSet>,
321 ) -> Result<(), HostError> {
322 #[cfg(any(test, feature = "testutils"))]
323 let _invocation_meter_scope = self.maybe_meter_invocation()?;
324
325 use crate::Env;
326 self.register_test_contract_with_constructor(
327 contract_address,
328 contract_fns,
329 self.vec_new()?,
330 )
331 }
332
333 pub fn register_test_contract_with_constructor(
334 &self,
335 contract_address: AddressObject,
336 contract_fns: Rc<dyn ContractFunctionSet>,
337 constructor_args: crate::VecObject,
338 ) -> Result<(), HostError> {
339 #[cfg(any(test, feature = "testutils"))]
340 let _invocation_meter_scope = self.maybe_meter_invocation()?;
341
342 let contract_id = self.contract_id_from_address(contract_address)?;
343 let instance_key = self.contract_instance_ledger_key(&contract_id)?;
344 let wasm_hash_obj = self.upload_contract_wasm(vec![])?;
345 let wasm_hash = self.hash_from_bytesobj_input("wasm_hash", wasm_hash_obj)?;
346 self.store_contract_instance(
352 Some(ContractExecutable::Wasm(wasm_hash)),
353 None,
354 contract_id.clone(),
355 &instance_key,
356 )?;
357 self.try_borrow_contracts_mut()?
358 .insert(contract_id.clone(), contract_fns);
359
360 self.call_constructor(&contract_id, self.call_args_from_obj(constructor_args)?)
361 }
362
363 pub fn call_constructor_for_stored_contract_unsafe(
369 &self,
370 contract_id: &Hash,
371 constructor_args: crate::VecObject,
372 ) -> Result<(), HostError> {
373 self.call_constructor(&contract_id, self.call_args_from_obj(constructor_args)?)
374 }
375}