1use tape_api::prelude::*;
2use steel::*;
3
4const LOW_REWARD_THRESHOLD: u64 = 32;
5const HIGH_REWARD_THRESHOLD: u64 = 256;
6const SMOOTHING_FACTOR: u64 = 2;
7
8pub fn process_advance(accounts: &[AccountInfo<'_>], _data: &[u8]) -> ProgramResult {
9 let current_time = Clock::get()?.unix_timestamp;
10 let [
11 signer_info,
12 spool_0_info,
13 spool_1_info,
14 spool_2_info,
15 spool_3_info,
16 spool_4_info,
17 spool_5_info,
18 spool_6_info,
19 spool_7_info,
20 epoch_info,
21 mint_info,
22 treasury_info,
23 treasury_ata_info,
24 token_program_info
25 ] = accounts else {
26 return Err(ProgramError::NotEnoughAccountKeys);
27 };
28
29 signer_info.is_signer()?;
30
31 let spool_0 = spool_0_info
32 .as_account_mut::<Spool>(&tape_api::ID)?
33 .assert_mut(|s| s.id == 0)?;
34 let spool_1 = spool_1_info
35 .as_account_mut::<Spool>(&tape_api::ID)?
36 .assert_mut(|s| s.id == 1)?;
37 let spool_2 = spool_2_info
38 .as_account_mut::<Spool>(&tape_api::ID)?
39 .assert_mut(|s| s.id == 2)?;
40 let spool_3 = spool_3_info
41 .as_account_mut::<Spool>(&tape_api::ID)?
42 .assert_mut(|s| s.id == 3)?;
43 let spool_4 = spool_4_info
44 .as_account_mut::<Spool>(&tape_api::ID)?
45 .assert_mut(|s| s.id == 4)?;
46 let spool_5 = spool_5_info
47 .as_account_mut::<Spool>(&tape_api::ID)?
48 .assert_mut(|s| s.id == 5)?;
49 let spool_6 = spool_6_info
50 .as_account_mut::<Spool>(&tape_api::ID)?
51 .assert_mut(|s| s.id == 6)?;
52 let spool_7 = spool_7_info
53 .as_account_mut::<Spool>(&tape_api::ID)?
54 .assert_mut(|s| s.id == 7)?;
55
56 let spools = [spool_0, spool_1, spool_2, spool_3, spool_4, spool_5, spool_6, spool_7];
57
58 let epoch = epoch_info
59 .is_epoch()?
60 .as_account_mut::<Epoch>(&tape_api::ID)?;
61
62 let mint = mint_info
63 .has_address(&MINT_ADDRESS)?
64 .is_writable()?
65 .as_mint()?;
66
67 treasury_info.is_treasury()?.is_writable()?;
68 treasury_ata_info.is_treasury_ata()?.is_writable()?;
69 token_program_info.is_program(&spl_token::ID)?;
70
71 if still_active(epoch, current_time) {
73 return Ok(());
74 }
75
76 let mint_supply = mint.supply();
78 if mint_supply >= MAX_SUPPLY {
79 return Err(TapeError::MaxSupply.into());
80 }
81
82 epoch.target_rate = get_emissions_rate(mint_supply);
84
85 let target_rewards = epoch.target_rate * EPOCH_DURATION_MINUTES as u64;
87
88 solana_program::msg!(
89 "epoch.target_rate: {}, target_rewards: {}",
90 epoch.target_rate,
91 target_rewards
92 );
93
94 let (amount_to_mint, actual_rewards) =
96 update_spools(spools, mint_supply, target_rewards);
97
98 epoch.base_rate = compute_new_reward_rate(
100 epoch.base_rate,
101 actual_rewards,
102 target_rewards,
103 );
104
105 adjust_difficulty(epoch);
107
108 solana_program::msg!(
109 "previous rewards: {}",
110 actual_rewards,
111 );
112
113 solana_program::msg!(
114 "new epoch.base_rate: {}",
115 epoch.base_rate
116 );
117
118 solana_program::msg!(
119 "new epoch.difficulty: {}",
120 epoch.difficulty
121 );
122
123 solana_program::msg!(
124 "minting: {}",
125 amount_to_mint,
126 );
127
128 mint_to_signed(
130 mint_info,
131 treasury_ata_info,
132 treasury_info,
133 token_program_info,
134 amount_to_mint,
135 &[TREASURY],
136 )?;
137
138 epoch.number += 1;
140 epoch.last_epoch_at = current_time;
141
142 Ok(())
143}
144
145#[inline(always)]
147fn still_active(epoch: &Epoch, current_time: i64) -> bool {
148 epoch.last_epoch_at
149 .saturating_add(EPOCH_DURATION_MINUTES)
150 .gt(¤t_time)
151}
152
153#[inline(always)]
155fn update_spools(
156 spools: [&mut Spool; SPOOL_COUNT],
157 mint_supply: u64,
158 target_rewards: u64,
159) -> (u64, u64) {
160 let mut amount_to_mint = 0u64;
161 let mut available_supply = MAX_SUPPLY.saturating_sub(mint_supply);
162 let mut theoretical_rewards = 0u64;
163
164 for spool in spools {
165 let spool_topup = target_rewards
166 .saturating_sub(spool.available_rewards)
167 .min(available_supply);
168
169 theoretical_rewards += spool.theoretical_rewards;
170 spool.theoretical_rewards = 0;
171
172 available_supply -= spool_topup;
173 amount_to_mint += spool_topup;
174 spool.available_rewards += spool_topup;
175 }
176
177 (amount_to_mint, theoretical_rewards)
178}
179
180#[inline(always)]
185fn adjust_difficulty(epoch: &mut Epoch) {
186 if epoch.base_rate < LOW_REWARD_THRESHOLD {
187 epoch.difficulty += 1;
188 epoch.base_rate *= 2;
189 }
190
191 if epoch.base_rate >= HIGH_REWARD_THRESHOLD && epoch.difficulty > 1 {
192 epoch.difficulty -= 1;
193 epoch.base_rate /= 2;
194 }
195
196 epoch.difficulty = epoch.difficulty.max(7);
197}
198
199#[inline(always)]
207fn compute_new_reward_rate(
208 current_rate: u64,
209 actual_rewards: u64,
210 target_rewards: u64,
211) -> u64 {
212
213 if actual_rewards == 0 {
214 return current_rate;
215 }
216
217 let adjusted_rate = (current_rate as u128)
218 .saturating_mul(target_rewards as u128)
219 .saturating_div(actual_rewards as u128) as u64;
220
221 let min_rate = current_rate.saturating_div(SMOOTHING_FACTOR);
222 let max_rate = current_rate.saturating_mul(SMOOTHING_FACTOR);
223 let smoothed_rate = adjusted_rate.min(max_rate).max(min_rate);
224
225 smoothed_rate
226 .max(1)
227 .min(target_rewards)
228}
229
230pub fn get_emissions_rate(current_supply: u64) -> u64 {
233 match current_supply {
234 n if n < ONE_TAPE * 1000000 => 19025875190, n if n < ONE_TAPE * 1861000 => 16381278538, n if n < ONE_TAPE * 2602321 => 14104280821, n if n < ONE_TAPE * 3240598 => 12143785787, n if n < ONE_TAPE * 3790155 => 10455799563, n if n < ONE_TAPE * 4263323 => 9002443423, n if n < ONE_TAPE * 4670721 => 7751103787, n if n < ONE_TAPE * 5021491 => 6673700361, n if n < ONE_TAPE * 5323504 => 5746056011, n if n < ONE_TAPE * 5583536 => 4947354225, n if n < ONE_TAPE * 5807425 => 4259671988, n if n < ONE_TAPE * 6000193 => 3667577581, n if n < ONE_TAPE * 6166166 => 3157784298, n if n < ONE_TAPE * 6309069 => 2718852280, n if n < ONE_TAPE * 6432108 => 2340931813, n if n < ONE_TAPE * 6538045 => 2015542291, n if n < ONE_TAPE * 6629257 => 1735381912, n if n < ONE_TAPE * 6707790 => 1494163827, n if n < ONE_TAPE * 6775407 => 1286475055, n if n < ONE_TAPE * 6833625 => 1107655022, n if n < ONE_TAPE * 6883751 => 953690974, n if n < ONE_TAPE * 6926910 => 821127928, n if n < ONE_TAPE * 6964069 => 706991146, n if n < ONE_TAPE * 6996064 => 608719377, n if n < ONE_TAPE * 7000000 => 524107383, _ => 0,
260 }
261}