Skip to main content

gear_common/gas_provider/
node.rs

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