1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
use crate::{
    xdr::{ContractCostParamEntry, ScErrorCode, ScErrorType},
    HostError,
};
use core::fmt::{Debug, Display};

/// We provide a "cost model" object that evaluates a linear expression:
///
///    f(x) = a + b * Option<x>
///
/// Where a, b are "fixed" parameters at construction time (extracted from an
/// on-chain cost schedule, so technically not _totally_ fixed) and Option<x>
/// is some abstract input variable -- say, event counts or object sizes --
/// provided at runtime. If the input cannot be defined, i.e., the cost is
/// constant, input-independent, then pass in `None` as the input.
///
/// The same `CostModel` type, i.e. `CostType` (applied to different parameters
/// and variables) is used for calculating memory as well as CPU time.
///
/// The various `CostType`s are carefully choosen such that 1. their underlying
/// cost characteristics (both cpu and memory) at runtime can be described
/// sufficiently by a linear model and 2. they together encompass the vast
/// majority of available operations done by the `env` -- the host and the VM.
///
/// The parameters for a `CostModel` are calibrated empirically. See this crate's
/// benchmarks for more details.
pub trait HostCostModel {
    fn evaluate(&self, input: Option<u64>) -> Result<u64, HostError>;

    #[cfg(any(test, feature = "testutils", feature = "bench"))]
    fn reset(&mut self);
}

/// The number of bits to scale the linear term by. The linear coefficient has
/// been scaled by this factor during parameter fitting to retain more significant
/// digits. Thus to get the cost from the raw input, we need to scale the result
/// back by the same factor.
pub const COST_MODEL_LIN_TERM_SCALE_BITS: u32 = 7;

/// A helper type that wraps an u64 to signify the wrapped value have been scaled.
#[derive(Clone, Default)]
pub(crate) struct ScaledU64(pub(crate) u64);

impl ScaledU64 {
    pub const fn unscale(self) -> u64 {
        self.0 >> COST_MODEL_LIN_TERM_SCALE_BITS
    }

    pub const fn from_unscaled_u64(u: u64) -> Self {
        ScaledU64(u << COST_MODEL_LIN_TERM_SCALE_BITS)
    }

    pub const fn is_zero(&self) -> bool {
        self.0 == 0
    }

    pub const fn saturating_mul(&self, rhs: u64) -> Self {
        ScaledU64(self.0.saturating_mul(rhs))
    }

    pub const fn safe_div(&self, rhs: u64) -> Self {
        ScaledU64(match self.0.checked_div(rhs) {
            Some(v) => v,
            None => 0,
        })
    }
}

impl Display for ScaledU64 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl Debug for ScaledU64 {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Scaled({})", self.0)
    }
}

#[derive(Clone, Debug, Default)]
pub(crate) struct MeteredCostComponent {
    pub(crate) const_term: u64,
    pub(crate) lin_term: ScaledU64,
}

impl TryFrom<&ContractCostParamEntry> for MeteredCostComponent {
    type Error = HostError;

    fn try_from(entry: &ContractCostParamEntry) -> Result<Self, Self::Error> {
        if entry.const_term < 0 || entry.linear_term < 0 {
            return Err((ScErrorType::Context, ScErrorCode::InvalidInput).into());
        }
        Ok(MeteredCostComponent {
            const_term: entry.const_term as u64,
            lin_term: ScaledU64(entry.linear_term as u64),
        })
    }
}

impl TryFrom<ContractCostParamEntry> for MeteredCostComponent {
    type Error = HostError;

    fn try_from(entry: ContractCostParamEntry) -> Result<Self, Self::Error> {
        Self::try_from(&entry)
    }
}

impl HostCostModel for MeteredCostComponent {
    fn evaluate(&self, input: Option<u64>) -> Result<u64, HostError> {
        let const_term = self.const_term;
        match input {
            Some(input) => {
                let mut res = const_term;
                if !self.lin_term.is_zero() {
                    let lin_cost = self.lin_term.saturating_mul(input).unscale();
                    res = res.saturating_add(lin_cost)
                }
                Ok(res)
            }
            None => Ok(const_term),
        }
    }

    #[cfg(any(test, feature = "testutils", feature = "bench"))]
    fn reset(&mut self) {
        self.const_term = 0;
        self.lin_term = ScaledU64(0);
    }
}