1use std::collections::HashMap;
16
17use lazy_static::lazy_static;
18
19use crate::{
20 amount::COIN,
21 block::{Height, HeightDiff},
22 parameters::{Network, NetworkUpgrade},
23 transparent,
24};
25
26pub const MAX_BLOCK_SUBSIDY: u64 = ((25 * COIN) / 2) as u64;
31
32pub const BLOSSOM_POW_TARGET_SPACING_RATIO: u32 = 2;
37
38pub const PRE_BLOSSOM_HALVING_INTERVAL: HeightDiff = 840_000;
42
43pub const POST_BLOSSOM_HALVING_INTERVAL: HeightDiff =
45 PRE_BLOSSOM_HALVING_INTERVAL * (BLOSSOM_POW_TARGET_SPACING_RATIO as HeightDiff);
46
47pub(crate) const FIRST_HALVING_TESTNET: Height = Height(1_116_000);
52
53const FIRST_HALVING_REGTEST: Height = Height(287);
55
56#[derive(Deserialize, Clone, Copy, Debug, Eq, Hash, PartialEq)]
58pub enum FundingStreamReceiver {
59 #[serde(rename = "ECC")]
61 Ecc,
62
63 ZcashFoundation,
65
66 MajorGrants,
68
69 Deferred,
71}
72
73impl FundingStreamReceiver {
74 pub fn info(&self, is_nu6: bool) -> (&'static str, &'static str) {
81 if is_nu6 {
82 (
83 match self {
84 FundingStreamReceiver::Ecc => "Electric Coin Company",
85 FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
86 FundingStreamReceiver::MajorGrants => "Zcash Community Grants NU6",
87 FundingStreamReceiver::Deferred => "Lockbox NU6",
88 },
89 LOCKBOX_SPECIFICATION,
90 )
91 } else {
92 (
93 match self {
94 FundingStreamReceiver::Ecc => "Electric Coin Company",
95 FundingStreamReceiver::ZcashFoundation => "Zcash Foundation",
96 FundingStreamReceiver::MajorGrants => "Major Grants",
97 FundingStreamReceiver::Deferred => "Lockbox NU6",
98 },
99 FUNDING_STREAM_SPECIFICATION,
100 )
101 }
102 }
103}
104
105pub const FUNDING_STREAM_RECEIVER_DENOMINATOR: u64 = 100;
109
110pub const FUNDING_STREAM_SPECIFICATION: &str = "https://zips.z.cash/zip-0214";
114
115pub const LOCKBOX_SPECIFICATION: &str = "https://zips.z.cash/zip-1015";
119
120#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
122pub struct FundingStreams {
123 height_range: std::ops::Range<Height>,
128 recipients: HashMap<FundingStreamReceiver, FundingStreamRecipient>,
130}
131
132impl FundingStreams {
133 pub fn new(
135 height_range: std::ops::Range<Height>,
136 recipients: HashMap<FundingStreamReceiver, FundingStreamRecipient>,
137 ) -> Self {
138 Self {
139 height_range,
140 recipients,
141 }
142 }
143
144 pub fn height_range(&self) -> &std::ops::Range<Height> {
146 &self.height_range
147 }
148
149 pub fn recipients(&self) -> &HashMap<FundingStreamReceiver, FundingStreamRecipient> {
151 &self.recipients
152 }
153
154 pub fn recipient(&self, receiver: FundingStreamReceiver) -> Option<&FundingStreamRecipient> {
156 self.recipients.get(&receiver)
157 }
158}
159
160#[derive(Deserialize, Clone, Debug, Eq, PartialEq)]
164pub struct FundingStreamRecipient {
165 numerator: u64,
170 addresses: Vec<transparent::Address>,
172}
173
174impl FundingStreamRecipient {
175 pub fn new<I, T>(numerator: u64, addresses: I) -> Self
177 where
178 T: ToString,
179 I: IntoIterator<Item = T>,
180 {
181 Self {
182 numerator,
183 addresses: addresses
184 .into_iter()
185 .map(|addr| {
186 let addr = addr.to_string();
187 addr.parse()
188 .expect("funding stream address must deserialize")
189 })
190 .collect(),
191 }
192 }
193
194 pub fn numerator(&self) -> u64 {
196 self.numerator
197 }
198
199 pub fn addresses(&self) -> &[transparent::Address] {
201 &self.addresses
202 }
203}
204
205lazy_static! {
206 pub static ref PRE_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams {
209 height_range: Height(1_046_400)..Height(2_726_400),
210 recipients: [
211 (
212 FundingStreamReceiver::Ecc,
213 FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_MAINNET),
214 ),
215 (
216 FundingStreamReceiver::ZcashFoundation,
217 FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_MAINNET),
218 ),
219 (
220 FundingStreamReceiver::MajorGrants,
221 FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_MAINNET),
222 ),
223 ]
224 .into_iter()
225 .collect(),
226 };
227
228 pub static ref POST_NU6_FUNDING_STREAMS_MAINNET: FundingStreams = FundingStreams {
230 height_range: POST_NU6_FUNDING_STREAM_START_RANGE_MAINNET,
231 recipients: [
232 (
233 FundingStreamReceiver::Deferred,
234 FundingStreamRecipient::new::<[&str; 0], &str>(12, []),
235 ),
236 (
237 FundingStreamReceiver::MajorGrants,
238 FundingStreamRecipient::new(8, POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_MAINNET),
239 ),
240 ]
241 .into_iter()
242 .collect()
243 };
244
245 pub static ref PRE_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams {
248 height_range: Height(1_028_500)..Height(2_796_000),
249 recipients: [
250 (
251 FundingStreamReceiver::Ecc,
252 FundingStreamRecipient::new(7, FUNDING_STREAM_ECC_ADDRESSES_TESTNET),
253 ),
254 (
255 FundingStreamReceiver::ZcashFoundation,
256 FundingStreamRecipient::new(5, FUNDING_STREAM_ZF_ADDRESSES_TESTNET),
257 ),
258 (
259 FundingStreamReceiver::MajorGrants,
260 FundingStreamRecipient::new(8, FUNDING_STREAM_MG_ADDRESSES_TESTNET),
261 ),
262 ]
263 .into_iter()
264 .collect(),
265 };
266
267 pub static ref POST_NU6_FUNDING_STREAMS_TESTNET: FundingStreams = FundingStreams {
269 height_range: POST_NU6_FUNDING_STREAM_START_RANGE_TESTNET,
270 recipients: [
271 (
272 FundingStreamReceiver::Deferred,
273 FundingStreamRecipient::new::<[&str; 0], &str>(12, []),
274 ),
275 (
276 FundingStreamReceiver::MajorGrants,
277 FundingStreamRecipient::new(8, POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_TESTNET),
278 ),
279 ]
280 .into_iter()
281 .collect()
282 };
283}
284
285const POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET: u32 = 2_726_400;
287
288const POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET: u32 = 2_976_000;
290
291const POST_NU6_FUNDING_STREAM_NUM_BLOCKS: u32 = 420_000;
294
295const POST_NU6_FUNDING_STREAM_START_RANGE_MAINNET: std::ops::Range<Height> =
297 Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET)
298 ..Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_MAINNET + POST_NU6_FUNDING_STREAM_NUM_BLOCKS);
299
300const POST_NU6_FUNDING_STREAM_START_RANGE_TESTNET: std::ops::Range<Height> =
302 Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET)
303 ..Height(POST_NU6_FUNDING_STREAM_START_HEIGHT_TESTNET + POST_NU6_FUNDING_STREAM_NUM_BLOCKS);
304
305pub const FUNDING_STREAM_ADDRESS_CHANGE_INTERVAL: HeightDiff = POST_BLOSSOM_HALVING_INTERVAL / 48;
310
311pub const FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 48;
317
318pub const FUNDING_STREAM_ECC_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] = [
320 "t3LmX1cxWPPPqL4TZHx42HU3U5ghbFjRiif",
321 "t3Toxk1vJQ6UjWQ42tUJz2rV2feUWkpbTDs",
322 "t3ZBdBe4iokmsjdhMuwkxEdqMCFN16YxKe6",
323 "t3ZuaJziLM8xZ32rjDUzVjVtyYdDSz8GLWB",
324 "t3bAtYWa4bi8VrtvqySxnbr5uqcG9czQGTZ",
325 "t3dktADfb5Rmxncpe1HS5BRS5Gcj7MZWYBi",
326 "t3hgskquvKKoCtvxw86yN7q8bzwRxNgUZmc",
327 "t3R1VrLzwcxAZzkX4mX3KGbWpNsgtYtMntj",
328 "t3ff6fhemqPMVujD3AQurxRxTdvS1pPSaa2",
329 "t3cEUQFG3KYnFG6qYhPxSNgGi3HDjUPwC3J",
330 "t3WR9F5U4QvUFqqx9zFmwT6xFqduqRRXnaa",
331 "t3PYc1LWngrdUrJJbHkYPCKvJuvJjcm85Ch",
332 "t3bgkjiUeatWNkhxY3cWyLbTxKksAfk561R",
333 "t3Z5rrR8zahxUpZ8itmCKhMSfxiKjUp5Dk5",
334 "t3PU1j7YW3fJ67jUbkGhSRto8qK2qXCUiW3",
335 "t3S3yaT7EwNLaFZCamfsxxKwamQW2aRGEkh",
336 "t3eutXKJ9tEaPSxZpmowhzKhPfJvmtwTEZK",
337 "t3gbTb7brxLdVVghSPSd3ycGxzHbUpukeDm",
338 "t3UCKW2LrHFqPMQFEbZn6FpjqnhAAbfpMYR",
339 "t3NyHsrnYbqaySoQqEQRyTWkjvM2PLkU7Uu",
340 "t3QEFL6acxuZwiXtW3YvV6njDVGjJ1qeaRo",
341 "t3PdBRr2S1XTDzrV8bnZkXF3SJcrzHWe1wj",
342 "t3ZWyRPpWRo23pKxTLtWsnfEKeq9T4XPxKM",
343 "t3he6QytKCTydhpztykFsSsb9PmBT5JBZLi",
344 "t3VWxWDsLb2TURNEP6tA1ZSeQzUmPKFNxRY",
345 "t3NmWLvZkbciNAipauzsFRMxoZGqmtJksbz",
346 "t3cKr4YxVPvPBG1mCvzaoTTdBNokohsRJ8n",
347 "t3T3smGZn6BoSFXWWXa1RaoQdcyaFjMfuYK",
348 "t3gkDUe9Gm4GGpjMk86TiJZqhztBVMiUSSA",
349 "t3eretuBeBXFHe5jAqeSpUS1cpxVh51fAeb",
350 "t3dN8g9zi2UGJdixGe9txeSxeofLS9t3yFQ",
351 "t3S799pq9sYBFwccRecoTJ3SvQXRHPrHqvx",
352 "t3fhYnv1S5dXwau7GED3c1XErzt4n4vDxmf",
353 "t3cmE3vsBc5xfDJKXXZdpydCPSdZqt6AcNi",
354 "t3h5fPdjJVHaH4HwynYDM5BB3J7uQaoUwKi",
355 "t3Ma35c68BgRX8sdLDJ6WR1PCrKiWHG4Da9",
356 "t3LokMKPL1J8rkJZvVpfuH7dLu6oUWqZKQK",
357 "t3WFFGbEbhJWnASZxVLw2iTJBZfJGGX73mM",
358 "t3L8GLEsUn4QHNaRYcX3EGyXmQ8kjpT1zTa",
359 "t3PgfByBhaBSkH8uq4nYJ9ZBX4NhGCJBVYm",
360 "t3WecsqKDhWXD4JAgBVcnaCC2itzyNZhJrv",
361 "t3ZG9cSfopnsMQupKW5v9sTotjcP5P6RTbn",
362 "t3hC1Ywb5zDwUYYV8LwhvF5rZ6m49jxXSG5",
363 "t3VgMqDL15ZcyQDeqBsBW3W6rzfftrWP2yB",
364 "t3LC94Y6BwLoDtBoK2NuewaEbnko1zvR9rm",
365 "t3cWCUZJR3GtALaTcatrrpNJ3MGbMFVLRwQ",
366 "t3YYF4rPLVxDcF9hHFsXyc5Yq1TFfbojCY6",
367 "t3XHAGxRP2FNfhAjxGjxbrQPYtQQjc3RCQD",
368];
369
370pub trait ParameterSubsidy {
372 fn height_for_first_halving(&self) -> Height;
377
378 fn post_blossom_halving_interval(&self) -> HeightDiff;
380
381 fn pre_blossom_halving_interval(&self) -> HeightDiff;
383
384 fn funding_stream_address_change_interval(&self) -> HeightDiff;
391}
392
393impl ParameterSubsidy for Network {
395 fn height_for_first_halving(&self) -> Height {
396 match self {
400 Network::Mainnet => NetworkUpgrade::Canopy
401 .activation_height(self)
402 .expect("canopy activation height should be available"),
403 Network::Testnet(params) => {
404 if params.is_regtest() {
405 FIRST_HALVING_REGTEST
406 } else if params.is_default_testnet() {
407 FIRST_HALVING_TESTNET
408 } else {
409 height_for_halving(1, self).expect("first halving height should be available")
410 }
411 }
412 }
413 }
414
415 fn post_blossom_halving_interval(&self) -> HeightDiff {
416 match self {
417 Network::Mainnet => POST_BLOSSOM_HALVING_INTERVAL,
418 Network::Testnet(params) => params.post_blossom_halving_interval(),
419 }
420 }
421
422 fn pre_blossom_halving_interval(&self) -> HeightDiff {
423 match self {
424 Network::Mainnet => PRE_BLOSSOM_HALVING_INTERVAL,
425 Network::Testnet(params) => params.pre_blossom_halving_interval(),
426 }
427 }
428
429 fn funding_stream_address_change_interval(&self) -> HeightDiff {
430 self.post_blossom_halving_interval() / 48
431 }
432}
433
434pub const FUNDING_STREAM_ZF_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
436 ["t3dvVE3SQEi7kqNzwrfNePxZ1d4hUyztBA1"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
437
438pub const FUNDING_STREAM_MG_ADDRESSES_MAINNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
440 ["t3XyYW8yBFRuMnfvm5KLGFbEVz25kckZXym"; FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
441
442pub const POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET: usize = 12;
448
449pub const POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_MAINNET: [&str;
451 POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET] =
452 ["t3cFfPt1Bcvgez9ZbMBFWeZsskxTkPzGCow"; POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_MAINNET];
453
454pub const FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 51;
460
461pub const FUNDING_STREAM_ECC_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] = [
463 "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz",
464 "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz",
465 "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz",
466 "t26ovBdKAJLtrvBsE2QGF4nqBkEuptuPFZz",
467 "t2NNHrgPpE388atmWSF4DxAb3xAoW5Yp45M",
468 "t2VMN28itPyMeMHBEd9Z1hm6YLkQcGA1Wwe",
469 "t2CHa1TtdfUV8UYhNm7oxbzRyfr8616BYh2",
470 "t2F77xtr28U96Z2bC53ZEdTnQSUAyDuoa67",
471 "t2ARrzhbgcpoVBDPivUuj6PzXzDkTBPqfcT",
472 "t278aQ8XbvFR15mecRguiJDQQVRNnkU8kJw",
473 "t2Dp1BGnZsrTXZoEWLyjHmg3EPvmwBnPDGB",
474 "t2KzeqXgf4ju33hiSqCuKDb8iHjPCjMq9iL",
475 "t2Nyxqv1BiWY1eUSiuxVw36oveawYuo18tr",
476 "t2DKFk5JRsVoiuinK8Ti6eM4Yp7v8BbfTyH",
477 "t2CUaBca4k1x36SC4q8Nc8eBoqkMpF3CaLg",
478 "t296SiKL7L5wvFmEdMxVLz1oYgd6fTfcbZj",
479 "t29fBCFbhgsjL3XYEZ1yk1TUh7eTusB6dPg",
480 "t2FGofLJXa419A76Gpf5ncxQB4gQXiQMXjK",
481 "t2ExfrnRVnRiXDvxerQ8nZbcUQvNvAJA6Qu",
482 "t28JUffLp47eKPRHKvwSPzX27i9ow8LSXHx",
483 "t2JXWPtrtyL861rFWMZVtm3yfgxAf4H7uPA",
484 "t2QdgbJoWfYHgyvEDEZBjHmgkr9yNJff3Hi",
485 "t2QW43nkco8r32ZGRN6iw6eSzyDjkMwCV3n",
486 "t2DgYDXMJTYLwNcxighQ9RCgPxMVATRcUdC",
487 "t2Bop7dg33HGZx3wunnQzi2R2ntfpjuti3M",
488 "t2HVeEwovcLq9RstAbYkqngXNEsCe2vjJh9",
489 "t2HxbP5keQSx7p592zWQ5bJ5GrMmGDsV2Xa",
490 "t2TJzUg2matao3mztBRJoWnJY6ekUau6tPD",
491 "t29pMzxmo6wod25YhswcjKv3AFRNiBZHuhj",
492 "t2QBQMRiJKYjshJpE6RhbF7GLo51yE6d4wZ",
493 "t2F5RqnqguzZeiLtYHFx4yYfy6pDnut7tw5",
494 "t2CHvyZANE7XCtg8AhZnrcHCC7Ys1jJhK13",
495 "t2BRzpMdrGWZJ2upsaNQv6fSbkbTy7EitLo",
496 "t2BFixHGQMAWDY67LyTN514xRAB94iEjXp3",
497 "t2Uvz1iVPzBEWfQBH1p7NZJsFhD74tKaG8V",
498 "t2CmFDj5q6rJSRZeHf1SdrowinyMNcj438n",
499 "t2ErNvWEReTfPDBaNizjMPVssz66aVZh1hZ",
500 "t2GeJQ8wBUiHKDVzVM5ZtKfY5reCg7CnASs",
501 "t2L2eFtkKv1G6j55kLytKXTGuir4raAy3yr",
502 "t2EK2b87dpPazb7VvmEGc8iR6SJ289RywGL",
503 "t2DJ7RKeZJxdA4nZn8hRGXE8NUyTzjujph9",
504 "t2K1pXo4eByuWpKLkssyMLe8QKUbxnfFC3H",
505 "t2TB4mbSpuAcCWkH94Leb27FnRxo16AEHDg",
506 "t2Phx4gVL4YRnNsH3jM1M7jE4Fo329E66Na",
507 "t2VQZGmeNomN8c3USefeLL9nmU6M8x8CVzC",
508 "t2RicCvTVTY5y9JkreSRv3Xs8q2K67YxHLi",
509 "t2JrSLxTGc8wtPDe9hwbaeUjCrCfc4iZnDD",
510 "t2Uh9Au1PDDSw117sAbGivKREkmMxVC5tZo",
511 "t2FDwoJKLeEBMTy3oP7RLQ1Fihhvz49a3Bv",
512 "t2FY18mrgtb7QLeHA8ShnxLXuW8cNQ2n1v8",
513 "t2L15TkDYum7dnQRBqfvWdRe8Yw3jVy9z7g",
514];
515
516pub const FUNDING_STREAM_ZF_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
518 ["t27eWDgjFYJGVXmzrXeVjnb5J3uXDM9xH9v"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
519
520pub const FUNDING_STREAM_MG_ADDRESSES_TESTNET: [&str; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
522 ["t2Gvxv2uNM7hbbACjNox4H6DjByoKZ2Fa3P"; FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
523
524pub const POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET: usize = 13;
530
531pub const POST_NU6_FUNDING_STREAM_FPF_ADDRESSES_TESTNET: [&str;
533 POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET] =
534 ["t2HifwjUj9uyxr9bknR8LFuQbc98c3vkXtu"; POST_NU6_FUNDING_STREAMS_NUM_ADDRESSES_TESTNET];
535
536pub fn funding_stream_address_period<N: ParameterSubsidy>(height: Height, network: &N) -> u32 {
541 let height_after_first_halving = height - network.height_for_first_halving();
551
552 let address_period = (height_after_first_halving + network.post_blossom_halving_interval())
553 / network.funding_stream_address_change_interval();
554
555 address_period
556 .try_into()
557 .expect("all values are positive and smaller than the input height")
558}
559
560pub fn height_for_halving(halving: u32, network: &Network) -> Option<Height> {
566 if halving == 0 {
567 return Some(Height(0));
568 }
569
570 let slow_start_shift = i64::from(network.slow_start_shift().0);
571 let blossom_height = i64::from(
572 NetworkUpgrade::Blossom
573 .activation_height(network)
574 .expect("blossom activation height should be available")
575 .0,
576 );
577 let pre_blossom_halving_interval = network.pre_blossom_halving_interval();
578 let halving_index = i64::from(halving);
579
580 let unscaled_height = halving_index
581 .checked_mul(pre_blossom_halving_interval)
582 .expect("Multiplication overflow: consider reducing the halving interval");
583
584 let pre_blossom_height = unscaled_height
585 .min(blossom_height)
586 .checked_add(slow_start_shift)
587 .expect("Addition overflow: consider reducing the halving interval");
588
589 let post_blossom_height = 0
590 .max(unscaled_height - blossom_height)
591 .checked_mul(i64::from(BLOSSOM_POW_TARGET_SPACING_RATIO))
592 .expect("Multiplication overflow: consider reducing the halving interval")
593 .checked_add(slow_start_shift)
594 .expect("Addition overflow: consider reducing the halving interval");
595
596 let height = pre_blossom_height
597 .checked_add(post_blossom_height)
598 .expect("Addition overflow: consider reducing the halving interval");
599
600 let height = u32::try_from(height).ok()?;
601 height.try_into().ok()
602}