soroban_env_host/host/
invocation_metering.rs

1use std::cell::RefMut;
2
3use soroban_env_common::Env;
4
5use crate::{
6    e2e_invoke::encode_contract_events,
7    fees::{FeeConfiguration, DATA_SIZE_1KB_INCREMENT, INSTRUCTIONS_INCREMENT, TTL_ENTRY_SIZE},
8    ledger_info::get_key_durability,
9    storage::{AccessType, Storage},
10    xdr::{ContractDataDurability, ScErrorCode, ScErrorType},
11};
12
13use super::{metered_xdr::metered_write_xdr, Host, HostError};
14
15/// Represents the resources measured during an invocation.
16///
17/// This resembles the resources necessary to build a Soroban transaction and
18/// compute its fee with a few exceptions (specifically, the transaction size
19/// and the return value size).
20#[derive(Default, Clone, Debug, Eq, PartialEq)]
21pub struct InvocationResources {
22    /// Number of modelled CPU instructions.
23    pub instructions: i64,
24    /// Size of modelled memory in bytes.
25    pub mem_bytes: i64,
26    /// Number of entries that need to be read from the ledger (without
27    /// modification, i.e. excluding the entries that need to be written).
28    pub read_entries: u32,
29    /// Number of entries that need to be written to the ledger due to
30    /// modification.
31    pub write_entries: u32,
32    /// Total number of bytes that need to be read from the ledger. This is
33    /// the total size of the 'initial' states all the storage entries accessed.
34    pub read_bytes: u32,
35    /// Total number of bytes that need to be written to the ledger. This is
36    /// the total size of all the entries accounted in `write_entries`.
37    pub write_bytes: u32,
38    /// Total size of the contract events emitted.
39    pub contract_events_size_bytes: u32,
40    /// Cumulative rent bump of all the persistent entries in 'ledger-bytes'.
41    /// 'Ledger-byte' is a rent bump of 1 byte for 1 ledger. Rent fee is
42    /// proportional to the total amount of 'ledger-bytes'.
43    pub persistent_rent_ledger_bytes: i64,
44    /// Number of persistent entries that had their rent bumped.
45    pub persistent_entry_rent_bumps: u32,
46    /// Cumulative rent bump of all the temporary entries in 'ledger-bytes'.
47    /// 'Ledger-byte' is a rent bump of 1 byte for 1 ledger. Rent fee is
48    /// proportional to the total amount of 'ledger-bytes'.    
49    pub temporary_rent_ledger_bytes: i64,
50    /// Number of temporary entries that had their rent bumped.
51    pub temporary_entry_rent_bumps: u32,
52}
53
54/// Detailed estimate of the transaction fees in stroops based on the
55/// `InvocationResources`.
56///
57/// Since `InvocationResources` don't account for certain metered resources,
58/// these are omitted from the estimate as well.
59#[derive(Default, Clone, Debug, Eq, PartialEq)]
60pub struct FeeEstimate {
61    /// Total fee (sum of all the remaining fields).
62    pub total: i64,
63    /// Fee for instructions.
64    pub instructions: i64,
65    /// Fee for ledger entry reads.
66    pub read_entries: i64,
67    /// Fee for ledger entry writes.
68    pub write_entries: i64,
69    /// Fee for the overall size of ledger reads.
70    pub read_bytes: i64,
71    /// Fee for the overall size of ledger writes.
72    pub write_bytes: i64,
73    /// Fee for the contract events emitted.
74    pub contract_events: i64,
75    /// Rent fee for the persistent entries.
76    pub persistent_entry_rent: i64,
77    /// Rent fee for the temporary entries.
78    pub temporary_entry_rent: i64,
79}
80
81impl InvocationResources {
82    /// Estimates the fees necesary for the current resources based on the
83    /// provided fee configuration.
84    ///
85    /// This is only an estimate and it can't be used for the actual transaction
86    /// submission (simulation using the Soroban RPC should be used instead).
87    ///
88    /// The quality of the estimate depends on the provided fee configuration,
89    /// so it must resemble the target network as close as possible.
90    pub fn estimate_fees(
91        &self,
92        fee_config: &FeeConfiguration,
93        persistent_rent_rate_denominator: i64,
94        temporary_rent_rate_denominator: i64,
95    ) -> FeeEstimate {
96        let instructions = compute_fee_per_increment(
97            self.instructions,
98            fee_config.fee_per_instruction_increment,
99            INSTRUCTIONS_INCREMENT,
100        );
101        let read_entries = fee_config
102            .fee_per_read_entry
103            .saturating_mul(self.read_entries.saturating_add(self.write_entries).into());
104        let write_entries = fee_config
105            .fee_per_write_entry
106            .saturating_mul(self.write_entries.into());
107        let read_bytes = compute_fee_per_increment(
108            self.read_bytes.into(),
109            fee_config.fee_per_read_1kb,
110            DATA_SIZE_1KB_INCREMENT,
111        );
112        let write_bytes = compute_fee_per_increment(
113            self.write_bytes.into(),
114            fee_config.fee_per_write_1kb,
115            DATA_SIZE_1KB_INCREMENT,
116        );
117        let contract_events = compute_fee_per_increment(
118            self.contract_events_size_bytes.into(),
119            fee_config.fee_per_contract_event_1kb,
120            DATA_SIZE_1KB_INCREMENT,
121        );
122
123        let mut persistent_entry_ttl_entry_writes = fee_config
124            .fee_per_write_entry
125            .saturating_mul(self.persistent_entry_rent_bumps.into());
126        persistent_entry_ttl_entry_writes =
127            persistent_entry_ttl_entry_writes.saturating_add(compute_fee_per_increment(
128                (TTL_ENTRY_SIZE as i64).saturating_mul(self.persistent_entry_rent_bumps.into()),
129                fee_config.fee_per_write_1kb,
130                DATA_SIZE_1KB_INCREMENT,
131            ));
132        let mut temp_entry_ttl_entry_writes = fee_config
133            .fee_per_write_entry
134            .saturating_mul(self.temporary_entry_rent_bumps.into());
135        temp_entry_ttl_entry_writes =
136            temp_entry_ttl_entry_writes.saturating_add(compute_fee_per_increment(
137                (TTL_ENTRY_SIZE as i64).saturating_mul(self.temporary_entry_rent_bumps.into()),
138                fee_config.fee_per_write_1kb,
139                DATA_SIZE_1KB_INCREMENT,
140            ));
141
142        let persistent_entry_rent = compute_fee_per_increment(
143            self.persistent_rent_ledger_bytes,
144            fee_config.fee_per_write_1kb,
145            DATA_SIZE_1KB_INCREMENT.saturating_mul(persistent_rent_rate_denominator),
146        )
147        .saturating_add(persistent_entry_ttl_entry_writes);
148        let temporary_entry_rent = compute_fee_per_increment(
149            self.temporary_rent_ledger_bytes,
150            fee_config.fee_per_write_1kb,
151            DATA_SIZE_1KB_INCREMENT.saturating_mul(temporary_rent_rate_denominator),
152        )
153        .saturating_add(temp_entry_ttl_entry_writes);
154        let total = instructions
155            .saturating_add(read_entries)
156            .saturating_add(write_entries)
157            .saturating_add(read_bytes)
158            .saturating_add(write_bytes)
159            .saturating_add(contract_events)
160            .saturating_add(persistent_entry_rent)
161            .saturating_add(temporary_entry_rent);
162        FeeEstimate {
163            total,
164            instructions,
165            read_entries,
166            write_entries,
167            read_bytes,
168            write_bytes,
169            contract_events,
170            persistent_entry_rent,
171            temporary_entry_rent,
172        }
173    }
174}
175
176/// A helper for metering the resources only within a logical host invocation
177/// without finalizing the host.
178///
179/// The 'logical' invocations are the typical entry points for the unit tests,
180/// such as invocations based on `HostFunction` XDR, lifecycle operations
181/// (registering Wasm, creating a contract instance), direct contract calls
182/// etc.
183#[derive(Default, Clone)]
184pub(crate) struct InvocationMeter {
185    active: bool,
186    enabled: bool,
187    storage_snapshot: Option<Storage>,
188    invocation_resources: Option<InvocationResources>,
189}
190
191/// Scope guard for `InvocationMeter` that automatically finishes the metered
192/// invocation when it goes out of scope.
193pub(crate) struct InvocationMeterScope<'a> {
194    meter: RefMut<'a, InvocationMeter>,
195    host: &'a Host,
196}
197
198impl Drop for InvocationMeterScope<'_> {
199    fn drop(&mut self) {
200        self.meter.finish_invocation(self.host);
201    }
202}
203
204impl InvocationMeter {
205    /// Gets the metered resources for the last metered invocation (if any).
206    pub(crate) fn get_invocation_resources(&self) -> Option<InvocationResources> {
207        self.invocation_resources.clone()
208    }
209
210    fn start_invocation<'a>(
211        mut scope: RefMut<'a, InvocationMeter>,
212        host: &'a Host,
213    ) -> Result<Option<InvocationMeterScope<'a>>, HostError> {
214        if scope.active || !scope.enabled {
215            return Ok(None);
216        }
217        scope.storage_snapshot = Some(host.try_borrow_storage()?.clone());
218        // Reset all the state relevant to the invocation resources. Note, that
219        // the storage itself shouldn't be reset, as it's treated as the ledger
220        // state before invocation.
221        host.try_borrow_storage_mut()?.reset_footprint();
222        host.try_borrow_events_mut()?.clear();
223        host.budget_ref().reset()?;
224        Ok(Some(InvocationMeterScope { meter: scope, host }))
225    }
226
227    fn finish_invocation(&mut self, host: &Host) -> () {
228        self.active = false;
229        let mut invocation_resources = InvocationResources::default();
230        let budget = host.budget_ref();
231        invocation_resources.instructions =
232            budget.get_cpu_insns_consumed().unwrap_or_default() as i64;
233        invocation_resources.mem_bytes = budget.get_mem_bytes_consumed().unwrap_or_default() as i64;
234
235        let measure_res = budget.with_observable_shadow_mode(|| {
236            self.try_measure_resources(&mut invocation_resources, host)
237        });
238
239        if measure_res.is_ok() {
240            self.invocation_resources = Some(invocation_resources);
241        } else {
242            self.invocation_resources = None;
243        }
244
245        self.storage_snapshot = None;
246    }
247
248    fn try_measure_resources(
249        &mut self,
250        invocation_resources: &mut InvocationResources,
251        host: &Host,
252    ) -> Result<(), HostError> {
253        let prev_storage = self.storage_snapshot.as_mut().ok_or_else(|| {
254            host.err(
255                ScErrorType::Context,
256                ScErrorCode::InternalError,
257                "missing a storage snapshot in metering scope, `open` must be called before `close`",
258                &[],
259            )
260        })?;
261
262        let mut curr_storage = host.try_borrow_storage_mut()?;
263        let footprint = curr_storage.footprint.clone();
264        let curr_ledger_seq: u32 = host.get_ledger_sequence()?.into();
265        for (key, access_type) in footprint.0.iter(host.budget_ref())? {
266            let maybe_init_entry = prev_storage.try_get_full_with_host(key, host, None)?;
267            let mut init_entry_size = 0;
268            let mut init_live_until_ledger = curr_ledger_seq;
269            if let Some((init_entry, init_entry_live_until)) = maybe_init_entry {
270                let mut buf = Vec::<u8>::new();
271                metered_write_xdr(host.budget_ref(), init_entry.as_ref(), &mut buf)?;
272                init_entry_size = buf.len() as u32;
273                invocation_resources.read_bytes += init_entry_size;
274                if let Some(live_until) = init_entry_live_until {
275                    init_live_until_ledger = live_until;
276                }
277            }
278            let mut entry_size = 0;
279            let mut entry_live_until_ledger = None;
280            let maybe_entry = curr_storage.try_get_full_with_host(key, host, None)?;
281            if let Some((entry, entry_live_until)) = maybe_entry {
282                let mut buf = Vec::<u8>::new();
283                metered_write_xdr(host.budget_ref(), entry.as_ref(), &mut buf)?;
284                entry_size = buf.len() as u32;
285                entry_live_until_ledger = entry_live_until;
286            }
287            match access_type {
288                AccessType::ReadOnly => {
289                    invocation_resources.read_entries += 1;
290                }
291                AccessType::ReadWrite => {
292                    invocation_resources.write_entries += 1;
293                    invocation_resources.write_bytes += entry_size;
294                }
295            }
296            if let Some(new_live_until) = entry_live_until_ledger {
297                let extension_ledgers = (new_live_until - init_live_until_ledger) as i64;
298                let size_delta = if entry_size > init_entry_size {
299                    (entry_size - init_entry_size) as i64
300                } else {
301                    0
302                };
303                let existing_ledgers = (init_live_until_ledger - curr_ledger_seq) as i64;
304                let rent_ledger_bytes =
305                    existing_ledgers * size_delta + extension_ledgers * (entry_size as i64);
306                if rent_ledger_bytes > 0 {
307                    match get_key_durability(key.as_ref()) {
308                        Some(ContractDataDurability::Temporary) => {
309                            invocation_resources.temporary_rent_ledger_bytes += rent_ledger_bytes;
310                            invocation_resources.temporary_entry_rent_bumps += 1;
311                        }
312                        Some(ContractDataDurability::Persistent) => {
313                            invocation_resources.persistent_rent_ledger_bytes += rent_ledger_bytes;
314                            invocation_resources.persistent_entry_rent_bumps += 1;
315                        }
316                        None => (),
317                    }
318                }
319            }
320        }
321        let events = host.try_borrow_events()?.externalize(&host)?;
322        let encoded_contract_events = encode_contract_events(host.budget_ref(), &events)?;
323        for event in &encoded_contract_events {
324            invocation_resources.contract_events_size_bytes += event.len() as u32;
325        }
326        Ok(())
327    }
328}
329
330impl Host {
331    /// Tries to start a metered invocation, when invocation metering is enabled.
332    ///
333    /// The returned object has to stay alive while the invocation is active.
334    ///
335    /// If there is already an invocation active, returns `None`.
336    pub(crate) fn maybe_meter_invocation(
337        &self,
338    ) -> Result<Option<InvocationMeterScope<'_>>, HostError> {
339        // Note: we're using the standard `try_borrow_mut` instead of a helper
340        // generated with `impl_checked_borrow_helpers` in order to not spam
341        // the logs with failures. It is expected for metering_scope to be
342        // borrowed.
343        if let Ok(scope) = self.0.invocation_meter.try_borrow_mut() {
344            InvocationMeter::start_invocation(scope, self)
345        } else {
346            Ok(None)
347        }
348    }
349
350    /// Enables invocation metering (it's disabled by default).
351    pub fn enable_invocation_metering(&self) {
352        if let Ok(mut meter) = self.0.invocation_meter.try_borrow_mut() {
353            meter.enabled = true;
354        }
355    }
356}
357
358fn compute_fee_per_increment(resource_value: i64, fee_rate: i64, increment: i64) -> i64 {
359    num_integer::div_ceil(resource_value.saturating_mul(fee_rate), increment.max(1))
360}
361
362#[cfg(test)]
363mod test {
364    use super::*;
365    use crate::{Symbol, TryFromVal, TryIntoVal};
366    use expect_test::expect;
367    use soroban_test_wasms::CONTRACT_STORAGE;
368
369    fn assert_resources_equal_to_budget(host: &Host) {
370        assert_eq!(
371            host.get_last_invocation_resources().unwrap().instructions as u64,
372            host.budget_ref().get_cpu_insns_consumed().unwrap()
373        );
374        assert_eq!(
375            host.get_last_invocation_resources().unwrap().mem_bytes as u64,
376            host.budget_ref().get_mem_bytes_consumed().unwrap()
377        );
378    }
379
380    // run `UPDATE_EXPECT=true cargo test` to update this test.
381    // The exact values of don't matter too much here (unless the diffs are
382    // produced without a protocol upgrade), but the presence/absence of certain
383    // resources is important (comments clarify which ones).
384    #[test]
385    fn test_invocation_resource_metering() {
386        let host = Host::test_host_with_recording_footprint();
387        host.enable_invocation_metering();
388        host.enable_debug().unwrap();
389        host.with_mut_ledger_info(|li| {
390            li.sequence_number = 100;
391            li.max_entry_ttl = 10000;
392            li.min_persistent_entry_ttl = 1000;
393            li.min_temp_entry_ttl = 16;
394        })
395        .unwrap();
396
397        let contract_id = host.register_test_contract_wasm(CONTRACT_STORAGE);
398        // We meter the whole registration procedure here (upload + create
399        // contract), so 2 writes/bumps are expected.
400        expect![[r#"
401            InvocationResources {
402                instructions: 4196698,
403                mem_bytes: 2863076,
404                read_entries: 0,
405                write_entries: 2,
406                read_bytes: 0,
407                write_bytes: 3132,
408                contract_events_size_bytes: 0,
409                persistent_rent_ledger_bytes: 3128868,
410                persistent_entry_rent_bumps: 2,
411                temporary_rent_ledger_bytes: 0,
412                temporary_entry_rent_bumps: 0,
413            }"#]]
414        .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
415        assert_resources_equal_to_budget(&host);
416
417        let key = Symbol::try_from_small_str("key_1").unwrap();
418
419        // Has with no entries - no writes/rent bumps expected.
420        let _ = &host
421            .call(
422                contract_id,
423                Symbol::try_from_val(&host, &"has_persistent").unwrap(),
424                test_vec![&host, key].into(),
425            )
426            .unwrap();
427        expect![[r#"
428            InvocationResources {
429                instructions: 846137,
430                mem_bytes: 1215737,
431                read_entries: 3,
432                write_entries: 0,
433                read_bytes: 3132,
434                write_bytes: 0,
435                contract_events_size_bytes: 0,
436                persistent_rent_ledger_bytes: 0,
437                persistent_entry_rent_bumps: 0,
438                temporary_rent_ledger_bytes: 0,
439                temporary_entry_rent_bumps: 0,
440            }"#]]
441        .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
442        assert_resources_equal_to_budget(&host);
443
444        // 1 persistent write together with the respective initial rent bump.
445        let _ = &host
446            .try_call(
447                contract_id,
448                Symbol::try_from_val(&host, &"put_persistent").unwrap(),
449                test_vec![&host, key, 1234_u64].into(),
450            )
451            .unwrap();
452        expect![[r#"
453            InvocationResources {
454                instructions: 850395,
455                mem_bytes: 1216336,
456                read_entries: 2,
457                write_entries: 1,
458                read_bytes: 3132,
459                write_bytes: 84,
460                contract_events_size_bytes: 0,
461                persistent_rent_ledger_bytes: 83916,
462                persistent_entry_rent_bumps: 1,
463                temporary_rent_ledger_bytes: 0,
464                temporary_entry_rent_bumps: 0,
465            }"#]]
466        .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
467        assert_resources_equal_to_budget(&host);
468
469        // Another has check, should have more data read than the first one.
470        let _ = &host
471            .call(
472                contract_id,
473                Symbol::try_from_val(&host, &"has_persistent").unwrap(),
474                test_vec![&host, key].into(),
475            )
476            .unwrap();
477        expect![[r#"
478            InvocationResources {
479                instructions: 846406,
480                mem_bytes: 1215721,
481                read_entries: 3,
482                write_entries: 0,
483                read_bytes: 3216,
484                write_bytes: 0,
485                contract_events_size_bytes: 0,
486                persistent_rent_ledger_bytes: 0,
487                persistent_entry_rent_bumps: 0,
488                temporary_rent_ledger_bytes: 0,
489                temporary_entry_rent_bumps: 0,
490            }"#]]
491        .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
492        assert_resources_equal_to_budget(&host);
493
494        // 1 temporary entry write with the initial rent bump.
495        let _ = &host
496            .try_call(
497                contract_id,
498                Symbol::try_from_val(&host, &"put_temporary").unwrap(),
499                test_vec![&host, key, 1234_u64].into(),
500            )
501            .unwrap();
502        expect![[r#"
503            InvocationResources {
504                instructions: 852007,
505                mem_bytes: 1216692,
506                read_entries: 2,
507                write_entries: 1,
508                read_bytes: 3132,
509                write_bytes: 84,
510                contract_events_size_bytes: 0,
511                persistent_rent_ledger_bytes: 0,
512                persistent_entry_rent_bumps: 0,
513                temporary_rent_ledger_bytes: 1260,
514                temporary_entry_rent_bumps: 1,
515            }"#]]
516        .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
517        assert_resources_equal_to_budget(&host);
518
519        // Has check, same amount of data is read as for persistent has check.
520        let _ = &host
521            .try_call(
522                contract_id,
523                Symbol::try_from_val(&host, &"has_temporary").unwrap(),
524                test_vec![&host, key].into(),
525            )
526            .unwrap();
527        expect![[r#"
528            InvocationResources {
529                instructions: 846795,
530                mem_bytes: 1215789,
531                read_entries: 3,
532                write_entries: 0,
533                read_bytes: 3216,
534                write_bytes: 0,
535                contract_events_size_bytes: 0,
536                persistent_rent_ledger_bytes: 0,
537                persistent_entry_rent_bumps: 0,
538                temporary_rent_ledger_bytes: 0,
539                temporary_entry_rent_bumps: 0,
540            }"#]]
541        .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
542        assert_resources_equal_to_budget(&host);
543
544        // Extend persistent entry, 1 persistent extension is expected.
545        let _ = &host
546            .call(
547                contract_id,
548                Symbol::try_from_val(&host, &"extend_persistent").unwrap(),
549                test_vec![&host, key, &5000_u32, &5000_u32].into(),
550            )
551            .unwrap();
552        expect![[r#"
553            InvocationResources {
554                instructions: 847840,
555                mem_bytes: 1216141,
556                read_entries: 3,
557                write_entries: 0,
558                read_bytes: 3216,
559                write_bytes: 0,
560                contract_events_size_bytes: 0,
561                persistent_rent_ledger_bytes: 336084,
562                persistent_entry_rent_bumps: 1,
563                temporary_rent_ledger_bytes: 0,
564                temporary_entry_rent_bumps: 0,
565            }"#]]
566        .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
567        assert_resources_equal_to_budget(&host);
568
569        // Extend temp entry, 1 persistent extension is expected.
570        let _ = &host
571            .call(
572                contract_id,
573                Symbol::try_from_val(&host, &"extend_temporary").unwrap(),
574                test_vec![&host, key, &3000_u32, &3000_u32].into(),
575            )
576            .unwrap();
577        expect![[r#"
578            InvocationResources {
579                instructions: 847960,
580                mem_bytes: 1216141,
581                read_entries: 3,
582                write_entries: 0,
583                read_bytes: 3216,
584                write_bytes: 0,
585                contract_events_size_bytes: 0,
586                persistent_rent_ledger_bytes: 0,
587                persistent_entry_rent_bumps: 0,
588                temporary_rent_ledger_bytes: 250740,
589                temporary_entry_rent_bumps: 1,
590            }"#]]
591        .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
592        assert_resources_equal_to_budget(&host);
593
594        // Try extending entry for a non-existent key, this should fail.
595        let non_existent_key = Symbol::try_from_small_str("non_exist").unwrap();
596        let res = &host.call(
597            contract_id,
598            Symbol::try_from_val(&host, &"extend_persistent").unwrap(),
599            test_vec![&host, non_existent_key, &5000_u32, &5000_u32].into(),
600        );
601        assert!(res.is_err());
602        expect![[r#"
603            InvocationResources {
604                instructions: 847829,
605                mem_bytes: 1216209,
606                read_entries: 3,
607                write_entries: 0,
608                read_bytes: 3132,
609                write_bytes: 0,
610                contract_events_size_bytes: 0,
611                persistent_rent_ledger_bytes: 0,
612                persistent_entry_rent_bumps: 0,
613                temporary_rent_ledger_bytes: 0,
614                temporary_entry_rent_bumps: 0,
615            }"#]]
616        .assert_eq(format!("{:#?}", host.get_last_invocation_resources().unwrap()).as_str());
617        assert_resources_equal_to_budget(&host);
618    }
619
620    #[test]
621    fn test_resource_fee_estimation() {
622        // No resources
623        assert_eq!(
624            InvocationResources {
625                instructions: 0,
626                mem_bytes: 100_000,
627                read_entries: 0,
628                write_entries: 0,
629                read_bytes: 0,
630                write_bytes: 0,
631                contract_events_size_bytes: 0,
632                persistent_rent_ledger_bytes: 0,
633                persistent_entry_rent_bumps: 0,
634                temporary_rent_ledger_bytes: 0,
635                temporary_entry_rent_bumps: 0
636            }
637            .estimate_fees(
638                &FeeConfiguration {
639                    fee_per_instruction_increment: 100,
640                    fee_per_read_entry: 100,
641                    fee_per_write_entry: 100,
642                    fee_per_read_1kb: 100,
643                    fee_per_write_1kb: 100,
644                    fee_per_historical_1kb: 100,
645                    fee_per_contract_event_1kb: 100,
646                    fee_per_transaction_size_1kb: 100,
647                },
648                1,
649                1
650            ),
651            FeeEstimate {
652                total: 0,
653                instructions: 0,
654                read_entries: 0,
655                write_entries: 0,
656                read_bytes: 0,
657                write_bytes: 0,
658                contract_events: 0,
659                persistent_entry_rent: 0,
660                temporary_entry_rent: 0,
661            }
662        );
663
664        // Minimal resources
665        assert_eq!(
666            InvocationResources {
667                instructions: 1,
668                mem_bytes: 100_000,
669                read_entries: 1,
670                write_entries: 1,
671                read_bytes: 1,
672                write_bytes: 1,
673                contract_events_size_bytes: 1,
674                persistent_rent_ledger_bytes: 1,
675                persistent_entry_rent_bumps: 1,
676                temporary_rent_ledger_bytes: 1,
677                temporary_entry_rent_bumps: 1
678            }
679            .estimate_fees(
680                &FeeConfiguration {
681                    fee_per_instruction_increment: 100,
682                    fee_per_read_entry: 100,
683                    fee_per_write_entry: 100,
684                    fee_per_read_1kb: 100,
685                    fee_per_write_1kb: 100,
686                    fee_per_historical_1kb: 100,
687                    fee_per_contract_event_1kb: 100,
688                    fee_per_transaction_size_1kb: 100,
689                },
690                1,
691                1
692            ),
693            FeeEstimate {
694                total: 516,
695                instructions: 1,
696                read_entries: 200,
697                write_entries: 100,
698                read_bytes: 1,
699                write_bytes: 1,
700                contract_events: 1,
701                persistent_entry_rent: 106,
702                temporary_entry_rent: 106
703            }
704        );
705
706        // Different resource/fee values, based on the values from
707        // fees::resource_fee_computation test.
708        assert_eq!(
709            InvocationResources {
710                instructions: 10_123_456,
711                mem_bytes: 100_000,
712                read_entries: 30,
713                write_entries: 10,
714                read_bytes: 25_600,
715                write_bytes: 10_340,
716                contract_events_size_bytes: 321_654,
717                persistent_rent_ledger_bytes: 1_000_000_000,
718                persistent_entry_rent_bumps: 3,
719                temporary_rent_ledger_bytes: 4_000_000_000,
720                temporary_entry_rent_bumps: 6
721            }
722            .estimate_fees(
723                &FeeConfiguration {
724                    fee_per_instruction_increment: 1000,
725                    fee_per_read_entry: 2000,
726                    fee_per_write_entry: 4000,
727                    fee_per_read_1kb: 1500,
728                    fee_per_write_1kb: 3000,
729                    fee_per_historical_1kb: 300,
730                    fee_per_contract_event_1kb: 200,
731                    fee_per_transaction_size_1kb: 900,
732                },
733                1000,
734                2000
735            ),
736            FeeEstimate {
737                // 1_200_139 + event fees + rent fees
738                total: 10_089_292,
739                instructions: 1_012_346,
740                read_entries: 80000,
741                write_entries: 40000,
742                read_bytes: 37500,
743                write_bytes: 30293,
744                contract_events: 62824,
745                persistent_entry_rent: 2942110,
746                temporary_entry_rent: 5884219
747            }
748        );
749
750        // Integer limits
751        assert_eq!(
752            InvocationResources {
753                instructions: i64::MAX,
754                mem_bytes: i64::MAX,
755                read_entries: u32::MAX,
756                write_entries: u32::MAX,
757                read_bytes: u32::MAX,
758                write_bytes: u32::MAX,
759                contract_events_size_bytes: u32::MAX,
760                persistent_rent_ledger_bytes: i64::MAX,
761                persistent_entry_rent_bumps: u32::MAX,
762                temporary_rent_ledger_bytes: i64::MAX,
763                temporary_entry_rent_bumps: u32::MAX
764            }
765            .estimate_fees(
766                &FeeConfiguration {
767                    fee_per_instruction_increment: i64::MAX,
768                    fee_per_read_entry: i64::MAX,
769                    fee_per_write_entry: i64::MAX,
770                    fee_per_read_1kb: i64::MAX,
771                    fee_per_write_1kb: i64::MAX,
772                    fee_per_historical_1kb: i64::MAX,
773                    fee_per_contract_event_1kb: i64::MAX,
774                    fee_per_transaction_size_1kb: i64::MAX,
775                },
776                i64::MAX,
777                i64::MAX
778            ),
779            FeeEstimate {
780                total: i64::MAX,
781                instructions: 922337203685478,
782                read_entries: i64::MAX,
783                write_entries: i64::MAX,
784                read_bytes: 9007199254740992,
785                write_bytes: 9007199254740992,
786                contract_events: 9007199254740992,
787                persistent_entry_rent: i64::MAX,
788                temporary_entry_rent: i64::MAX
789            }
790        );
791    }
792}