gear_common/gas_provider/
node.rs

1// This file is part of Gear.
2
3// Copyright (C) 2021-2025 Gear Technologies Inc.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use super::*;
20use core::ops::{Add, Index, IndexMut};
21use enum_iterator::cardinality;
22use gear_core::ids::ReservationId;
23use scale_decode::DecodeAsType;
24use scale_encode::EncodeAsType;
25use sp_runtime::{
26    codec::{self, MaxEncodedLen},
27    scale_info,
28    traits::Zero,
29};
30
31/// ID of the [`GasNode`].
32#[derive(
33    Debug,
34    Copy,
35    Clone,
36    Hash,
37    Eq,
38    PartialEq,
39    Ord,
40    PartialOrd,
41    Encode,
42    EncodeAsType,
43    Decode,
44    DecodeAsType,
45    TypeInfo,
46)]
47#[codec(crate = codec)]
48#[scale_info(crate = scale_info)]
49pub enum GasNodeId<T, U> {
50    Node(T),
51    Reservation(U),
52}
53
54impl<T, U> GasNodeId<T, U> {
55    pub fn to_node_id(self) -> Option<T> {
56        match self {
57            GasNodeId::Node(message_id) => Some(message_id),
58            GasNodeId::Reservation(_) => None,
59        }
60    }
61
62    pub fn to_reservation_id(self) -> Option<U> {
63        match self {
64            GasNodeId::Node(_) => None,
65            GasNodeId::Reservation(reservation_id) => Some(reservation_id),
66        }
67    }
68}
69
70impl<T, U> fmt::Display for GasNodeId<T, U>
71where
72    T: fmt::Display,
73    U: fmt::Display,
74{
75    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
76        match self {
77            GasNodeId::Node(id) => fmt::Display::fmt(id, f),
78            GasNodeId::Reservation(id) => fmt::Display::fmt(id, f),
79        }
80    }
81}
82
83impl<U> From<MessageId> for GasNodeId<MessageId, U> {
84    fn from(id: MessageId) -> Self {
85        Self::Node(id)
86    }
87}
88
89impl<T> From<ReservationId> for GasNodeId<T, ReservationId> {
90    fn from(id: ReservationId) -> Self {
91        Self::Reservation(id)
92    }
93}
94
95#[derive(Clone, Copy, Decode, Encode, Debug, Default, PartialEq, Eq, TypeInfo, MaxEncodedLen)]
96#[codec(crate = codec)]
97#[scale_info(crate = scale_info)]
98pub struct NodeLock<Balance>([Balance; cardinality::<LockId>()]);
99
100impl<Balance> Index<LockId> for NodeLock<Balance> {
101    type Output = Balance;
102
103    fn index(&self, index: LockId) -> &Self::Output {
104        &self.0[index as usize]
105    }
106}
107
108impl<Balance> IndexMut<LockId> for NodeLock<Balance> {
109    fn index_mut(&mut self, index: LockId) -> &mut Self::Output {
110        &mut self.0[index as usize]
111    }
112}
113
114impl<Balance: Zero + Copy> Zero for NodeLock<Balance> {
115    fn zero() -> Self {
116        Self([Balance::zero(); cardinality::<LockId>()])
117    }
118
119    fn is_zero(&self) -> bool {
120        self.0.iter().all(|x| x.is_zero())
121    }
122}
123
124impl<Balance: Add<Output = Balance> + Copy> Add<Self> for NodeLock<Balance> {
125    type Output = Self;
126
127    fn add(self, other: Self) -> Self::Output {
128        let NodeLock(mut inner) = self;
129        let NodeLock(other) = other;
130
131        for (i, elem) in inner.iter_mut().enumerate() {
132            *elem = *elem + other[i];
133        }
134
135        Self(inner)
136    }
137}
138
139// TODO: decide whether this method should stay or be removed as unused.
140// The only use case currently is to check Gas Tree migration upon runtime upgrade.
141impl<Balance: Zero + Copy + sp_runtime::traits::Saturating> NodeLock<Balance> {
142    pub fn total_locked(&self) -> Balance {
143        self.0
144            .iter()
145            .fold(Balance::zero(), |acc, v| acc.saturating_add(*v))
146    }
147}
148
149/// Node of the ['Tree'] gas tree
150#[derive(Clone, Decode, Debug, Encode, MaxEncodedLen, TypeInfo, PartialEq, Eq)]
151#[codec(crate = codec)]
152#[scale_info(crate = scale_info)]
153pub enum GasNode<ExternalId: Clone, Id: Clone, Balance: Zero + Clone, Funds> {
154    /// A root node for each gas tree.
155    ///
156    /// Usually created when a new gasful logic started (i.e., message sent).
157    External {
158        id: ExternalId,
159        multiplier: GasMultiplier<Funds, Balance>,
160        value: Balance,
161        lock: NodeLock<Balance>,
162        system_reserve: Balance,
163        refs: ChildrenRefs,
164        consumed: bool,
165        deposit: bool,
166    },
167
168    /// A node created by "cutting" value from some other tree node.
169    ///
170    /// Such node types are detached and aren't part of the tree structure
171    /// (not node's parent, not node's child).
172    Cut {
173        id: ExternalId,
174        multiplier: GasMultiplier<Funds, Balance>,
175        value: Balance,
176        lock: NodeLock<Balance>,
177    },
178
179    /// A node used for gas reservation feature.
180    ///
181    /// Such node types are detached from initial tree and may act the a root of new tree.
182    Reserved {
183        id: ExternalId,
184        multiplier: GasMultiplier<Funds, Balance>,
185        value: Balance,
186        lock: NodeLock<Balance>,
187        refs: ChildrenRefs,
188        consumed: bool,
189    },
190
191    /// A node, which is a part of the tree structure, that can be
192    /// a parent and/or a child.
193    ///
194    /// As well as `External` node, it has an internal balance and can exist
195    /// while being consumed (see [`Tree::consume`] for details).
196    ///
197    /// However, it has a `parent` field pointing to the node,
198    /// from which that one was created.
199    SpecifiedLocal {
200        parent: Id,
201        root: Id,
202        value: Balance,
203        lock: NodeLock<Balance>,
204        system_reserve: Balance,
205        refs: ChildrenRefs,
206        consumed: bool,
207    },
208
209    /// Pretty same as `SpecifiedLocal`, but doesn't have internal balance,
210    /// so relies on its `parent`.
211    ///
212    /// Such nodes don't have children references.
213    UnspecifiedLocal {
214        parent: Id,
215        root: Id,
216        lock: NodeLock<Balance>,
217        system_reserve: Balance,
218    },
219}
220
221/// Children references convenience struct
222#[derive(Clone, Copy, Default, Decode, Debug, Encode, MaxEncodedLen, TypeInfo, PartialEq, Eq)]
223#[codec(crate = codec)]
224#[scale_info(crate = scale_info)]
225pub struct ChildrenRefs {
226    spec_refs: u32,
227    unspec_refs: u32,
228}
229
230impl<
231    ExternalId: Clone,
232    Id: Clone + Copy,
233    Balance: Default + Zero + Clone + Copy + sp_runtime::traits::Saturating,
234    Funds: Clone,
235> GasNode<ExternalId, Id, Balance, Funds>
236{
237    /// Returns total gas value inside GasNode.
238    pub fn total_value(&self) -> Balance {
239        self.value()
240            .unwrap_or_default()
241            .saturating_add(self.lock().total_locked())
242            .saturating_add(self.system_reserve().unwrap_or_default())
243    }
244}
245
246impl<ExternalId: Clone, Id: Clone + Copy, Balance: Default + Zero + Clone + Copy, Funds: Clone>
247    GasNode<ExternalId, Id, Balance, Funds>
248{
249    /// Creates a new `GasNode::External` root node for a new tree.
250    pub fn new(
251        id: ExternalId,
252        multiplier: GasMultiplier<Funds, Balance>,
253        value: Balance,
254        deposit: bool,
255    ) -> Self {
256        Self::External {
257            id,
258            multiplier,
259            value,
260            lock: Zero::zero(),
261            system_reserve: Zero::zero(),
262            refs: Default::default(),
263            consumed: false,
264            deposit,
265        }
266    }
267
268    /// Increases node's spec refs, if it can have any
269    pub fn increase_spec_refs(&mut self) {
270        self.adjust_refs(true, true);
271    }
272
273    /// Decreases node's spec refs, if it can have any
274    pub fn decrease_spec_refs(&mut self) {
275        self.adjust_refs(false, true);
276    }
277
278    /// Increases node's unspec refs, if it can have any
279    pub fn increase_unspec_refs(&mut self) {
280        self.adjust_refs(true, false);
281    }
282
283    /// Decreases node's unspec refs, if it can have any
284    pub fn decrease_unspec_refs(&mut self) {
285        self.adjust_refs(false, false);
286    }
287
288    /// Marks the node as consumed, if it has the flag
289    pub fn mark_consumed(&mut self) {
290        if let Self::External { consumed, .. }
291        | Self::SpecifiedLocal { consumed, .. }
292        | Self::Reserved { consumed, .. } = self
293        {
294            *consumed = true;
295        }
296    }
297
298    /// Returns whether the node is marked consumed or not
299    ///
300    /// Only `GasNode::External`, `GasNode::SpecifiedLocal`, `GasNode::Reserved` can be marked
301    /// consumed and not deleted. See [`Tree::consume`] for details.
302    pub fn is_consumed(&self) -> bool {
303        if let Self::External { consumed, .. }
304        | Self::SpecifiedLocal { consumed, .. }
305        | Self::Reserved { consumed, .. } = self
306        {
307            *consumed
308        } else {
309            false
310        }
311    }
312
313    /// Returns whether the node is patron or not.
314    ///
315    /// The flag signals whether the node isn't available
316    /// for the gas to be spent from it.
317    ///
318    /// These are nodes that have one of the following requirements:
319    /// 1. Have unspec refs (regardless of being consumed).
320    /// 2. Are not consumed.
321    ///
322    /// Patron nodes are those on which other nodes of the tree rely
323    /// (including the self node).
324    pub fn is_patron(&self) -> bool {
325        if let Self::External { refs, consumed, .. }
326        | Self::SpecifiedLocal { refs, consumed, .. }
327        | Self::Reserved { refs, consumed, .. } = self
328        {
329            !consumed || refs.unspec_refs != 0
330        } else {
331            false
332        }
333    }
334
335    /// Returns node's inner gas balance, if it can have any
336    pub fn value(&self) -> Option<Balance> {
337        match self {
338            Self::External { value, .. }
339            | Self::Cut { value, .. }
340            | Self::Reserved { value, .. }
341            | Self::SpecifiedLocal { value, .. } => Some(*value),
342            Self::UnspecifiedLocal { .. } => None,
343        }
344    }
345
346    /// Get's a mutable access to node's inner gas balance, if it can have any
347    pub fn value_mut(&mut self) -> Option<&mut Balance> {
348        match *self {
349            Self::External { ref mut value, .. }
350            | Self::Cut { ref mut value, .. }
351            | Self::Reserved { ref mut value, .. }
352            | Self::SpecifiedLocal { ref mut value, .. } => Some(value),
353            Self::UnspecifiedLocal { .. } => None,
354        }
355    }
356
357    /// Returns node's locked gas balance, if it can have any.
358    pub fn lock(&self) -> &NodeLock<Balance> {
359        match self {
360            Self::External { lock, .. }
361            | Self::UnspecifiedLocal { lock, .. }
362            | Self::SpecifiedLocal { lock, .. }
363            | Self::Reserved { lock, .. }
364            | Self::Cut { lock, .. } => lock,
365        }
366    }
367
368    /// Get's a mutable access to node's locked gas balance, if it can have any.
369    pub fn lock_mut(&mut self) -> &mut NodeLock<Balance> {
370        match *self {
371            Self::External { ref mut lock, .. }
372            | Self::UnspecifiedLocal { ref mut lock, .. }
373            | Self::SpecifiedLocal { ref mut lock, .. }
374            | Self::Reserved { ref mut lock, .. }
375            | Self::Cut { ref mut lock, .. } => lock,
376        }
377    }
378
379    /// Returns node's system reserved gas balance, if it can have any.
380    pub fn system_reserve(&self) -> Option<Balance> {
381        match self {
382            GasNode::External { system_reserve, .. }
383            | GasNode::SpecifiedLocal { system_reserve, .. }
384            | GasNode::UnspecifiedLocal { system_reserve, .. } => Some(*system_reserve),
385            GasNode::Cut { .. } | GasNode::Reserved { .. } => None,
386        }
387    }
388
389    /// Gets a mutable access to node's system reserved gas balance, if it can have any.
390    pub fn system_reserve_mut(&mut self) -> Option<&mut Balance> {
391        match self {
392            GasNode::External { system_reserve, .. }
393            | GasNode::SpecifiedLocal { system_reserve, .. }
394            | GasNode::UnspecifiedLocal { system_reserve, .. } => Some(system_reserve),
395            GasNode::Cut { .. } | GasNode::Reserved { .. } => None,
396        }
397    }
398
399    /// Returns node's parent, if it can have any.
400    ///
401    /// That is, `GasNode::External`, `GasNode::Cut`, 'GasNode::Reserved` nodes
402    /// don't have a parent, so a `None` is returned if the function is
403    /// called on them.
404    pub fn parent(&self) -> Option<Id> {
405        match self {
406            Self::External { .. } | Self::Cut { .. } | Self::Reserved { .. } => None,
407            Self::SpecifiedLocal { parent, .. } | Self::UnspecifiedLocal { parent, .. } => {
408                Some(*parent)
409            }
410        }
411    }
412
413    /// Returns node's total refs
414    pub fn refs(&self) -> u32 {
415        self.spec_refs().saturating_add(self.unspec_refs())
416    }
417
418    /// Returns node's spec refs
419    pub fn spec_refs(&self) -> u32 {
420        match self {
421            Self::External { refs, .. }
422            | Self::SpecifiedLocal { refs, .. }
423            | Self::Reserved { refs, .. } => refs.spec_refs,
424            _ => 0,
425        }
426    }
427
428    /// Returns node's unspec refs
429    pub fn unspec_refs(&self) -> u32 {
430        match self {
431            Self::External { refs, .. }
432            | Self::SpecifiedLocal { refs, .. }
433            | Self::Reserved { refs, .. } => refs.unspec_refs,
434            _ => 0,
435        }
436    }
437
438    /// Returns id of the root node.
439    pub fn root_id(&self) -> Option<Id> {
440        match self {
441            Self::SpecifiedLocal { root, .. } | Self::UnspecifiedLocal { root, .. } => Some(*root),
442            Self::External { .. } | Self::Cut { .. } | Self::Reserved { .. } => None,
443        }
444    }
445
446    /// Returns external origin and funds multiplier of the node if contains that data inside.
447    pub fn external_data(&self) -> Option<(ExternalId, GasMultiplier<Funds, Balance>)> {
448        match self {
449            Self::External { id, multiplier, .. }
450            | Self::Cut { id, multiplier, .. }
451            | Self::Reserved { id, multiplier, .. } => Some((id.clone(), multiplier.clone())),
452            Self::SpecifiedLocal { .. } | Self::UnspecifiedLocal { .. } => None,
453        }
454    }
455
456    /// Returns whether the node is of `External` type
457    pub(crate) fn is_external(&self) -> bool {
458        matches!(self, Self::External { .. })
459    }
460
461    /// Returns whether the node is of `SpecifiedLocal` type
462    pub(crate) fn is_specified_local(&self) -> bool {
463        matches!(self, Self::SpecifiedLocal { .. })
464    }
465
466    /// Returns whether the node is of `UnspecifiedLocal` type
467    pub(crate) fn is_unspecified_local(&self) -> bool {
468        matches!(self, Self::UnspecifiedLocal { .. })
469    }
470
471    /// Returns whether the node is of `Cut` type
472    pub(crate) fn is_cut(&self) -> bool {
473        matches!(self, Self::Cut { .. })
474    }
475
476    /// Returns whether the node is of `Reserve` type
477    pub(crate) fn is_reserved(&self) -> bool {
478        matches!(self, Self::Reserved { .. })
479    }
480
481    /// Returns whether the node has system reserved gas.
482    pub(crate) fn is_system_reservable(&self) -> bool {
483        self.system_reserve().is_some()
484    }
485
486    fn adjust_refs(&mut self, increase: bool, spec: bool) {
487        if let Self::External { refs, .. }
488        | Self::SpecifiedLocal { refs, .. }
489        | Self::Reserved { refs, .. } = self
490        {
491            match (increase, spec) {
492                (true, true) => refs.spec_refs = refs.spec_refs.saturating_add(1),
493                (true, false) => refs.unspec_refs = refs.unspec_refs.saturating_add(1),
494                (false, true) => refs.spec_refs = refs.spec_refs.saturating_sub(1),
495                (false, false) => refs.unspec_refs = refs.unspec_refs.saturating_sub(1),
496            }
497        }
498    }
499}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504
505    #[test]
506    // Checking node that have external data do not have root id
507    fn asserts_node_have_either_external_data_or_root_id() {
508        let nodes_with_external_data: [gas_provider::node::GasNode<i32, i32, i32, i32>; 3] = [
509            GasNode::External {
510                id: Default::default(),
511                multiplier: GasMultiplier::ValuePerGas(100),
512                value: Default::default(),
513                lock: Default::default(),
514                system_reserve: Default::default(),
515                refs: Default::default(),
516                consumed: Default::default(),
517                deposit: Default::default(),
518            },
519            GasNode::Cut {
520                id: Default::default(),
521                multiplier: GasMultiplier::ValuePerGas(100),
522                value: Default::default(),
523                lock: Default::default(),
524            },
525            GasNode::Reserved {
526                id: Default::default(),
527                multiplier: GasMultiplier::ValuePerGas(100),
528                value: Default::default(),
529                lock: Default::default(),
530                refs: Default::default(),
531                consumed: Default::default(),
532            },
533        ];
534
535        for node in nodes_with_external_data {
536            assert!(node.external_data().is_some() || node.root_id().is_none());
537        }
538
539        let nodes_with_root_id: [gas_provider::node::GasNode<i32, i32, i32, i32>; 2] = [
540            GasNode::SpecifiedLocal {
541                parent: Default::default(),
542                root: Default::default(),
543                value: Default::default(),
544                lock: Default::default(),
545                system_reserve: Default::default(),
546                refs: Default::default(),
547                consumed: Default::default(),
548            },
549            GasNode::UnspecifiedLocal {
550                parent: Default::default(),
551                root: Default::default(),
552                lock: Default::default(),
553                system_reserve: Default::default(),
554            },
555        ];
556
557        for node in nodes_with_root_id {
558            assert!(node.external_data().is_none() || node.root_id().is_some());
559        }
560    }
561}