use zcash_pool_migration::denomination::{DENOM_CAP, MAX_RESIDUAL_VALUE};
use zcash_pool_migration::scheduling::AnchorBucketInterval;
use crate::config::ChainType;
use super::split::{CANONICAL_PART_FEE, SWEEP_MIN};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MigrationParams {
pub version: u32,
pub denominations: Vec<u64>,
pub denom_cap: u64,
pub max_residual_value: u64,
pub sweep_min: u64,
pub bucket_modulus: u32,
pub k_max: u32,
pub target_sessions: u32,
pub max_actions_per_split_tx: usize,
pub part_fee: u64,
}
fn one_two_five_ladder(cap: u64, floor: u64) -> Vec<u64> {
let mut ladder = Vec::new();
let mut power = 1u64;
loop {
for n in [1u64, 2, 5] {
let Some(value) = n.checked_mul(power) else {
continue;
};
if (floor..=cap).contains(&value) {
ladder.push(value);
}
}
match power.checked_mul(10) {
Some(next) if power <= cap => power = next,
_ => break,
}
}
ladder.sort_unstable_by(|a, b| b.cmp(a));
ladder
}
impl MigrationParams {
pub fn provisional(_chain: ChainType) -> Self {
let denom_cap = u64::from(DENOM_CAP);
let max_residual_value = u64::from(MAX_RESIDUAL_VALUE);
MigrationParams {
version: 2,
denominations: one_two_five_ladder(denom_cap, max_residual_value),
denom_cap,
max_residual_value,
sweep_min: SWEEP_MIN,
bucket_modulus: AnchorBucketInterval::ZIP_318.block_count().get(),
k_max: 8,
target_sessions: 6,
max_actions_per_split_tx: 16,
part_fee: CANONICAL_PART_FEE,
}
}
pub fn params_hash(&self) -> [u8; 32] {
let mut hasher = blake2b_simd::Params::new()
.hash_length(32)
.personal(b"ZingoMigParamsV0")
.to_state();
hasher.update(&self.version.to_le_bytes());
hasher.update(&(self.denominations.len() as u64).to_le_bytes());
for denomination in &self.denominations {
hasher.update(&denomination.to_le_bytes());
}
hasher.update(&self.denom_cap.to_le_bytes());
hasher.update(&self.max_residual_value.to_le_bytes());
hasher.update(&self.sweep_min.to_le_bytes());
hasher.update(&self.bucket_modulus.to_le_bytes());
hasher.update(&self.k_max.to_le_bytes());
hasher.update(&self.target_sessions.to_le_bytes());
hasher.update(&(self.max_actions_per_split_tx as u64).to_le_bytes());
hasher.update(&self.part_fee.to_le_bytes());
hasher
.finalize()
.as_bytes()
.try_into()
.expect("hash length is 32")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn provisional_denominations_are_capped_and_floored() {
let params = MigrationParams::provisional(ChainType::Mainnet);
assert_eq!(params.denominations.first(), Some(¶ms.denom_cap));
assert_eq!(
params.denominations.last(),
Some(¶ms.max_residual_value)
);
assert!(params.denominations.windows(2).all(|w| w[0] > w[1]));
}
#[test]
fn params_hash_is_stable() {
let params = MigrationParams::provisional(ChainType::Mainnet);
let hash = params.params_hash();
assert_eq!(hash, params.params_hash());
let mut altered = MigrationParams::provisional(ChainType::Mainnet);
altered.bucket_modulus += 1;
assert_ne!(hash, altered.params_hash());
}
}