use serde::{Deserialize, Serialize};
use thiserror::Error;
pub const ADDRESS_SIZE: usize = 12;
pub const HASH_SIZE: usize = 32;
pub const SLOT_SIZE: usize = 64;
pub const PULSE_INTERVAL_MS: u64 = 1500;
pub const DEFAULT_LADDER_HEIGHT: usize = 100_000;
pub const ROLE_SHUFFLE_PULSES: u64 = 4800;
pub const DIAMOND_REWARD_PERC: u64 = 60; pub const GOLD_REWARD_PERC: u64 = 30; pub const REGULAR_REWARD_PERC: u64 = 10;
pub const GOLD_TO_DIAMOND_RATIO: usize = 6;
pub const REGULAR_TO_GOLD_RATIO: usize = 6;
pub const COIN_DECIMALS: u32 = 8;
pub const ONE_YD: u64 = 100_000_000;
pub const MAX_SUPPLY: u64 = 19_000_000 * ONE_YD;
pub const HALVING_PULSES: u64 = 84_000_000;
pub type Address = [u8; ADDRESS_SIZE];
pub type Hash = [u8; HASH_SIZE];
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Slot {
pub user_address: Address,
pub pulse_id: u32, pub notepad_crc: u32, pub ladder_id: u32,
pub step_number: u32,
pub balance: u64,
pub last_hash: Hash, }
impl Slot {
pub fn new(user_address: Address, ladder_id: u32, step_number: u32, last_hash: Hash) -> Self {
Self {
user_address,
pulse_id: 1, notepad_crc: 0,
ladder_id,
step_number,
balance: 0,
last_hash,
}
}
pub fn to_bytes(&self) -> Vec<u8> {
bincode::serialize(self).unwrap_or_default()
}
pub fn from_bytes(bytes: &[u8]) -> Self {
bincode::deserialize(bytes).unwrap()
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SlotKey {
pub user_address: Address,
pub ladder_id: u32,
pub last_hash: Hash,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Transaction {
pub timestamp: u32,
pub from: Address,
pub to: Address,
pub amount: u64,
pub proof: Hash,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct HistoryRecord {
pub counterparty: Address,
pub timestamp: u32,
pub amount: u64,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct NotepadEntry {
pub pulse_id: u32, pub final_blake3: Hash, pub notepad_crc: u32, }
#[derive(Error, Debug, Clone, Serialize, Deserialize)]
pub enum YedadError {
#[error("Account not found")]
AccountNotFound,
#[error("Insufficient balance")]
InsufficientBalance,
#[error("Invalid proof")]
InvalidProof,
#[error("Already exists")]
AlreadyExists,
#[error("Ladder exhausted")]
LadderExhausted,
#[error("Arithmetic overflow")]
ArithmeticOverflow,
#[error("IO error: {0}")]
Io(String),
}
pub fn aggregate_hashes(hashes: &[Hash]) -> Hash {
let mut hasher = blake3::Hasher::new();
for h in hashes {
hasher.update(h);
}
*hasher.finalize().as_bytes()
}
pub fn calculate_interlinked_crc(old_crc: u32, current_calc_hash: &Hash) -> u32 {
use crc32fast::Hasher as CrcHasher;
let mut current_hasher = CrcHasher::new();
current_hasher.update(current_calc_hash);
let current_calc_crc = current_hasher.finalize();
let mut combined = [0u8; 8];
combined[0..4].copy_from_slice(&old_crc.to_be_bytes());
combined[4..8].copy_from_slice(¤t_calc_crc.to_be_bytes());
let mut final_hasher = CrcHasher::new();
final_hasher.update(&combined);
final_hasher.finalize()
}
pub fn calculate_emergency_vote_score(slot_address: &Address, pulse_num: u64) -> Hash {
let mut pulse_hasher = blake3::Hasher::new();
pulse_hasher.update(&pulse_num.to_le_bytes());
let pulse_hash = pulse_hasher.finalize();
let mut final_hasher = blake3::Hasher::new();
final_hasher.update(slot_address);
final_hasher.update(pulse_hash.as_bytes());
*final_hasher.finalize().as_bytes()
}
pub fn address_to_string(address: &Address) -> String {
hex::encode(address)
}
pub fn string_to_address(s: &str) -> Result<Address, hex::FromHexError> {
let bytes = hex::decode(s)?;
let mut addr = [0u8; ADDRESS_SIZE];
addr.copy_from_slice(&bytes);
Ok(addr)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_slot_serialization() {
let addr = [1u8; 12];
let last_hash = [2u8; 32];
let slot = Slot::new(addr, 1, DEFAULT_LADDER_HEIGHT as u32, last_hash);
let bytes = slot.to_bytes();
let restored = Slot::from_bytes(&bytes);
assert_eq!(slot.user_address, restored.user_address);
assert_eq!(slot.pulse_id, restored.pulse_id);
assert_eq!(slot.last_hash, restored.last_hash);
assert_eq!(slot.balance, restored.balance);
}
#[test]
fn test_blake3_aggregation() {
let h1 = [1u8; 32];
let h2 = [2u8; 32];
let agg1 = aggregate_hashes(&[h1, h2]);
let agg2 = aggregate_hashes(&[h1, h2]);
assert_eq!(agg1, agg2);
}
#[test]
fn test_interlinked_crc() {
let old_crc = 12345u32;
let mock_hash = [5u8; 32];
let crc1 = calculate_interlinked_crc(old_crc, &mock_hash);
let crc2 = calculate_interlinked_crc(old_crc, &mock_hash);
assert_eq!(crc1, crc2);
}
#[test]
fn test_emergency_vote_logic() {
let addr = [1u8; 12];
let score1 = calculate_emergency_vote_score(&addr, 100);
let score2 = calculate_emergency_vote_score(&addr, 100);
assert_eq!(score1, score2);
}
}