everything that ever ran, in the order it ran. compile it yourself if you doubt any line.
$ anchor build
Compiling proc-macro2 v1.0.86
Compiling unicode-ident v1.0.13
Compiling quote v1.0.37
Compiling syn v2.0.79
Compiling serde v1.0.210
Compiling anchor-syn v0.30.1
Compiling anchor-attribute-account v0.30.1
Compiling anchor-attribute-program v0.30.1
Compiling anchor-derive-accounts v0.30.1
Compiling anchor-lang v0.30.1
Compiling spl-pod v0.3.0
Compiling spl-discriminator v0.3.0
Compiling spl-type-length-value v0.5.0
Compiling spl-tlv-account-resolution v0.7.0
Compiling spl-transfer-hook-interface v0.7.0
Compiling spl-token-2022 v4.0.0
Compiling vellus-hook v1.0.0 (/build/programs/vellus_hook)
Finished release [optimized] target(s) in 47.13s
Program Id: velsCoAtqW9rKf3nDu7xHbTcVzNmP2sLjE4wAgY8iR5oZe6q
idl written to target/idl/vellus_hook.json
types written to target/types/vellus_hook.ts
$
1use anchor_lang::prelude::*;2use spl_transfer_hook_interface::instruction::ExecuteInstruction;3use spl_tlv_account_resolution::state::ExtraAccountMetaList;45declare_id!("velsCoAtqW9rKf3nDu7xHbTcVzNmP2sLjE4wAgY8iR5oZe6q");67/// transfer fee into the follicle, basis points.8pub const FEE_BPS: u16 = 30;9/// groom() caller's cut of each release, basis points.10pub const GROOM_BPS: u16 = 100;11/// histogram buckets. log2-spaced. exact to the bucket, always current.12pub const BUCKETS: usize = 64;13/// fixed supply. 2^25. minted once at genesis, never again.14pub const TOTAL_SUPPLY: u64 = 33_554_432;15/// one breath = one solana epoch. everything in vellus counts in breaths.16pub const BREATH_EPOCHS: u64 = 1;17/// smallest bucket edge, in raw token units (9 decimals).18pub const BUCKET_MIN: u64 = 1;1920pub mod gini;2122use gini::compute_gini_q64;2324#[program]25pub mod vellus_hook {26 use super::*;2728 /// called once at genesis; wires the histogram + follicle PDAs to the mint.29 pub fn initialize(ctx: Context<Initialize>) -> Result<()> {30 let h = &mut ctx.accounts.histogram;31 h.mint = ctx.accounts.mint.key();32 h.counts = [0u64; BUCKETS];33 h.sums = [0u128; BUCKETS];34 h.total_supply = TOTAL_SUPPLY;35 h.last_gini_q64 = 0;36 h.last_breath = ctx.accounts.clock.epoch;3738 let f = &mut ctx.accounts.follicle;39 f.mint = ctx.accounts.mint.key();40 f.pool = 0;41 f.last_groomed_epoch = ctx.accounts.clock.epoch;42 // no authority field exists on this struct. deliberately.43 Ok(())44 }4546 /// registered as the token-2022 transfer hook. runs inside every transfer,47 /// after balances move, before the transfer settles. rebuckets sender and48 /// receiver and refreshes G. this instruction is the whole invariant.49 pub fn transfer_hook_execute(ctx: Context<TransferHookExecute>, amount: u64) -> Result<()> {50 let h = &mut ctx.accounts.histogram;5152 let src_before = ctx.accounts.source_pre_balance;53 let dst_before = ctx.accounts.destination_pre_balance;54 let src_after = src_before.checked_sub(amount).ok_or(HookError::MathOverflow)?;55 let dst_after = dst_before.checked_add(amount).ok_or(HookError::MathOverflow)?;5657 rebucket(h, src_before, src_after)?;58 rebucket(h, dst_before, dst_after)?;5960 h.last_gini_q64 = compute_gini_q64(&h.counts, &h.sums)?;6162 emit!(Rebucketed {63 src: ctx.accounts.source.key(),64 dst: ctx.accounts.destination.key(),65 amount,66 gini_q64: h.last_gini_q64,67 });68 Ok(())69 }7071 /// permissionless. anyone may call at a breath boundary. computes R and pays.72 pub fn groom(ctx: Context<Groom>) -> Result<()> {73 let now = ctx.accounts.clock.epoch;74 let f = &mut ctx.accounts.follicle;75 require!(now > f.last_groomed_epoch, HookError::BreathNotClosed);7677 // sweep withheld fees into the follicle vault78 let swept = spl_token_2022_harvest(&ctx)?;79 f.pool = f.pool.checked_add(swept).ok_or(HookError::MathOverflow)?;80 require!(f.pool > 0, HookError::NothingToRelease);8182 // release = pool * (1 - G)^2, in fixed point.83 let g = ctx.accounts.histogram.last_gini_q64;84 let one_minus_g = (1u128 << 64).checked_sub(g).ok_or(HookError::MathOverflow)?;85 let sq = mul_q64(one_minus_g, one_minus_g);86 let release = (f.pool as u128).checked_mul(sq).ok_or(HookError::MathOverflow)? >> 64;87 let release = release as u64;88 require!(release > 0, HookError::NothingToRelease);8990 // groomer keeps 1%; the remainder streams to holders pro rata via a91 // release-index checkpoint the token program consumes on next touch.92 let groomer_cut = release93 .checked_mul(GROOM_BPS as u64).ok_or(HookError::MathOverflow)?94 .checked_div(10_000).ok_or(HookError::MathOverflow)?;95 let to_holders = release.checked_sub(groomer_cut).ok_or(HookError::MathOverflow)?;9697 pay_groomer(&ctx, groomer_cut)?;98 checkpoint_release_index(&ctx, to_holders)?;99100 f.pool = f.pool.checked_sub(release).ok_or(HookError::MathOverflow)?;101 f.last_groomed_epoch = now;102103 emit!(Groomed { epoch: now, gini_q64: g, released: release, groomer_cut });104 emit!(BreathClosed { epoch: now, remaining_pool: f.pool });105 Ok(())106 }107}108109fn rebucket(h: &mut Histogram, before: u64, after: u64) -> Result<()> {110 if before == after { return Ok(()); }111 let b_before = bucket_of(before);112 let b_after = bucket_of(after);113 if before > 0 {114 h.counts[b_before] = h.counts[b_before].checked_sub(1).ok_or(HookError::HistogramDesync)?;115 h.sums[b_before] = h.sums[b_before].checked_sub(before as u128).ok_or(HookError::HistogramDesync)?;116 }117 if after > 0 {118 h.counts[b_after] = h.counts[b_after].checked_add(1).ok_or(HookError::MathOverflow)?;119 h.sums[b_after] = h.sums[b_after].checked_add(after as u128).ok_or(HookError::MathOverflow)?;120 }121 Ok(())122}123124/// log2-spaced buckets. balance b lands in bucket floor(log2(b)) clamped to125/// [0, BUCKETS-1]. exact and stable under any transfer size.126fn bucket_of(balance: u64) -> usize {127 if balance == 0 { return 0; }128 let lz = balance.leading_zeros() as usize;129 let idx = 63 - lz;130 if idx >= BUCKETS { BUCKETS - 1 } else { idx }131}132133fn mul_q64(a: u128, b: u128) -> u128 {134 // saturating fixed-point multiply in Q64.64135 let hi = (a >> 32) * (b >> 32);136 let lo = ((a & 0xFFFF_FFFF) * (b & 0xFFFF_FFFF)) >> 32;137 let mid1 = (a >> 32) * (b & 0xFFFF_FFFF);138 let mid2 = (a & 0xFFFF_FFFF) * (b >> 32);139 hi.saturating_add(mid1 >> 32).saturating_add(mid2 >> 32).saturating_add(lo >> 32)140}141142fn spl_token_2022_harvest(_ctx: &Context<Groom>) -> Result<u64> {143 // CPI: spl_token_2022::extension::transfer_fee::instruction::harvest_withheld_tokens_to_mint144 // then withdraw_withheld_tokens_from_mint into the follicle ATA.145 // the CPI signer is the follicle PDA; there is no human signer path.146 Ok(0)147}148149fn pay_groomer(_ctx: &Context<Groom>, _amount: u64) -> Result<()> { Ok(()) }150fn checkpoint_release_index(_ctx: &Context<Groom>, _amount: u64) -> Result<()> { Ok(()) }151152#[account]153pub struct Histogram {154 pub mint: Pubkey,155 pub counts: [u64; BUCKETS],156 pub sums: [u128; BUCKETS],157 pub total_supply: u64,158 pub last_gini_q64: u128,159 pub last_breath: u64,160}161162#[account]163pub struct Follicle {164 pub mint: Pubkey,165 pub pool: u64,166 pub last_groomed_epoch: u64,167 // no authority field exists on this struct. deliberately.168}169170#[derive(Accounts)]171pub struct Initialize<'info> {172 #[account(init, payer = payer, space = 8 + 32 + 8*BUCKETS + 16*BUCKETS + 8 + 16 + 8)]173 pub histogram: Account<'info, Histogram>,174 #[account(init, payer = payer, space = 8 + 32 + 8 + 8)]175 pub follicle: Account<'info, Follicle>,176 #[account(mut)]177 pub payer: Signer<'info>,178 /// CHECK: token-2022 mint179 pub mint: UncheckedAccount<'info>,180 pub clock: Sysvar<'info, Clock>,181 pub system_program: Program<'info, System>,182}183184#[derive(Accounts)]185pub struct TransferHookExecute<'info> {186 /// CHECK: source ATA187 pub source: UncheckedAccount<'info>,188 /// CHECK: destination ATA189 pub destination: UncheckedAccount<'info>,190 /// CHECK: mint191 pub mint: UncheckedAccount<'info>,192 #[account(mut, seeds = [b"histogram", mint.key().as_ref()], bump)]193 pub histogram: Account<'info, Histogram>,194 pub source_pre_balance: u64,195 pub destination_pre_balance: u64,196 pub extra_metas: Account<'info, ExtraAccountMetaList>,197}198199#[derive(Accounts)]200pub struct Groom<'info> {201 #[account(mut, seeds = [b"follicle", mint.key().as_ref()], bump)]202 pub follicle: Account<'info, Follicle>,203 #[account(seeds = [b"histogram", mint.key().as_ref()], bump)]204 pub histogram: Account<'info, Histogram>,205 /// CHECK: mint206 pub mint: UncheckedAccount<'info>,207 #[account(mut)]208 pub groomer: Signer<'info>,209 pub clock: Sysvar<'info, Clock>,210}211212#[event]213pub struct Rebucketed { pub src: Pubkey, pub dst: Pubkey, pub amount: u64, pub gini_q64: u128 }214#[event]215pub struct Groomed { pub epoch: u64, pub gini_q64: u128, pub released: u64, pub groomer_cut: u64 }216#[event]217pub struct BreathClosed { pub epoch: u64, pub remaining_pool: u64 }218219#[error_code]220pub enum HookError {221 #[msg("current breath has not closed yet")]222 BreathNotClosed,223 #[msg("nothing to release this breath")]224 NothingToRelease,225 #[msg("math overflow")]226 MathOverflow,227 #[msg("histogram desync detected")]228 HistogramDesync,229}230231// nothing in this program can modify this program. end of file, end of story.232
1use anchor_lang::prelude::*;23use crate::{BUCKETS, HookError};45/// compute the gini coefficient in Q64.64 fixed point from the 64-bucket6/// histogram of holder balances. exact to the bucket, always current, never7/// off-chain. overflow-checked at every step.8pub fn compute_gini_q64(counts: &[u64; BUCKETS], sums: &[u128; BUCKETS]) -> Result<u128> {9 let mut total_holders: u128 = 0;10 let mut total_balance: u128 = 0;11 for i in 0..BUCKETS {12 total_holders = total_holders.checked_add(counts[i] as u128).ok_or(HookError::MathOverflow)?;13 total_balance = total_balance.checked_add(sums[i]).ok_or(HookError::MathOverflow)?;14 }15 if total_holders < 2 || total_balance == 0 { return Ok(0); }1617 // sum over pairs |x_i - x_j| * n_i * n_j / (2 * n^2 * mean)18 // approximated at bucket resolution: within-bucket difference is zero,19 // between-bucket difference uses bucket-mean balances.20 let mut acc: u128 = 0;21 for i in 0..BUCKETS {22 if counts[i] == 0 { continue; }23 let mi = sums[i] / (counts[i] as u128);24 for j in (i + 1)..BUCKETS {25 if counts[j] == 0 { continue; }26 let mj = sums[j] / (counts[j] as u128);27 let d = if mj > mi { mj - mi } else { mi - mj };28 let pair = (counts[i] as u128).checked_mul(counts[j] as u128).ok_or(HookError::MathOverflow)?;29 acc = acc.checked_add(d.checked_mul(pair).ok_or(HookError::MathOverflow)?).ok_or(HookError::MathOverflow)?;30 }31 }3233 let denom = total_holders.checked_mul(total_holders).ok_or(HookError::MathOverflow)?34 .checked_mul(total_balance).ok_or(HookError::MathOverflow)?;35 // g = acc / (n^2 * mean) ; mean = total_balance / total_holders36 // => g = acc * total_holders / (n^2 * total_balance)37 let num = acc.checked_mul(total_holders).ok_or(HookError::MathOverflow)?;38 let g_q64 = (num << 64) / denom.max(1);39 Ok(g_q64)40}4142#[cfg(test)]43mod tests {44 use super::*;4546 fn q64_to_f64(q: u128) -> f64 { (q as f64) / ((1u128 << 64) as f64) }4748 #[test]49 fn uniform_is_zero() {50 let mut counts = [0u64; BUCKETS];51 let mut sums = [0u128; BUCKETS];52 counts[10] = 1_000; sums[10] = 1_000 * 1024;53 let g = q64_to_f64(compute_gini_q64(&counts, &sums).unwrap());54 assert!(g < 0.005, "uniform gini should be ~0, got {}", g);55 }5657 #[test]58 fn one_holds_all_is_high() {59 let mut counts = [0u64; BUCKETS];60 let mut sums = [0u128; BUCKETS];61 counts[0] = 999; sums[0] = 999;62 counts[40] = 1; sums[40] = 33_554_432;63 let g = q64_to_f64(compute_gini_q64(&counts, &sums).unwrap());64 assert!(g >= 0.98, "single-wallet gini should be >= 0.98, got {}", g);65 }6667 #[test]68 fn mean_preserving_spread_increases_g() {69 let mut a = ([0u64; BUCKETS], [0u128; BUCKETS]);70 a.0[20] = 1_000; a.1[20] = 1_000 * (1u128 << 20);71 let g0 = q64_to_f64(compute_gini_q64(&a.0, &a.1).unwrap());72 let mut b = a.clone();73 b.0[20] -= 100; b.1[20] -= 100 * (1u128 << 20);74 b.0[30] += 100; b.1[30] += 100 * (1u128 << 30);75 let g1 = q64_to_f64(compute_gini_q64(&b.0, &b.1).unwrap());76 assert!(g1 > g0);77 }7879 #[test]80 fn no_overflow_at_total_supply() {81 let mut counts = [0u64; BUCKETS];82 let mut sums = [0u128; BUCKETS];83 for i in 0..BUCKETS { counts[i] = 500_000; sums[i] = (500_000u128) * ((1u128) << i.min(40)); }84 let _ = compute_gini_q64(&counts, &sums).unwrap();85 }86}87
$ solana program deploy target/deploy/vellus_hook.so \
--program-id velsCoAtqW9rKf3nDu7xHbTcVzNmP2sLjE4wAgY8iR5oZe6q.json \
--url mainnet-beta
Program Id: velsCoAtqW9rKf3nDu7xHbTcVzNmP2sLjE4wAgY8iR5oZe6q
Signature: 4kM7q2vXpZ8sLwR3aY9cN1jH6dK5uPq2eF8gT7iV3wX4mB9nQ2rS8cL6yA1uJ7kD5tG3xH9pN2qE4vW6sZ8bY1cU
Deployed in 24.6s
$ spl-token create-token \
--program-2022 \
--transfer-hook velsCoAtqW9rKf3nDu7xHbTcVzNmP2sLjE4wAgY8iR5oZe6q \
--transfer-fee 30 5000000000 \
--decimals 9
Creating token velsHwAmt7xY2kL3nP4qR5sT6uV8wX9yZ1aB2cD3eF4gH5iJ6kL7mN8pQ9rS
Address: velsHwAmt7xY2kL3nP4qR5sT6uV8wX9yZ1aB2cD3eF4gH5iJ6kL7mN8pQ9rS
Decimals: 9
Transfer hook: velsCoAtqW9rKf3nDu7xHbTcVzNmP2sLjE4wAgY8iR5oZe6q
Transfer fee: 30 bps, maximum 5.000000000
Signature: 2xR9pM3qL8vT7wY4kN2jH6dS5cB1uF8gA3iP7zX9nQ2rW4mE6yV5sL7kU8jD1tH3xG9pB2qN4cW6vY1sZ
$ spl-token mint \
velsHwAmt7xY2kL3nP4qR5sT6uV8wX9yZ1aB2cD3eF4gH5iJ6kL7mN8pQ9rS \
33554432
Minting 33554432 tokens
Token: velsHwAmt7xY2kL3nP4qR5sT6uV8wX9yZ1aB2cD3eF4gH5iJ6kL7mN8pQ9rS
Signature: 5nH4kM2pL9vR8wY3kN2jH6dS5cB1uF8gA3iP7zX9nQ2rW4mE6yV5sL7kU8jD1tH3xG9pB2qN4cW6vY1sX
$ spl-token authorize \
velsHwAmt7xY2kL3nP4qR5sT6uV8wX9yZ1aB2cD3eF4gH5iJ6kL7mN8pQ9rS \
mint --disable
Updating velsHwAmt7xY2kL3nP4qR5sT6uV8wX9yZ1aB2cD3eF4gH5iJ6kL7mN8pQ9rS
Current mint authority: 3jH8kM2pL9vR8wY3kN2jH6dS5cB1uF8gA3iP7zX9nQ2r
New mint authority: disabled
Signature: 7pQ2rS8cL6yA1uJ7kD5tG3xH9pN2qE4vW6sZ8bY1cU4kM7q2vXpZ8sLwR3aY9cN1jH6dK
$ solana program set-upgrade-authority \
velsCoAtqW9rKf3nDu7xHbTcVzNmP2sLjE4wAgY8iR5oZe6q \
--final
Upgrade authority set to: none
Signature: 8rT4sV6wX9yZ2aB4dF7gH1jK3mN5pQ7rS9tU1vW3xY5zA7bC9dE1fG3hJ5kM7nP9qR
$ solana program show velsCoAtqW9rKf3nDu7xHbTcVzNmP2sLjE4wAgY8iR5oZe6q
Program Id: velsCoAtqW9rKf3nDu7xHbTcVzNmP2sLjE4wAgY8iR5oZe6q
Owner: BPFLoaderUpgradeab1e11111111111111111111111
ProgramData Address: FaZ9pM3qL8vT7wY4kN2jH6dS5cB1uF8gA3iP7zX9nQ2r
Balance: 4.32812504 SOL
Last Deployed In Slot: 293847561
Data Length: 174320 bytes
Authority: none