penumbra_sdk_fee/
gas.rs

1use std::{
2    iter::Sum,
3    ops::{Add, AddAssign},
4};
5
6use penumbra_sdk_asset::{asset, Value, STAKING_TOKEN_ASSET_ID};
7use serde::{Deserialize, Serialize};
8
9use penumbra_sdk_num::Amount;
10use penumbra_sdk_proto::{core::component::fee::v1 as pb, DomainType};
11
12use crate::Fee;
13
14/// Represents the different resources that a transaction can consume,
15/// for purposes of calculating multidimensional fees based on real
16/// transaction resource consumption.
17#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
18#[serde(try_from = "pb::Gas", into = "pb::Gas")]
19pub struct Gas {
20    pub block_space: u64,
21    pub compact_block_space: u64,
22    pub verification: u64,
23    pub execution: u64,
24}
25
26impl DomainType for Gas {
27    type Proto = pb::Gas;
28}
29
30impl From<Gas> for pb::Gas {
31    fn from(gas: Gas) -> Self {
32        pb::Gas {
33            block_space: gas.block_space,
34            compact_block_space: gas.compact_block_space,
35            verification: gas.verification,
36            execution: gas.execution,
37        }
38    }
39}
40
41impl TryFrom<pb::Gas> for Gas {
42    type Error = anyhow::Error;
43
44    fn try_from(proto: pb::Gas) -> Result<Self, Self::Error> {
45        Ok(Gas {
46            block_space: proto.block_space,
47            compact_block_space: proto.compact_block_space,
48            verification: proto.verification,
49            execution: proto.execution,
50        })
51    }
52}
53
54impl Gas {
55    pub fn zero() -> Self {
56        Self {
57            block_space: 0,
58            compact_block_space: 0,
59            verification: 0,
60            execution: 0,
61        }
62    }
63}
64
65impl Add for Gas {
66    type Output = Self;
67
68    fn add(self, rhs: Self) -> Self::Output {
69        Self {
70            block_space: self.block_space + rhs.block_space,
71            compact_block_space: self.compact_block_space + rhs.compact_block_space,
72            verification: self.verification + rhs.verification,
73            execution: self.execution + rhs.execution,
74        }
75    }
76}
77
78impl AddAssign for Gas {
79    fn add_assign(&mut self, rhs: Self) {
80        *self = *self + rhs;
81    }
82}
83
84impl Sum for Gas {
85    fn sum<I: Iterator<Item = Gas>>(iter: I) -> Gas {
86        iter.fold(Gas::zero(), |acc, x| acc + x)
87    }
88}
89
90/// Expresses the price of each unit of gas in terms of the staking token.
91///
92/// These prices have an implicit denominator of 1,000 relative to the base unit
93/// of the staking token, so gas price 1,000 times 1 unit of gas is 1 base unit
94/// of staking token.
95#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
96#[serde(try_from = "pb::GasPrices", into = "pb::GasPrices")]
97pub struct GasPrices {
98    pub asset_id: asset::Id,
99    pub block_space_price: u64,
100    pub compact_block_space_price: u64,
101    pub verification_price: u64,
102    pub execution_price: u64,
103}
104
105impl Default for GasPrices {
106    fn default() -> Self {
107        Self {
108            asset_id: *STAKING_TOKEN_ASSET_ID,
109            block_space_price: 0,
110            compact_block_space_price: 0,
111            verification_price: 0,
112            execution_price: 0,
113        }
114    }
115}
116
117impl GasPrices {
118    pub fn zero() -> Self {
119        Self::default()
120    }
121
122    /// Use these gas prices to calculate the fee for a given gas vector.
123    pub fn fee(&self, gas: &Gas) -> Fee {
124        let amount = Amount::from(
125            (self.block_space_price * gas.block_space) / 1_000
126                + (self.compact_block_space_price * gas.compact_block_space) / 1_000
127                + (self.verification_price * gas.verification) / 1_000
128                + (self.execution_price * gas.execution) / 1_000,
129        );
130
131        Fee(Value {
132            asset_id: self.asset_id,
133            amount,
134        })
135    }
136}
137
138impl DomainType for GasPrices {
139    type Proto = pb::GasPrices;
140}
141
142impl From<GasPrices> for pb::GasPrices {
143    fn from(prices: GasPrices) -> Self {
144        pb::GasPrices {
145            // Skip serializing the asset ID if it's the staking token.
146            asset_id: if prices.asset_id == *STAKING_TOKEN_ASSET_ID {
147                None
148            } else {
149                Some(prices.asset_id.into())
150            },
151            block_space_price: prices.block_space_price,
152            compact_block_space_price: prices.compact_block_space_price,
153            verification_price: prices.verification_price,
154            execution_price: prices.execution_price,
155        }
156    }
157}
158
159impl TryFrom<pb::GasPrices> for GasPrices {
160    type Error = anyhow::Error;
161
162    fn try_from(proto: pb::GasPrices) -> Result<Self, Self::Error> {
163        Ok(GasPrices {
164            block_space_price: proto.block_space_price,
165            compact_block_space_price: proto.compact_block_space_price,
166            verification_price: proto.verification_price,
167            execution_price: proto.execution_price,
168            asset_id: proto
169                .asset_id
170                .map(TryInto::try_into)
171                .transpose()?
172                .unwrap_or_else(|| *STAKING_TOKEN_ASSET_ID),
173        })
174    }
175}