Skip to main content

blvm_protocol/
economic.rs

1//! Economic Model Parameters
2//!
3//! Expanded economic model abstraction beyond basic halving.
4//! Provides comprehensive economic parameters for protocol variants.
5
6use crate::ProtocolVersion;
7use serde::{Deserialize, Serialize};
8
9/// Economic model parameters for a protocol version
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11pub struct EconomicParameters {
12    /// Initial block subsidy (in satoshis)
13    pub initial_subsidy: u64,
14    /// Block subsidy halving interval
15    pub halving_interval: u64,
16    /// Maximum money supply (in satoshis)
17    pub max_money_supply: u64,
18    /// Coinbase maturity (blocks before coinbase can be spent)
19    pub coinbase_maturity: u64,
20    /// Dust limit (minimum output value in satoshis)
21    pub dust_limit: u64,
22    /// Minimum transaction fee rate (satoshis per vbyte)
23    pub min_fee_rate: u64,
24    /// Maximum transaction fee rate (satoshis per vbyte)
25    pub max_fee_rate: u64,
26    /// Minimum relay fee (satoshis per vbyte)
27    pub min_relay_fee: u64,
28    /// Block subsidy schedule (for custom schedules)
29    pub subsidy_schedule: Vec<(u64, u64)>, // (height, subsidy)
30}
31
32impl EconomicParameters {
33    /// Get economic parameters for a protocol version
34    pub fn for_protocol(version: ProtocolVersion) -> Self {
35        match version {
36            ProtocolVersion::BitcoinV1 => Self::mainnet(),
37            ProtocolVersion::Testnet3 => Self::testnet(),
38            ProtocolVersion::Regtest => Self::regtest(),
39        }
40    }
41
42    /// Mainnet economic parameters (Bitcoin production network)
43    pub fn mainnet() -> Self {
44        Self {
45            initial_subsidy: 50_0000_0000, // 50 BTC in satoshis
46            halving_interval: 210_000,
47            max_money_supply: 21_0000_0000_0000_0000, // 21M BTC in satoshis
48            coinbase_maturity: 100,                   // 100 blocks
49            dust_limit: 546,                          // 546 satoshis
50            min_fee_rate: 1,                          // 1 sat/vbyte
51            max_fee_rate: 1_000_000,                  // 1M sat/vbyte (safety limit)
52            min_relay_fee: 1000,                      // 1000 satoshis per transaction (BIP125)
53            subsidy_schedule: Vec::new(),             // Use halving formula instead
54        }
55    }
56
57    /// Testnet economic parameters (same as mainnet)
58    pub fn testnet() -> Self {
59        Self {
60            initial_subsidy: 50_0000_0000,
61            halving_interval: 210_000,
62            max_money_supply: 21_0000_0000_0000_0000,
63            coinbase_maturity: 100,
64            dust_limit: 546,
65            min_fee_rate: 1,
66            max_fee_rate: 1_000_000,
67            min_relay_fee: 1000,
68            subsidy_schedule: Vec::new(),
69        }
70    }
71
72    /// Regtest economic parameters (relaxed for testing)
73    pub fn regtest() -> Self {
74        Self {
75            initial_subsidy: 50_0000_0000,
76            halving_interval: 150, // Faster halving for testing
77            max_money_supply: 21_0000_0000_0000_0000,
78            coinbase_maturity: 100,
79            dust_limit: 546,
80            min_fee_rate: 0, // No minimum fee for testing
81            max_fee_rate: 1_000_000,
82            min_relay_fee: 0, // No minimum relay fee for testing
83            subsidy_schedule: Vec::new(),
84        }
85    }
86
87    /// Calculate block subsidy for a given height
88    pub fn get_block_subsidy(&self, height: u64) -> u64 {
89        // If custom subsidy schedule exists, use it
90        if !self.subsidy_schedule.is_empty() {
91            for (schedule_height, subsidy) in self.subsidy_schedule.iter().rev() {
92                if height >= *schedule_height {
93                    return *subsidy;
94                }
95            }
96            return 0;
97        }
98
99        // Use standard halving formula
100        let halving_period = height / self.halving_interval;
101
102        // After 64 halvings, subsidy becomes 0
103        if halving_period >= 64 {
104            return 0;
105        }
106
107        // Calculate: initial_subsidy / 2^halving_period
108        self.initial_subsidy >> halving_period
109    }
110
111    /// Calculate total supply up to a given height
112    pub fn total_supply_at_height(&self, height: u64) -> u64 {
113        let mut total = 0u64;
114
115        for h in 0..=height {
116            total = total.saturating_add(self.get_block_subsidy(h));
117        }
118
119        total
120    }
121
122    /// Check if a value meets dust limit
123    pub fn is_dust(&self, value: u64) -> bool {
124        value < self.dust_limit
125    }
126
127    /// Check if a fee rate is valid
128    pub fn is_valid_fee_rate(&self, fee_rate: u64) -> bool {
129        fee_rate >= self.min_fee_rate && fee_rate <= self.max_fee_rate
130    }
131
132    /// Calculate fee for a transaction size
133    pub fn calculate_fee(&self, size_vbytes: usize, fee_rate_sat_per_vbyte: u64) -> u64 {
134        if !self.is_valid_fee_rate(fee_rate_sat_per_vbyte) {
135            return 0;
136        }
137
138        (size_vbytes as u64).saturating_mul(fee_rate_sat_per_vbyte)
139    }
140
141    /// Check if total supply exceeds maximum
142    pub fn exceeds_max_supply(&self, height: u64) -> bool {
143        self.total_supply_at_height(height) > self.max_money_supply
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn test_mainnet_economic_parameters() {
153        let params = EconomicParameters::mainnet();
154
155        assert_eq!(params.initial_subsidy, 50_0000_0000);
156        assert_eq!(params.halving_interval, 210_000);
157        assert_eq!(params.max_money_supply, 21_0000_0000_0000_0000);
158        assert_eq!(params.coinbase_maturity, 100);
159        assert_eq!(params.dust_limit, 546);
160    }
161
162    #[test]
163    fn test_block_subsidy_halving() {
164        let params = EconomicParameters::mainnet();
165
166        // Initial subsidy
167        assert_eq!(params.get_block_subsidy(0), 50_0000_0000);
168        assert_eq!(params.get_block_subsidy(209_999), 50_0000_0000);
169
170        // First halving
171        assert_eq!(params.get_block_subsidy(210_000), 25_0000_0000);
172        assert_eq!(params.get_block_subsidy(419_999), 25_0000_0000);
173
174        // Second halving
175        assert_eq!(params.get_block_subsidy(420_000), 12_5000_0000);
176
177        // After 64 halvings (13,440,000 blocks)
178        assert_eq!(params.get_block_subsidy(13_440_000), 0);
179        assert_eq!(params.get_block_subsidy(20_000_000), 0);
180    }
181
182    #[test]
183    fn test_total_supply_calculation() {
184        let params = EconomicParameters::mainnet();
185
186        // Genesis block
187        assert_eq!(params.total_supply_at_height(0), 50_0000_0000);
188
189        // After 10 blocks
190        assert_eq!(params.total_supply_at_height(9), 10 * 50_0000_0000);
191
192        // At first halving
193        let first_halving_height = 210_000;
194        let _before_halving_subsidy = first_halving_height * 50_0000_0000;
195        // Approximate calculation (simplified)
196        assert!(params.total_supply_at_height(first_halving_height) > 0);
197    }
198
199    #[test]
200    fn test_dust_limit() {
201        let params = EconomicParameters::mainnet();
202
203        assert!(params.is_dust(545));
204        assert!(!params.is_dust(546));
205        assert!(!params.is_dust(1000));
206    }
207
208    #[test]
209    fn test_fee_rate_validation() {
210        let params = EconomicParameters::mainnet();
211
212        // Valid fee rates
213        assert!(params.is_valid_fee_rate(1));
214        assert!(params.is_valid_fee_rate(100));
215        assert!(params.is_valid_fee_rate(1_000_000));
216
217        // Invalid fee rates
218        assert!(!params.is_valid_fee_rate(0));
219        assert!(!params.is_valid_fee_rate(1_000_001));
220    }
221
222    #[test]
223    fn test_fee_calculation() {
224        let params = EconomicParameters::mainnet();
225
226        // 250 vbyte transaction at 10 sat/vbyte = 2500 sats
227        assert_eq!(params.calculate_fee(250, 10), 2500);
228
229        // Invalid fee rate returns 0
230        assert_eq!(params.calculate_fee(250, 0), 0);
231        assert_eq!(params.calculate_fee(250, 2_000_000), 0);
232    }
233
234    #[test]
235    fn test_regtest_relaxed_parameters() {
236        let params = EconomicParameters::regtest();
237
238        // Faster halving
239        assert_eq!(params.halving_interval, 150);
240
241        // No minimum fees
242        assert_eq!(params.min_fee_rate, 0);
243        assert_eq!(params.min_relay_fee, 0);
244
245        // Can use zero fees
246        assert!(params.is_valid_fee_rate(0));
247    }
248
249    #[test]
250    fn test_regtest_faster_halving() {
251        let params = EconomicParameters::regtest();
252
253        // Subsidy halves at block 150 instead of 210,000
254        assert_eq!(params.get_block_subsidy(0), 50_0000_0000);
255        assert_eq!(params.get_block_subsidy(149), 50_0000_0000);
256        assert_eq!(params.get_block_subsidy(150), 25_0000_0000);
257        assert_eq!(params.get_block_subsidy(299), 25_0000_0000);
258        assert_eq!(params.get_block_subsidy(300), 12_5000_0000);
259    }
260
261    #[test]
262    fn test_testnet_same_as_mainnet() {
263        let mainnet = EconomicParameters::mainnet();
264        let testnet = EconomicParameters::testnet();
265
266        assert_eq!(mainnet.initial_subsidy, testnet.initial_subsidy);
267        assert_eq!(mainnet.halving_interval, testnet.halving_interval);
268        assert_eq!(mainnet.max_money_supply, testnet.max_money_supply);
269        assert_eq!(mainnet.coinbase_maturity, testnet.coinbase_maturity);
270        assert_eq!(mainnet.dust_limit, testnet.dust_limit);
271    }
272
273    #[test]
274    fn test_custom_subsidy_schedule() {
275        let mut params = EconomicParameters::mainnet();
276        params.subsidy_schedule = vec![
277            (0, 100_0000_0000),      // 100 BTC for first 1000 blocks
278            (1000, 50_0000_0000),    // 50 BTC after
279            (210_000, 25_0000_0000), // 25 BTC after halving
280        ];
281
282        assert_eq!(params.get_block_subsidy(0), 100_0000_0000);
283        assert_eq!(params.get_block_subsidy(999), 100_0000_0000);
284        assert_eq!(params.get_block_subsidy(1000), 50_0000_0000);
285        assert_eq!(params.get_block_subsidy(210_000), 25_0000_0000);
286    }
287
288    #[test]
289    fn test_max_supply_check() {
290        let params = EconomicParameters::mainnet();
291
292        // At reasonable heights, shouldn't exceed
293        assert!(!params.exceeds_max_supply(100_000));
294        assert!(!params.exceeds_max_supply(1_000_000));
295
296        // At extremely high heights (beyond 64 halvings), should check
297        // Note: This test may need adjustment based on actual calculation
298    }
299
300    #[test]
301    fn test_economic_parameters_serialization() {
302        let mainnet = EconomicParameters::mainnet();
303        let json = serde_json::to_string(&mainnet).unwrap();
304        let deserialized: EconomicParameters = serde_json::from_str(&json).unwrap();
305
306        assert_eq!(mainnet.initial_subsidy, deserialized.initial_subsidy);
307        assert_eq!(mainnet.halving_interval, deserialized.halving_interval);
308        assert_eq!(mainnet.max_money_supply, deserialized.max_money_supply);
309        assert_eq!(mainnet.coinbase_maturity, deserialized.coinbase_maturity);
310        assert_eq!(mainnet.dust_limit, deserialized.dust_limit);
311    }
312
313    #[test]
314    fn test_economic_parameters_equality() {
315        let mainnet1 = EconomicParameters::mainnet();
316        let mainnet2 = EconomicParameters::mainnet();
317        let testnet = EconomicParameters::testnet();
318
319        assert_eq!(mainnet1, mainnet2);
320        assert_eq!(mainnet1, testnet); // Mainnet and testnet have same economic params
321    }
322}