contract
fabler's on-chain program, in full and verbatim.
// fabler / program
// the on-chain rails. the validator's inflation and block-revenue commission
// are routed into a collector the program owns. sol that lands there is folded
// into a reward-per-token index, a share retained into a locked liquidity
// floor, and the rest claimed by holders as arithmetic. nothing here trusts a
// number it did not read on chain.
use anchor_lang::prelude::*;
use anchor_spl::token_2022::Token2022;
use anchor_spl::token_interface::Mint;
declare_id!("fab1er11111111111111111111111111111111BANK");
pub const PRECISION: u128 = 1_000_000_000_000;
pub const BPS_DENOMINATOR: u64 = 10_000;
pub const STATE_SEED: &[u8] = b"fabler_state";
pub const COLLECTOR_SEED: &[u8] = b"collector";
pub const HOLDER_SEED: &[u8] = b"holder";
#[program]
pub mod fabler {
use super::*;
// stand fabler up. bind the mint, set the retained share, and open the
// collector the validator's commission is routed into. the index and the
// floor both start at zero.
pub fn initialize(ctx: Context<Initialize>, retain_bps: u16) -> Result<()> {
require!((retain_bps as u64) <= BPS_DENOMINATOR, FablerError::ShareOutOfRange);
let s = &mut ctx.accounts.state;
s.authority = ctx.accounts.authority.key();
s.mint = ctx.accounts.mint.key();
s.index = 0;
s.total_held = 0;
s.floor = 0;
s.retain_bps = retain_bps;
s.last_seen = 0;
s.bump = ctx.bumps.state;
Ok(())
}
// fold whatever the runtime deposited into the collector since the last
// sync into the reward index. inflation lands at the turn of an epoch,
// block revenue lands each block, neither is signalled, so the program
// reads the collector balance and takes the difference. a share is retained
// to the floor, the rest raises the index and is owed to every holder at
// once. anyone may call this, it moves nothing to the caller.
pub fn sync(ctx: Context<Sync>) -> Result<()> {
let s = &mut ctx.accounts.state;
let balance = ctx.accounts.collector.lamports();
let landed = balance.saturating_sub(s.last_seen);
s.last_seen = balance;
if landed == 0 || s.total_held == 0 {
return Ok(());
}
let retained = (landed as u128)
.checked_mul(s.retain_bps as u128).unwrap()
.checked_div(BPS_DENOMINATOR as u128).unwrap() as u64;
let cast = landed.checked_sub(retained).unwrap();
// retain to the floor, which only ever rises.
retain_to_floor(&ctx.accounts, retained)?;
s.floor = s.floor.checked_add(retained).unwrap();
// raise the index. this credits every holder in proportion at once.
let delta = (cast as u128)
.checked_mul(PRECISION).unwrap()
.checked_div(s.total_held as u128).unwrap();
s.index = s.index.checked_add(delta).unwrap();
emit!(Cast { landed, cast, retained, index: s.index });
Ok(())
}
// claim a holder's share. what they are owed is their balance times how far
// the index has moved since they last settled. the program pays it from the
// collector and marks them current. someone who arrived after a deposit
// does not share in it.
pub fn claim(ctx: Context<Claim>) -> Result<()> {
let s = &ctx.accounts.state;
let h = &mut ctx.accounts.holder;
let owed = (h.held as u128)
.checked_mul((s.index - h.debt) as u128).unwrap()
.checked_div(PRECISION).unwrap() as u64;
if owed > 0 {
pay_from_collector(&ctx.accounts, owed)?;
}
h.debt = s.index; // mark current at the index
emit!(Claimed { owner: h.owner, owed });
Ok(())
}
}
// helpers. retain_to_floor moves the retained share into locked liquidity the
// program owns, pay_from_collector sends a claim out of the collector. their
// bodies are elided in this listing.
fn retain_to_floor(_a: &Sync, _amt: u64) -> Result<()> { Ok(()) }
fn pay_from_collector(_a: &Claim, _amt: u64) -> Result<()> { Ok(()) }
#[account]
#[derive(InitSpace)]
pub struct FablerState {
pub authority: Pubkey,
pub mint: Pubkey,
pub index: u128, // reward per token, scaled by PRECISION
pub total_held: u64, // supply that shares the cast
pub floor: u64, // retained, locked, only rises
pub retain_bps: u16,
pub last_seen: u64, // collector balance at the last sync
pub bump: u8,
}
#[account]
#[derive(InitSpace)]
pub struct Holder {
pub owner: Pubkey,
pub held: u64, // token balance sharing the cast
pub debt: u128, // index as it stood when last settled
pub bump: u8,
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub authority: Signer<'info>,
#[account(init, payer = authority, space = 8 + FablerState::INIT_SPACE, seeds = [STATE_SEED], bump)]
pub state: Account<'info, FablerState>,
#[account(mut)]
pub mint: InterfaceAccount<'info, Mint>,
pub token_program: Program<'info, Token2022>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts)]
pub struct Sync<'info> {
#[account(mut, seeds = [STATE_SEED], bump = state.bump)]
pub state: Account<'info, FablerState>,
// the collector the validator's commission is routed into. system-owned,
// rent-exempt, program-controlled. the runtime deposits into it directly.
#[account(mut, seeds = [COLLECTOR_SEED], bump)]
pub collector: SystemAccount<'info>,
}
#[derive(Accounts)]
pub struct Claim<'info> {
#[account(mut, seeds = [STATE_SEED], bump = state.bump)]
pub state: Account<'info, FablerState>,
#[account(mut, seeds = [HOLDER_SEED, holder.owner.as_ref()], bump = holder.bump)]
pub holder: Account<'info, Holder>,
#[account(mut, seeds = [COLLECTOR_SEED], bump)]
pub collector: SystemAccount<'info>,
}
#[event]
pub struct Cast { pub landed: u64, pub cast: u64, pub retained: u64, pub index: u128 }
#[event]
pub struct Claimed { pub owner: Pubkey, pub owed: u64 }
#[error_code]
pub enum FablerError {
#[msg("the retained share is outside the allowed range")]
ShareOutOfRange,
}