use std::{convert::TryFrom, iter::once};
use rand::prelude::SliceRandom;
use tari_common::configuration::Network;
use tari_common_types::types::{
CompressedCommitment,
CompressedPublicKey,
CompressedSignature,
PrivateKey,
UncompressedPublicKey,
UncompressedSignature,
};
use tari_crypto::keys::SecretKey as SkTrait;
use tari_script::{ExecutionStack, script};
use tari_transaction_components::{
key_manager::{KeyManager, SecretTransactionKeyManagerInterface, TransactionKeyManagerInterface},
tari_amount::{MicroMinotari, Minotari},
transaction_components::{
CoinBaseExtra,
KernelFeatures,
MemoField,
OutputFeatures,
OutputFeaturesVersion,
OutputType,
RangeProofType,
TransactionKernel,
TransactionKernelVersion,
TransactionOutput,
TransactionOutputVersion,
WalletOutputBuilder,
one_sided::public_key_to_output_encryption_key,
},
};
use tari_utilities::ByteArray;
pub const BLOCKS_PER_DAY: u64 = 24 * 60 / 2;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct UnlockSchedule {
pub network_rewards: Apportionment,
pub protocol: Apportionment,
pub community: Apportionment,
pub contributors: Apportionment,
pub participants: Apportionment,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Apportionment {
pub beneficiary: String,
pub percentage: u64,
pub tokens_amount: Minotari,
pub schedule: Option<ReleaseCadence>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ReleaseCadence {
pub initial_lockup_days: u64,
pub monthly_fraction_denominator: u64,
pub upfront_release: Vec<ReleaseStrategy>,
pub expected_payout_period_blocks: u64,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum ReleaseStrategy {
Proportional(ProportionalRelease),
Custom(Vec<CustomRelease>),
FromCadence(Vec<CadenceRelease>),
}
impl Default for ReleaseStrategy {
fn default() -> Self {
Self::Proportional(ProportionalRelease::default())
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct ProportionalRelease {
pub percentage: u64,
pub number_of_tokens: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CustomRelease {
pub value: Minotari,
pub maturity: u64,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CadenceRelease {
pub value: Minotari,
pub taken_from_period: u64,
pub maturity: u64,
}
fn get_expected_payout_grace_period_blocks(network: Network) -> u64 {
match network {
Network::MainNet => {
BLOCKS_PER_DAY * 30 * 6 },
_ => {
BLOCKS_PER_DAY },
}
}
pub fn pre_mine_spendable_at_height(height: u64, network: Network) -> Result<MicroMinotari, String> {
let pre_mine_items = get_pre_mine_items(network)?;
Ok(pre_mine_items
.iter()
.filter(|v| v.original_maturity <= height)
.map(|v| v.value)
.sum())
}
pub fn get_tokenomics_pre_mine_unlock_schedule(network: Network) -> UnlockSchedule {
UnlockSchedule {
network_rewards: Apportionment {
beneficiary: "network_rewards".to_string(),
percentage: 70,
tokens_amount: 14_700_000_000.into(),
schedule: None,
},
protocol: Apportionment {
beneficiary: "protocol".to_string(),
percentage: 9,
tokens_amount: 1_890_000_000.into(),
schedule: Some(ReleaseCadence {
initial_lockup_days: 180,
monthly_fraction_denominator: 48,
upfront_release: vec![
ReleaseStrategy::Proportional(ProportionalRelease {
percentage: 40,
number_of_tokens: 20,
}),
ReleaseStrategy::Custom({
vec![
CustomRelease {
value: 1.into(),
maturity: 0,
},
CustomRelease {
value: 1.into(),
maturity: 0,
},
CustomRelease {
value: 1.into(),
maturity: 129_600,
},
CustomRelease {
value: 1.into(),
maturity: 129_600,
},
]
}),
],
expected_payout_period_blocks: get_expected_payout_grace_period_blocks(network),
}),
},
community: Apportionment {
beneficiary: "community".to_string(),
percentage: 5,
tokens_amount: 1_050_000_000.into(),
schedule: Some(ReleaseCadence {
initial_lockup_days: 180,
monthly_fraction_denominator: 12,
upfront_release: vec![],
expected_payout_period_blocks: get_expected_payout_grace_period_blocks(network),
}),
},
contributors: Apportionment {
beneficiary: "contributors".to_string(),
percentage: 4,
tokens_amount: 840_000_000.into(),
schedule: Some(ReleaseCadence {
initial_lockup_days: 365,
monthly_fraction_denominator: 60,
upfront_release: contributors_upfront_release(),
expected_payout_period_blocks: get_expected_payout_grace_period_blocks(network),
}),
},
participants: Apportionment {
beneficiary: "participants".to_string(),
percentage: 12,
tokens_amount: 2_520_000_000.into(),
schedule: Some(ReleaseCadence {
initial_lockup_days: 365,
monthly_fraction_denominator: 24,
upfront_release: vec![],
expected_payout_period_blocks: get_expected_payout_grace_period_blocks(network),
}),
},
}
}
#[rustfmt::skip]
#[allow(clippy::too_many_lines)]
fn contributors_upfront_release() -> Vec<ReleaseStrategy> {
vec![
ReleaseStrategy::FromCadence({
vec![
CadenceRelease { value: 809_645.into(), taken_from_period: 0, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 1, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 2, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 3, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 4, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 5, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 6, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 7, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 8, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 9, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 10, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 11, maturity: 0 },
CadenceRelease { value: 809_645.into(), taken_from_period: 12, maturity: 0 },
]
}),
ReleaseStrategy::FromCadence({
vec![
CadenceRelease { value: 1_260_000.into(), taken_from_period: 0, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 1, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 2, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 3, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 4, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 5, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 6, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 7, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 8, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 9, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 10, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 11, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 12, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 13, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 14, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 15, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 16, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 17, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 18, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 19, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 20, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 21, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 22, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 23, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 24, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 25, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 26, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 27, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 28, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 29, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 30, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 31, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 32, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 33, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 34, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 35, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 36, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 37, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 38, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 39, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 40, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 41, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 42, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 43, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 44, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 45, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 46, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 47, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 48, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 49, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 50, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 51, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 52, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 53, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 54, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 55, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 56, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 57, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 58, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 59, maturity: 0 },
]
}),
ReleaseStrategy::FromCadence({
vec![
CadenceRelease { value: 1_260_000.into(), taken_from_period: 0, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 1, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 2, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 3, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 4, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 5, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 6, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 7, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 8, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 9, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 10, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 11, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 12, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 13, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 14, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 15, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 16, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 17, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 18, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 19, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 20, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 21, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 22, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 23, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 24, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 25, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 26, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 27, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 28, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 29, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 30, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 31, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 32, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 33, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 34, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 35, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 36, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 37, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 38, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 39, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 40, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 41, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 42, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 43, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 44, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 45, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 46, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 47, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 48, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 49, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 50, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 51, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 52, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 53, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 54, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 55, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 56, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 57, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 58, maturity: 0 },
CadenceRelease { value: 1_260_000.into(), taken_from_period: 59, maturity: 0 },
]
}),
ReleaseStrategy::FromCadence({
vec![
CadenceRelease { value: 840_000.into(), taken_from_period: 0, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 1, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 2, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 3, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 4, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 5, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 6, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 7, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 8, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 9, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 10, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 11, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 12, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 13, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 14, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 15, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 16, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 17, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 18, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 19, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 20, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 21, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 22, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 23, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 24, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 25, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 26, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 27, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 28, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 29, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 30, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 31, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 32, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 33, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 34, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 35, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 36, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 37, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 38, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 39, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 40, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 41, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 42, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 43, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 44, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 45, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 46, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 47, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 48, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 49, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 50, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 51, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 52, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 53, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 54, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 55, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 56, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 57, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 58, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 59, maturity: 0 },
]
}),
ReleaseStrategy::FromCadence({
vec![
CadenceRelease { value: 840_000.into(), taken_from_period: 0, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 1, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 2, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 3, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 4, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 5, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 6, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 7, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 8, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 9, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 10, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 11, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 12, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 13, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 14, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 15, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 16, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 17, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 18, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 19, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 20, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 21, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 22, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 23, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 24, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 25, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 26, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 27, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 28, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 29, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 30, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 31, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 32, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 33, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 34, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 35, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 36, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 37, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 38, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 39, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 40, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 41, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 42, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 43, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 44, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 45, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 46, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 47, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 48, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 49, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 50, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 51, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 52, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 53, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 54, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 55, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 56, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 57, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 58, maturity: 0 },
CadenceRelease { value: 840_000.into(), taken_from_period: 59, maturity: 0 },
]
}),
]
}
#[derive(Debug)]
pub struct PreMineItem {
pub value: MicroMinotari,
pub maturity: u64,
pub original_maturity: u64,
pub fail_safe_height: u64,
pub beneficiary: String,
}
pub fn get_pre_mine_value(network: Network) -> Result<MicroMinotari, String> {
let schedule = get_tokenomics_pre_mine_unlock_schedule(network);
let pre_mine_items = create_pre_mine_output_values(schedule.clone())?;
Ok(pre_mine_items.iter().map(|item| item.value).sum::<MicroMinotari>())
}
struct CadenceItem {
taken_from_period: u64,
value: Minotari,
}
#[allow(clippy::too_many_lines)]
pub fn create_pre_mine_output_values(schedule: UnlockSchedule) -> Result<Vec<PreMineItem>, String> {
let mut values_with_maturity = Vec::new();
let days_per_month = 365.25 / 12f64;
#[allow(clippy::cast_possible_truncation)]
let blocks_per_month = (days_per_month * BLOCKS_PER_DAY as f64) as u64;
for apportionment in &[
&schedule.network_rewards,
&schedule.protocol,
&schedule.community,
&schedule.contributors,
&schedule.participants,
] {
if let Some(schedule) = apportionment.schedule.as_ref() {
let mut tokens_value = apportionment.tokens_amount.uT().as_u64();
let mut early_payout = Vec::new();
for item in &schedule.upfront_release {
match item {
ReleaseStrategy::Proportional(upfront_release) => {
if upfront_release.percentage > 100 {
return Err(format!(
"Upfront percentage must be less than or equal to 100 in {apportionment:?}"
));
}
if apportionment
.tokens_amount
.uT()
.as_u64()
.checked_mul(upfront_release.percentage)
.is_none()
{
return Err(format!("Minotari calculation overflow in {apportionment:?}"));
}
if upfront_release.percentage > 0 {
let upfront_tokens = tokens_value * upfront_release.percentage / 100;
tokens_value -= upfront_tokens;
let value_per_round = upfront_tokens / upfront_release.number_of_tokens;
let mut assigned_tokens = 0;
for _ in 0..upfront_release.number_of_tokens - 1 {
values_with_maturity.push(PreMineItem {
value: MicroMinotari::from(value_per_round),
maturity: 0,
original_maturity: 0,
fail_safe_height: schedule.expected_payout_period_blocks,
beneficiary: apportionment.beneficiary.clone(),
});
assigned_tokens += value_per_round;
}
values_with_maturity.push(PreMineItem {
value: MicroMinotari::from(upfront_tokens - assigned_tokens),
maturity: 0,
original_maturity: 0,
fail_safe_height: schedule.expected_payout_period_blocks,
beneficiary: apportionment.beneficiary.clone(),
});
}
},
ReleaseStrategy::Custom(upfront_release) => {
for release in upfront_release {
tokens_value -= release.value.uT().as_u64();
values_with_maturity.push(PreMineItem {
value: release.value.uT(),
maturity: release.maturity,
original_maturity: release.maturity,
fail_safe_height: release.maturity + schedule.expected_payout_period_blocks,
beneficiary: apportionment.beneficiary.clone(),
});
}
},
ReleaseStrategy::FromCadence(upfront_release) => {
for release in upfront_release {
early_payout.push(CadenceItem {
taken_from_period: release.taken_from_period,
value: release.value,
});
let original_maturity = schedule.initial_lockup_days * BLOCKS_PER_DAY +
release.taken_from_period * blocks_per_month;
values_with_maturity.push(PreMineItem {
value: release.value.uT(),
maturity: release.maturity,
original_maturity,
fail_safe_height: original_maturity + schedule.expected_payout_period_blocks,
beneficiary: apportionment.beneficiary.clone(),
});
}
},
}
}
early_payout.sort_by_key(|x| x.taken_from_period);
let mut periods = early_payout
.iter()
.map(|item| item.taken_from_period)
.collect::<Vec<_>>();
periods.dedup();
let mut early_payouts_summed = Vec::with_capacity(periods.len());
for period in periods {
let period_value: Minotari = MicroMinotari::from(
early_payout
.iter()
.filter(|item| item.taken_from_period == period)
.map(|item| item.value.uT().as_u64())
.sum::<u64>(),
)
.into();
early_payouts_summed.push(CadenceItem {
taken_from_period: period,
value: period_value,
});
}
let monthly_tokens = tokens_value / schedule.monthly_fraction_denominator;
let mut total_tokens = 0;
let mut maturity = 0;
for i in 0..schedule.monthly_fraction_denominator - 1 {
total_tokens += monthly_tokens;
maturity = schedule.initial_lockup_days * BLOCKS_PER_DAY + i * blocks_per_month;
let adjusted_monthly_tokens =
if let Some(payout) = early_payouts_summed.iter().find(|item| item.taken_from_period == i) {
if payout.value.uT().as_u64() >= monthly_tokens {
return Err(format!(
"upfront 'FromCadence' payout exceeds allocated monthly payout {}, allocated: {}, \
early payout {}",
i,
MicroMinotari::from(monthly_tokens),
payout.value.uT()
));
}
monthly_tokens - payout.value.uT().as_u64()
} else {
monthly_tokens
};
values_with_maturity.push(PreMineItem {
value: MicroMinotari::from(adjusted_monthly_tokens),
maturity,
original_maturity: maturity,
fail_safe_height: maturity + schedule.expected_payout_period_blocks,
beneficiary: apportionment.beneficiary.clone(),
});
}
let last_tokens = tokens_value - total_tokens;
let adjusted_last_tokens = if let Some(payout) = early_payouts_summed
.iter()
.find(|item| item.taken_from_period == schedule.monthly_fraction_denominator - 1)
{
if payout.value.uT().as_u64() >= last_tokens {
return Err(format!(
"upfront 'FromCadence' payout exceeds allocated monthly payout {}, allocated: {}, early \
payout {}",
schedule.monthly_fraction_denominator - 1,
MicroMinotari::from(last_tokens),
payout.value.uT()
));
}
last_tokens - payout.value.uT().as_u64()
} else {
last_tokens
};
maturity += blocks_per_month;
values_with_maturity.push(PreMineItem {
value: MicroMinotari::from(adjusted_last_tokens),
maturity,
original_maturity: maturity,
fail_safe_height: maturity + schedule.expected_payout_period_blocks,
beneficiary: apportionment.beneficiary.clone(),
});
}
}
Ok(values_with_maturity)
}
pub fn get_pre_mine_items(network: Network) -> Result<Vec<PreMineItem>, String> {
let schedule = get_tokenomics_pre_mine_unlock_schedule(network);
create_pre_mine_output_values(schedule)
}
fn get_signature_threshold(number_of_keys: usize) -> Result<u8, String> {
if number_of_keys < 2 {
return Err("Invalid number of parties, must be > 1".to_string());
}
u8::try_from(number_of_keys / 2 + 1).map_err(|e| e.to_string())
}
pub fn verify_script_keys_for_index(
index: usize,
script_threshold_keys: &[CompressedPublicKey],
script_backup_key: &CompressedPublicKey,
expected_threshold_keys: &[CompressedPublicKey],
expected_backup_key: &CompressedPublicKey,
) -> Result<(), String> {
let mut all_script_keys = script_threshold_keys
.iter()
.chain(once(script_backup_key))
.cloned()
.collect::<Vec<_>>();
let mut all_expected_keys = expected_threshold_keys
.iter()
.chain(once(expected_backup_key))
.cloned()
.collect::<Vec<_>>();
all_script_keys.sort();
all_expected_keys.sort();
if all_script_keys.len() != all_expected_keys.len() {
return Err(format!(
"Output at index {} script key count mismatch ({} != {})",
index,
all_script_keys.len(),
all_expected_keys.len()
));
}
all_script_keys.dedup();
if all_expected_keys.len() != all_script_keys.len() {
return Err(format!("Output at index {index} script keys not unique"));
}
for (index, (script_key, party_key)) in all_script_keys.iter().zip(all_expected_keys).enumerate() {
if script_key != &party_key {
return Err(format!(
"\nError: Output {index} script key mismatch ({script_key} != {party_key})\n"
));
}
}
Ok(())
}
#[allow(clippy::too_many_lines)]
pub fn create_pre_mine_genesis_block_info(
pre_mine_items: &[PreMineItem],
threshold_spend_keys: &[Vec<CompressedPublicKey>],
backup_spend_keys: &[CompressedPublicKey],
) -> Result<(Vec<TransactionOutput>, TransactionKernel), String> {
let mut outputs = Vec::new();
let mut total_private_key = PrivateKey::default();
for (i, ((item, public_keys), backup_key)) in pre_mine_items
.iter()
.zip(threshold_spend_keys)
.zip(backup_spend_keys)
.enumerate()
{
let signature_threshold = get_signature_threshold(public_keys.len())?;
let mut total_script_key = UncompressedPublicKey::default();
for key in public_keys {
total_script_key = total_script_key + key.to_public_key().map_err(|e| e.to_string())?;
}
let key_manager = KeyManager::new_random().map_err(|e| e.to_string())?;
let view_key = public_key_to_output_encryption_key(&CompressedPublicKey::new_from_pk(total_script_key))
.map_err(|e| e.to_string())?;
let view_key_id = key_manager
.create_encrypted_key(view_key.clone(), None)
.map_err(|e| e.to_string())?;
let address_len = u8::try_from(public_keys.len()).map_err(|e| e.to_string())?;
let (commitment_mask, script_key) = key_manager
.get_next_commitment_mask_and_script_key()
.map_err(|e| e.to_string())?;
total_private_key = total_private_key +
&key_manager
.get_private_key(&commitment_mask.key_id)
.map_err(|e| e.to_string())?;
let commitment = key_manager
.get_commitment(&commitment_mask.key_id, &item.value.into())
.map_err(|e| e.to_string())?;
let mut commitment_bytes = [0u8; 32];
commitment_bytes.clone_from_slice(commitment.as_bytes());
let sender_offset = key_manager.get_random_key(None, None).map_err(|e| e.to_string())?;
let mut public_keys = public_keys.clone();
public_keys.shuffle(&mut rand::rng());
let script = script!(
CheckHeight(item.fail_safe_height) LeZero
IfThen
CheckMultiSigVerifyAggregatePubKey(signature_threshold, address_len, public_keys.clone(), Box::new(commitment_bytes))
Else
PushPubKey(Box::new(backup_key.clone()))
EndIf
).map_err(|e| e.to_string())?;
let output = WalletOutputBuilder::new(item.value, commitment_mask.key_id)
.with_features(OutputFeatures::new(
OutputFeaturesVersion::get_current_version(),
OutputType::Standard,
item.maturity,
CoinBaseExtra::default(),
None,
RangeProofType::RevealedValue,
))
.with_script(script)
.encrypt_data_for_recovery(&key_manager, Some(&view_key_id), MemoField::new_u256(i.into()))
.map_err(|e| e.to_string())?
.with_input_data(ExecutionStack::default())
.with_version(TransactionOutputVersion::get_current_version())
.with_sender_offset_public_key(sender_offset.pub_key)
.with_script_key(script_key.key_id)
.with_minimum_value_promise(item.value)
.sign_metadata_signature(&key_manager, &sender_offset.key_id)
.map_err(|e| e.to_string())?
.try_build(&key_manager)
.map_err(|e| e.to_string())?;
outputs.push(output.to_transaction_output().map_err(|e| e.to_string())?);
}
let r = PrivateKey::random(&mut rand::rng());
let total_public_key = CompressedPublicKey::from_secret_key(&total_private_key);
let e = TransactionKernel::build_kernel_signature_challenge(
TransactionKernelVersion::get_current_version(),
&CompressedPublicKey::from_secret_key(&r),
&total_public_key,
0.into(),
0,
&KernelFeatures::empty(),
&None,
);
let signature = UncompressedSignature::sign_raw_uniform(&total_private_key, r, &e).map_err(|e| e.to_string())?;
let compressed_signature = CompressedSignature::new_from_schnorr(signature);
let excess = CompressedCommitment::from_compressed_key(total_public_key);
let kernel = TransactionKernel::new_current_version(
KernelFeatures::empty(),
0.into(),
0,
excess,
compressed_signature,
None,
);
Ok((outputs, kernel))
}
#[cfg(test)]
mod test {
#![allow(clippy::indexing_slicing)]
use std::{fs, fs::File, io::Write, ops::Deref};
use tari_common::configuration::Network;
use tari_common_types::{tari_address::TariAddress, types::CompressedPublicKey};
use tari_script::{Opcode, Opcode::CheckHeight};
use tari_transaction_components::{
consensus::{consensus_constants::MAINNET_PRE_MINE_VALUE, emission::Emission},
tari_amount::{MicroMinotari, Minotari},
transaction_components::{TransactionKernel, TransactionOutput},
};
use crate::{
blocks::pre_mine::{
Apportionment,
BLOCKS_PER_DAY,
CustomRelease,
PreMineItem,
ProportionalRelease,
ReleaseCadence,
ReleaseStrategy,
contributors_upfront_release,
create_pre_mine_genesis_block_info,
create_pre_mine_output_values,
get_expected_payout_grace_period_blocks,
get_pre_mine_items,
get_pre_mine_value,
get_signature_threshold,
get_tokenomics_pre_mine_unlock_schedule,
pre_mine_spendable_at_height,
verify_script_keys_for_index,
},
consensus::BaseNodeConsensusManager,
};
async fn genesis_block_test_info(
pre_mine_items: &[PreMineItem],
) -> (
Vec<TransactionOutput>,
TransactionKernel,
Vec<Vec<CompressedPublicKey>>,
Vec<CompressedPublicKey>,
) {
let threshold_addresses_for_index = [
TariAddress::from_base58(
"f4bYsv3sEMroDGKMMjhgm7cp1jDShdRWQzmV8wZiD6sJPpAEuezkiHtVhn7akK3YqswH5t3sUASW7rbvPSqMBDSCSp",
)
.unwrap(),
TariAddress::from_base58(
"f44jftbpTid23oDsEjTodayvMmudSr3g66R6scTJkB5911ZfJRq32FUJDD4CiQSkAPq574i8pMjqzm5RtzdH3Kuknwz",
)
.unwrap(),
TariAddress::from_base58(
"f4GYN3QVRboH6uwG9oFj3LjmUd4XVd1VDYiT6rNd4gCpZF6pY7iuoCpoajfDfuPynS7kspXU5hKRMWLTP9CRjoe1hZU",
)
.unwrap(),
];
let backup_address_for_index = TariAddress::from_base58(
"f27nBFv1GQBW6SuPPCUhjpLRJm3Y5uJxhbEh5EHkunsgqEi78mvzZ7uH1eEuLoRLWVSAeZTKP1BCQyrjeRJZW2pr6DR",
)
.unwrap();
let threshold_spend_keys_for_index: Vec<_> = threshold_addresses_for_index
.iter()
.map(|address| address.public_spend_key().clone())
.collect();
let mut threshold_spend_keys = Vec::with_capacity(pre_mine_items.len());
let mut backup_spend_keys = Vec::with_capacity(pre_mine_items.len());
for _ in 0..pre_mine_items.len() {
threshold_spend_keys.push(threshold_spend_keys_for_index.clone());
backup_spend_keys.push(backup_address_for_index.public_spend_key().clone());
}
let (outputs, kernel) =
create_pre_mine_genesis_block_info(pre_mine_items, &threshold_spend_keys, &backup_spend_keys).unwrap();
(outputs, kernel, threshold_spend_keys, backup_spend_keys)
}
#[ignore]
#[tokio::test]
async fn print_pre_mine_genesis_block_test_info() {
let schedule = get_tokenomics_pre_mine_unlock_schedule(Network::MainNet);
let pre_mine_items = create_pre_mine_output_values(schedule.clone()).unwrap();
let (outputs, kernel, _, _) = genesis_block_test_info(&pre_mine_items).await;
let base_dir = dirs_next::document_dir().unwrap();
let file_path = base_dir.join("tari_pre_mine").join("create").join("utxos.json");
if let Some(path) = file_path.parent() &&
!path.exists()
{
fs::create_dir_all(path).unwrap();
}
let mut utxo_file = File::create(&file_path).expect("Could not create 'utxos.json'");
for output in outputs {
let utxo_s = serde_json::to_string(&output).unwrap();
utxo_file.write_all(format!("{utxo_s}\n").as_bytes()).unwrap();
}
let kernel = serde_json::to_string(&kernel).unwrap();
let _result = utxo_file.write_all(format!("{kernel}\n").as_bytes());
println!(
"\nOutputs written to: '{}'\n",
fs::canonicalize(&file_path).unwrap().display()
);
}
#[ignore]
#[tokio::test]
async fn print_pre_mine_list() {
let schedule = get_tokenomics_pre_mine_unlock_schedule(Network::MainNet);
let pre_mine_items = create_pre_mine_output_values(schedule.clone()).unwrap();
let base_dir = dirs_next::document_dir().unwrap();
let file_path = base_dir.join("tari_pre_mine").join("create").join("pre_mine_items.csv");
if let Some(path) = file_path.parent() &&
!path.exists()
{
fs::create_dir_all(path).unwrap();
}
let mut file_stream = File::create(&file_path).expect("Could not create 'utxos.json'");
file_stream
.write_all("index,value,maturity,original_maturity,fail_safe_height,beneficiary\n".as_bytes())
.unwrap();
for (index, item) in pre_mine_items.iter().enumerate() {
file_stream
.write_all(
format!(
"{},{},{},{},{},{}\n",
index,
item.value,
item.maturity,
item.original_maturity,
item.fail_safe_height,
item.beneficiary,
)
.as_bytes(),
)
.unwrap();
}
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_get_tokenomics_pre_mine_unlock_schedule() {
for network in [
Network::LocalNet,
Network::MainNet,
Network::Esmeralda,
Network::Igor,
Network::NextNet,
Network::StageNet,
] {
let expected_payout_period_blocks = match network {
Network::MainNet => {
BLOCKS_PER_DAY * 30 * 6 },
_ => {
BLOCKS_PER_DAY },
};
let schedule = get_tokenomics_pre_mine_unlock_schedule(network);
assert_eq!(schedule.network_rewards, Apportionment {
beneficiary: "network_rewards".to_string(),
percentage: 70,
tokens_amount: 14_700_000_000.into(),
schedule: None,
});
assert_eq!(schedule.protocol, Apportionment {
beneficiary: "protocol".to_string(),
percentage: 9,
tokens_amount: 1_890_000_000.into(),
schedule: Some(ReleaseCadence {
initial_lockup_days: 180,
monthly_fraction_denominator: 48,
upfront_release: vec![
ReleaseStrategy::Proportional(ProportionalRelease {
percentage: 40,
number_of_tokens: 20,
}),
ReleaseStrategy::Custom({
vec![
CustomRelease {
value: 1.into(),
maturity: 0,
},
CustomRelease {
value: 1.into(),
maturity: 0,
},
CustomRelease {
value: 1.into(),
maturity: 129_600,
},
CustomRelease {
value: 1.into(),
maturity: 129_600,
},
]
}),
],
expected_payout_period_blocks,
}),
});
let percentage = if let ReleaseStrategy::Proportional(release) =
&schedule.protocol.schedule.unwrap().upfront_release[0]
{
release.percentage
} else {
panic!("Expected ReleaseStrategy::Proportional");
};
assert_eq!(schedule.protocol.tokens_amount * percentage / 100, 756_000_000.into());
assert_eq!(schedule.community, Apportionment {
beneficiary: "community".to_string(),
percentage: 5,
tokens_amount: 1_050_000_000.into(),
schedule: Some(ReleaseCadence {
initial_lockup_days: 180,
monthly_fraction_denominator: 12,
upfront_release: vec![],
expected_payout_period_blocks,
}),
});
assert_eq!(schedule.contributors, Apportionment {
beneficiary: "contributors".to_string(),
percentage: 4,
tokens_amount: 840_000_000.into(),
schedule: Some(ReleaseCadence {
initial_lockup_days: 365,
monthly_fraction_denominator: 60,
upfront_release: contributors_upfront_release(),
expected_payout_period_blocks,
}),
});
assert_eq!(schedule.participants, Apportionment {
beneficiary: "participants".to_string(),
percentage: 12,
tokens_amount: 2_520_000_000.into(),
schedule: Some(ReleaseCadence {
initial_lockup_days: 365,
monthly_fraction_denominator: 24,
upfront_release: vec![],
expected_payout_period_blocks,
}),
});
assert_eq!(
schedule.participants.percentage +
schedule.contributors.percentage +
schedule.community.percentage +
schedule.protocol.percentage +
schedule.network_rewards.percentage,
100
);
assert_eq!(
schedule.participants.tokens_amount +
schedule.contributors.tokens_amount +
schedule.community.tokens_amount +
schedule.protocol.tokens_amount +
schedule.network_rewards.tokens_amount,
21_000_000_000.into()
);
}
}
#[test]
fn test_create_pre_mine_total_value() {
for network in [
Network::LocalNet,
Network::MainNet,
Network::Esmeralda,
Network::Igor,
Network::NextNet,
Network::StageNet,
] {
let total_pre_mine_value = get_pre_mine_value(network).unwrap();
assert_eq!(total_pre_mine_value, MAINNET_PRE_MINE_VALUE)
}
}
#[test]
fn test_contributors_upfront_release_totals() {
let upfront_release = contributors_upfront_release();
let min_period = upfront_release
.iter()
.filter_map(|strategy| match strategy {
ReleaseStrategy::FromCadence(item) => item.iter().map(|entry| entry.taken_from_period).min(),
_ => None,
})
.min()
.unwrap();
assert_eq!(min_period, 0);
let max_period = upfront_release
.iter()
.filter_map(|strategy| match strategy {
ReleaseStrategy::FromCadence(item) => item.iter().map(|entry| entry.taken_from_period).max(),
_ => None,
})
.max()
.unwrap();
assert_eq!(max_period, 59);
let mut total_value = 0;
for (i, item) in upfront_release.iter().enumerate() {
let tranche_value = match item {
ReleaseStrategy::FromCadence(item) => item.iter().map(|entry| entry.value.uT().as_u64()).sum::<u64>(),
_ => 0,
};
total_value += tranche_value;
match i {
0 => assert_eq!(tranche_value, MicroMinotari::from(Minotari::from(10_525_385)).as_u64()),
1 => assert_eq!(tranche_value, MicroMinotari::from(Minotari::from(75_600_000)).as_u64()),
2 => assert_eq!(tranche_value, MicroMinotari::from(Minotari::from(75_600_000)).as_u64()),
3 => assert_eq!(tranche_value, MicroMinotari::from(Minotari::from(50_400_000)).as_u64()),
4 => assert_eq!(tranche_value, MicroMinotari::from(Minotari::from(50_400_000)).as_u64()),
_ => panic!("Unexpected upfront release strategy"),
}
}
assert_eq!(total_value, MicroMinotari::from(Minotari::from(262_525_385)).as_u64());
for i in 0..60 {
let value_per_period = upfront_release
.iter()
.map(|entry| match entry {
ReleaseStrategy::FromCadence(item) => item
.iter()
.filter(|entry| entry.taken_from_period == i)
.map(|entry| MicroMinotari::from(entry.value).as_u64())
.collect::<Vec<_>>()
.iter()
.sum::<u64>(),
_ => 0,
})
.collect::<Vec<_>>()
.iter()
.sum::<u64>();
if i <= 12 {
assert_eq!(
value_per_period,
MicroMinotari::from(Minotari::from(809_645 + 4_200_000)).as_u64()
);
} else {
assert_eq!(
value_per_period,
MicroMinotari::from(Minotari::from(4_200_000)).as_u64()
);
}
}
}
#[test]
fn test_pre_mine_fail_safe_height() {
for network in [
Network::LocalNet,
Network::MainNet,
Network::Esmeralda,
Network::Igor,
Network::NextNet,
Network::StageNet,
] {
let schedule = get_tokenomics_pre_mine_unlock_schedule(network);
let pre_mine_items = create_pre_mine_output_values(schedule.clone()).unwrap();
for item in pre_mine_items {
assert_eq!(
item.fail_safe_height,
item.original_maturity + get_expected_payout_grace_period_blocks(network)
);
}
}
}
#[test]
fn test_create_pre_mine_output_values() {
let schedule = get_tokenomics_pre_mine_unlock_schedule(Network::default());
let pre_mine_items = create_pre_mine_output_values(schedule.clone()).unwrap();
let total_pre_mine_value = get_pre_mine_value(Network::default()).unwrap();
let total_tokens = schedule.network_rewards.tokens_amount +
schedule.protocol.tokens_amount +
schedule.community.tokens_amount +
schedule.contributors.tokens_amount +
schedule.participants.tokens_amount;
let total_value = MicroMinotari::from(total_tokens);
assert_eq!(
total_pre_mine_value + MicroMinotari::from(schedule.network_rewards.tokens_amount),
total_value
);
let protocol_tokens = pre_mine_items
.iter()
.filter(|item| item.beneficiary == "protocol")
.map(|item| item.value)
.sum::<MicroMinotari>();
assert_eq!(protocol_tokens, MicroMinotari::from(schedule.protocol.tokens_amount));
let protocol_tokens_at_start = pre_mine_items
.iter()
.filter(|item| item.beneficiary == "protocol" && item.maturity == 0)
.map(|item| item.value)
.sum::<MicroMinotari>();
assert_eq!(protocol_tokens_at_start, MicroMinotari::from(756_000_002 * 1_000_000));
let community_tokens_at_start = pre_mine_items
.iter()
.filter(|item| item.beneficiary == "community" && item.maturity == 0)
.map(|item| item.value)
.sum::<MicroMinotari>();
assert_eq!(community_tokens_at_start, MicroMinotari::zero());
let contributors_tokens_at_start = pre_mine_items
.iter()
.filter(|item| item.beneficiary == "contributors" && item.maturity == 0)
.map(|item| item.value)
.sum::<MicroMinotari>();
assert_eq!(
contributors_tokens_at_start,
MicroMinotari::from(262_525_385 * 1_000_000)
);
let participants_tokens_at_start = pre_mine_items
.iter()
.filter(|item| item.beneficiary == "participants" && item.maturity == 0)
.map(|item| item.value)
.sum::<MicroMinotari>();
assert_eq!(participants_tokens_at_start, MicroMinotari::zero());
let all_tokens_at_start = pre_mine_items
.iter()
.filter(|item| item.maturity == 0)
.map(|item| item.value)
.sum::<MicroMinotari>();
assert_eq!(
all_tokens_at_start,
protocol_tokens_at_start +
community_tokens_at_start +
contributors_tokens_at_start +
participants_tokens_at_start
);
let community_tokens = pre_mine_items
.iter()
.filter(|item| item.beneficiary == "community")
.map(|item| item.value)
.sum::<MicroMinotari>();
assert_eq!(community_tokens, MicroMinotari::from(schedule.community.tokens_amount));
let contributors_tokens = pre_mine_items
.iter()
.filter(|item| item.beneficiary == "contributors")
.map(|item| item.value)
.sum::<MicroMinotari>();
assert_eq!(
contributors_tokens,
MicroMinotari::from(schedule.contributors.tokens_amount)
);
let participants_tokens = pre_mine_items
.iter()
.filter(|item| item.beneficiary == "participants")
.map(|item| item.value)
.sum::<MicroMinotari>();
assert_eq!(
participants_tokens,
MicroMinotari::from(schedule.participants.tokens_amount)
);
}
#[ignore]
#[tokio::test]
async fn test_create_genesis_block_info() {
for network in [
Network::LocalNet,
Network::MainNet,
Network::Esmeralda,
Network::Igor,
Network::NextNet,
Network::StageNet,
] {
let schedule = get_tokenomics_pre_mine_unlock_schedule(network);
let pre_mine_items = create_pre_mine_output_values(schedule.clone()).unwrap();
let (outputs, kernel, threshold_spend_keys, backup_spend_keys) =
genesis_block_test_info(&pre_mine_items).await;
assert!(kernel.verify_signature().is_ok());
let grace_period = get_expected_payout_grace_period_blocks(network);
for (index, (output, (pre_mine_item, (threshold_keys, backup_key)))) in outputs
.iter()
.zip(
pre_mine_items
.iter()
.zip(threshold_spend_keys.iter().zip(backup_spend_keys.iter())),
)
.enumerate()
{
let script_height = if let Some(CheckHeight(height)) = output.script.as_slice().first() {
*height
} else {
panic!("Expected CheckHeight opcode in script at index {index}");
};
let script_threshold_keys =
if let Some(Opcode::CheckMultiSigVerifyAggregatePubKey(_n, _m, keys, _msg)) =
output.script.as_slice().get(3)
{
keys.clone()
} else {
panic!("Expected CheckMultiSigVerifyAggregatePubKey opcode in script at index {index}");
};
let script_backup_key = if let Some(Opcode::PushPubKey(key)) = output.script.as_slice().get(5) {
key.deref().clone()
} else {
panic!("Expected PushPubKey opcode in script at index {index}");
};
assert_eq!(script_height, pre_mine_item.original_maturity + grace_period);
assert_eq!(output.features.maturity, pre_mine_item.maturity);
assert!(
verify_script_keys_for_index(
index,
&script_threshold_keys,
&script_backup_key,
threshold_keys,
backup_key
)
.is_ok()
);
}
}
}
#[test]
fn test_get_signature_threshold() {
assert!(get_signature_threshold(0).is_err());
assert!(get_signature_threshold(1).is_err());
assert_eq!(get_signature_threshold(2).unwrap(), 2);
assert_eq!(get_signature_threshold(3).unwrap(), 2);
assert_eq!(get_signature_threshold(4).unwrap(), 3);
assert_eq!(get_signature_threshold(5).unwrap(), 3);
assert_eq!(get_signature_threshold(6).unwrap(), 4);
assert_eq!(get_signature_threshold(7).unwrap(), 4);
assert_eq!(get_signature_threshold(8).unwrap(), 5);
assert_eq!(get_signature_threshold(9).unwrap(), 5);
assert_eq!(get_signature_threshold(10).unwrap(), 6);
assert_eq!(get_signature_threshold(11).unwrap(), 6);
assert_eq!(get_signature_threshold(12).unwrap(), 7);
assert_eq!(get_signature_threshold(13).unwrap(), 7);
assert_eq!(get_signature_threshold(14).unwrap(), 8);
assert_eq!(get_signature_threshold(15).unwrap(), 8);
assert_eq!(get_signature_threshold(16).unwrap(), 9);
assert_eq!(get_signature_threshold(17).unwrap(), 9);
assert_eq!(get_signature_threshold(18).unwrap(), 10);
assert_eq!(get_signature_threshold(19).unwrap(), 10);
assert_eq!(get_signature_threshold(20).unwrap(), 11);
}
#[test]
#[allow(clippy::too_many_lines)]
fn test_total_spendable_supply_at_height() {
let network = Network::MainNet;
let consensus_manager = BaseNodeConsensusManager::builder(network)
.build()
.map_err(|e| e.to_string())
.unwrap();
let emission_schedule = consensus_manager.emission_schedule();
let pre_mine_items = get_pre_mine_items(network).unwrap();
let maturity_tranches = consensus_manager.get_maturity_tranches();
for (height, value) in [
(0, 756000002000000),
(maturity_tranches[0].maturity / 2, 756000002000000),
(maturity_tranches[1].effective_from_height, 816162165168137),
(
maturity_tranches[1].effective_from_height + maturity_tranches[0].maturity / 2,
821165374278621,
),
(
maturity_tranches[1].effective_from_height + maturity_tranches[0].maturity,
828667219912912,
),
(maturity_tranches[2].effective_from_height, 888553971940358),
(
maturity_tranches[2].effective_from_height + maturity_tranches[1].maturity / 2,
892289348766953,
),
(
maturity_tranches[2].effective_from_height + maturity_tranches[1].maturity,
898513007030503,
),
(maturity_tranches[3].effective_from_height, 958961532039375),
(
maturity_tranches[3].effective_from_height + maturity_tranches[2].maturity / 2,
961440742937559,
),
(
maturity_tranches[3].effective_from_height + maturity_tranches[2].maturity,
966397988109264,
),
(180 * BLOCKS_PER_DAY, 2573982676047120),
(180 * BLOCKS_PER_DAY + 2 * 31 * BLOCKS_PER_DAY, 3341352003000927),
(180 * BLOCKS_PER_DAY + 5 * 31 * BLOCKS_PER_DAY, 4453332200422863),
(365 * BLOCKS_PER_DAY, 4925001471171519),
(365 * BLOCKS_PER_DAY + 2 * 31 * BLOCKS_PER_DAY, 5870144522232540),
(365 * BLOCKS_PER_DAY + 5 * 31 * BLOCKS_PER_DAY, 7253103292383049),
] {
let rewards_at_height = emission_schedule
.supply_at_block(height)
.saturating_sub(MAINNET_PRE_MINE_VALUE);
let matured_rewards_at_height = consensus_manager.block_rewards_spendable_at_height(height).unwrap();
if height > 0 {
assert!(rewards_at_height > matured_rewards_at_height);
}
let pre_mine_spendable_supply = pre_mine_spendable_at_height(height, network).unwrap();
assert_eq!(
pre_mine_spendable_supply,
pre_mine_items
.iter()
.filter(|v| v.original_maturity <= height)
.map(|v| v.value)
.sum()
);
let total_spendable_supply = matured_rewards_at_height + pre_mine_spendable_supply;
assert_eq!(total_spendable_supply, MicroMinotari(value));
}
for tranche in [
maturity_tranches[1].clone(),
maturity_tranches[2].clone(),
maturity_tranches[3].clone(),
] {
assert!(
consensus_manager
.block_rewards_spendable_at_height(tranche.effective_from_height + tranche.maturity - 2,)
.unwrap() <
consensus_manager
.block_rewards_spendable_at_height(tranche.effective_from_height + tranche.maturity - 1,)
.unwrap()
);
assert!(
consensus_manager
.block_rewards_spendable_at_height(tranche.effective_from_height + tranche.maturity - 1,)
.unwrap() <
consensus_manager
.block_rewards_spendable_at_height(tranche.effective_from_height + tranche.maturity,)
.unwrap()
);
assert!(
consensus_manager
.block_rewards_spendable_at_height(tranche.effective_from_height + tranche.maturity,)
.unwrap() <
consensus_manager
.block_rewards_spendable_at_height(tranche.effective_from_height + tranche.maturity + 1,)
.unwrap()
);
assert!(
consensus_manager
.block_rewards_spendable_at_height(tranche.effective_from_height + tranche.maturity + 1,)
.unwrap() <
consensus_manager
.block_rewards_spendable_at_height(tranche.effective_from_height + tranche.maturity + 2,)
.unwrap()
);
}
let simple_calc = emission_schedule
.supply_at_block(maturity_tranches[1].effective_from_height - 1)
.saturating_sub(MAINNET_PRE_MINE_VALUE);
assert_eq!(
simple_calc,
consensus_manager
.block_rewards_spendable_at_height(
maturity_tranches[1].effective_from_height - 1 + maturity_tranches[0].maturity,
)
.unwrap()
);
let simple_calc = emission_schedule
.supply_at_block(maturity_tranches[1].effective_from_height)
.saturating_sub(MAINNET_PRE_MINE_VALUE);
assert!(
simple_calc >
consensus_manager
.block_rewards_spendable_at_height(
maturity_tranches[1].effective_from_height + maturity_tranches[1].maturity,
)
.unwrap()
);
}
}