1use 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
14pub struct EconomicParameters {
15 pub initial_subsidy: u64,
17 pub halving_interval: u64,
19 pub max_money_supply: u64,
21 pub coinbase_maturity: u64,
23 pub dust_limit: u64,
25 pub min_fee_rate: u64,
27 pub max_fee_rate: u64,
29 pub min_relay_fee: u64,
31 pub subsidy_schedule: Vec<(u64, u64)>, }
34
35impl EconomicParameters {
36 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 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, coinbase_maturity: 100, dust_limit: 546, min_fee_rate: 1, max_fee_rate: 1_000_000, min_relay_fee: 1000, subsidy_schedule: Vec::new(), }
60 }
61
62 pub fn testnet() -> Self {
64 Self::mainnet()
65 }
66
67 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, max_fee_rate: 1_000_000,
77 min_relay_fee: 0, subsidy_schedule: Vec::new(),
79 }
80 }
81
82 pub fn signet() -> Self {
84 Self::mainnet()
85 }
86
87 pub fn get_block_subsidy(&self, height: u64) -> u64 {
89 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 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 pub fn is_dust(&self, value: u64) -> bool {
115 value < self.dust_limit
116 }
117
118 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 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 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 assert_eq!(params.get_block_subsidy(0), 50_0000_0000);
159 assert_eq!(params.get_block_subsidy(209_999), 50_0000_0000);
160
161 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 assert_eq!(params.get_block_subsidy(420_000), 12_5000_0000);
167
168 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 assert_eq!(params.total_supply_at_height(0), 50_0000_0000);
179
180 assert_eq!(params.total_supply_at_height(9), 10 * 50_0000_0000);
182
183 let first_halving_height = 210_000;
185 let _before_halving_subsidy = first_halving_height * 50_0000_0000;
186 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 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 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 assert_eq!(params.calculate_fee(250, 10), 2500);
219
220 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 assert_eq!(params.halving_interval, 150);
231
232 assert_eq!(params.min_fee_rate, 0);
234 assert_eq!(params.min_relay_fee, 0);
235
236 assert!(params.is_valid_fee_rate(0));
238 }
239
240 #[test]
241 fn test_regtest_faster_halving() {
242 let params = EconomicParameters::regtest();
243
244 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), (1000, 50_0000_0000), (210_000, 25_0000_0000), ];
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 assert!(!params.exceeds_max_supply(100_000));
285 assert!(!params.exceeds_max_supply(1_000_000));
286
287 }
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); }
313}