1use soroban_env_common::Env;
2
3use crate::{
4 builtin_contracts::account_contract::ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME,
5 e2e_invoke::{encode_contract_events, entry_size_for_rent},
6 fees::{FeeConfiguration, DATA_SIZE_1KB_INCREMENT, INSTRUCTIONS_INCREMENT, TTL_ENTRY_SIZE},
7 ledger_info::get_key_durability,
8 storage::{is_persistent_key, AccessType, Storage},
9 xdr::{
10 ContractDataDurability, ContractId, HostFunction, LedgerKey, ScAddress, ScErrorCode,
11 ScErrorType, ScSymbol,
12 },
13 AddressObject, Symbol, SymbolStr, TryFromVal,
14};
15
16use super::{metered_xdr::metered_write_xdr, Host, HostError};
17
18#[derive(Default, Clone, Debug, Eq, PartialEq)]
29pub struct InvocationResources {
30 pub instructions: i64,
32 pub mem_bytes: i64,
37 pub disk_read_entries: u32,
45 pub memory_read_entries: u32,
53 pub write_entries: u32,
56 pub disk_read_bytes: u32,
64 pub write_bytes: u32,
66 pub contract_events_size_bytes: u32,
68 pub persistent_rent_ledger_bytes: i64,
72 pub persistent_entry_rent_bumps: u32,
74 pub temporary_rent_ledger_bytes: i64,
78 pub temporary_entry_rent_bumps: u32,
80}
81
82#[derive(Default, Clone, Debug, Eq, PartialEq)]
88pub struct SubInvocationResources {
89 pub instructions: i64,
91 pub mem_bytes: i64,
96 pub disk_read_entries: i32,
104 pub memory_read_entries: i32,
112 pub write_entries: i32,
115 pub disk_read_bytes: i32,
123 pub write_bytes: i32,
125 pub contract_events_size_bytes: i32,
127 pub persistent_rent_ledger_bytes: i64,
131 pub persistent_entry_rent_bumps: i32,
133 pub temporary_rent_ledger_bytes: i64,
137 pub temporary_entry_rent_bumps: i32,
139}
140
141impl From<SubInvocationResources> for InvocationResources {
142 fn from(sub: SubInvocationResources) -> Self {
143 Self {
144 instructions: sub.instructions,
145 mem_bytes: sub.mem_bytes,
146 disk_read_entries: sub.disk_read_entries.max(0) as u32,
147 memory_read_entries: sub.memory_read_entries.max(0) as u32,
148 write_entries: sub.write_entries.max(0) as u32,
149 disk_read_bytes: sub.disk_read_bytes.max(0) as u32,
150 write_bytes: sub.write_bytes.max(0) as u32,
151 contract_events_size_bytes: sub.contract_events_size_bytes.max(0) as u32,
152 persistent_rent_ledger_bytes: sub.persistent_rent_ledger_bytes,
153 persistent_entry_rent_bumps: sub.persistent_entry_rent_bumps.max(0) as u32,
154 temporary_rent_ledger_bytes: sub.temporary_rent_ledger_bytes,
155 temporary_entry_rent_bumps: sub.temporary_entry_rent_bumps.max(0) as u32,
156 }
157 }
158}
159
160impl SubInvocationResources {
161 fn subtract(mut self, other: &SubInvocationResources) -> Self {
162 self.instructions = self.instructions.saturating_sub(other.instructions);
163 self.mem_bytes = self.mem_bytes.saturating_sub(other.mem_bytes);
164 self.disk_read_entries = self
165 .disk_read_entries
166 .saturating_sub(other.disk_read_entries);
167 self.memory_read_entries = self
168 .memory_read_entries
169 .saturating_sub(other.memory_read_entries);
170 self.write_entries = self.write_entries.saturating_sub(other.write_entries);
171 self.disk_read_bytes = self.disk_read_bytes.saturating_sub(other.disk_read_bytes);
172 self.write_bytes = self.write_bytes.saturating_sub(other.write_bytes);
173 self.contract_events_size_bytes = self
174 .contract_events_size_bytes
175 .saturating_sub(other.contract_events_size_bytes);
176 self.persistent_rent_ledger_bytes = self
177 .persistent_rent_ledger_bytes
178 .saturating_sub(other.persistent_rent_ledger_bytes);
179 self.persistent_entry_rent_bumps = self
180 .persistent_entry_rent_bumps
181 .saturating_sub(other.persistent_entry_rent_bumps);
182 self.temporary_rent_ledger_bytes = self
183 .temporary_rent_ledger_bytes
184 .saturating_sub(other.temporary_rent_ledger_bytes);
185 self.temporary_entry_rent_bumps = self
186 .temporary_entry_rent_bumps
187 .saturating_sub(other.temporary_entry_rent_bumps);
188 self
189 }
190}
191
192#[derive(Clone, Debug, Eq, PartialEq)]
193pub struct DetailedInvocationResources {
194 pub invocation: MeteringInvocation,
196 pub resources: SubInvocationResources,
198 pub sub_call_resources: Vec<DetailedInvocationResources>,
201}
202
203#[derive(Default, Clone, Debug, Eq, PartialEq)]
209pub struct FeeEstimate {
210 pub total: i64,
212 pub instructions: i64,
214 pub disk_read_entries: i64,
216 pub write_entries: i64,
218 pub disk_read_bytes: i64,
220 pub write_bytes: i64,
222 pub contract_events: i64,
224 pub persistent_entry_rent: i64,
226 pub temporary_entry_rent: i64,
228}
229
230#[derive(Clone, Debug, Eq, PartialEq)]
231pub struct DetailedFeeEstimate {
232 pub invocation: MeteringInvocation,
234 pub fee_estimate: FeeEstimate,
236 pub sub_call_fee_estimates: Vec<DetailedFeeEstimate>,
238}
239
240impl InvocationResources {
241 pub fn estimate_fees(
250 &self,
251 fee_config: &FeeConfiguration,
252 fee_per_rent_1kb: i64,
253 persistent_rent_rate_denominator: i64,
254 temporary_rent_rate_denominator: i64,
255 ) -> FeeEstimate {
256 let instructions = compute_fee_per_increment(
257 self.instructions,
258 fee_config.fee_per_instruction_increment,
259 INSTRUCTIONS_INCREMENT,
260 );
261 let disk_read_entries = fee_config.fee_per_disk_read_entry.saturating_mul(
262 self.disk_read_entries
263 .saturating_add(self.write_entries)
264 .into(),
265 );
266 let write_entries = fee_config
267 .fee_per_write_entry
268 .saturating_mul(self.write_entries.into());
269 let disk_read_bytes = compute_fee_per_increment(
270 self.disk_read_bytes.into(),
271 fee_config.fee_per_disk_read_1kb,
272 DATA_SIZE_1KB_INCREMENT,
273 );
274 let write_bytes = compute_fee_per_increment(
275 self.write_bytes.into(),
276 fee_config.fee_per_write_1kb,
277 DATA_SIZE_1KB_INCREMENT,
278 );
279 let contract_events = compute_fee_per_increment(
280 self.contract_events_size_bytes.into(),
281 fee_config.fee_per_contract_event_1kb,
282 DATA_SIZE_1KB_INCREMENT,
283 );
284
285 let mut persistent_entry_ttl_entry_writes = fee_config
286 .fee_per_write_entry
287 .saturating_mul(self.persistent_entry_rent_bumps.into());
288 persistent_entry_ttl_entry_writes =
289 persistent_entry_ttl_entry_writes.saturating_add(compute_fee_per_increment(
290 (TTL_ENTRY_SIZE as i64).saturating_mul(self.persistent_entry_rent_bumps.into()),
291 fee_config.fee_per_write_1kb,
292 DATA_SIZE_1KB_INCREMENT,
293 ));
294 let mut temp_entry_ttl_entry_writes = fee_config
295 .fee_per_write_entry
296 .saturating_mul(self.temporary_entry_rent_bumps.into());
297 temp_entry_ttl_entry_writes =
298 temp_entry_ttl_entry_writes.saturating_add(compute_fee_per_increment(
299 (TTL_ENTRY_SIZE as i64).saturating_mul(self.temporary_entry_rent_bumps.into()),
300 fee_config.fee_per_write_1kb,
301 DATA_SIZE_1KB_INCREMENT,
302 ));
303
304 let persistent_entry_rent = compute_fee_per_increment(
305 self.persistent_rent_ledger_bytes,
306 fee_per_rent_1kb,
307 DATA_SIZE_1KB_INCREMENT.saturating_mul(persistent_rent_rate_denominator),
308 )
309 .saturating_add(persistent_entry_ttl_entry_writes);
310 let temporary_entry_rent = compute_fee_per_increment(
311 self.temporary_rent_ledger_bytes,
312 fee_per_rent_1kb,
313 DATA_SIZE_1KB_INCREMENT.saturating_mul(temporary_rent_rate_denominator),
314 )
315 .saturating_add(temp_entry_ttl_entry_writes);
316 let total = instructions
317 .saturating_add(disk_read_entries)
318 .saturating_add(write_entries)
319 .saturating_add(disk_read_bytes)
320 .saturating_add(write_bytes)
321 .saturating_add(contract_events)
322 .saturating_add(persistent_entry_rent)
323 .saturating_add(temporary_entry_rent);
324 FeeEstimate {
325 total,
326 instructions,
327 disk_read_entries,
328 write_entries,
329 disk_read_bytes,
330 write_bytes,
331 contract_events,
332 persistent_entry_rent,
333 temporary_entry_rent,
334 }
335 }
336}
337
338impl DetailedInvocationResources {
339 pub fn estimate_fees(
348 &self,
349 fee_config: &FeeConfiguration,
350 fee_per_rent_1kb: i64,
351 persistent_rent_rate_denominator: i64,
352 temporary_rent_rate_denominator: i64,
353 ) -> DetailedFeeEstimate {
354 let resources: InvocationResources = self.resources.clone().into();
355 let fee_estimate = resources.estimate_fees(
356 fee_config,
357 fee_per_rent_1kb,
358 persistent_rent_rate_denominator,
359 temporary_rent_rate_denominator,
360 );
361 let sub_call_fee_estimates = self
362 .sub_call_resources
363 .iter()
364 .map(|r| {
365 r.estimate_fees(
366 fee_config,
367 fee_per_rent_1kb,
368 persistent_rent_rate_denominator,
369 temporary_rent_rate_denominator,
370 )
371 })
372 .collect();
373 DetailedFeeEstimate {
374 invocation: self.invocation.clone(),
375 fee_estimate,
376 sub_call_fee_estimates,
377 }
378 }
379}
380
381#[derive(Clone, Default)]
389pub(crate) struct InvocationMeter {
390 enabled: bool,
391 stack_depth: u32,
392 storage_snapshot: Storage,
393 detailed_invocation_resources: Option<DetailedInvocationResources>,
394}
395
396#[derive(Clone, Eq, PartialEq, Debug)]
398pub enum MeteringInvocation {
399 InvokeContract(ScAddress, ScSymbol),
402 WasmUploadEntryPoint,
407 CreateContractEntryPoint,
410}
411
412impl MeteringInvocation {
413 pub(crate) fn from_host_function(hf: &HostFunction) -> Self {
414 match hf {
415 HostFunction::InvokeContract(invoke_args) => MeteringInvocation::InvokeContract(
416 invoke_args.contract_address.clone(),
417 invoke_args.function_name.clone(),
418 ),
419 HostFunction::UploadContractWasm(_) => MeteringInvocation::WasmUploadEntryPoint,
420 HostFunction::CreateContract(_) | HostFunction::CreateContractV2(_) => {
421 MeteringInvocation::CreateContractEntryPoint
422 }
423 }
424 }
425
426 pub(crate) fn contract_invocation_with_address_obj(
427 host: &Host,
428 address: AddressObject,
429 function_name: Symbol,
430 ) -> Self {
431 let mut address_xdr = ScAddress::Contract(Default::default());
432 let mut function_name_xdr = ScSymbol::default();
433 host.with_debug_mode(|| {
434 address_xdr = host.visit_obj(address, |a: &ScAddress| Ok(a.clone()))?;
435 function_name_xdr = SymbolStr::try_from_val(host, &function_name)?
436 .to_string()
437 .as_str()
438 .try_into()
439 .map_err(|_| {
440 host.err(
441 ScErrorType::Value,
442 ScErrorCode::InternalError,
443 "can't convert Symbol to ScSymbol",
444 &[],
445 )
446 })?;
447 Ok(())
448 });
449 MeteringInvocation::InvokeContract(address_xdr, function_name_xdr)
450 }
451
452 pub(crate) fn contract_invocation(
453 host: &Host,
454 contract_id: &ContractId,
455 function_name: Symbol,
456 ) -> Self {
457 let address = ScAddress::Contract(contract_id.clone());
458 let mut function_name_xdr = ScSymbol::default();
459 host.with_debug_mode(|| {
460 function_name_xdr = SymbolStr::try_from_val(host, &function_name)?
461 .to_string()
462 .as_str()
463 .try_into()
464 .map_err(|_| {
465 host.err(
466 ScErrorType::Value,
467 ScErrorCode::InternalError,
468 "can't convert Symbol to ScSymbol",
469 &[],
470 )
471 })?;
472 Ok(())
473 });
474 MeteringInvocation::InvokeContract(address, function_name_xdr)
475 }
476
477 pub(crate) fn check_auth_invocation(host: &Host, address: AddressObject) -> Self {
478 let mut address_xdr = ScAddress::Contract(Default::default());
479 let function_name = ACCOUNT_CONTRACT_CHECK_AUTH_FN_NAME
480 .try_into()
481 .unwrap_or_default();
482 host.with_debug_mode(|| {
483 address_xdr = host.visit_obj(address, |a: &ScAddress| Ok(a.clone()))?;
484 Ok(())
485 });
486 MeteringInvocation::InvokeContract(address_xdr, function_name)
487 }
488}
489pub(crate) struct InvocationMeterScope<'a> {
492 host: &'a Host,
493}
494
495impl<'a> InvocationMeterScope<'a> {
496 fn new(host: &'a Host) -> Self {
497 Self { host }
498 }
499}
500impl Drop for InvocationMeterScope<'_> {
501 fn drop(&mut self) {
502 if let Ok(mut meter) = self.host.try_borrow_invocation_meter_mut() {
503 let _res = meter.pop_invocation(self.host);
504 _res.unwrap();
505 }
506 }
507}
508
509impl InvocationMeter {
510 pub(crate) fn get_root_invocation_resources(&self) -> Option<InvocationResources> {
512 self.detailed_invocation_resources
513 .as_ref()
514 .map(|r| r.resources.clone().into())
515 }
516
517 pub(crate) fn get_detailed_invocation_resources(&self) -> Option<DetailedInvocationResources> {
520 self.detailed_invocation_resources.clone()
521 }
522
523 fn push_invocation<'a>(
524 &mut self,
525 host: &'a Host,
526 invocation: MeteringInvocation,
527 ) -> Result<Option<InvocationMeterScope<'a>>, HostError> {
528 if !self.enabled {
529 return Ok(None);
530 }
531 if self.stack_depth == 0 {
532 host.budget_ref().reset()?;
534 host.try_borrow_events_mut()?.clear();
535 host.try_borrow_storage_mut()?.reset_footprint();
538 self.storage_snapshot = host.try_borrow_storage()?.clone();
539 self.stack_depth = 1;
540 self.detailed_invocation_resources = Some(DetailedInvocationResources {
541 invocation,
542 resources: host.snapshot_current_resources(&self.storage_snapshot),
543 sub_call_resources: vec![],
544 });
545 return Ok(Some(InvocationMeterScope::new(host)));
546 }
547 let mut parent_invocation_resources =
548 self.detailed_invocation_resources.as_mut().ok_or_else(|| {
549 host.err(
550 ScErrorType::Context,
551 ScErrorCode::InternalError,
552 "missing invocation resources for non-root invocation",
553 &[],
554 )
555 })?;
556
557 for _ in 0..(self.stack_depth - 1) {
558 parent_invocation_resources = parent_invocation_resources
559 .sub_call_resources
560 .last_mut()
561 .ok_or_else(|| {
562 host.err(
563 ScErrorType::Context,
564 ScErrorCode::InternalError,
565 "incorrect stack depth for invocation metering",
566 &[],
567 )
568 })?;
569 }
570 if parent_invocation_resources.invocation == invocation {
576 return Ok(None);
577 }
578
579 if !matches!(invocation, MeteringInvocation::InvokeContract(_, _)) {
583 return Ok(None);
584 }
585 parent_invocation_resources
586 .sub_call_resources
587 .push(DetailedInvocationResources {
588 invocation: invocation.clone(),
589 resources: host.snapshot_current_resources(&self.storage_snapshot),
590 sub_call_resources: vec![],
591 });
592
593 self.stack_depth += 1;
594 return Ok(Some(InvocationMeterScope::new(host)));
595 }
596
597 fn pop_invocation(&mut self, host: &Host) -> Result<(), HostError> {
598 if self.stack_depth == 0 {
599 return Ok(());
600 }
601 let mut current_invocation_resources =
602 self.detailed_invocation_resources.as_mut().ok_or_else(|| {
603 host.err(
604 ScErrorType::Context,
605 ScErrorCode::InternalError,
606 "missing invocation resources for non-root invocation",
607 &[],
608 )
609 })?;
610
611 for _ in 0..(self.stack_depth - 1) {
612 current_invocation_resources = current_invocation_resources
613 .sub_call_resources
614 .last_mut()
615 .ok_or_else(|| {
616 host.err(
617 ScErrorType::Context,
618 ScErrorCode::InternalError,
619 "incorrect stack depth for invocation metering",
620 &[],
621 )
622 })?;
623 }
624 current_invocation_resources.resources = host
625 .snapshot_current_resources(&self.storage_snapshot)
626 .subtract(¤t_invocation_resources.resources);
627
628 #[cfg(any(test, feature = "testutils"))]
633 if self.stack_depth == 1 {
634 host.budget_ref()
639 .with_shadow_mode(|| host.ensure_module_cache_contains_host_storage_contracts());
640 }
641
642 self.stack_depth -= 1;
643 Ok(())
644 }
645}
646
647impl Host {
648 pub(crate) fn maybe_meter_invocation(
654 &self,
655 invocation: MeteringInvocation,
656 ) -> Option<InvocationMeterScope<'_>> {
657 if let Ok(mut scope) = self.0.invocation_meter.try_borrow_mut() {
662 let res = scope.push_invocation(self, invocation);
663 if let Ok(maybe_scope) = res {
664 maybe_scope
665 } else {
666 #[cfg(any(test, feature = "testutils"))]
667 {
668 res.unwrap();
669 }
670 None
671 }
672 } else {
673 None
674 }
675 }
676
677 pub fn enable_invocation_metering(&self) {
679 self.enable_debug().unwrap();
682 if let Ok(mut meter) = self.0.invocation_meter.try_borrow_mut() {
683 meter.enabled = true;
684 }
685 }
686
687 fn snapshot_current_resources(
688 &self,
689 init_storage_snapshot: &Storage,
690 ) -> SubInvocationResources {
691 let mut invocation_resources = SubInvocationResources::default();
692 let budget = self.budget_ref();
693 invocation_resources.instructions =
694 budget.get_cpu_insns_consumed().unwrap_or_default() as i64;
695 invocation_resources.mem_bytes = budget.get_mem_bytes_consumed().unwrap_or_default() as i64;
696
697 self.with_debug_mode(|| {
700 let _res = self.try_snapshot_storage_and_event_resources(
701 init_storage_snapshot,
702 &mut invocation_resources,
703 );
704 #[cfg(test)]
705 _res.unwrap();
706 Ok(())
707 });
708
709 invocation_resources
710 }
711
712 fn try_snapshot_storage_and_event_resources(
713 &self,
714 init_storage_snapshot: &Storage,
715 invocation_resources: &mut SubInvocationResources,
716 ) -> Result<(), HostError> {
717 let mut curr_storage = self.try_borrow_storage_mut()?;
718 let curr_footprint = curr_storage.footprint.clone();
719
720 let curr_ledger_seq: u32 = self.get_ledger_sequence()?.into();
721 for (key, curr_access_type) in curr_footprint.0.iter(self.budget_ref())? {
722 let maybe_init_entry = init_storage_snapshot.get_from_map(key, self)?;
723 let mut init_entry_size_for_rent = 0;
724 let mut init_live_until_ledger = curr_ledger_seq;
725 let mut is_disk_read = match key.as_ref() {
726 LedgerKey::ContractData(_) | LedgerKey::ContractCode(_) => false,
727 _ => true,
728 };
729 if let Some((init_entry, init_entry_live_until)) = maybe_init_entry {
730 if let Some(live_until) = init_entry_live_until {
731 if live_until >= curr_ledger_seq {
732 init_live_until_ledger = live_until;
735 } else {
736 is_disk_read = is_persistent_key(key.as_ref());
740 }
741 }
742
743 let mut buf = Vec::<u8>::new();
744 metered_write_xdr(self.budget_ref(), init_entry.as_ref(), &mut buf)?;
745 if is_disk_read {
746 invocation_resources.disk_read_bytes += buf.len() as i32;
747 }
748 init_entry_size_for_rent =
749 entry_size_for_rent(self.budget_ref(), &init_entry, buf.len() as u32)?;
750 }
751 let mut entry_size = 0;
752 let mut new_entry_size_for_rent = 0;
753 let mut entry_live_until_ledger = None;
754 let maybe_entry = curr_storage.try_get_full(key, self, None)?;
755 if let Some((entry, entry_live_until)) = maybe_entry {
756 let mut buf = Vec::<u8>::new();
757 metered_write_xdr(self.budget_ref(), entry.as_ref(), &mut buf)?;
758 entry_size = buf.len() as u32;
759 new_entry_size_for_rent =
760 entry_size_for_rent(self.budget_ref(), &entry, entry_size)?;
761 entry_live_until_ledger = entry_live_until;
762 }
763 if is_disk_read {
764 invocation_resources.disk_read_entries += 1;
765 } else {
766 invocation_resources.memory_read_entries += 1;
767 }
768 if matches!(curr_access_type, AccessType::ReadWrite) {
769 invocation_resources.write_entries += 1;
770 invocation_resources.write_bytes += entry_size as i32;
771 }
772
773 if let Some(new_live_until) = entry_live_until_ledger {
774 let extension_ledgers = (new_live_until - init_live_until_ledger) as i64;
775 let rent_size_delta = if new_entry_size_for_rent > init_entry_size_for_rent {
776 (new_entry_size_for_rent - init_entry_size_for_rent) as i64
777 } else {
778 0
779 };
780 let existing_ledgers = (init_live_until_ledger - curr_ledger_seq) as i64;
781 let rent_ledger_bytes = existing_ledgers * rent_size_delta
782 + extension_ledgers * (new_entry_size_for_rent as i64);
783 if rent_ledger_bytes > 0 {
784 match get_key_durability(key.as_ref()) {
785 Some(ContractDataDurability::Temporary) => {
786 invocation_resources.temporary_rent_ledger_bytes += rent_ledger_bytes;
787 invocation_resources.temporary_entry_rent_bumps += 1;
788 }
789 Some(ContractDataDurability::Persistent) => {
790 invocation_resources.persistent_rent_ledger_bytes += rent_ledger_bytes;
791 invocation_resources.persistent_entry_rent_bumps += 1;
792 }
793 None => (),
794 }
795 }
796 }
797 }
798 let events = self.try_borrow_events()?.externalize(self)?;
799 let encoded_contract_events = encode_contract_events(self.budget_ref(), &events)?;
800 for event in &encoded_contract_events {
801 invocation_resources.contract_events_size_bytes += event.len() as i32;
802 }
803 Ok(())
804 }
805}
806
807fn compute_fee_per_increment(resource_value: i64, fee_rate: i64, increment: i64) -> i64 {
808 num_integer::div_ceil(resource_value.saturating_mul(fee_rate), increment.max(1))
809}
810
811#[cfg(test)]
812mod test {
813 use super::*;
814 use crate::{
815 xdr::{ContractId, Hash},
816 Symbol, TryFromVal, TryIntoVal,
817 };
818 use expect_test::expect;
819 use soroban_test_wasms::CONTRACT_STORAGE;
820
821 fn assert_resources_equal_to_budget(host: &Host) {
822 assert_eq!(
823 host.get_last_invocation_resources().unwrap().instructions as u64,
824 host.budget_ref().get_cpu_insns_consumed().unwrap()
825 );
826 assert_eq!(
827 host.get_last_invocation_resources().unwrap().mem_bytes as u64,
828 host.budget_ref().get_mem_bytes_consumed().unwrap()
829 );
830 }
831
832 #[test]
837 fn test_invocation_resource_metering() {
838 let host = Host::test_host_with_recording_footprint();
839 host.enable_invocation_metering();
840 host.enable_debug().unwrap();
841 host.with_mut_ledger_info(|li| {
842 li.sequence_number = 100;
843 li.max_entry_ttl = 10000;
844 li.min_persistent_entry_ttl = 1000;
845 li.min_temp_entry_ttl = 16;
846 })
847 .unwrap();
848
849 let contract_id = host.register_test_contract_wasm(CONTRACT_STORAGE);
850 expect![[r#"
853 InvocationResources {
854 instructions: 4199640,
855 mem_bytes: 2863204,
856 disk_read_entries: 0,
857 memory_read_entries: 2,
858 write_entries: 2,
859 disk_read_bytes: 0,
860 write_bytes: 3132,
861 contract_events_size_bytes: 0,
862 persistent_rent_ledger_bytes: 80531388,
863 persistent_entry_rent_bumps: 2,
864 temporary_rent_ledger_bytes: 0,
865 temporary_entry_rent_bumps: 0,
866 }"#]]
867 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
868 expect![[r#"
869 DetailedInvocationResources {
870 invocation: CreateContractEntryPoint,
871 resources: SubInvocationResources {
872 instructions: 4199640,
873 mem_bytes: 2863204,
874 disk_read_entries: 0,
875 memory_read_entries: 2,
876 write_entries: 2,
877 disk_read_bytes: 0,
878 write_bytes: 3132,
879 contract_events_size_bytes: 0,
880 persistent_rent_ledger_bytes: 80531388,
881 persistent_entry_rent_bumps: 2,
882 temporary_rent_ledger_bytes: 0,
883 temporary_entry_rent_bumps: 0,
884 },
885 sub_call_resources: [],
886 }"#]]
887 .assert_eq(
888 format!(
889 "{:#?}",
890 host.get_detailed_last_invocation_resources().unwrap()
891 )
892 .as_str(),
893 );
894 assert_resources_equal_to_budget(&host);
895
896 let key = Symbol::try_from_small_str("key_1").unwrap();
897
898 let _ = &host
900 .call(
901 contract_id,
902 Symbol::try_from_val(&host, &"has_persistent").unwrap(),
903 test_vec![&host, key].into(),
904 )
905 .unwrap();
906 expect![[r#"
907 InvocationResources {
908 instructions: 316637,
909 mem_bytes: 1134859,
910 disk_read_entries: 0,
911 memory_read_entries: 3,
912 write_entries: 0,
913 disk_read_bytes: 0,
914 write_bytes: 0,
915 contract_events_size_bytes: 0,
916 persistent_rent_ledger_bytes: 0,
917 persistent_entry_rent_bumps: 0,
918 temporary_rent_ledger_bytes: 0,
919 temporary_entry_rent_bumps: 0,
920 }"#]]
921 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
922 expect![[r#"
923 DetailedInvocationResources {
924 invocation: InvokeContract(
925 Contract(
926 ContractId(
927 Hash(ba863dea340f907c97f640ecbe669125e9f8f3b63ed1f4ed0f30073b869e5441),
928 ),
929 ),
930 ScSymbol(
931 StringM(has_persistent),
932 ),
933 ),
934 resources: SubInvocationResources {
935 instructions: 316637,
936 mem_bytes: 1134859,
937 disk_read_entries: 0,
938 memory_read_entries: 3,
939 write_entries: 0,
940 disk_read_bytes: 0,
941 write_bytes: 0,
942 contract_events_size_bytes: 0,
943 persistent_rent_ledger_bytes: 0,
944 persistent_entry_rent_bumps: 0,
945 temporary_rent_ledger_bytes: 0,
946 temporary_entry_rent_bumps: 0,
947 },
948 sub_call_resources: [],
949 }"#]]
950 .assert_eq(
951 format!(
952 "{:#?}",
953 host.get_detailed_last_invocation_resources().unwrap()
954 )
955 .as_str(),
956 );
957 assert_resources_equal_to_budget(&host);
958
959 let _ = &host
961 .try_call(
962 contract_id,
963 Symbol::try_from_val(&host, &"put_persistent").unwrap(),
964 test_vec![&host, key, 1234_u64].into(),
965 )
966 .unwrap();
967 expect![[r#"
968 InvocationResources {
969 instructions: 320246,
970 mem_bytes: 1135322,
971 disk_read_entries: 0,
972 memory_read_entries: 3,
973 write_entries: 1,
974 disk_read_bytes: 0,
975 write_bytes: 84,
976 contract_events_size_bytes: 0,
977 persistent_rent_ledger_bytes: 83916,
978 persistent_entry_rent_bumps: 1,
979 temporary_rent_ledger_bytes: 0,
980 temporary_entry_rent_bumps: 0,
981 }"#]]
982 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
983 assert_resources_equal_to_budget(&host);
984
985 let _ = &host
987 .call(
988 contract_id,
989 Symbol::try_from_val(&host, &"has_persistent").unwrap(),
990 test_vec![&host, key].into(),
991 )
992 .unwrap();
993 expect![[r#"
994 InvocationResources {
995 instructions: 315936,
996 mem_bytes: 1134707,
997 disk_read_entries: 0,
998 memory_read_entries: 3,
999 write_entries: 0,
1000 disk_read_bytes: 0,
1001 write_bytes: 0,
1002 contract_events_size_bytes: 0,
1003 persistent_rent_ledger_bytes: 0,
1004 persistent_entry_rent_bumps: 0,
1005 temporary_rent_ledger_bytes: 0,
1006 temporary_entry_rent_bumps: 0,
1007 }"#]]
1008 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
1009 assert_resources_equal_to_budget(&host);
1010
1011 let _ = &host
1013 .try_call(
1014 contract_id,
1015 Symbol::try_from_val(&host, &"put_temporary").unwrap(),
1016 test_vec![&host, key, 1234_u64].into(),
1017 )
1018 .unwrap();
1019 expect![[r#"
1020 InvocationResources {
1021 instructions: 322157,
1022 mem_bytes: 1135678,
1023 disk_read_entries: 0,
1024 memory_read_entries: 3,
1025 write_entries: 1,
1026 disk_read_bytes: 0,
1027 write_bytes: 84,
1028 contract_events_size_bytes: 0,
1029 persistent_rent_ledger_bytes: 0,
1030 persistent_entry_rent_bumps: 0,
1031 temporary_rent_ledger_bytes: 1260,
1032 temporary_entry_rent_bumps: 1,
1033 }"#]]
1034 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
1035 assert_resources_equal_to_budget(&host);
1036
1037 let _ = &host
1039 .try_call(
1040 contract_id,
1041 Symbol::try_from_val(&host, &"has_temporary").unwrap(),
1042 test_vec![&host, key].into(),
1043 )
1044 .unwrap();
1045 expect![[r#"
1046 InvocationResources {
1047 instructions: 316476,
1048 mem_bytes: 1134775,
1049 disk_read_entries: 0,
1050 memory_read_entries: 3,
1051 write_entries: 0,
1052 disk_read_bytes: 0,
1053 write_bytes: 0,
1054 contract_events_size_bytes: 0,
1055 persistent_rent_ledger_bytes: 0,
1056 persistent_entry_rent_bumps: 0,
1057 temporary_rent_ledger_bytes: 0,
1058 temporary_entry_rent_bumps: 0,
1059 }"#]]
1060 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
1061 assert_resources_equal_to_budget(&host);
1062
1063 let _ = &host
1065 .call(
1066 contract_id,
1067 Symbol::try_from_val(&host, &"extend_persistent").unwrap(),
1068 test_vec![&host, key, &5000_u32, &5000_u32].into(),
1069 )
1070 .unwrap();
1071 expect![[r#"
1072 InvocationResources {
1073 instructions: 317701,
1074 mem_bytes: 1135127,
1075 disk_read_entries: 0,
1076 memory_read_entries: 3,
1077 write_entries: 0,
1078 disk_read_bytes: 0,
1079 write_bytes: 0,
1080 contract_events_size_bytes: 0,
1081 persistent_rent_ledger_bytes: 336084,
1082 persistent_entry_rent_bumps: 1,
1083 temporary_rent_ledger_bytes: 0,
1084 temporary_entry_rent_bumps: 0,
1085 }"#]]
1086 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
1087 assert_resources_equal_to_budget(&host);
1088
1089 let _ = &host
1091 .call(
1092 contract_id,
1093 Symbol::try_from_val(&host, &"extend_temporary").unwrap(),
1094 test_vec![&host, key, &3000_u32, &3000_u32].into(),
1095 )
1096 .unwrap();
1097 expect![[r#"
1098 InvocationResources {
1099 instructions: 318103,
1100 mem_bytes: 1135127,
1101 disk_read_entries: 0,
1102 memory_read_entries: 3,
1103 write_entries: 0,
1104 disk_read_bytes: 0,
1105 write_bytes: 0,
1106 contract_events_size_bytes: 0,
1107 persistent_rent_ledger_bytes: 0,
1108 persistent_entry_rent_bumps: 0,
1109 temporary_rent_ledger_bytes: 250740,
1110 temporary_entry_rent_bumps: 1,
1111 }"#]]
1112 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
1113 assert_resources_equal_to_budget(&host);
1114
1115 let non_existent_key = Symbol::try_from_small_str("non_exist").unwrap();
1117 let res = &host.call(
1118 contract_id,
1119 Symbol::try_from_val(&host, &"extend_persistent").unwrap(),
1120 test_vec![&host, non_existent_key, &5000_u32, &5000_u32].into(),
1121 );
1122 assert!(res.is_err());
1123 expect![[r#"
1124 InvocationResources {
1125 instructions: 317540,
1126 mem_bytes: 1135195,
1127 disk_read_entries: 0,
1128 memory_read_entries: 3,
1129 write_entries: 0,
1130 disk_read_bytes: 0,
1131 write_bytes: 0,
1132 contract_events_size_bytes: 0,
1133 persistent_rent_ledger_bytes: 0,
1134 persistent_entry_rent_bumps: 0,
1135 temporary_rent_ledger_bytes: 0,
1136 temporary_entry_rent_bumps: 0,
1137 }"#]]
1138 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
1139 assert_resources_equal_to_budget(&host);
1140
1141 host.with_mut_ledger_info(|li| {
1144 li.sequence_number += li.min_persistent_entry_ttl;
1145 })
1146 .unwrap();
1147 let _ = &host
1150 .call(
1151 contract_id,
1152 Symbol::try_from_val(&host, &"has_persistent").unwrap(),
1153 test_vec![&host, key].into(),
1154 )
1155 .unwrap();
1156 expect![[r#"
1157 InvocationResources {
1158 instructions: 320711,
1159 mem_bytes: 1135662,
1160 disk_read_entries: 2,
1161 memory_read_entries: 1,
1162 write_entries: 2,
1163 disk_read_bytes: 3132,
1164 write_bytes: 3132,
1165 contract_events_size_bytes: 0,
1166 persistent_rent_ledger_bytes: 80531388,
1167 persistent_entry_rent_bumps: 2,
1168 temporary_rent_ledger_bytes: 0,
1169 temporary_entry_rent_bumps: 0,
1170 }"#]]
1171 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
1172 assert_resources_equal_to_budget(&host);
1173
1174 host.with_mut_ledger_info(|li| {
1177 li.sequence_number += 5000 - li.min_persistent_entry_ttl + 1;
1178 })
1179 .unwrap();
1180 let _ = &host
1182 .call(
1183 contract_id,
1184 Symbol::try_from_val(&host, &"has_persistent").unwrap(),
1185 test_vec![&host, key].into(),
1186 )
1187 .unwrap();
1188 expect![[r#"
1189 InvocationResources {
1190 instructions: 323248,
1191 mem_bytes: 1136109,
1192 disk_read_entries: 3,
1193 memory_read_entries: 0,
1194 write_entries: 3,
1195 disk_read_bytes: 3216,
1196 write_bytes: 3216,
1197 contract_events_size_bytes: 0,
1198 persistent_rent_ledger_bytes: 80615304,
1199 persistent_entry_rent_bumps: 3,
1200 temporary_rent_ledger_bytes: 0,
1201 temporary_entry_rent_bumps: 0,
1202 }"#]]
1203 .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
1204 assert_resources_equal_to_budget(&host);
1205 }
1206
1207 #[test]
1208 fn test_resource_fee_estimation() {
1209 assert_eq!(
1211 InvocationResources {
1212 instructions: 0,
1213 mem_bytes: 100_000,
1214 disk_read_entries: 0,
1215 memory_read_entries: 100,
1216 write_entries: 0,
1217 disk_read_bytes: 0,
1218 write_bytes: 0,
1219 contract_events_size_bytes: 0,
1220 persistent_rent_ledger_bytes: 0,
1221 persistent_entry_rent_bumps: 0,
1222 temporary_rent_ledger_bytes: 0,
1223 temporary_entry_rent_bumps: 0,
1224 }
1225 .estimate_fees(
1226 &FeeConfiguration {
1227 fee_per_instruction_increment: 100,
1228 fee_per_disk_read_entry: 100,
1229 fee_per_write_entry: 100,
1230 fee_per_disk_read_1kb: 100,
1231 fee_per_write_1kb: 100,
1232 fee_per_historical_1kb: 100,
1233 fee_per_contract_event_1kb: 100,
1234 fee_per_transaction_size_1kb: 100,
1235 },
1236 100,
1237 1,
1238 1
1239 ),
1240 FeeEstimate {
1241 total: 0,
1242 instructions: 0,
1243 disk_read_entries: 0,
1244 write_entries: 0,
1245 disk_read_bytes: 0,
1246 write_bytes: 0,
1247 contract_events: 0,
1248 persistent_entry_rent: 0,
1249 temporary_entry_rent: 0,
1250 }
1251 );
1252
1253 assert_eq!(
1255 InvocationResources {
1256 instructions: 1,
1257 mem_bytes: 100_000,
1258 disk_read_entries: 1,
1259 memory_read_entries: 100,
1260 write_entries: 1,
1261 disk_read_bytes: 1,
1262 write_bytes: 1,
1263 contract_events_size_bytes: 1,
1264 persistent_rent_ledger_bytes: 1,
1265 persistent_entry_rent_bumps: 1,
1266 temporary_rent_ledger_bytes: 1,
1267 temporary_entry_rent_bumps: 1
1268 }
1269 .estimate_fees(
1270 &FeeConfiguration {
1271 fee_per_instruction_increment: 100,
1272 fee_per_disk_read_entry: 100,
1273 fee_per_write_entry: 100,
1274 fee_per_disk_read_1kb: 100,
1275 fee_per_write_1kb: 100,
1276 fee_per_historical_1kb: 100,
1277 fee_per_contract_event_1kb: 100,
1278 fee_per_transaction_size_1kb: 100,
1279 },
1280 100,
1281 1,
1282 1
1283 ),
1284 FeeEstimate {
1285 total: 516,
1286 instructions: 1,
1287 disk_read_entries: 200,
1288 write_entries: 100,
1289 disk_read_bytes: 1,
1290 write_bytes: 1,
1291 contract_events: 1,
1292 persistent_entry_rent: 106,
1293 temporary_entry_rent: 106
1294 }
1295 );
1296
1297 assert_eq!(
1300 InvocationResources {
1301 instructions: 10_123_456,
1302 mem_bytes: 100_000,
1303 disk_read_entries: 30,
1304 memory_read_entries: 100,
1305 write_entries: 10,
1306 disk_read_bytes: 25_600,
1307 write_bytes: 10_340,
1308 contract_events_size_bytes: 321_654,
1309 persistent_rent_ledger_bytes: 1_000_000_000,
1310 persistent_entry_rent_bumps: 3,
1311 temporary_rent_ledger_bytes: 4_000_000_000,
1312 temporary_entry_rent_bumps: 6
1313 }
1314 .estimate_fees(
1315 &FeeConfiguration {
1316 fee_per_instruction_increment: 1000,
1317 fee_per_disk_read_entry: 2000,
1318 fee_per_write_1kb: 3000,
1319 fee_per_write_entry: 4000,
1320 fee_per_disk_read_1kb: 1500,
1321 fee_per_historical_1kb: 300,
1322 fee_per_contract_event_1kb: 200,
1323 fee_per_transaction_size_1kb: 900,
1324 },
1325 6000,
1326 1000,
1327 2000
1328 ),
1329 FeeEstimate {
1330 total: 18_878_354,
1332 instructions: 1_012_346,
1333 disk_read_entries: 80000,
1334 write_entries: 40000,
1335 disk_read_bytes: 37500,
1336 write_bytes: 30293,
1337 contract_events: 62824,
1338 persistent_entry_rent: 5871797,
1339 temporary_entry_rent: 11743594
1340 }
1341 );
1342
1343 assert_eq!(
1345 InvocationResources {
1346 instructions: i64::MAX,
1347 mem_bytes: i64::MAX,
1348 disk_read_entries: u32::MAX,
1349 memory_read_entries: 100,
1350 write_entries: u32::MAX,
1351 disk_read_bytes: u32::MAX,
1352 write_bytes: u32::MAX,
1353 contract_events_size_bytes: u32::MAX,
1354 persistent_rent_ledger_bytes: i64::MAX,
1355 persistent_entry_rent_bumps: u32::MAX,
1356 temporary_rent_ledger_bytes: i64::MAX,
1357 temporary_entry_rent_bumps: u32::MAX
1358 }
1359 .estimate_fees(
1360 &FeeConfiguration {
1361 fee_per_instruction_increment: i64::MAX,
1362 fee_per_disk_read_entry: i64::MAX,
1363 fee_per_write_entry: i64::MAX,
1364 fee_per_disk_read_1kb: i64::MAX,
1365 fee_per_write_1kb: i64::MAX,
1366 fee_per_historical_1kb: i64::MAX,
1367 fee_per_contract_event_1kb: i64::MAX,
1368 fee_per_transaction_size_1kb: i64::MAX,
1369 },
1370 i64::MAX,
1371 i64::MAX,
1372 i64::MAX
1373 ),
1374 FeeEstimate {
1375 total: i64::MAX,
1376 instructions: 922337203685478,
1377 disk_read_entries: i64::MAX,
1378 write_entries: i64::MAX,
1379 disk_read_bytes: 9007199254740992,
1380 write_bytes: 9007199254740992,
1381 contract_events: 9007199254740992,
1382 persistent_entry_rent: i64::MAX,
1383 temporary_entry_rent: i64::MAX
1384 }
1385 );
1386 }
1387
1388 #[test]
1389 fn test_estimate_detailed_fees() {
1390 let resources = DetailedInvocationResources {
1391 invocation: MeteringInvocation::InvokeContract(
1392 ScAddress::Contract(ContractId(Hash([1; 32]))),
1393 "foo".try_into().unwrap(),
1394 ),
1395 resources: SubInvocationResources {
1396 instructions: 10_123_456,
1397 mem_bytes: 100_000,
1398 disk_read_entries: 30,
1399 memory_read_entries: 100,
1400 write_entries: 10,
1401 disk_read_bytes: 25_600,
1402 write_bytes: 10_340,
1403 contract_events_size_bytes: 321_654,
1404 persistent_rent_ledger_bytes: 1_000_000_000,
1405 persistent_entry_rent_bumps: 3,
1406 temporary_rent_ledger_bytes: 4_000_000_000,
1407 temporary_entry_rent_bumps: 6,
1408 },
1409 sub_call_resources: vec![
1410 DetailedInvocationResources {
1411 invocation: MeteringInvocation::WasmUploadEntryPoint,
1412 resources: SubInvocationResources {
1413 instructions: 1,
1414 mem_bytes: 200_000,
1415 disk_read_entries: 1,
1416 memory_read_entries: 100,
1417 write_entries: 1,
1418 disk_read_bytes: 1,
1419 write_bytes: 1,
1420 contract_events_size_bytes: 1,
1421 persistent_rent_ledger_bytes: 1,
1422 persistent_entry_rent_bumps: 1,
1423 temporary_rent_ledger_bytes: 1,
1424 temporary_entry_rent_bumps: 1,
1425 },
1426 sub_call_resources: vec![DetailedInvocationResources {
1427 invocation: MeteringInvocation::CreateContractEntryPoint,
1428 resources: SubInvocationResources {
1429 instructions: 0,
1430 mem_bytes: 300_000,
1431 disk_read_entries: 0,
1432 memory_read_entries: 100,
1433 write_entries: 0,
1434 disk_read_bytes: 0,
1435 write_bytes: 0,
1436 contract_events_size_bytes: 0,
1437 persistent_rent_ledger_bytes: 0,
1438 persistent_entry_rent_bumps: 0,
1439 temporary_rent_ledger_bytes: 0,
1440 temporary_entry_rent_bumps: 0,
1441 },
1442 sub_call_resources: vec![],
1443 }],
1444 },
1445 DetailedInvocationResources {
1446 invocation: MeteringInvocation::InvokeContract(
1447 ScAddress::Contract(ContractId(Hash([2; 32]))),
1448 "bar".try_into().unwrap(),
1449 ),
1450 resources: SubInvocationResources {
1451 instructions: 10_000,
1452 mem_bytes: 500_000,
1453 contract_events_size_bytes: 100,
1454 disk_read_entries: -1,
1458 memory_read_entries: -2,
1459 write_entries: -3,
1460 disk_read_bytes: -4,
1461 write_bytes: -5,
1462 persistent_rent_ledger_bytes: -6,
1463 persistent_entry_rent_bumps: -7,
1464 temporary_rent_ledger_bytes: -8,
1465 temporary_entry_rent_bumps: -9,
1466 },
1467 sub_call_resources: vec![],
1468 },
1469 ],
1470 };
1471
1472 let fee_estimate = resources.estimate_fees(
1473 &FeeConfiguration {
1474 fee_per_instruction_increment: 1000,
1475 fee_per_disk_read_entry: 2000,
1476 fee_per_write_1kb: 3000,
1477 fee_per_write_entry: 4000,
1478 fee_per_disk_read_1kb: 1500,
1479 fee_per_historical_1kb: 300,
1480 fee_per_contract_event_1kb: 200,
1481 fee_per_transaction_size_1kb: 900,
1482 },
1483 6000,
1484 1000,
1485 2000,
1486 );
1487 expect![[r#"
1488 DetailedFeeEstimate {
1489 invocation: InvokeContract(
1490 Contract(
1491 ContractId(
1492 Hash(0101010101010101010101010101010101010101010101010101010101010101),
1493 ),
1494 ),
1495 ScSymbol(
1496 StringM(foo),
1497 ),
1498 ),
1499 fee_estimate: FeeEstimate {
1500 total: 18878354,
1501 instructions: 1012346,
1502 disk_read_entries: 80000,
1503 write_entries: 40000,
1504 disk_read_bytes: 37500,
1505 write_bytes: 30293,
1506 contract_events: 62824,
1507 persistent_entry_rent: 5871797,
1508 temporary_entry_rent: 11743594,
1509 },
1510 sub_call_fee_estimates: [
1511 DetailedFeeEstimate {
1512 invocation: WasmUploadEntryPoint,
1513 fee_estimate: FeeEstimate {
1514 total: 16291,
1515 instructions: 1,
1516 disk_read_entries: 4000,
1517 write_entries: 4000,
1518 disk_read_bytes: 2,
1519 write_bytes: 3,
1520 contract_events: 1,
1521 persistent_entry_rent: 4142,
1522 temporary_entry_rent: 4142,
1523 },
1524 sub_call_fee_estimates: [
1525 DetailedFeeEstimate {
1526 invocation: CreateContractEntryPoint,
1527 fee_estimate: FeeEstimate {
1528 total: 0,
1529 instructions: 0,
1530 disk_read_entries: 0,
1531 write_entries: 0,
1532 disk_read_bytes: 0,
1533 write_bytes: 0,
1534 contract_events: 0,
1535 persistent_entry_rent: 0,
1536 temporary_entry_rent: 0,
1537 },
1538 sub_call_fee_estimates: [],
1539 },
1540 ],
1541 },
1542 DetailedFeeEstimate {
1543 invocation: InvokeContract(
1544 Contract(
1545 ContractId(
1546 Hash(0202020202020202020202020202020202020202020202020202020202020202),
1547 ),
1548 ),
1549 ScSymbol(
1550 StringM(bar),
1551 ),
1552 ),
1553 fee_estimate: FeeEstimate {
1554 total: 1020,
1555 instructions: 1000,
1556 disk_read_entries: 0,
1557 write_entries: 0,
1558 disk_read_bytes: 0,
1559 write_bytes: 0,
1560 contract_events: 20,
1561 persistent_entry_rent: 0,
1562 temporary_entry_rent: 0,
1563 },
1564 sub_call_fee_estimates: [],
1565 },
1566 ],
1567 }"#]]
1568 .assert_eq(format!("{:#?}", fee_estimate).as_str());
1569 }
1570}