soroban-sdk 27.0.3

Soroban SDK.
Documentation
use soroban_env_host::{
    fees::FeeConfiguration, FeeEstimate, InvocationResourceLimits, InvocationResources,
};

use crate::{testutils::budget::Budget, Env};

pub struct CostEstimate {
    env: Env,
}

impl CostEstimate {
    pub(crate) fn new(env: Env) -> Self {
        Self { env }
    }

    /// Returns the resources metered during the last top level contract
    /// invocation.    
    /// Take the return value with a grain of salt. The returned resources mostly
    /// correspond only to the operations that have happened during the host
    /// invocation, i.e. this won't try to simulate the work that happens in
    /// production scenarios (e.g. certain XDR rountrips). This also doesn't try
    /// to model resources related to the transaction size.
    ///
    /// The returned value is as useful as the preceding setup, e.g. if a test
    /// contract is used instead of a Wasm contract, all the costs related to
    /// VM instantiation and execution, as well as Wasm reads/rent bumps will be
    /// missed.    
    pub fn resources(&self) -> InvocationResources {
        if let Some(res) = self.env.host().get_last_invocation_resources() {
            res
        } else {
            panic!("Invocation cost estimate is not available. Make sure invocation cost metering is enabled in the EnvTestConfig and this is called after an invocation.")
        }
    }

    /// Estimates the fee for the last invocation's resources, i.e. the
    /// resources returned by `resources()`.
    ///
    /// The fees are computed using a snapshot of the Stellar Mainnet fees made
    /// on 2026-07-10. Because the fees are hardcoded rather than pulled
    /// dynamically, they may drift from the live network over time; the current
    /// values can be checked via `stellar network settings --network mainnet`
    /// or on Stellar Lab: <https://lab.stellar.org/network-limits>. The one
    /// exception is the per-1KB storage rent rate, which is a deliberate
    /// conservative overestimate rather than the snapshot value, so storage
    /// rent estimates may be higher than the live network charges.
    ///
    /// Take the return value with a grain of salt as both the resource estimate
    /// and the fee rates may be imprecise.
    ///
    /// The returned value is as useful as the preceding setup, e.g. if a test
    /// contract is used instead of a Wasm contract, all the costs related to
    /// VM instantiation and execution, as well as Wasm reads/rent bumps will be
    /// missed.
    pub fn fee(&self) -> FeeEstimate {
        // This is a snapshot of the Stellar Mainnet fees as of 2026-07-10.
        // Refresh it with the values from `stellar network settings --network
        // mainnet` (or <https://lab.stellar.org/network-limits>) when it drifts.
        let pubnet_fee_config = FeeConfiguration {
            fee_per_instruction_increment: 7,
            fee_per_disk_read_entry: 1563,
            fee_per_write_entry: 2500,
            fee_per_disk_read_1kb: 447,
            fee_per_write_1kb: 875,
            fee_per_historical_1kb: 4059,
            fee_per_contract_event_1kb: 5000,
            fee_per_transaction_size_1kb: 406,
        };
        let pubnet_persistent_rent_rate_denominator = 1215;
        let pubnet_temp_rent_rate_denominator = 2430;
        // This is a bit higher than the current network fee, it's an
        // overestimate for the sake of providing a bit more conservative
        // results in case if the state grows.
        let fee_per_rent_1kb = 12000;
        self.resources().estimate_fees(
            &pubnet_fee_config,
            fee_per_rent_1kb,
            pubnet_persistent_rent_rate_denominator,
            pubnet_temp_rent_rate_denominator,
        )
    }

    /// Returns the budget object that provides the detailed CPU and memory
    /// metering information recorded thus far.
    ///
    /// The budget metering resets before every top-level contract level
    /// invocation.
    ///
    /// budget() may also be used to adjust the CPU and memory limits via the
    /// `reset_` methods.
    ///
    /// Note, that unlike `resources()`/`fee()` this will always return some
    /// value. If there was no contract call, then the resulting value will
    /// correspond to metering any environment setup that has been made thus
    /// far.
    pub fn budget(&self) -> Budget {
        Budget::new(self.env.host().budget_cloned())
    }

    /// Enforces custom resource limits for contract invocations in tests.
    ///
    /// When limit enforcement is enabled, for every contract invocation the
    /// resource usage is checked against the provided limits, and if any of the
    /// limits is exceeded, the contract invocation will result in a panic
    /// that indicates which limits were exceeded.
    ///
    /// Limit enforcement is meant to provide an early warning sign that a
    /// contract might be too resource heavy to run on a real network. If the
    /// high resource usage is intentional and expected (e.g. for
    /// experimentation), disable the enforcement via
    /// `disable_resource_limits()`.
    ///
    /// By default, `InvocationResourceLimits::mainnet()` limits are enforced.
    pub fn enforce_resource_limits(&self, limits: InvocationResourceLimits) {
        self.env
            .host()
            .set_invocation_resource_limits(Some(limits))
            .unwrap();
    }

    /// Disables resource limit enforcement for contract invocations in tests.
    ///
    /// This may be useful for the experimental contracts that are still being
    /// optimized.
    pub fn disable_resource_limits(&self) {
        self.env
            .host()
            .set_invocation_resource_limits(None)
            .unwrap();
    }
}

/// Predefined network invocation resource limits.
pub trait NetworkInvocationResourceLimits {
    fn mainnet() -> Self;
}

impl NetworkInvocationResourceLimits for InvocationResourceLimits {
    /// Returns the invocation resource limits used on Stellar Mainnet.
    ///
    /// These values are a snapshot of the Mainnet network settings as of
    /// 2026-07-10; they are hardcoded rather than pulled dynamically, so
    /// updating the SDK is necessary to pick up the most recent values. The
    /// current values can be checked via `stellar network settings --network
    /// mainnet` or on Stellar Lab: <https://lab.stellar.org/network-limits>.
    fn mainnet() -> Self {
        InvocationResourceLimits {
            instructions: 400_000_000,
            mem_bytes: 41943040,
            disk_read_entries: 200,
            write_entries: 200,
            ledger_entries: 400,
            disk_read_bytes: 200000,
            write_bytes: 132096,
            contract_events_size_bytes: 16384,
            max_contract_data_key_size_bytes: 250,
            max_contract_data_entry_size_bytes: 65536,
            max_contract_code_entry_size_bytes: 131072,
        }
    }
}