1use core::cmp::min;
2use std::rc::Rc;
3
4use crate::{
5 budget::AsBudget,
6 err,
7 host::metered_clone::{MeteredAlloc, MeteredClone},
8 storage::{InstanceStorageMap, Storage},
9 vm::VersionedContractCodeCostInputs,
10 xdr::{
11 AccountEntry, AccountId, Asset, BytesM, ContractCodeEntry, ContractDataDurability,
12 ContractDataEntry, ContractExecutable, ContractId, ContractIdPreimage, ExtensionPoint,
13 Hash, HashIdPreimage, HashIdPreimageContractId, LedgerEntry, LedgerEntryData,
14 LedgerEntryExt, LedgerKey, LedgerKeyAccount, LedgerKeyContractCode, LedgerKeyContractData,
15 LedgerKeyTrustLine, PublicKey, ScAddress, ScContractInstance, ScErrorCode, ScErrorType,
16 ScMap, ScVal, Signer, SignerKey, ThresholdIndexes, TrustLineAsset, Uint256,
17 },
18 AddressObject, Env, ErrorHandler, Host, HostError, StorageType, U32Val, Val,
19};
20
21impl Host {
22 pub(crate) fn with_mut_storage<F, U>(&self, f: F) -> Result<U, HostError>
23 where
24 F: FnOnce(&mut Storage) -> Result<U, HostError>,
25 {
26 f(&mut *self.try_borrow_storage_mut()?)
27 }
28
29 pub(crate) fn with_instance_storage<F, U>(&self, f: F) -> Result<U, HostError>
33 where
34 F: FnOnce(&InstanceStorageMap) -> Result<U, HostError>,
35 {
36 self.with_current_context_mut(|ctx| {
37 self.maybe_init_instance_storage(ctx)?;
38 f(ctx.storage.as_ref().ok_or_else(|| {
39 self.err(
40 ScErrorType::Context,
41 ScErrorCode::InternalError,
42 "missing instance storage",
43 &[],
44 )
45 })?)
46 })
47 }
48
49 pub(crate) fn with_mut_instance_storage<F, U>(&self, f: F) -> Result<U, HostError>
53 where
54 F: FnOnce(&mut InstanceStorageMap) -> Result<U, HostError>,
55 {
56 self.with_current_context_mut(|ctx| {
57 self.maybe_init_instance_storage(ctx)?;
58 let storage = ctx.storage.as_mut().ok_or_else(|| {
59 self.err(
60 ScErrorType::Context,
61 ScErrorCode::InternalError,
62 "missing instance storage",
63 &[],
64 )
65 })?;
66 storage.is_modified = true;
71 f(storage)
72 })
73 }
74
75 pub(crate) fn contract_instance_ledger_key(
76 &self,
77 contract_id: &ContractId,
78 ) -> Result<Rc<LedgerKey>, HostError> {
79 let contract_id = contract_id.metered_clone(self)?;
80 Rc::metered_new(
81 LedgerKey::ContractData(LedgerKeyContractData {
82 key: ScVal::LedgerKeyContractInstance,
83 durability: ContractDataDurability::Persistent,
84 contract: ScAddress::Contract(contract_id),
85 }),
86 self,
87 )
88 }
89
90 pub(crate) fn extract_contract_instance_from_ledger_entry(
91 &self,
92 entry: &LedgerEntry,
93 ) -> Result<ScContractInstance, HostError> {
94 match &entry.data {
95 LedgerEntryData::ContractData(e) => match &e.val {
96 ScVal::ContractInstance(instance) => instance.metered_clone(self),
97 _ => Err(self.err(
98 ScErrorType::Storage,
99 ScErrorCode::InternalError,
100 "ledger entry for contract instance does not contain contract instance",
101 &[],
102 )),
103 },
104 _ => Err(self.err(
105 ScErrorType::Storage,
106 ScErrorCode::InternalError,
107 "expected ContractData ledger entry",
108 &[],
109 )),
110 }
111 }
112
113 pub(crate) fn retrieve_contract_instance_from_storage(
115 &self,
116 key: &Rc<LedgerKey>,
117 ) -> Result<ScContractInstance, HostError> {
118 let entry = self.try_borrow_storage_mut()?.get(key, self, None)?;
119 self.extract_contract_instance_from_ledger_entry(&entry)
120 }
121
122 pub(crate) fn contract_code_ledger_key(
123 &self,
124 wasm_hash: &Hash,
125 ) -> Result<Rc<LedgerKey>, HostError> {
126 let wasm_hash = wasm_hash.metered_clone(self)?;
127 Rc::metered_new(
128 LedgerKey::ContractCode(LedgerKeyContractCode { hash: wasm_hash }),
129 self,
130 )
131 }
132
133 pub(crate) fn retrieve_wasm_from_storage(
134 &self,
135 wasm_hash: &Hash,
136 ) -> Result<(BytesM, VersionedContractCodeCostInputs), HostError> {
137 let key = self.contract_code_ledger_key(wasm_hash)?;
138 match &self.try_borrow_storage_mut()?.get(&key, self, None)?.data {
139 LedgerEntryData::ContractCode(e) => {
140 let code = e.code.metered_clone(self)?;
141 let costs = match &e.ext {
142 crate::xdr::ContractCodeEntryExt::V0 => VersionedContractCodeCostInputs::V0 {
143 wasm_bytes: code.len(),
144 },
145 crate::xdr::ContractCodeEntryExt::V1(v1) => {
146 VersionedContractCodeCostInputs::V1(
147 v1.cost_inputs.metered_clone(self.as_budget())?,
148 )
149 }
150 };
151 Ok((code, costs))
152 }
153 _ => Err(err!(
154 self,
155 (ScErrorType::Storage, ScErrorCode::InternalError),
156 "expected ContractCode ledger entry",
157 *wasm_hash
158 )),
159 }
160 }
161
162 pub(crate) fn wasm_exists(&self, wasm_hash: &Hash) -> Result<bool, HostError> {
163 let key = self.contract_code_ledger_key(wasm_hash)?;
164 self.try_borrow_storage_mut()?.has(&key, self, None)
165 }
166
167 pub(crate) fn store_contract_instance(
174 &self,
175 executable: Option<ContractExecutable>,
176 instance_storage: Option<ScMap>,
177 contract_id: ContractId,
178 key: &Rc<LedgerKey>,
179 ) -> Result<(), HostError> {
180 if self.try_borrow_storage_mut()?.has(key, self, None)? {
181 let (current, live_until_ledger) = self
182 .try_borrow_storage_mut()?
183 .get_with_live_until_ledger(key, self, None)?;
184 let mut current = (*current).metered_clone(self)?;
185
186 if let LedgerEntryData::ContractData(ref mut entry) = current.data {
187 if let ScVal::ContractInstance(ref mut instance) = entry.val {
188 if let Some(executable) = executable {
189 instance.executable = executable;
190 }
191 if let Some(storage) = instance_storage {
192 instance.storage = Some(storage);
193 }
194 } else {
195 return Err(self.err(
196 ScErrorType::Storage,
197 ScErrorCode::InternalError,
198 "expected ScVal::ContractInstance for contract instance",
199 &[],
200 ));
201 }
202 } else {
203 return Err(self.err(
204 ScErrorType::Storage,
205 ScErrorCode::InternalError,
206 "expected DataEntry for contract instance",
207 &[],
208 ));
209 }
210
211 self.try_borrow_storage_mut()?.put(
212 &key,
213 &Rc::metered_new(current, self)?,
214 live_until_ledger,
215 self,
216 None,
217 )?;
218 } else {
219 let data = ContractDataEntry {
220 contract: ScAddress::Contract(contract_id.metered_clone(self)?),
221 key: ScVal::LedgerKeyContractInstance,
222 val: ScVal::ContractInstance(ScContractInstance {
223 executable: executable.ok_or_else(|| {
224 self.err(
225 ScErrorType::Context,
226 ScErrorCode::InternalError,
227 "can't initialize contract without executable",
228 &[],
229 )
230 })?,
231 storage: instance_storage,
232 }),
233 durability: ContractDataDurability::Persistent,
234 ext: ExtensionPoint::V0,
235 };
236 self.try_borrow_storage_mut()?.put(
237 key,
238 &Host::new_contract_data(self, data)?,
239 Some(self.get_min_live_until_ledger(ContractDataDurability::Persistent)?),
240 self,
241 None,
242 )?;
243 }
244 Ok(())
245 }
246
247 pub(crate) fn extend_contract_code_ttl_from_contract_id(
248 &self,
249 instance_key: Rc<LedgerKey>,
250 threshold: u32,
251 extend_to: u32,
252 ) -> Result<(), HostError> {
253 match self
254 .retrieve_contract_instance_from_storage(&instance_key)?
255 .executable
256 {
257 ContractExecutable::Wasm(wasm_hash) => {
258 let key = self.contract_code_ledger_key(&wasm_hash)?;
259 self.try_borrow_storage_mut()?
260 .extend_ttl(self, key, threshold, extend_to, None)?;
261 }
262 ContractExecutable::StellarAsset => {}
263 }
264 Ok(())
265 }
266
267 pub(crate) fn extend_contract_instance_ttl_from_contract_id(
268 &self,
269 instance_key: Rc<LedgerKey>,
270 threshold: u32,
271 extend_to: u32,
272 ) -> Result<(), HostError> {
273 self.try_borrow_storage_mut()?.extend_ttl(
274 self,
275 instance_key.metered_clone(self)?,
276 threshold,
277 extend_to,
278 None,
279 )?;
280 Ok(())
281 }
282
283 pub(crate) fn get_full_contract_id_preimage(
285 &self,
286 init_preimage: ContractIdPreimage,
287 ) -> Result<HashIdPreimage, HostError> {
288 Ok(HashIdPreimage::ContractId(HashIdPreimageContractId {
289 network_id: self
290 .hash_from_bytesobj_input("network_id", self.get_ledger_network_id()?)?,
291 contract_id_preimage: init_preimage,
292 }))
293 }
294
295 pub(crate) fn load_account(&self, account_id: AccountId) -> Result<AccountEntry, HostError> {
297 let acc = self.to_account_key(account_id)?;
298 self.with_mut_storage(|storage| match &storage.get(&acc, self, None)?.data {
299 LedgerEntryData::Account(ae) => ae.metered_clone(self),
300 e => Err(err!(
301 self,
302 (ScErrorType::Storage, ScErrorCode::InternalError),
303 "ledger entry is not account",
304 e.name()
305 )),
306 })
307 }
308
309 pub(crate) fn to_account_key(&self, account_id: AccountId) -> Result<Rc<LedgerKey>, HostError> {
310 Rc::metered_new(LedgerKey::Account(LedgerKeyAccount { account_id }), self)
311 }
312
313 pub(crate) fn create_asset_4(&self, asset_code: [u8; 4], issuer: AccountId) -> Asset {
314 use crate::xdr::{AlphaNum4, AssetCode4};
315 Asset::CreditAlphanum4(AlphaNum4 {
316 asset_code: AssetCode4(asset_code),
317 issuer,
318 })
319 }
320
321 pub(crate) fn create_asset_12(&self, asset_code: [u8; 12], issuer: AccountId) -> Asset {
322 use crate::xdr::{AlphaNum12, AssetCode12};
323 Asset::CreditAlphanum12(AlphaNum12 {
324 asset_code: AssetCode12(asset_code),
325 issuer,
326 })
327 }
328
329 pub(crate) fn to_trustline_key(
330 &self,
331 account_id: AccountId,
332 asset: TrustLineAsset,
333 ) -> Result<Rc<LedgerKey>, HostError> {
334 Rc::metered_new(
335 LedgerKey::Trustline(LedgerKeyTrustLine { account_id, asset }),
336 self,
337 )
338 }
339
340 pub(crate) fn get_signer_weight_from_account(
341 &self,
342 target_signer: Uint256,
343 account: &AccountEntry,
344 ) -> Result<u8, HostError> {
345 if account.account_id
346 == AccountId(PublicKey::PublicKeyTypeEd25519(
347 target_signer.metered_clone(self)?,
348 ))
349 {
350 let Some(threshold) = account
352 .thresholds
353 .0
354 .get(ThresholdIndexes::MasterWeight as usize)
355 else {
356 return Err(self.error(
357 (ScErrorType::Value, ScErrorCode::InternalError).into(),
358 "unexpected thresholds-array size",
359 &[],
360 ));
361 };
362 Ok(*threshold)
363 } else {
364 let signers: &Vec<Signer> = account.signers.as_ref();
366 for signer in signers {
367 if let SignerKey::Ed25519(ref this_signer) = signer.key {
368 if &target_signer == this_signer {
369 let weight = min(signer.weight, u8::MAX as u32);
374 return weight.try_into().map_err(|_| {
376 self.err(
377 ScErrorType::Auth,
378 ScErrorCode::ArithDomain,
379 "signer weight does not fit in u8",
380 &[U32Val::from(weight).to_val()],
381 )
382 });
383 }
384 }
385 }
386 Ok(0u8)
388 }
389 }
390
391 pub(crate) fn new_contract_data(
392 &self,
393 data: ContractDataEntry,
394 ) -> Result<Rc<LedgerEntry>, HostError> {
395 Rc::metered_new(
396 LedgerEntry {
397 last_modified_ledger_seq: 0,
400 data: LedgerEntryData::ContractData(data),
401 ext: LedgerEntryExt::V0,
402 },
403 self,
404 )
405 }
406
407 pub(crate) fn new_contract_code(
408 &self,
409 data: ContractCodeEntry,
410 ) -> Result<Rc<LedgerEntry>, HostError> {
411 Rc::metered_new(
412 LedgerEntry {
413 last_modified_ledger_seq: 0,
416 data: LedgerEntryData::ContractCode(data),
417 ext: LedgerEntryExt::V0,
418 },
419 self,
420 )
421 }
422
423 pub(crate) fn modify_ledger_entry_data(
424 &self,
425 original_entry: &LedgerEntry,
426 new_data: LedgerEntryData,
427 ) -> Result<Rc<LedgerEntry>, HostError> {
428 Rc::metered_new(
429 LedgerEntry {
430 last_modified_ledger_seq: 0,
433 data: new_data,
434 ext: original_entry.ext.metered_clone(self)?,
435 },
436 self,
437 )
438 }
439
440 pub(crate) fn contract_id_from_scaddress(
441 &self,
442 address: ScAddress,
443 ) -> Result<ContractId, HostError> {
444 match address {
445 ScAddress::Contract(contract_id) => Ok(contract_id),
446 _ => Err(self.err(
447 ScErrorType::Object,
448 ScErrorCode::InvalidInput,
449 "not a contract address",
450 &[],
451 )),
452 }
453 }
454
455 pub(crate) fn contract_id_from_address(
456 &self,
457 address: AddressObject,
458 ) -> Result<ContractId, HostError> {
459 self.visit_obj(address, |addr: &ScAddress| {
460 self.contract_id_from_scaddress(addr.metered_clone(self)?)
461 })
462 }
463
464 pub(super) fn put_contract_data_into_ledger(
465 &self,
466 k: Val,
467 v: Val,
468 t: StorageType,
469 ) -> Result<(), HostError> {
470 let durability: ContractDataDurability = t.try_into()?;
471 let key = self.storage_key_from_val(k, durability)?;
472 if self.try_borrow_storage_mut()?.has(&key, self, Some(k))? {
477 let (current, live_until_ledger) = self
478 .try_borrow_storage_mut()?
479 .get_with_live_until_ledger(&key, self, Some(k))?;
480 let mut current = (*current).metered_clone(self)?;
481 match current.data {
482 LedgerEntryData::ContractData(ref mut entry) => {
483 entry.val = self.from_host_val(v)?;
484 }
485 _ => {
486 return Err(self.err(
487 ScErrorType::Storage,
488 ScErrorCode::InternalError,
489 "expected DataEntry",
490 &[],
491 ));
492 }
493 }
494 self.try_borrow_storage_mut()?.put(
495 &key,
496 &Rc::metered_new(current, self)?,
497 live_until_ledger,
498 self,
499 Some(k),
500 )?;
501 } else {
502 let data = ContractDataEntry {
503 contract: ScAddress::Contract(self.get_current_contract_id_internal()?),
504 key: self.from_host_val(k)?,
505 val: self.from_host_val(v)?,
506 durability,
507 ext: ExtensionPoint::V0,
508 };
509 self.try_borrow_storage_mut()?.put(
510 &key,
511 &Host::new_contract_data(self, data)?,
512 Some(self.get_min_live_until_ledger(durability)?),
513 self,
514 Some(k),
515 )?;
516 }
517
518 Ok(())
519 }
520}
521
522#[cfg(any(test, feature = "testutils"))]
523use crate::crypto;
524#[cfg(any(test, feature = "testutils"))]
525use crate::storage::{AccessType, EntryWithLiveUntil, Footprint};
526
527#[cfg(any(test, feature = "testutils"))]
528impl Host {
529 pub fn add_ledger_entry(
531 &self,
532 key: &Rc<LedgerKey>,
533 val: &Rc<soroban_env_common::xdr::LedgerEntry>,
534 live_until_ledger: Option<u32>,
535 ) -> Result<(), HostError> {
536 self.with_mut_storage(|storage| storage.put(key, val, live_until_ledger, self, None))
537 }
538
539 pub fn get_ledger_entry(
543 &self,
544 key: &Rc<LedgerKey>,
545 ) -> Result<Option<EntryWithLiveUntil>, HostError> {
546 self.with_mut_storage(|storage| storage.try_get_full(key, self, None))
547 }
548
549 #[allow(clippy::type_complexity)]
551 pub fn get_stored_entries(
552 &self,
553 ) -> Result<Vec<(Rc<LedgerKey>, Option<EntryWithLiveUntil>)>, HostError> {
554 self.with_mut_storage(|storage| Ok(storage.map.map.clone()))
555 }
556
557 pub fn setup_storage_entry(
560 &self,
561 key: Rc<LedgerKey>,
562 val: Option<(Rc<soroban_env_common::xdr::LedgerEntry>, Option<u32>)>,
563 access_type: AccessType,
564 ) -> Result<(), HostError> {
565 self.with_mut_storage(|storage| {
566 storage
567 .footprint
568 .record_access(&key, access_type, self.as_budget())?;
569 storage.map = storage.map.insert(key, val, self.as_budget())?;
570 Ok(())
571 })
572 }
573
574 pub fn setup_storage_footprint(&self, footprint: Footprint) -> Result<(), HostError> {
578 for (key, access_type) in footprint.0.map {
579 self.setup_storage_entry(key, None, access_type)?;
580 }
581 Ok(())
582 }
583
584 pub(crate) fn is_test_contract_executable(
587 &self,
588 contract_id: &ContractId,
589 ) -> Result<bool, HostError> {
590 let key = self.contract_instance_ledger_key(contract_id)?;
591 let instance = self.retrieve_contract_instance_from_storage(&key)?;
592 let test_contract_executable = ContractExecutable::Wasm(
593 crypto::sha256_hash_from_bytes(&[], self)?
594 .try_into()
595 .map_err(|_| {
596 self.err(
597 ScErrorType::Value,
598 ScErrorCode::InternalError,
599 "unexpected hash length",
600 &[],
601 )
602 })?,
603 );
604 Ok(test_contract_executable == instance.executable)
605 }
606
607 #[cfg(test)]
608 pub(crate) fn create_tl_asset_4(
609 &self,
610 asset_code: [u8; 4],
611 issuer: AccountId,
612 ) -> TrustLineAsset {
613 use crate::xdr::{AlphaNum4, AssetCode4};
614 TrustLineAsset::CreditAlphanum4(AlphaNum4 {
615 asset_code: AssetCode4(asset_code),
616 issuer,
617 })
618 }
619
620 #[cfg(test)]
621 pub(crate) fn create_tl_asset_12(
622 &self,
623 asset_code: [u8; 12],
624 issuer: AccountId,
625 ) -> TrustLineAsset {
626 use crate::xdr::{AlphaNum12, AssetCode12};
627 TrustLineAsset::CreditAlphanum12(AlphaNum12 {
628 asset_code: AssetCode12(asset_code),
629 issuer,
630 })
631 }
632}