Skip to main content

midnight_base_crypto/
cost_model.rs

1// This file is part of midnight-ledger.
2// Copyright (C) 2025 Midnight Foundation
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// You may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7// http://www.apache.org/licenses/LICENSE-2.0
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14//! This module contains primitives used in the cost model, including the
15//! structures used for costing, the time abstraction used in the cost model,
16//! and the arithmetic defined over them.
17
18use ethnum::i256;
19#[cfg(feature = "proptest")]
20use proptest_derive::Arbitrary;
21use rand::{distributions::Standard, prelude::Distribution};
22use serde::{Deserialize, Serialize};
23use serialize::{Deserializable, Serializable, Tagged};
24use std::{
25    fmt::Debug,
26    iter::Sum,
27    ops::{Add, AddAssign, Div, Mul, Neg, Sub, SubAssign},
28};
29
30#[derive(
31    Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serializable, Serialize, Deserialize, Default,
32)]
33#[tag = "cost-duration[v1]"]
34#[serde(transparent)]
35#[cfg_attr(feature = "proptest", derive(Arbitrary))]
36/// A 'costed' time, measured in picoseconds.
37pub struct CostDuration(u64);
38
39impl Debug for CostDuration {
40    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41        match self.0 {
42            1..1_000 => write!(f, "{}ps", self.0),
43            1_000..1_000_000 => write!(f, "{:.3}ns", self.0 as f64 / 1e3f64),
44            1_000_000..1_000_000_000 => write!(f, "{:.3}μs", self.0 as f64 / 1e6f64),
45            1_000_000_000..1_000_000_000_000 => write!(f, "{:.3}ms", self.0 as f64 / 1e9f64),
46            _ => write!(f, "{:.3}s", self.0 as f64 / 1e12f64),
47        }
48    }
49}
50
51impl Distribution<CostDuration> for Standard {
52    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> CostDuration {
53        CostDuration(self.sample(rng))
54    }
55}
56
57impl Sum for CostDuration {
58    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
59        CostDuration(
60            iter.map(|i| i.0)
61                .reduce(|a, b| a.saturating_add(b))
62                .unwrap_or(0),
63        )
64    }
65}
66
67impl SubAssign for CostDuration {
68    fn sub_assign(&mut self, rhs: Self) {
69        *self = *self - rhs;
70    }
71}
72
73impl AddAssign for CostDuration {
74    fn add_assign(&mut self, rhs: Self) {
75        *self = *self + rhs;
76    }
77}
78
79impl Add for CostDuration {
80    type Output = CostDuration;
81    fn add(self, rhs: Self) -> Self::Output {
82        CostDuration(self.0.saturating_add(rhs.0))
83    }
84}
85
86impl Sub for CostDuration {
87    type Output = CostDuration;
88    fn sub(self, rhs: Self) -> Self::Output {
89        CostDuration(self.0.saturating_sub(rhs.0))
90    }
91}
92
93impl Mul<CostDuration> for usize {
94    type Output = CostDuration;
95    fn mul(self, rhs: CostDuration) -> Self::Output {
96        CostDuration((self as u64).saturating_mul(rhs.0))
97    }
98}
99
100impl Mul<CostDuration> for u64 {
101    type Output = CostDuration;
102    fn mul(self, rhs: CostDuration) -> Self::Output {
103        CostDuration(self.saturating_mul(rhs.0))
104    }
105}
106
107impl Mul<usize> for CostDuration {
108    type Output = CostDuration;
109    fn mul(self, rhs: usize) -> Self::Output {
110        CostDuration(self.0.saturating_mul(rhs as u64))
111    }
112}
113
114impl Mul<u64> for CostDuration {
115    type Output = CostDuration;
116    fn mul(self, rhs: u64) -> Self::Output {
117        CostDuration(self.0.saturating_mul(rhs))
118    }
119}
120
121impl Mul<f64> for CostDuration {
122    type Output = CostDuration;
123    fn mul(self, rhs: f64) -> Self::Output {
124        CostDuration((self.0 as f64 * rhs) as u64)
125    }
126}
127
128impl Mul<CostDuration> for f64 {
129    type Output = CostDuration;
130    fn mul(self, rhs: CostDuration) -> Self::Output {
131        CostDuration((self * rhs.0 as f64) as u64)
132    }
133}
134
135impl Div for CostDuration {
136    type Output = FixedPoint;
137    fn div(self, rhs: Self) -> Self::Output {
138        FixedPoint::from_u64_div(self.0, rhs.0)
139    }
140}
141
142impl Div<u64> for CostDuration {
143    type Output = CostDuration;
144    fn div(self, rhs: u64) -> Self::Output {
145        CostDuration(self.0.div_ceil(rhs))
146    }
147}
148
149#[derive(
150    PartialEq,
151    Eq,
152    PartialOrd,
153    Ord,
154    Debug,
155    Copy,
156    Clone,
157    Serializable,
158    Serialize,
159    Deserialize,
160    Default,
161)]
162#[tag = "synthetic-cost[v1]"]
163/// The synthetic (modeled) cost of execution, typically over a transaction or
164/// block.
165pub struct SyntheticCost {
166    #[serde(rename = "readTime")]
167    /// The time spent in IO reads
168    pub read_time: CostDuration,
169    #[serde(rename = "computeTime")]
170    /// The time spent in single-threaded compute
171    pub compute_time: CostDuration,
172    #[serde(rename = "blockUsage")]
173    /// The bytes used of block size capacity
174    pub block_usage: u64,
175    #[serde(rename = "bytesWritten")]
176    /// The bytes written persistently to disk.
177    /// Unlike in [`RunningCost`], this represents net bytes written, defined for `r: RunningCost`
178    /// as `max(0, r.bytes_written - r.bytes_deleted)`
179    pub bytes_written: u64,
180    #[serde(rename = "bytesChurned")]
181    /// The bytes written temporarily or overwritten
182    pub bytes_churned: u64,
183}
184
185impl Mul<f64> for SyntheticCost {
186    type Output = SyntheticCost;
187    fn mul(self, rhs: f64) -> Self::Output {
188        SyntheticCost {
189            compute_time: self.compute_time * rhs,
190            read_time: self.read_time * rhs,
191            block_usage: (self.block_usage as f64 * rhs).ceil() as u64,
192            bytes_written: (self.bytes_written as f64 * rhs).ceil() as u64,
193            bytes_churned: (self.bytes_churned as f64 * rhs).ceil() as u64,
194        }
195    }
196}
197
198impl Add for SyntheticCost {
199    type Output = SyntheticCost;
200    fn add(self, rhs: Self) -> Self::Output {
201        SyntheticCost {
202            read_time: self.read_time + rhs.read_time,
203            compute_time: self.compute_time + rhs.compute_time,
204            block_usage: self.block_usage.saturating_add(rhs.block_usage),
205            bytes_written: self.bytes_written.saturating_add(rhs.bytes_written),
206            bytes_churned: self.bytes_churned.saturating_add(rhs.bytes_churned),
207        }
208    }
209}
210
211#[derive(
212    PartialEq, Eq, PartialOrd, Ord, Debug, Copy, Clone, Serialize, Deserialize, Serializable,
213)]
214#[tag = "normalized-cost[v1]"]
215/// The costs normalized to a block's limit in each dimension
216pub struct NormalizedCost {
217    #[serde(rename = "readTime")]
218    /// The fraction of a block's read time used
219    pub read_time: FixedPoint,
220    #[serde(rename = "computeTime")]
221    /// The fraction of a block's compute time used
222    pub compute_time: FixedPoint,
223    #[serde(rename = "blockUsage")]
224    /// The fraction of a block's size used
225    pub block_usage: FixedPoint,
226    #[serde(rename = "bytesWritten")]
227    /// The fraction of a block's data write allowance used
228    pub bytes_written: FixedPoint,
229    #[serde(rename = "bytesChurned")]
230    /// The fraction of a block's data churn allowance used
231    pub bytes_churned: FixedPoint,
232}
233
234impl NormalizedCost {
235    /// The empty cost
236    pub const ZERO: NormalizedCost = NormalizedCost {
237        read_time: FixedPoint::ZERO,
238        compute_time: FixedPoint::ZERO,
239        block_usage: FixedPoint::ZERO,
240        bytes_written: FixedPoint::ZERO,
241        bytes_churned: FixedPoint::ZERO,
242    };
243}
244
245impl SyntheticCost {
246    /// The empty cost
247    pub const ZERO: SyntheticCost = SyntheticCost {
248        read_time: CostDuration::ZERO,
249        compute_time: CostDuration::ZERO,
250        block_usage: 0,
251        bytes_written: 0,
252        bytes_churned: 0,
253    };
254
255    /// The longest time spent in this cost
256    pub fn max_time(&self) -> CostDuration {
257        CostDuration::max(self.read_time, self.compute_time)
258    }
259
260    /// Normalizes the cost against block limits, returning `None` if they exceed them
261    pub fn normalize(self, limits: SyntheticCost) -> Option<NormalizedCost> {
262        let res = NormalizedCost {
263            read_time: self.read_time / limits.read_time,
264            compute_time: self.compute_time / limits.compute_time,
265            block_usage: FixedPoint::from_u64_div(self.block_usage, limits.block_usage),
266            bytes_written: FixedPoint::from_u64_div(self.bytes_written, limits.bytes_written),
267            bytes_churned: FixedPoint::from_u64_div(self.bytes_churned, limits.bytes_churned),
268        };
269        let vals = [
270            &res.read_time,
271            &res.compute_time,
272            &res.block_usage,
273            &res.bytes_written,
274            &res.bytes_churned,
275        ];
276        if vals.into_iter().any(|val| *val > FixedPoint::ONE) {
277            None
278        } else {
279            Some(res)
280        }
281    }
282}
283
284#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Serializable, Serialize, Deserialize)]
285#[tag = "fee-prices[v2]"]
286/// The pricing of the various block operations
287///
288/// All values are denominated in DUST (*not* atomic units, or SPECKs)
289pub struct FeePrices {
290    /// The price in DUST of a full block for the average cost dimension
291    #[serde(rename = "overallPrice")]
292    #[cfg_attr(
293        feature = "fixed-point-custom-serde",
294        serde(with = "fixed_point_custom_serde")
295    )]
296    pub overall_price: FixedPoint,
297    /// The price factor applied to the read cost dimension
298    #[serde(rename = "readFactor")]
299    #[cfg_attr(
300        feature = "fixed-point-custom-serde",
301        serde(with = "fixed_point_custom_serde")
302    )]
303    pub read_factor: FixedPoint,
304    /// The price in DUST of a block's full compute capacity
305    #[serde(rename = "computeFactor")]
306    #[cfg_attr(
307        feature = "fixed-point-custom-serde",
308        serde(with = "fixed_point_custom_serde")
309    )]
310    pub compute_factor: FixedPoint,
311    /// The price in DUST of a block's full size capacity
312    #[serde(rename = "blockUsageFactor")]
313    #[cfg_attr(
314        feature = "fixed-point-custom-serde",
315        serde(with = "fixed_point_custom_serde")
316    )]
317    pub block_usage_factor: FixedPoint,
318    /// The price in DUST of a block's full write allowance capacity
319    #[serde(rename = "writeFactor")]
320    #[cfg_attr(
321        feature = "fixed-point-custom-serde",
322        serde(with = "fixed_point_custom_serde")
323    )]
324    pub write_factor: FixedPoint,
325}
326
327impl FeePrices {
328    /// Compute an updated cost from a given block fullness. This should be the
329    /// sum of the normalized costs of all transactions in a block.
330    ///
331    /// Overall block fullness is the total fullness of a block, while detailed block fullness
332    /// references the how full individual cost dimensions are.
333    ///
334    /// `min_ratio` specifies a bound that the smallest price will not fall
335    /// below, as a ratio of the highest price. It should be `0 < min_ratio < 1`.
336    ///
337    /// `a` is the `a` parameter from [`price_adjustment_function`].
338    pub fn update_from_fullness(
339        &self,
340        detailed_block_fullness: NormalizedCost,
341        overall_block_fullness: FixedPoint,
342        min_ratio: FixedPoint,
343        a: FixedPoint,
344    ) -> Self {
345        let multiplier = |frac| price_adjustment_function(frac, a) + FixedPoint::ONE;
346        let mut updated = FeePrices {
347            overall_price: self.overall_price * multiplier(overall_block_fullness),
348            read_factor: self.read_factor * multiplier(detailed_block_fullness.read_time),
349            compute_factor: self.compute_factor * multiplier(detailed_block_fullness.compute_time),
350            block_usage_factor: self.block_usage_factor
351                * multiplier(detailed_block_fullness.block_usage),
352            write_factor: self.write_factor
353                * multiplier(FixedPoint::max(
354                    detailed_block_fullness.bytes_written,
355                    detailed_block_fullness.bytes_churned,
356                )),
357        };
358        let mut dimensions = [
359            &mut updated.read_factor,
360            &mut updated.compute_factor,
361            &mut updated.block_usage_factor,
362            &mut updated.write_factor,
363        ];
364        let most_expensive_dimension = **dimensions
365            .iter()
366            .max()
367            .expect("max of 4 elements must exist");
368        for dim in dimensions.iter_mut() {
369            **dim = FixedPoint::max(**dim, most_expensive_dimension * min_ratio);
370        }
371        // The smallest fixed point cost is *not* MIN_POSITIVE, to ensure that we don't get 'stuck'
372        // there and unable to adjust up. Because adjustments are small, single-digit percentages,
373        // rounding would keep us at 1 if this was 1.
374        const MIN_COST: FixedPoint = FixedPoint(100);
375        updated.overall_price = FixedPoint::max(updated.overall_price, MIN_COST);
376        // Now we normalize the factors to have an average of 1.
377        let factor_mean = dimensions
378            .iter()
379            .map(|d| **d)
380            .fold(FixedPoint::ZERO, FixedPoint::add)
381            / FixedPoint::from_u64_div(4, 1);
382        for dim in dimensions.into_iter() {
383            *dim = *dim / factor_mean;
384        }
385        updated
386    }
387
388    /// The overall (dust) cost of a synthetic resource cost, given this
389    /// resource price object.
390    ///
391    /// The final cost is denominated in DUST.
392    pub fn overall_cost(&self, tx_normalized: &NormalizedCost) -> FixedPoint {
393        let read_cost = self.read_factor * tx_normalized.read_time;
394        let compute_cost = self.compute_factor * tx_normalized.compute_time;
395        let block_usage_cost = self.block_usage_factor * tx_normalized.block_usage;
396        let write_cost = self.write_factor * tx_normalized.bytes_written;
397        let churn_cost = self.write_factor * tx_normalized.bytes_churned;
398        let utilization_cost =
399            FixedPoint::max(read_cost, FixedPoint::max(compute_cost, block_usage_cost));
400        self.overall_price * (utilization_cost + write_cost + churn_cost)
401    }
402}
403
404#[derive(
405    PartialEq,
406    Eq,
407    PartialOrd,
408    Ord,
409    Debug,
410    Copy,
411    Clone,
412    Serializable,
413    Serialize,
414    Deserialize,
415    Default,
416)]
417#[tag = "running-cost[v1]"]
418/// The cost during computation, tracking read time, compute time, and bytes
419/// written and deleted.
420pub struct RunningCost {
421    #[serde(rename = "readTime")]
422    /// The time spent reading according to the model
423    pub read_time: CostDuration,
424    #[serde(rename = "computeTime")]
425    /// The time spent in single-threaded compute according to the model
426    pub compute_time: CostDuration,
427    #[serde(rename = "bytesWritten")]
428    /// The number of bytes written according to the model.
429    /// Unlike `bytes_written` in [`SyntheticCost`], this one represents the absolute bytes
430    /// written, not net bytes written.
431    pub bytes_written: u64,
432    #[serde(rename = "bytesDeleted")]
433    /// The number of bytes deleted according to the model
434    pub bytes_deleted: u64,
435}
436
437impl RunningCost {
438    /// The running cost of zero.
439    pub const ZERO: RunningCost = RunningCost {
440        read_time: CostDuration::ZERO,
441        compute_time: CostDuration::ZERO,
442        bytes_written: 0,
443        bytes_deleted: 0,
444    };
445
446    /// Captures only some compute time
447    pub const fn compute(time: CostDuration) -> RunningCost {
448        RunningCost {
449            read_time: CostDuration::ZERO,
450            compute_time: time,
451            bytes_written: 0,
452            bytes_deleted: 0,
453        }
454    }
455
456    /// The longest time spent in this cost
457    pub fn max_time(&self) -> CostDuration {
458        CostDuration::max(self.read_time, self.compute_time)
459    }
460}
461
462impl Distribution<RunningCost> for Standard {
463    fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> RunningCost {
464        RunningCost {
465            read_time: self.sample(rng),
466            compute_time: self.sample(rng),
467            bytes_written: self.sample(rng),
468            bytes_deleted: self.sample(rng),
469        }
470    }
471}
472
473impl From<RunningCost> for SyntheticCost {
474    fn from(value: RunningCost) -> Self {
475        // Convert from absolute bytes written to net bytes written
476        let bytes_written = value.bytes_written.saturating_sub(value.bytes_deleted);
477        SyntheticCost {
478            read_time: value.read_time,
479            compute_time: value.compute_time,
480            block_usage: 0,
481            bytes_written,
482            bytes_churned: value.bytes_written - bytes_written,
483        }
484    }
485}
486
487// Lossy conversion back to running cost, used in cost estimation heuristics
488impl From<SyntheticCost> for RunningCost {
489    fn from(value: SyntheticCost) -> Self {
490        RunningCost {
491            read_time: value.read_time,
492            compute_time: value.compute_time,
493            bytes_written: value.bytes_written,
494            bytes_deleted: 0,
495        }
496    }
497}
498
499impl std::iter::Sum for RunningCost {
500    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
501        let mut res = RunningCost::ZERO;
502        for i in iter {
503            res += i;
504        }
505        res
506    }
507}
508
509impl Mul<f64> for RunningCost {
510    type Output = RunningCost;
511    fn mul(self, rhs: f64) -> Self::Output {
512        RunningCost {
513            compute_time: self.compute_time * rhs,
514            read_time: self.read_time * rhs,
515            bytes_written: (self.bytes_written as f64 * rhs).ceil() as u64,
516            bytes_deleted: (self.bytes_deleted as f64 * rhs).ceil() as u64,
517        }
518    }
519}
520
521impl Mul<usize> for RunningCost {
522    type Output = RunningCost;
523    fn mul(self, rhs: usize) -> Self::Output {
524        self * rhs as u64
525    }
526}
527
528impl Mul<u64> for RunningCost {
529    type Output = RunningCost;
530    fn mul(self, rhs: u64) -> Self::Output {
531        RunningCost {
532            compute_time: self.compute_time * rhs,
533            read_time: self.read_time * rhs,
534            bytes_written: self.bytes_written.saturating_mul(rhs),
535            bytes_deleted: self.bytes_deleted.saturating_mul(rhs),
536        }
537    }
538}
539
540impl Add for RunningCost {
541    type Output = RunningCost;
542    fn add(self, rhs: Self) -> Self::Output {
543        RunningCost {
544            read_time: self.read_time + rhs.read_time,
545            compute_time: self.compute_time + rhs.compute_time,
546            bytes_written: self.bytes_written.saturating_add(rhs.bytes_written),
547            bytes_deleted: self.bytes_deleted.saturating_add(rhs.bytes_deleted),
548        }
549    }
550}
551
552impl AddAssign for RunningCost {
553    fn add_assign(&mut self, rhs: Self) {
554        *self = *self + rhs;
555    }
556}
557
558impl CostDuration {
559    /// No cost duration
560    pub const ZERO: CostDuration = CostDuration(0);
561    /// A second in [`CostDuration`] representation.
562    pub const SECOND: CostDuration = CostDuration(1_000_000_000_000);
563    /// Initializes this cost duration measurement from raw picoseconds
564    pub const fn from_picoseconds(picoseconds: u64) -> CostDuration {
565        CostDuration(picoseconds)
566    }
567
568    /// The raw picosecond count of this cost duration measurement
569    pub const fn into_picoseconds(self) -> u64 {
570        self.0
571    }
572}
573
574#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Serializable)]
575#[tag = "fixed-point[v1]"]
576/// Represents a rational number deterministically. Internally, numbers are
577/// represented by an integer `x: i128`, which represents the real `x / (2 ** 64)`.
578///
579/// Addition, multiplication, and division are defined; as this is used for cost
580/// estimations, addition and multiplication are saturating (because the maximum
581/// should always be rejected), and division rounds up.
582pub struct FixedPoint(i128);
583
584impl Serialize for FixedPoint {
585    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
586    where
587        S: serde::Serializer,
588    {
589        serializer.serialize_f64((*self).into())
590    }
591}
592
593impl<'de> Deserialize<'de> for FixedPoint {
594    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
595    where
596        D: serde::Deserializer<'de>,
597    {
598        f64::deserialize(deserializer).map(Into::into)
599    }
600}
601
602impl Debug for FixedPoint {
603    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
604        write!(f, "FixedPoint({})", f64::from(*self))
605    }
606}
607
608impl From<f64> for FixedPoint {
609    fn from(float: f64) -> FixedPoint {
610        FixedPoint((float * 2f64.powi(64)) as i128)
611    }
612}
613
614impl From<FixedPoint> for i128 {
615    fn from(fp: FixedPoint) -> i128 {
616        fp.0 >> 64
617    }
618}
619
620impl From<FixedPoint> for f64 {
621    fn from(fp: FixedPoint) -> f64 {
622        fp.0 as f64 / 2f64.powi(64)
623    }
624}
625
626/// Custom serde for the `FixedPoint` structure, sacrificing readability for precision
627pub mod fixed_point_custom_serde {
628    use super::FixedPoint;
629
630    /// serializing `FixedPoint`` structure
631    pub fn serialize<S>(value: &FixedPoint, s: S) -> Result<S::Ok, S::Error>
632    where
633        S: serde::Serializer,
634    {
635        s.serialize_i128(value.0)
636    }
637
638    /// deserializing `FixedPoint` structure
639    pub fn deserialize<'de, D>(d: D) -> Result<FixedPoint, D::Error>
640    where
641        D: serde::Deserializer<'de>,
642    {
643        let inner_value = serde::Deserialize::deserialize(d)?;
644        Ok(FixedPoint(inner_value))
645    }
646}
647
648// NOTE: For reasoning about arith, convention is that:
649// - A/B is the 'real' number
650// - a/b is the representation
651// - Therefore, A = a / (2 ** 64)
652
653impl FixedPoint {
654    /// The value of 0.0
655    pub const ZERO: FixedPoint = FixedPoint(0);
656    /// The value of 1.0
657    pub const ONE: FixedPoint = FixedPoint::from_u64_div(1, 1);
658    /// The smallest positive fraction representable in this fixed point representation
659    pub const MIN_POSITIVE: FixedPoint = FixedPoint(1);
660    /// The maximum representable fixed point number
661    pub const MAX: FixedPoint = FixedPoint(i128::MAX);
662
663    /// Takes a [`FixedPoint`] denominated in a non-base token unit (for instance,
664    /// 1.0 representing DUST) to it's base unit.
665    ///
666    /// Conceptually, acts as `self * base_unit` as `u128` with smarter overflow
667    /// handling.
668    ///
669    /// Rounds up, and returns zero for negatives.
670    pub fn into_atomic_units(self, base_unit: u128) -> u128 {
671        let raw = i256::from(self.0) * i256::from(base_unit);
672        let (res, rem) = raw.div_rem(i256::from(1u128 << 64));
673        let res = if rem <= 0 { 0 } else { 1 } + res;
674        if res < 0 {
675            0
676        } else if res > i256::from(u128::MAX) {
677            u128::MAX
678        } else {
679            res.as_u128()
680        }
681    }
682
683    /// Raises the number to an integer power.
684    pub fn powi(self, mut exp: i32) -> Self {
685        match exp {
686            i32::MIN..=-1 => FixedPoint::ONE / self.powi(-exp),
687            0 => FixedPoint::ONE,
688            1..=i32::MAX => {
689                let mut acc = FixedPoint::ONE;
690                let mut cur = self;
691                while exp >= 1 {
692                    if exp & 0b1 != 0 {
693                        acc = acc * cur;
694                    }
695                    cur = cur * cur;
696                    exp >>= 1;
697                }
698                acc
699            }
700        }
701    }
702
703    /// Instantiates a fixed point from a/b (rounded up to the nearest 2^-64)
704    ///
705    /// Unlike [`FixedPoint::from_u128_div`], this method is `const`, due to using only builtin
706    /// rust arithmetic operations.
707    pub const fn from_u64_div(a: u64, b: u64) -> FixedPoint {
708        // C = a / b
709        // c / (2 ** 64) = a / b
710        // c = a * (2 ** 64) / b
711        if b == 0 {
712            return FixedPoint(i128::MAX);
713        }
714        let ashift = (a as u128) << 64;
715        let c = ashift.div_ceil(b as u128) as i128;
716        FixedPoint(c)
717    }
718
719    /// Instantiates a fixed point from a/b (rounded up to the nearest 2^-64)
720    pub fn from_u128_div(a: u128, b: u128) -> FixedPoint {
721        // C = a / b
722        // c / (2 ** 64) = a / b
723        // c = a * (2 ** 64) / b
724        if b == 0 {
725            return FixedPoint(i128::MAX);
726        }
727        let ashift = i256::from(a) * i256::from(1u128 << 64);
728        let (c, rem) = ashift.div_rem(i256::from(b));
729        let c = if rem == i256::ZERO {
730            i256::from(0u64)
731        } else {
732            i256::from(1u64)
733        } + c;
734        if c > i256::from(u128::MAX) {
735            FixedPoint(i128::MAX)
736        } else {
737            FixedPoint(c.as_i128())
738        }
739    }
740}
741
742impl Add for FixedPoint {
743    type Output = Self;
744    fn add(self, rhs: Self) -> Self::Output {
745        // C = A + B
746        // c / (2 ** 64) = (a / (2 ** 64)) + (b / (2 ** 64)) = (a + b) / (2 ** 64)
747        // c = a + b
748        FixedPoint(self.0.saturating_add(rhs.0))
749    }
750}
751
752impl Sub for FixedPoint {
753    type Output = FixedPoint;
754    fn sub(self, rhs: Self) -> Self::Output {
755        // C = A - B
756        // c / (2 ** 64) = (a / (2 ** 64)) - (b / (2 ** 64)) = (a - b) / (2 ** 64)
757        // c = a - b
758        FixedPoint(self.0.saturating_sub(rhs.0))
759    }
760}
761
762impl Neg for FixedPoint {
763    type Output = FixedPoint;
764    fn neg(self) -> Self::Output {
765        FixedPoint(self.0.saturating_neg())
766    }
767}
768
769impl Mul for FixedPoint {
770    type Output = Self;
771    fn mul(self, rhs: Self) -> Self::Output {
772        // C = A * B
773        // (c / (2 ** 64)) = (a / (2 ** 64)) * (b / (2 ** 64)) = (a * b) / (2 ** 128)
774        // c = (a * b) / (2 ** 64)
775        let ab = i256::from(self.0) * rhs.0;
776        let c = i256::min(i256::from(i128::MAX), ab >> 64).as_i128();
777        FixedPoint(c)
778    }
779}
780
781impl Div for FixedPoint {
782    type Output = Self;
783    /// Division rounding up to the nearest 2^-64
784    fn div(self, rhs: Self) -> Self::Output {
785        // C = A / B
786        // C = |A| / |B| * sign(A) * sign(B)
787        if rhs.0 == 0 {
788            // Rather max out pricing than panic
789            return FixedPoint(i128::MAX);
790        }
791        let a_abs = self.0.unsigned_abs();
792        let b_abs = rhs.0.unsigned_abs();
793        let a_sign = self.0.signum();
794        let b_sign = rhs.0.signum();
795        FixedPoint(FixedPoint::from_u128_div(a_abs, b_abs).0 * a_sign * b_sign)
796    }
797}
798
799/// The raw price adjustment function from fullness, as specified in tokenomics
800/// documents
801pub fn price_adjustment_function(usage: FixedPoint, a: FixedPoint) -> FixedPoint {
802    // Points of the function to linearly interpolate between. 0 is point 0, the
803    // final point is 1.
804    //
805    // As output and enforced by test_price_adjustment_static
806    const POINTS: &[FixedPoint] = &[
807        FixedPoint(-84764999863455367168), // 0.00 => -4.59512
808        FixedPoint(-84764999863455367168), // 0.01 => -4.59512
809        FixedPoint(-71791413020114739200), // 0.02 => -3.89182
810        FixedPoint(-64122702906348363776), // 0.03 => -3.47610
811        FixedPoint(-58624745660900909056), // 0.04 => -3.17805
812        FixedPoint(-54315312289337933824), // 0.05 => -2.94444
813        FixedPoint(-50756867729459126272), // 0.06 => -2.75154
814        FixedPoint(-47715996328766365696), // 0.07 => -2.58669
815        FixedPoint(-45053350700638961664), // 0.08 => -2.44235
816        FixedPoint(-42679031418590216192), // 0.09 => -2.31363
817        FixedPoint(-40531639450585882624), // 0.10 => -2.19722
818        FixedPoint(-38567365939524018176), // 0.11 => -2.09074
819        FixedPoint(-36753849332779208704), // 0.12 => -1.99243
820        FixedPoint(-35066499762404089856), // 0.13 => -1.90096
821        FixedPoint(-33486189434148528128), // 0.14 => -1.81529
822        FixedPoint(-31997741738730885120), // 0.15 => -1.73460
823        FixedPoint(-30588908944945000448), // 0.16 => -1.65823
824        FixedPoint(-29249660330515177472), // 0.17 => -1.58563
825        FixedPoint(-27971674063185141760), // 0.18 => -1.51635
826        FixedPoint(-26747966611833823232), // 0.19 => -1.45001
827        FixedPoint(-25572617290405310464), // 0.20 => -1.38629
828        FixedPoint(-24440560042528645120), // 0.21 => -1.32493
829        FixedPoint(-23347423671542177792), // 0.22 => -1.26567
830        FixedPoint(-22289407577085239296), // 0.23 => -1.20831
831        FixedPoint(-21263183918842343424), // 0.24 => -1.15268
832        FixedPoint(-20265819725292941312), // 0.25 => -1.09861
833        FixedPoint(-19294714246602784768), // 0.26 => -1.04597
834        FixedPoint(-18347548093616457728), // 0.27 => -0.99462
835        FixedPoint(-17422241585731162112), // 0.28 => -0.94446
836        FixedPoint(-16516920363702972416), // 0.29 => -0.89538
837        FixedPoint(-15629886784764432384), // 0.30 => -0.84730
838        FixedPoint(-14759595957603760128), // 0.31 => -0.80012
839        FixedPoint(-13904635528415858688), // 0.32 => -0.75377
840        FixedPoint(-13063708520358164480), // 0.33 => -0.70819
841        FixedPoint(-12235618674138603520), // 0.34 => -0.66329
842        FixedPoint(-11419257849061355520), // 0.35 => -0.61904
843        FixedPoint(-10613595130224742400), // 0.36 => -0.57536
844        FixedPoint(-9817667354921738240),  // 0.37 => -0.53222
845        FixedPoint(-9030570824192866304),  // 0.38 => -0.48955
846        FixedPoint(-8251454007294845952),  // 0.39 => -0.44731
847        FixedPoint(-7479511080090284032),  // 0.40 => -0.40547
848        FixedPoint(-6713976164925600768),  // 0.41 => -0.36397
849        FixedPoint(-5954118160879564800),  // 0.42 => -0.32277
850        FixedPoint(-5199236070424976384),  // 0.43 => -0.28185
851        FixedPoint(-4448654742390539264),  // 0.44 => -0.24116
852        FixedPoint(-3701720962283612672),  // 0.45 => -0.20067
853        FixedPoint(-2957799830037197824),  // 0.46 => -0.16034
854        FixedPoint(-2216271372462491904),  // 0.47 => -0.12014
855        FixedPoint(-1476527343420476672),  // 0.48 => -0.08004
856        FixedPoint(-737968169202023424),   // 0.49 => -0.04001
857        FixedPoint(0),                     // 0.50 => -0.00000
858        FixedPoint(737968169202024192),    // 0.51 => 0.04001
859        FixedPoint(1476527343420477440),   // 0.52 => 0.08004
860        FixedPoint(2216271372462496512),   // 0.53 => 0.12014
861        FixedPoint(2957799830037204480),   // 0.54 => 0.16034
862        FixedPoint(3701720962283612672),   // 0.55 => 0.20067
863        FixedPoint(4448654742390539264),   // 0.56 => 0.24116
864        FixedPoint(5199236070424971264),   // 0.57 => 0.28185
865        FixedPoint(5954118160879561728),   // 0.58 => 0.32277
866        FixedPoint(6713976164925597696),   // 0.59 => 0.36397
867        FixedPoint(7479511080090281984),   // 0.60 => 0.40547
868        FixedPoint(8251454007294848000),   // 0.61 => 0.44731
869        FixedPoint(9030570824192860160),   // 0.62 => 0.48955
870        FixedPoint(9817667354921742336),   // 0.63 => 0.53222
871        FixedPoint(10613595130224742400),  // 0.64 => 0.57536
872        FixedPoint(11419257849061359616),  // 0.65 => 0.61904
873        FixedPoint(12235618674138605568),  // 0.66 => 0.66329
874        FixedPoint(13063708520358168576),  // 0.67 => 0.70819
875        FixedPoint(13904635528415862784),  // 0.68 => 0.75377
876        FixedPoint(14759595957603747840),  // 0.69 => 0.80012
877        FixedPoint(15629886784764430336),  // 0.70 => 0.84730
878        FixedPoint(16516920363702964224),  // 0.71 => 0.89538
879        FixedPoint(17422241585731166208),  // 0.72 => 0.94446
880        FixedPoint(18347548093616463872),  // 0.73 => 0.99462
881        FixedPoint(19294714246602788864),  // 0.74 => 1.04597
882        FixedPoint(20265819725292945408),  // 0.75 => 1.09861
883        FixedPoint(21263183918842335232),  // 0.76 => 1.15268
884        FixedPoint(22289407577085243392),  // 0.77 => 1.20831
885        FixedPoint(23347423671542181888),  // 0.78 => 1.26567
886        FixedPoint(24440560042528653312),  // 0.79 => 1.32493
887        FixedPoint(25572617290405310464),  // 0.80 => 1.38629
888        FixedPoint(26747966611833827328),  // 0.81 => 1.45001
889        FixedPoint(27971674063185145856),  // 0.82 => 1.51635
890        FixedPoint(29249660330515173376),  // 0.83 => 1.58563
891        FixedPoint(30588908944945000448),  // 0.84 => 1.65823
892        FixedPoint(31997741738730881024),  // 0.85 => 1.73460
893        FixedPoint(33486189434148524032),  // 0.86 => 1.81529
894        FixedPoint(35066499762404098048),  // 0.87 => 1.90096
895        FixedPoint(36753849332779192320),  // 0.88 => 1.99243
896        FixedPoint(38567365939524001792),  // 0.89 => 2.09074
897        FixedPoint(40531639450585874432),  // 0.90 => 2.19722
898        FixedPoint(42679031418590240768),  // 0.91 => 2.31363
899        FixedPoint(45053350700638978048),  // 0.92 => 2.44235
900        FixedPoint(47715996328766390272),  // 0.93 => 2.58669
901        FixedPoint(50756867729459134464),  // 0.94 => 2.75154
902        FixedPoint(54315312289337958400),  // 0.95 => 2.94444
903        FixedPoint(58624745660900876288),  // 0.96 => 3.17805
904        FixedPoint(64122702906348298240),  // 0.97 => 3.47610
905        FixedPoint(71791413020114722816),  // 0.98 => 3.89182
906        FixedPoint(84764999863455252480),  // 0.99 => 4.59512
907        FixedPoint(84764999863455252480),  // 1.00 => 4.59512
908    ];
909
910    // Distance between points, in unwrapped FixedPoint
911    const POINT_LEN_FP: i128 = FixedPoint::ONE.0 / (POINTS.len() as i128 - 1);
912    // Which line segment we're on, defined as the segment between POINTS[bucket] and POINTS[bucket + 1]
913    let bucket = (usage.0 / POINT_LEN_FP).clamp(0, POINTS.len() as i128 - 2) as usize;
914    let b = POINTS[bucket];
915    let c = POINTS[bucket + 1];
916    let frac = FixedPoint::from_u128_div(
917        i128::max(0, usage.0 - bucket as i128 * POINT_LEN_FP) as u128,
918        POINT_LEN_FP as u128,
919    );
920    (b * (FixedPoint::ONE - frac) + c * frac) / a
921}
922
923#[cfg(test)]
924// The price adjustment function, (almost) as specified in tokenomics document
925fn price_adjustment_target(usage: f64, a: f64) -> f64 {
926    // original
927    // -(1f64 / (f64::max(usage, 0.01)) - 0.99).ln() / a
928    // instead of this, we clamp usage at 0.01 and 0.99, in order for the result to be symmetric.
929    -(1f64 / usage.clamp(0.01, 0.99) - 1f64).ln() / a
930}
931
932#[cfg(test)]
933mod tests {
934    use super::*;
935    use proptest::prelude::*;
936
937    fn within_permissible_error(a: f64, b: f64, epsilon: f64) {
938        if a - epsilon >= b || a + epsilon <= b {
939            panic!("{a} != {b} (with error {epsilon})");
940        }
941    }
942
943    #[test]
944    // This test ensures parity between the spec implementation of the price adjustment function
945    // `price_adjustment_target` and the real implementation `price_adjustment_function`.
946    //
947    // While the test `test_price_adjustment` tests this as a proptest, this function tests a fixed
948    // number of statically defined points, which, if this test fails, can be extracted from stdout
949    // and directly used to instantiate the lookup table in `price_adjustment_function` to correct
950    // it.
951    fn test_price_adjustment_static() {
952        const N: usize = 100;
953        let xs = (0..=N).map(|x| x as f64 / N as f64).collect::<Vec<_>>();
954        let target = xs
955            .iter()
956            .map(|x| price_adjustment_target(*x, 1f64))
957            .collect::<Vec<_>>();
958        let approx = xs
959            .iter()
960            .map(|x| f64::from(price_adjustment_function((*x).into(), FixedPoint::ONE)))
961            .collect::<Vec<_>>();
962        assert_eq!(price_adjustment_target(0.5, 1f64), 0f64);
963        // Print out the initialization for `POINTS` derived from `target`
964        // Manually munged, as debug printing for fixedpoint is lossy
965        for (x, point) in xs.iter().zip(target.iter()) {
966            println!(
967                "FixedPoint({}), // {x:0.2} => {point:0.5}",
968                FixedPoint::from(*point).0
969            );
970        }
971        for ((t, a), _x) in target.iter().zip(approx.iter()).zip(xs.iter()) {
972            within_permissible_error(*t, *a, 1e-9f64);
973        }
974    }
975
976    #[test]
977    fn test_pricing_cant_get_stuck() {
978        let mut cur = FeePrices {
979            overall_price: FixedPoint::ONE,
980            block_usage_factor: FixedPoint::ONE,
981            read_factor: FixedPoint::ONE,
982            write_factor: FixedPoint::ONE,
983            compute_factor: FixedPoint::ONE,
984        };
985        for _ in 0..10_000 {
986            cur = cur.update_from_fullness(
987                NormalizedCost {
988                    read_time: FixedPoint::ZERO,
989                    compute_time: FixedPoint::ZERO,
990                    block_usage: FixedPoint::ZERO,
991                    bytes_written: FixedPoint::ZERO,
992                    bytes_churned: FixedPoint::ZERO,
993                },
994                FixedPoint::ZERO,
995                FixedPoint::from_u64_div(1, 4),
996                FixedPoint::from_u64_div(100, 1),
997            );
998        }
999        let dims = |cur: &FeePrices| {
1000            [
1001                cur.overall_price,
1002                cur.block_usage_factor,
1003                cur.read_factor,
1004                cur.write_factor,
1005                cur.compute_factor,
1006            ]
1007        };
1008        assert!(dims(&cur).into_iter().all(|price| price > FixedPoint::ZERO));
1009        let fraction = FixedPoint::from_u64_div(3, 4);
1010        let fullness = NormalizedCost {
1011            block_usage: fraction,
1012            compute_time: fraction,
1013            read_time: fraction,
1014            bytes_written: fraction,
1015            bytes_churned: fraction,
1016        };
1017        let next = cur.update_from_fullness(
1018            fullness,
1019            fraction,
1020            FixedPoint::from_u64_div(1, 4),
1021            FixedPoint::from_u64_div(100, 1),
1022        );
1023        assert!(next.overall_price > cur.overall_price);
1024        assert_eq!(next.compute_factor, cur.compute_factor);
1025    }
1026
1027    proptest! {
1028        #[test]
1029        fn test_price_adjustment(usage in (0f64..1f64)) {
1030            let a = price_adjustment_target(usage, 1f64);
1031            let b = f64::from(price_adjustment_function(FixedPoint::from(usage), FixedPoint::ONE));
1032            let epsilon = (a / 50f64).abs();
1033            within_permissible_error(
1034                a, b, epsilon
1035            );
1036        }
1037        #[test]
1038        fn fixed_point_powi(a in (1e-1f64..1e1f64), b in (-15..15)) {
1039            if a != 0.0 {
1040                let pure_error_factor = a.powi(b) / f64::from(FixedPoint::from(a).powi(b));
1041                let non_compounded_error_factor = pure_error_factor.powi(-b.abs());
1042                within_permissible_error(1.0, non_compounded_error_factor, 0.001);
1043            }
1044        }
1045        #[test]
1046        fn fixed_point_addition(a in (-1e18f64..1e18f64), b in (-1e18f64..1e18f64)) {
1047            assert_eq!(a + b, f64::from(FixedPoint::from(a) + FixedPoint::from(b)));
1048        }
1049        #[test]
1050        fn fixed_point_subtraction(a in (-1e18f64..1e18f64), b in (-1e18f64..1e18f64)) {
1051            assert_eq!(a - b, f64::from(FixedPoint::from(a) - FixedPoint::from(b)));
1052        }
1053        #[test]
1054        fn fixed_point_negation(a in (-1e18f64..1e18f64)) {
1055            assert_eq!(-a, f64::from(-FixedPoint::from(a)));
1056        }
1057        #[test]
1058        fn fixed_point_mul(a in (-1e9f64..1e9f64), b in (-1e9f64..1e9f64)) {
1059            assert_eq!(a * b, f64::from(FixedPoint::from(a) * FixedPoint::from(b)));
1060        }
1061        #[test]
1062        fn fixed_point_div(a in (-1e9f64..1e9f64), b in (-1e9f64..1e9f64)) {
1063            within_permissible_error(a / b, f64::from(FixedPoint::from(a) / FixedPoint::from(b)), 1e-3f64);
1064        }
1065        #[test]
1066        fn u64_div(a: u64, b in 1u64..u64::MAX) {
1067            within_permissible_error(a as f64 / b as f64, f64::from(FixedPoint::from_u64_div(a, b)), 1e-9f64);
1068        }
1069        #[test]
1070        fn u128_div(a: u128, b in 1u128..u128::MAX) {
1071            within_permissible_error(a as f64 / b as f64, f64::from(FixedPoint::from_u128_div(a, b)), 1e-9f64);
1072        }
1073    }
1074
1075    #[test]
1076    #[cfg(feature = "fixed-point-custom-serde")]
1077    fn test_fixed_point_custom_serde() {
1078        let expected_fee_prices = FeePrices {
1079            overall_price: FixedPoint::ONE,
1080            block_usage_factor: FixedPoint::MAX,
1081            read_factor: FixedPoint::from_u64_div(3, 4),
1082            write_factor: FixedPoint::from_u64_div(100, 1),
1083            compute_factor: FixedPoint::ZERO,
1084        };
1085
1086        let actual_fee_prices_str = r#"
1087            {
1088            "overallPrice": 18446744073709551616,
1089            "readFactor": 13835058055282163712,
1090            "computeFactor": 0,
1091            "blockUsageFactor": 170141183460469231731687303715884105727,
1092            "writeFactor": 1844674407370955161600
1093            }
1094        "#;
1095
1096        // test deserialize
1097        let actual_fee_prices: FeePrices =
1098            serde_json::from_str(actual_fee_prices_str).expect("failed to deserialize");
1099        assert_eq!(actual_fee_prices, expected_fee_prices);
1100
1101        // test serialize
1102        let clean_actual_fee_prices_str = actual_fee_prices_str
1103            .replace(" ", "")
1104            .replace("\t", "")
1105            .replace("\n", "");
1106
1107        let expected_fee_prices_str =
1108            serde_json::to_string(&expected_fee_prices).expect("failed to serialize");
1109        assert_eq!(clean_actual_fee_prices_str, expected_fee_prices_str);
1110    }
1111}