solana_fee/
lib.rs

1#![cfg_attr(
2    not(feature = "agave-unstable-api"),
3    deprecated(
4        since = "3.1.0",
5        note = "This crate has been marked for formal inclusion in the Agave Unstable API. From \
6                v4.0.0 onward, the `agave-unstable-api` crate feature must be specified to \
7                acknowledge use of an interface that may break without warning."
8    )
9)]
10use {
11    agave_feature_set::{enable_secp256r1_precompile, FeatureSet},
12    solana_fee_structure::FeeDetails,
13    solana_svm_transaction::svm_message::SVMMessage,
14};
15
16/// Bools indicating the activation of features relevant
17/// to the fee calculation.
18// DEVELOPER NOTE:
19// This struct may become empty at some point. It is preferable to keep it
20// instead of removing, since fees will naturally be changed via feature-gates
21// in the future. Keeping this struct will help keep things organized.
22#[derive(Copy, Clone)]
23pub struct FeeFeatures {
24    pub enable_secp256r1_precompile: bool,
25}
26
27impl From<&FeatureSet> for FeeFeatures {
28    fn from(feature_set: &FeatureSet) -> Self {
29        Self {
30            enable_secp256r1_precompile: feature_set.is_active(&enable_secp256r1_precompile::ID),
31        }
32    }
33}
34
35/// Calculate fee for `SanitizedMessage`
36pub fn calculate_fee(
37    message: &impl SVMMessage,
38    zero_fees_for_test: bool,
39    lamports_per_signature: u64,
40    prioritization_fee: u64,
41    fee_features: FeeFeatures,
42) -> u64 {
43    calculate_fee_details(
44        message,
45        zero_fees_for_test,
46        lamports_per_signature,
47        prioritization_fee,
48        fee_features,
49    )
50    .total_fee()
51}
52
53pub fn calculate_fee_details(
54    message: &impl SVMMessage,
55    zero_fees_for_test: bool,
56    lamports_per_signature: u64,
57    prioritization_fee: u64,
58    fee_features: FeeFeatures,
59) -> FeeDetails {
60    if zero_fees_for_test {
61        return FeeDetails::default();
62    }
63
64    FeeDetails::new(
65        calculate_signature_fee(
66            SignatureCounts::from(message),
67            lamports_per_signature,
68            fee_features.enable_secp256r1_precompile,
69        ),
70        prioritization_fee,
71    )
72}
73
74/// Calculate fees from signatures.
75pub fn calculate_signature_fee(
76    SignatureCounts {
77        num_transaction_signatures,
78        num_ed25519_signatures,
79        num_secp256k1_signatures,
80        num_secp256r1_signatures,
81    }: SignatureCounts,
82    lamports_per_signature: u64,
83    enable_secp256r1_precompile: bool,
84) -> u64 {
85    let signature_count = num_transaction_signatures
86        .saturating_add(num_ed25519_signatures)
87        .saturating_add(num_secp256k1_signatures)
88        .saturating_add(
89            u64::from(enable_secp256r1_precompile).wrapping_mul(num_secp256r1_signatures),
90        );
91    signature_count.saturating_mul(lamports_per_signature)
92}
93
94pub struct SignatureCounts {
95    pub num_transaction_signatures: u64,
96    pub num_ed25519_signatures: u64,
97    pub num_secp256k1_signatures: u64,
98    pub num_secp256r1_signatures: u64,
99}
100
101impl<Tx: SVMMessage> From<&Tx> for SignatureCounts {
102    fn from(message: &Tx) -> Self {
103        Self {
104            num_transaction_signatures: message.num_transaction_signatures(),
105            num_ed25519_signatures: message.num_ed25519_signatures(),
106            num_secp256k1_signatures: message.num_secp256k1_signatures(),
107            num_secp256r1_signatures: message.num_secp256r1_signatures(),
108        }
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_calculate_signature_fee() {
118        const LAMPORTS_PER_SIGNATURE: u64 = 5_000;
119
120        // Impossible case - 0 signatures.
121        assert_eq!(
122            calculate_signature_fee(
123                SignatureCounts {
124                    num_transaction_signatures: 0,
125                    num_ed25519_signatures: 0,
126                    num_secp256k1_signatures: 0,
127                    num_secp256r1_signatures: 0,
128                },
129                LAMPORTS_PER_SIGNATURE,
130                true,
131            ),
132            0
133        );
134
135        // Simple signature
136        assert_eq!(
137            calculate_signature_fee(
138                SignatureCounts {
139                    num_transaction_signatures: 1,
140                    num_ed25519_signatures: 0,
141                    num_secp256k1_signatures: 0,
142                    num_secp256r1_signatures: 0,
143                },
144                LAMPORTS_PER_SIGNATURE,
145                true,
146            ),
147            LAMPORTS_PER_SIGNATURE
148        );
149
150        // Pre-compile signatures.
151        assert_eq!(
152            calculate_signature_fee(
153                SignatureCounts {
154                    num_transaction_signatures: 1,
155                    num_ed25519_signatures: 2,
156                    num_secp256k1_signatures: 3,
157                    num_secp256r1_signatures: 4,
158                },
159                LAMPORTS_PER_SIGNATURE,
160                true,
161            ),
162            10 * LAMPORTS_PER_SIGNATURE
163        );
164
165        // Pre-compile signatures (no secp256r1)
166        assert_eq!(
167            calculate_signature_fee(
168                SignatureCounts {
169                    num_transaction_signatures: 1,
170                    num_ed25519_signatures: 2,
171                    num_secp256k1_signatures: 3,
172                    num_secp256r1_signatures: 4,
173                },
174                LAMPORTS_PER_SIGNATURE,
175                false,
176            ),
177            6 * LAMPORTS_PER_SIGNATURE
178        );
179    }
180}