dpp/fee/fee_result/
mod.rs

1// MIT LICENSE
2//
3// Copyright (c) 2021 Dash Core Group
4//
5// Permission is hereby granted, free of charge, to any
6// person obtaining a copy of this software and associated
7// documentation files (the "Software"), to deal in the
8// Software without restriction, including without
9// limitation the rights to use, copy, modify, merge,
10// publish, distribute, sublicense, and/or sell copies of
11// the Software, and to permit persons to whom the Software
12// is furnished to do so, subject to the following
13// conditions:
14//
15// The above copyright notice and this permission notice
16// shall be included in all copies or substantial portions
17// of the Software.
18//
19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
20// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
21// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
22// PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
23// SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
24// CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
26// IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27// DEALINGS IN THE SOFTWARE.
28//
29
30//! Fee Result
31//!
32//! Each drive operation returns FeeResult after execution.
33//! This result contains fees which are required to pay for
34//! computation and storage. It also contains fees to refund
35//! for removed data from the state.
36//!
37
38use crate::consensus::fee::balance_is_not_enough_error::BalanceIsNotEnoughError;
39use crate::consensus::fee::fee_error::FeeError;
40
41use crate::fee::fee_result::refunds::FeeRefunds;
42use crate::fee::fee_result::BalanceChange::{AddToBalance, NoBalanceChange, RemoveFromBalance};
43use crate::fee::Credits;
44use crate::prelude::UserFeeIncrease;
45use crate::ProtocolError;
46use platform_value::Identifier;
47use std::cmp::Ordering;
48use std::collections::BTreeMap;
49use std::convert::TryFrom;
50
51pub mod refunds;
52
53/// Fee Result
54#[derive(Debug, Clone, Eq, PartialEq, Default)]
55pub struct FeeResult {
56    /// Storage fee
57    pub storage_fee: Credits,
58    /// Processing fee
59    pub processing_fee: Credits,
60    /// Credits to refund to identities
61    pub fee_refunds: FeeRefunds,
62    /// Removed bytes not needing to be refunded to identities
63    pub removed_bytes_from_system: u32,
64}
65
66impl TryFrom<Vec<FeeResult>> for FeeResult {
67    type Error = ProtocolError;
68    fn try_from(value: Vec<FeeResult>) -> Result<Self, Self::Error> {
69        let mut aggregate_fee_result = FeeResult::default();
70        value
71            .into_iter()
72            .try_for_each(|fee_result| aggregate_fee_result.checked_add_assign(fee_result))?;
73        Ok(aggregate_fee_result)
74    }
75}
76
77impl TryFrom<Vec<Option<FeeResult>>> for FeeResult {
78    type Error = ProtocolError;
79    fn try_from(value: Vec<Option<FeeResult>>) -> Result<Self, Self::Error> {
80        let mut aggregate_fee_result = FeeResult::default();
81        value.into_iter().try_for_each(|fee_result| {
82            if let Some(fee_result) = fee_result {
83                aggregate_fee_result.checked_add_assign(fee_result)
84            } else {
85                Ok(())
86            }
87        })?;
88        Ok(aggregate_fee_result)
89    }
90}
91
92/// The balance change for an identity
93#[derive(Clone, Debug, PartialEq, Eq)]
94pub enum BalanceChange {
95    /// Add Balance
96    AddToBalance(Credits),
97    /// Remove Balance
98    RemoveFromBalance {
99        /// the required removed balance
100        required_removed_balance: Credits,
101        /// the desired removed balance
102        desired_removed_balance: Credits,
103    },
104    /// There was no balance change
105    NoBalanceChange,
106}
107
108/// The fee expense for the identity from a fee result
109#[derive(Clone, Debug)]
110pub struct BalanceChangeForIdentity {
111    /// The identifier of the identity
112    pub identity_id: Identifier,
113
114    fee_result: FeeResult,
115    change: BalanceChange,
116}
117
118impl BalanceChangeForIdentity {
119    /// Balance change
120    pub fn change(&self) -> &BalanceChange {
121        &self.change
122    }
123
124    /// Returns refund amount of credits for other identities
125    pub fn other_refunds(&self) -> BTreeMap<Identifier, Credits> {
126        self.fee_result
127            .fee_refunds
128            .calculate_all_refunds_except_identity(self.identity_id)
129    }
130
131    /// Convert into a fee result
132    pub fn into_fee_result(self) -> FeeResult {
133        self.fee_result
134    }
135
136    /// Convert into a fee result minus some processing
137    fn into_fee_result_less_processing_debt(self, processing_debt: u64) -> FeeResult {
138        FeeResult {
139            processing_fee: self.fee_result.processing_fee - processing_debt,
140            ..self.fee_result
141        }
142    }
143
144    /// The fee result outcome based on user balance
145    pub fn fee_result_outcome<E>(self, user_balance: u64) -> Result<FeeResult, E>
146    where
147        E: From<FeeError>,
148    {
149        match self.change {
150            AddToBalance { .. } => {
151                // when we add balance we are sure that all the storage fee and processing fee has
152                // been paid
153                Ok(self.into_fee_result())
154            }
155            RemoveFromBalance {
156                required_removed_balance,
157                desired_removed_balance,
158            } => {
159                if user_balance >= desired_removed_balance {
160                    Ok(self.into_fee_result())
161                } else if user_balance >= required_removed_balance {
162                    // We do not take into account balance debt for total credits balance verification
163                    // so we shouldn't add them to pools
164                    Ok(self.into_fee_result_less_processing_debt(
165                        desired_removed_balance - user_balance,
166                    ))
167                } else {
168                    // The user could not pay for required storage space
169                    Err(
170                        FeeError::BalanceIsNotEnoughError(BalanceIsNotEnoughError::new(
171                            user_balance,
172                            required_removed_balance,
173                        ))
174                        .into(),
175                    )
176                }
177            }
178            NoBalanceChange => {
179                // while there might be no balance change we still need to deal with refunds
180                Ok(self.into_fee_result())
181            }
182        }
183    }
184}
185
186impl FeeResult {
187    /// Convenience method to create a fee result from processing credits
188    pub fn new_from_processing_fee(credits: Credits) -> Self {
189        Self {
190            storage_fee: 0,
191            processing_fee: credits,
192            fee_refunds: Default::default(),
193            removed_bytes_from_system: 0,
194        }
195    }
196
197    /// Apply a fee multiplier to a fee result
198    pub fn apply_user_fee_increase(&mut self, add_fee_percentage_multiplier: UserFeeIncrease) {
199        let additional_processing_fee = (self.processing_fee as u128)
200            .saturating_mul(add_fee_percentage_multiplier as u128)
201            .saturating_div(100);
202        if additional_processing_fee > u64::MAX as u128 {
203            self.processing_fee = u64::MAX;
204        } else {
205            self.processing_fee = self
206                .processing_fee
207                .saturating_add(additional_processing_fee as u64);
208        }
209    }
210
211    /// Convenience method to get total fee
212    pub fn total_base_fee(&self) -> Credits {
213        self.storage_fee.saturating_add(self.processing_fee)
214    }
215
216    /// Convenience method to get required removed balance
217    pub fn into_balance_change(self, identity_id: Identifier) -> BalanceChangeForIdentity {
218        let storage_credits_returned = self
219            .fee_refunds
220            .calculate_refunds_amount_for_identity(identity_id)
221            .unwrap_or_default();
222
223        let base_required_removed_balance = self.storage_fee;
224        let base_desired_removed_balance = self.storage_fee + self.processing_fee;
225
226        let balance_change = match storage_credits_returned.cmp(&base_desired_removed_balance) {
227            Ordering::Less => {
228                // If we refund more than require to pay we should nil the required
229                let required_removed_balance =
230                    base_required_removed_balance.saturating_sub(storage_credits_returned);
231
232                let desired_removed_balance =
233                    base_desired_removed_balance - storage_credits_returned;
234
235                RemoveFromBalance {
236                    required_removed_balance,
237                    desired_removed_balance,
238                }
239            }
240            Ordering::Equal => NoBalanceChange,
241            Ordering::Greater => {
242                // Credits returned are greater than our spend
243                AddToBalance(storage_credits_returned - base_desired_removed_balance)
244            }
245        };
246
247        BalanceChangeForIdentity {
248            identity_id,
249            fee_result: self,
250            change: balance_change,
251        }
252    }
253
254    /// Creates a FeeResult instance with specified storage and processing fees
255    pub fn default_with_fees(storage_fee: Credits, processing_fee: Credits) -> Self {
256        FeeResult {
257            storage_fee,
258            processing_fee,
259            ..Default::default()
260        }
261    }
262
263    /// Adds and self assigns result between two Fee Results
264    pub fn checked_add_assign(&mut self, rhs: Self) -> Result<(), ProtocolError> {
265        self.storage_fee = self
266            .storage_fee
267            .checked_add(rhs.storage_fee)
268            .ok_or(ProtocolError::Overflow("storage fee overflow error"))?;
269        self.processing_fee = self
270            .processing_fee
271            .checked_add(rhs.processing_fee)
272            .ok_or(ProtocolError::Overflow("processing fee overflow error"))?;
273        self.fee_refunds.checked_add_assign(rhs.fee_refunds)?;
274        self.removed_bytes_from_system = self
275            .removed_bytes_from_system
276            .checked_add(rhs.removed_bytes_from_system)
277            .ok_or(ProtocolError::Overflow(
278                "removed_bytes_from_system overflow error",
279            ))?;
280        Ok(())
281    }
282}