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