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