Skip to main content

iota_sdk_types/
version.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2026 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::ops::{Add, AddAssign, Sub, SubAssign};
6
7#[derive(Clone, Debug, Eq, PartialEq, thiserror::Error)]
8#[non_exhaustive]
9pub enum VersionError {
10    #[error("cannot increment Version: maximum valid Version has already been reached")]
11    InvalidIncrement,
12    #[error("cannot decrement Version: minimum valid Version has already been reached")]
13    InvalidDecrement,
14    #[error("Version arithmetic resulted in an overflow")]
15    Overflow,
16    #[error("not a version used for congested shared objects in the gas price feedback mechanism")]
17    InvalidCongestedVersion,
18}
19
20#[derive(
21    Clone,
22    Copy,
23    Debug,
24    Default,
25    derive_more::Add,
26    derive_more::AddAssign,
27    derive_more::Display,
28    derive_more::Div,
29    derive_more::DivAssign,
30    derive_more::From,
31    derive_more::FromStr,
32    derive_more::Mul,
33    derive_more::MulAssign,
34    derive_more::Rem,
35    derive_more::RemAssign,
36    derive_more::Sub,
37    derive_more::SubAssign,
38    derive_more::Sum,
39    Eq,
40    Hash,
41    Ord,
42    PartialEq,
43    PartialOrd,
44)]
45#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
46#[cfg_attr(
47    feature = "serde",
48    derive(serde::Deserialize, serde::Serialize),
49    serde(transparent)
50)]
51#[cfg_attr(feature = "bcs-schema", derive(iota_bcs_schema::BcsSchema))]
52#[repr(transparent)]
53pub struct Version(
54    #[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))] u64,
55);
56
57impl Version {
58    /// An inclusive lower limit on a valid version.
59    ///
60    /// A valid version means an object, which this version
61    /// is assigned to, does not appear in a canceled transaction.
62    pub const MIN_VALID_INCL: Self = Self(u64::MIN);
63
64    /// The initial shared version for shared system objects.
65    pub const INITIAL_SHARED_VERSION: Self = Self(1);
66
67    /// An exclusive upper limit on a valid version: versions
68    /// strictly smaller than this limit are valid versions.
69    ///
70    /// A valid version means an object, which this version
71    /// is assigned to, does not appear in a canceled transaction.
72    /// Versions larger than this value are "special" and
73    /// assigned to objects that appear in canceled transactions.
74    pub const MAX_VALID_EXCL: Self = Self(0x7fff_ffff_ffff_ffff);
75
76    /// Special version that is assigned to objects which are accessed
77    /// immutably in a canceled transaction.
78    pub const CANCELED_READ: Self = Self(Self::MAX_VALID_EXCL.0 + 1);
79
80    /// Special version that was assigned to congested objects which
81    /// cause transaction cancellations. Note that this special version
82    /// was only used prior to the introduction of a gas price feedback
83    /// mechanism, but it is kept for backward compatibility.
84    pub const CONGESTED_PRIOR_TO_GAS_PRICE_FEEDBACK: Self = Self(Self::MAX_VALID_EXCL.0 + 2);
85
86    pub const RANDOMNESS_UNAVAILABLE: Self = Self(Self::MAX_VALID_EXCL.0 + 3);
87
88    // NOTE: if you want to add new Version constants used for cancellation
89    // reasons different than those used for cancellations due to shared object
90    // congestion, please make sure their offset is less than
91    // CONGESTED_BASE_OFFSET_FOR_GAS_PRICE_FEEDBACK
92
93    /// In the gas price feedback mechanism, versions >=
94    /// `Version::MAX_VALID_EXCL` +
95    /// `CONGESTED_BASE_OFFSET_FOR_GAS_PRICE_FEEDBACK` are assigned to
96    /// objects that cause transactions cancellations due to congestion.
97    ///
98    /// Versions larger than `Version::MAX_VALID_EXCL` but
99    /// smaller than `Version::MAX_VALID_EXCL` +
100    /// `CONGESTED_BASE_OFFSET_FOR_GAS_PRICE_FEEDBACK` are
101    /// intended for other transaction cancellation reasons.
102    ///
103    /// There unlikely will be more than 1000 non-congestion cancellation
104    /// reasons, but this offset can be increased if needed, as long as
105    /// (`Version::MIN_CONGESTED.value()` + maximum gas price) does not
106    /// overflow `u64::MAX`.
107    const CONGESTED_BASE_OFFSET_FOR_GAS_PRICE_FEEDBACK: Self = Self(1_000);
108
109    /// Minimum congested version used in the gas price feedback
110    /// mechanism. A congested version is assigned to objects that
111    /// cause transaction cancellations.
112    const MIN_CONGESTED_FOR_GAS_PRICE_FEEDBACK: Self =
113        Self(Self::MAX_VALID_EXCL.0 + Self::CONGESTED_BASE_OFFSET_FOR_GAS_PRICE_FEEDBACK.0);
114
115    pub const OBJECT_START: Self = Self(1);
116
117    /// Create a new Version from a u64 value
118    pub const fn from_u64(value: u64) -> Self {
119        Self(value)
120    }
121
122    /// Get the underlying u64 value of this version
123    pub const fn as_u64(&self) -> u64 {
124        self.0
125    }
126
127    /// Returns the next version, or an error if overflow occurs.
128    pub fn next(mut self) -> Result<Self, VersionError> {
129        if !self.is_valid() {
130            return Err(VersionError::InvalidIncrement);
131        }
132        self.0 += 1;
133        Ok(self)
134    }
135
136    /// Increments this version by one, or returns an error if overflow occurs.
137    pub fn increment(&mut self) -> Result<(), VersionError> {
138        *self = self.next()?;
139        Ok(())
140    }
141
142    /// Returns the previous version, or an error if underflow occurs.
143    pub fn previous(mut self) -> Result<Self, VersionError> {
144        if self == Self::MIN_VALID_INCL {
145            return Err(VersionError::InvalidDecrement);
146        }
147        self.0 -= 1;
148        Ok(self)
149    }
150
151    /// Decrements this version by one, or returns an error if underflow occurs.
152    pub fn decrement(&mut self) -> Result<(), VersionError> {
153        *self = self.previous()?;
154        Ok(())
155    }
156
157    /// Returns a special version used for congested shared objects:
158    /// `Version::MIN_CONGESTED + suggested_gas_price`,
159    /// where `suggested_gas_price` is embedded into a congested version
160    /// to facilitate a gas price feedback mechanism for transactions
161    /// canceled due to shared object congestion.
162    pub fn new_congested_with_suggested_gas_price(
163        suggested_gas_price: u64,
164    ) -> Result<Self, VersionError> {
165        let (version, overflows) = Self::MIN_CONGESTED_FOR_GAS_PRICE_FEEDBACK
166            .0
167            .overflowing_add(suggested_gas_price);
168        if overflows {
169            return Err(VersionError::Overflow);
170        }
171
172        Ok(Self(version))
173    }
174
175    /// Check if this version is congested, i.e., the corresponding
176    /// object is the reason for transaction cancellation.
177    pub fn is_congested(&self) -> bool {
178        *self == Self::CONGESTED_PRIOR_TO_GAS_PRICE_FEEDBACK
179            || *self >= Self::MIN_CONGESTED_FOR_GAS_PRICE_FEEDBACK
180    }
181
182    /// Returns the `suggested_gas_price` embedded in this congested shared
183    /// object version. The `suggested_gas_price` here is used for a
184    /// gas price feedback mechanism for transactions canceled due to
185    /// shared object congestion.
186    pub fn get_congested_version_suggested_gas_price(&self) -> Result<u64, VersionError> {
187        if *self < Self::MIN_CONGESTED_FOR_GAS_PRICE_FEEDBACK {
188            return Err(VersionError::InvalidCongestedVersion);
189        }
190
191        Ok(self.0 - Self::MIN_CONGESTED_FOR_GAS_PRICE_FEEDBACK.0)
192    }
193
194    /// Returns a new version that is greater than all versions
195    /// in `inputs`, assuming this operation will not overflow.
196    pub fn lamport_increment(inputs: impl IntoIterator<Item = Self>) -> Result<Self, VersionError> {
197        let max_input = inputs.into_iter().max().unwrap_or_default();
198        max_input.next()
199    }
200
201    /// Checks if this version is canceled, i.e., the corresponding
202    /// object appears in a canceled transaction.
203    pub fn is_canceled(&self) -> bool {
204        *self == Self::CANCELED_READ || *self == Self::RANDOMNESS_UNAVAILABLE || self.is_congested()
205    }
206
207    /// Checks if this version is valid, i.e., the corresponding
208    /// object does not appear in a canceled transaction.
209    pub fn is_valid(&self) -> bool {
210        *self < Self::MAX_VALID_EXCL
211    }
212}
213
214impl Add<u64> for Version {
215    type Output = Self;
216
217    fn add(self, rhs: u64) -> Self::Output {
218        Self(self.0 + rhs)
219    }
220}
221
222impl AddAssign<u64> for Version {
223    fn add_assign(&mut self, rhs: u64) {
224        self.0 += rhs;
225    }
226}
227
228impl Sub<u64> for Version {
229    type Output = Self;
230
231    fn sub(self, rhs: u64) -> Self::Output {
232        Self(self.0 - rhs)
233    }
234}
235
236impl SubAssign<u64> for Version {
237    fn sub_assign(&mut self, rhs: u64) {
238        self.0 -= rhs;
239    }
240}
241
242impl TryFrom<i64> for Version {
243    type Error = <u64 as TryFrom<i64>>::Error;
244
245    fn try_from(value: i64) -> Result<Self, Self::Error> {
246        value.try_into().map(Self)
247    }
248}
249
250impl PartialEq<u64> for Version {
251    fn eq(&self, other: &u64) -> bool {
252        self.0.eq(other)
253    }
254}
255
256impl PartialEq<Version> for u64 {
257    fn eq(&self, other: &Version) -> bool {
258        self.eq(&other.0)
259    }
260}
261
262impl PartialOrd<u64> for Version {
263    fn partial_cmp(&self, other: &u64) -> Option<std::cmp::Ordering> {
264        self.0.partial_cmp(other)
265    }
266}
267
268impl PartialOrd<Version> for u64 {
269    fn partial_cmp(&self, other: &Version) -> Option<std::cmp::Ordering> {
270        self.partial_cmp(&other.0)
271    }
272}