Skip to main content

tribewarez_staking/
lib.rs

1//! # tribewarez-staking
2//!
3//! PTtC (Power-to-Trust-to-Collaborate) Staking Program for Tribewarez DeFi.
4//!
5//! This crate implements a staking program that allows users to stake PTtC tokens and earn rewards
6//! over time. It supports flexible staking strategies with v0.2.0 introducing tensor network entanglement
7//! for cooperative staking with entropy-based unlock probabilities and efficiency bonuses.
8//!
9//! ## Core Features
10//!
11//! - **Token Staking**: Secure staking of PTtC tokens with time-based rewards
12//! - **Reward Distribution**: Distribute rewards to stakers based on stake amount and duration
13//! - **Staking Pools**: Multiple pools with different reward rates and lock durations
14//! - **Tensor Entanglement**: v0.2.0 feature enabling cooperative staking with quantum-like entanglement
15//! - **Unlock Mechanics**: Entropy-based unlock probabilities for enhanced security
16//!
17//! ## Key Instructions
18//!
19//! - `initialize_pool`: Create a new staking pool with specific parameters
20//! - `stake`: Stake tokens into a pool
21//! - `unstake`: Withdraw staked tokens (subject to lock duration)
22//! - `claim_rewards`: Claim accumulated rewards
23//! - `entangle_stakes`: Link stakes in tensor network for cooperative rewards (v0.2.0)
24//!
25//! ## Events
26//!
27//! This program emits events for stake deposits, withdrawals, reward claims, and pool configuration changes.
28//! See the [`events`] module for detailed event documentation.
29//!
30//! ## Security Considerations
31//!
32//! - Staking locks are enforced via timestamp checks
33//! - Token transfers use SPL Token program via CPI for security
34//! - Admin-only operations are protected via Anchor's access control
35
36use anchor_lang::prelude::*;
37use anchor_spl::token::{self, Token, Transfer};
38
39// Module declarations
40pub mod events;
41pub mod services;
42
43// Re-export services for use in instructions
44use events::{PoolInitialized, PoolUpdated, RewardsClaimed, Staked, Unstaked};
45
46declare_id!("Go2BZRhNLoaVni3QunrKPAXYdHtwZtTXuVspxpdAeDS8");
47
48/// Tribewarez Staking Program
49/// Allows users to stake PTtC tokens and earn rewards over time.
50/// Using native SPL token CPI calls for compatibility.
51///
52/// v0.2.0 includes tensor network entanglement for cooperative staking
53/// with entropy-based unlock probabilities and efficiency bonuses.
54
55#[program]
56pub mod tribewarez_staking {
57    use super::*;
58
59    /// Initialize a new staking pool for a specific token mint (PTtC)
60    pub fn initialize_pool(
61        ctx: Context<InitializePool>,
62        reward_rate: u64,   // Rewards per second per token staked (in basis points)
63        lock_duration: i64, // Minimum lock duration in seconds
64    ) -> Result<()> {
65        let pool = &mut ctx.accounts.staking_pool;
66
67        pool.authority = ctx.accounts.authority.key();
68        pool.token_mint = ctx.accounts.token_mint.key();
69        pool.reward_mint = ctx.accounts.reward_mint.key();
70        pool.pool_token_account = ctx.accounts.pool_token_account.key();
71        pool.reward_token_account = ctx.accounts.reward_token_account.key();
72        pool.reward_rate = reward_rate;
73        pool.lock_duration = lock_duration;
74        pool.total_staked = 0;
75        pool.total_rewards_distributed = 0;
76        pool.bump = ctx.bumps.staking_pool;
77        pool.is_active = true;
78        pool.created_at = Clock::get()?.unix_timestamp;
79
80        emit!(PoolInitialized {
81            pool: pool.key(),
82            authority: pool.authority,
83            token_mint: pool.token_mint,
84            reward_rate,
85            lock_duration,
86        });
87
88        Ok(())
89    }
90
91    /// Stake PTtC tokens into the pool
92    pub fn stake(ctx: Context<Stake>, amount: u64) -> Result<()> {
93        require!(amount > 0, StakingError::InvalidAmount);
94        require!(
95            ctx.accounts.staking_pool.is_active,
96            StakingError::PoolInactive
97        );
98
99        let clock = Clock::get()?;
100        let stake_account = &mut ctx.accounts.stake_account;
101        let pool = &mut ctx.accounts.staking_pool;
102
103        // Calculate pending rewards before updating stake
104        if stake_account.amount > 0 {
105            let pending = calculate_rewards(
106                stake_account.amount,
107                stake_account.last_reward_time,
108                clock.unix_timestamp,
109                pool.reward_rate,
110            )?;
111            stake_account.pending_rewards = stake_account
112                .pending_rewards
113                .checked_add(pending)
114                .ok_or(StakingError::MathOverflow)?;
115        }
116
117        // Transfer tokens from user to pool using anchor-spl
118        let cpi_accounts = Transfer {
119            from: ctx.accounts.user_token_account.to_account_info(),
120            to: ctx.accounts.pool_token_account.to_account_info(),
121            authority: ctx.accounts.user.to_account_info(),
122        };
123        token::transfer(
124            CpiContext::new(ctx.accounts.token_program.to_account_info(), cpi_accounts),
125            amount,
126        )?;
127
128        // Update stake account
129        stake_account.owner = ctx.accounts.user.key();
130        stake_account.pool = pool.key();
131        stake_account.amount = stake_account
132            .amount
133            .checked_add(amount)
134            .ok_or(StakingError::MathOverflow)?;
135        stake_account.stake_time = clock.unix_timestamp;
136        stake_account.last_reward_time = clock.unix_timestamp;
137        stake_account.unlock_time = clock.unix_timestamp + pool.lock_duration;
138
139        // Update pool totals
140        pool.total_staked = pool
141            .total_staked
142            .checked_add(amount)
143            .ok_or(StakingError::MathOverflow)?;
144
145        emit!(Staked {
146            user: ctx.accounts.user.key(),
147            pool: pool.key(),
148            amount,
149            total_staked: stake_account.amount,
150            unlock_time: stake_account.unlock_time,
151        });
152
153        Ok(())
154    }
155
156    /// Unstake tokens from the pool
157    pub fn unstake(ctx: Context<Unstake>, amount: u64) -> Result<()> {
158        require!(amount > 0, StakingError::InvalidAmount);
159
160        let clock = Clock::get()?;
161        // Capture AccountInfo before the mutable borrow below: the PDA-signed CPI needs
162        // staking_pool's AccountInfo as authority, but the state update also needs &mut.
163        let pool_account_info = ctx.accounts.staking_pool.to_account_info();
164        let stake_account = &mut ctx.accounts.stake_account;
165        let pool = &mut ctx.accounts.staking_pool;
166
167        require!(
168            stake_account.amount >= amount,
169            StakingError::InsufficientStake
170        );
171        require!(
172            clock.unix_timestamp >= stake_account.unlock_time,
173            StakingError::StillLocked
174        );
175
176        // Calculate and add pending rewards
177        let pending = calculate_rewards(
178            stake_account.amount,
179            stake_account.last_reward_time,
180            clock.unix_timestamp,
181            pool.reward_rate,
182        )?;
183        stake_account.pending_rewards = stake_account
184            .pending_rewards
185            .checked_add(pending)
186            .ok_or(StakingError::MathOverflow)?;
187        stake_account.last_reward_time = clock.unix_timestamp;
188
189        // Transfer tokens back to user using PDA signer
190        let token_mint = pool.token_mint;
191        let seeds = &[b"staking_pool", token_mint.as_ref(), &[pool.bump]];
192        let signer = &[&seeds[..]];
193
194        let cpi_accounts = Transfer {
195            from: ctx.accounts.pool_token_account.to_account_info(),
196            to: ctx.accounts.user_token_account.to_account_info(),
197            authority: pool_account_info,
198        };
199        token::transfer(
200            CpiContext::new_with_signer(
201                ctx.accounts.token_program.to_account_info(),
202                cpi_accounts,
203                signer,
204            ),
205            amount,
206        )?;
207
208        // Update stake account
209        stake_account.amount = stake_account
210            .amount
211            .checked_sub(amount)
212            .ok_or(StakingError::MathOverflow)?;
213
214        // Update pool totals
215        pool.total_staked = pool
216            .total_staked
217            .checked_sub(amount)
218            .ok_or(StakingError::MathOverflow)?;
219
220        emit!(Unstaked {
221            user: ctx.accounts.user.key(),
222            pool: pool.key(),
223            amount,
224            remaining_stake: stake_account.amount,
225        });
226
227        Ok(())
228    }
229
230    /// Claim accumulated rewards
231    pub fn claim_rewards(ctx: Context<ClaimRewards>) -> Result<()> {
232        let clock = Clock::get()?;
233        // Capture AccountInfo before the mutable borrow below: the PDA-signed CPI needs
234        // staking_pool's AccountInfo as authority, but the state update also needs &mut.
235        let pool_account_info = ctx.accounts.staking_pool.to_account_info();
236        let stake_account = &mut ctx.accounts.stake_account;
237        let pool = &mut ctx.accounts.staking_pool;
238
239        // Calculate current pending rewards
240        let pending = calculate_rewards(
241            stake_account.amount,
242            stake_account.last_reward_time,
243            clock.unix_timestamp,
244            pool.reward_rate,
245        )?;
246
247        let total_rewards = stake_account
248            .pending_rewards
249            .checked_add(pending)
250            .ok_or(StakingError::MathOverflow)?;
251
252        require!(total_rewards > 0, StakingError::NoRewardsToClaim);
253
254        // Transfer rewards to user using PDA signer
255        let token_mint = pool.token_mint;
256        let seeds = &[b"staking_pool", token_mint.as_ref(), &[pool.bump]];
257        let signer = &[&seeds[..]];
258
259        let cpi_accounts = Transfer {
260            from: ctx.accounts.reward_token_account.to_account_info(),
261            to: ctx.accounts.user_reward_account.to_account_info(),
262            authority: pool_account_info,
263        };
264        token::transfer(
265            CpiContext::new_with_signer(
266                ctx.accounts.token_program.to_account_info(),
267                cpi_accounts,
268                signer,
269            ),
270            total_rewards,
271        )?;
272
273        // Update state
274        stake_account.pending_rewards = 0;
275        stake_account.last_reward_time = clock.unix_timestamp;
276        stake_account.total_rewards_claimed = stake_account
277            .total_rewards_claimed
278            .checked_add(total_rewards)
279            .ok_or(StakingError::MathOverflow)?;
280
281        pool.total_rewards_distributed = pool
282            .total_rewards_distributed
283            .checked_add(total_rewards)
284            .ok_or(StakingError::MathOverflow)?;
285
286        emit!(RewardsClaimed {
287            user: ctx.accounts.user.key(),
288            pool: pool.key(),
289            amount: total_rewards,
290            total_claimed: stake_account.total_rewards_claimed,
291        });
292
293        Ok(())
294    }
295
296    /// Admin: Update pool parameters
297    pub fn update_pool(
298        ctx: Context<UpdatePool>,
299        new_reward_rate: Option<u64>,
300        new_lock_duration: Option<i64>,
301        is_active: Option<bool>,
302    ) -> Result<()> {
303        let pool = &mut ctx.accounts.staking_pool;
304
305        if let Some(rate) = new_reward_rate {
306            pool.reward_rate = rate;
307        }
308        if let Some(duration) = new_lock_duration {
309            pool.lock_duration = duration;
310        }
311        if let Some(active) = is_active {
312            pool.is_active = active;
313        }
314
315        emit!(PoolUpdated {
316            pool: pool.key(),
317            reward_rate: pool.reward_rate,
318            lock_duration: pool.lock_duration,
319            is_active: pool.is_active,
320        });
321
322        Ok(())
323    }
324}
325
326// ============ Helper Functions ============
327
328fn calculate_rewards(
329    staked_amount: u64,
330    last_reward_time: i64,
331    current_time: i64,
332    reward_rate: u64,
333) -> Result<u64> {
334    let time_elapsed = (current_time - last_reward_time) as u64;
335
336    // rewards = (staked_amount * time_elapsed * reward_rate) / 10000 / SECONDS_PER_DAY
337    let rewards = (staked_amount as u128)
338        .checked_mul(time_elapsed as u128)
339        .ok_or(StakingError::MathOverflow)?
340        .checked_mul(reward_rate as u128)
341        .ok_or(StakingError::MathOverflow)?
342        .checked_div(10000 * 86400)
343        .ok_or(StakingError::MathOverflow)?;
344
345    Ok(rewards as u64)
346}
347
348// ============ Account Structs ============
349
350#[derive(Accounts)]
351pub struct InitializePool<'info> {
352    #[account(mut)]
353    pub authority: Signer<'info>,
354
355    #[account(
356        init,
357        payer = authority,
358        space = 8 + StakingPool::INIT_SPACE,
359        seeds = [b"staking_pool", token_mint.key().as_ref()],
360        bump,
361    )]
362    pub staking_pool: Account<'info, StakingPool>,
363
364    /// CHECK: Token mint account
365    pub token_mint: AccountInfo<'info>,
366    /// CHECK: Reward mint account
367    pub reward_mint: AccountInfo<'info>,
368
369    /// CHECK: Pool's token account for staked tokens
370    #[account(mut)]
371    pub pool_token_account: AccountInfo<'info>,
372
373    /// CHECK: Pool's reward token account
374    #[account(mut)]
375    pub reward_token_account: AccountInfo<'info>,
376
377    /// CHECK: SPL Token program
378    pub token_program: Program<'info, Token>,
379    pub system_program: Program<'info, System>,
380}
381
382#[derive(Accounts)]
383pub struct Stake<'info> {
384    #[account(mut)]
385    pub user: Signer<'info>,
386
387    #[account(
388        mut,
389        seeds = [b"staking_pool", staking_pool.token_mint.as_ref()],
390        bump = staking_pool.bump,
391    )]
392    pub staking_pool: Account<'info, StakingPool>,
393
394    #[account(
395        init_if_needed,
396        payer = user,
397        space = 8 + StakeAccount::INIT_SPACE,
398        seeds = [b"stake", staking_pool.key().as_ref(), user.key().as_ref()],
399        bump,
400    )]
401    pub stake_account: Account<'info, StakeAccount>,
402
403    /// CHECK: User's token account
404    #[account(mut)]
405    pub user_token_account: AccountInfo<'info>,
406
407    /// CHECK: Pool's token account
408    #[account(mut)]
409    pub pool_token_account: AccountInfo<'info>,
410
411    pub token_program: Program<'info, Token>,
412    pub system_program: Program<'info, System>,
413}
414
415#[derive(Accounts)]
416pub struct Unstake<'info> {
417    #[account(mut)]
418    pub user: Signer<'info>,
419
420    #[account(
421        mut,
422        seeds = [b"staking_pool", staking_pool.token_mint.as_ref()],
423        bump = staking_pool.bump,
424    )]
425    pub staking_pool: Account<'info, StakingPool>,
426
427    #[account(
428        mut,
429        seeds = [b"stake", staking_pool.key().as_ref(), user.key().as_ref()],
430        bump,
431        constraint = stake_account.owner == user.key(),
432    )]
433    pub stake_account: Account<'info, StakeAccount>,
434
435    /// CHECK: User's token account
436    #[account(mut)]
437    pub user_token_account: AccountInfo<'info>,
438
439    /// CHECK: Pool's token account
440    #[account(mut)]
441    pub pool_token_account: AccountInfo<'info>,
442
443    pub token_program: Program<'info, Token>,
444}
445
446#[derive(Accounts)]
447pub struct ClaimRewards<'info> {
448    #[account(mut)]
449    pub user: Signer<'info>,
450
451    #[account(
452        mut,
453        seeds = [b"staking_pool", staking_pool.token_mint.as_ref()],
454        bump = staking_pool.bump,
455    )]
456    pub staking_pool: Account<'info, StakingPool>,
457
458    #[account(
459        mut,
460        seeds = [b"stake", staking_pool.key().as_ref(), user.key().as_ref()],
461        bump,
462        constraint = stake_account.owner == user.key(),
463    )]
464    pub stake_account: Account<'info, StakeAccount>,
465
466    /// CHECK: User's reward token account
467    #[account(mut)]
468    pub user_reward_account: AccountInfo<'info>,
469
470    /// CHECK: Pool's reward token account
471    #[account(mut)]
472    pub reward_token_account: AccountInfo<'info>,
473
474    pub token_program: Program<'info, Token>,
475}
476
477#[derive(Accounts)]
478pub struct UpdatePool<'info> {
479    #[account(
480        constraint = authority.key() == staking_pool.authority,
481    )]
482    pub authority: Signer<'info>,
483
484    #[account(
485        mut,
486        seeds = [b"staking_pool", staking_pool.token_mint.as_ref()],
487        bump = staking_pool.bump,
488    )]
489    pub staking_pool: Account<'info, StakingPool>,
490}
491
492// ============ State Accounts ============
493
494/// A staking pool that holds staked tokens and distributes rewards.
495/// Each pool has its own configuration, token accounts, and reward parameters.
496#[account]
497#[derive(InitSpace)]
498pub struct StakingPool {
499    /// Authority/admin who can update pool configuration
500    pub authority: Pubkey,
501    /// Mint of the token being staked in this pool
502    pub token_mint: Pubkey,
503    /// Mint of the reward token (may be same as token_mint)
504    pub reward_mint: Pubkey,
505    /// Pool's token account holding staked tokens
506    pub pool_token_account: Pubkey,
507    /// Pool's token account holding rewards to distribute
508    pub reward_token_account: Pubkey,
509    /// Reward amount per unit time (in lamports per slot)
510    pub reward_rate: u64,
511    /// Minimum lock duration for stakes (in seconds)
512    pub lock_duration: i64,
513    /// Total tokens currently staked in this pool
514    pub total_staked: u64,
515    /// Cumulative rewards distributed from this pool
516    pub total_rewards_distributed: u64,
517    /// PDA bump seed for this account
518    pub bump: u8,
519    /// Whether this pool is accepting new stakes
520    pub is_active: bool,
521    /// Timestamp when this pool was created
522    pub created_at: i64,
523
524    // --- v0.2.0 Tensor Network Extensions ---
525    /// Whether tensor network enhancements are enabled for this pool
526    pub tensor_enabled: u8, // 0 = disabled, 1 = enabled
527    /// Maximum entropy target for this pool (1e6 scale)
528    pub s_max: u64, // Maximum entropy (1e6 scale)
529    /// Weight factor for entropy in reward calculations (1e6 scale)
530    pub entropy_weight: u64, // Entropy contribution weight (1e6 scale)
531    /// Number of stakes participating in entanglement
532    pub total_entangled_stakes: u32, // Number of stakes in entangled pools
533    /// Sum of all stake entropy contributions
534    pub total_pool_entropy: u64, // Sum of all stake entropies
535    /// Average coherence preservation across all pool members
536    pub average_coherence: u64, // Average coherence of pool members
537}
538
539/// Individual stake account tracking one user's stake in a pool.
540/// Each staker has one account per pool they're participating in.
541#[account]
542#[derive(InitSpace)]
543pub struct StakeAccount {
544    /// User/owner of this stake
545    pub owner: Pubkey,
546    /// Pool this stake belongs to
547    pub pool: Pubkey,
548    /// Amount of tokens staked
549    pub amount: u64,
550    /// Timestamp when the stake was created
551    pub stake_time: i64,
552    /// Timestamp when the stake can be withdrawn (after lock_duration)
553    pub unlock_time: i64,
554    /// Last timestamp when rewards were calculated
555    pub last_reward_time: i64,
556    /// Rewards accumulated but not yet claimed
557    pub pending_rewards: u64,
558    /// Total rewards claimed from this stake
559    pub total_rewards_claimed: u64,
560
561    // --- v0.2.0 Tensor Network Extensions ---
562    /// Stake's contribution to network entropy (1e6 scale)
563    pub entropy_score: u64, // Stake's entropy contribution (1e6 scale)
564    /// Device's ability to preserve quantum coherence (1e6 scale, 0-1000000)
565    pub coherence: u64, // Device coherence preservation (1e6 scale)
566    /// ID of the entanglement pool this stake belongs to (0 = not entangled)
567    pub pool_id: u32, // Entanglement pool assignment (0 = not entangled)
568    /// Last timestamp when entropy metrics were calculated
569    pub last_entropy_update: i64, // Last slot when entropy was calculated
570    /// Probability of early unlock based on entropy (1e6 scale, 0-1000000)
571    pub unlock_probability: u64, // P(early unlock) from entropy (1e6 scale)
572    /// Bonus multiplier from coherence contribution (1e6 scale)
573    pub coherence_bonus: u64, // Bonus multiplier from coherence (1e6 scale)
574}
575
576// ============ Errors ============
577
578#[error_code]
579pub enum StakingError {
580    #[msg("Invalid amount")]
581    InvalidAmount,
582    #[msg("Pool is not active")]
583    PoolInactive,
584    #[msg("Insufficient stake balance")]
585    InsufficientStake,
586    #[msg("Tokens are still locked")]
587    StillLocked,
588    #[msg("No rewards to claim")]
589    NoRewardsToClaim,
590    #[msg("No stake to withdraw")]
591    NoStake,
592    #[msg("Math overflow")]
593    MathOverflow,
594}