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