arrow_sunny/
lib.rs

1//! Sunny protocol integration for Arrow.
2#![deny(rustdoc::all)]
3#![allow(rustdoc::missing_doc_code_examples)]
4
5#[macro_use]
6mod macros;
7
8mod account_validators;
9mod instructions;
10
11pub mod addresses;
12pub mod events;
13pub mod state;
14
15use anchor_lang::prelude::*;
16use anchor_spl::token::{Mint, Token, TokenAccount};
17use sunny_anchor::{Pool, Vault};
18use vipers::prelude::*;
19
20pub use events::*;
21pub use state::*;
22
23declare_id!("ARoWLTBWoWrKMvxEiaE2EH9DrWyV7mLpKywGDWxBGeq9");
24
25/// [arrow_sunny] program.
26#[program]
27pub mod arrow_sunny {
28    use super::*;
29
30    /// Creates a new [Arrow].
31    #[access_control(ctx.accounts.validate())]
32    pub fn new_arrow(ctx: Context<NewArrow>, _bump: u8, vault_bump: u8) -> Result<()> {
33        let bump = unwrap_bump!(ctx, "arrow");
34        ctx.accounts.init_vault(vault_bump)?;
35        ctx.accounts.init_arrow(bump)
36    }
37
38    /// Initializes the [Arrow]'s internal miner.
39    #[access_control(ctx.accounts.validate())]
40    pub fn init_arrow_internal_miner(
41        ctx: Context<InitArrowMiner>,
42        internal_miner_bump: u8,
43    ) -> Result<()> {
44        ctx.accounts.init_arrow_internal_miner(internal_miner_bump)
45    }
46
47    /// Initializes the [Arrow]'s vendor miner.
48    #[access_control(ctx.accounts.validate())]
49    pub fn init_arrow_vendor_miner(
50        ctx: Context<InitArrowMiner>,
51        vendor_miner_bump: u8,
52    ) -> Result<()> {
53        ctx.accounts.init_arrow_vendor_miner(vendor_miner_bump)
54    }
55
56    /// Stakes tokens into an [Arrow].
57    #[access_control(ctx.accounts.validate())]
58    pub fn deposit_vendor(ctx: Context<DepositVendor>, amount: u64) -> Result<()> {
59        ctx.accounts.deposit_vendor(amount)
60    }
61
62    /// Stakes the internal tokens.
63    ///
64    /// Anybody can call this function, but ideally it is called right after [deposit_vendor].
65    /// This ensures that the staked tokens are always earning maximum yield.
66    #[access_control(ctx.accounts.validate())]
67    pub fn stake_internal(ctx: Context<StakeInternal>) -> Result<()> {
68        ctx.accounts.stake_internal()
69    }
70
71    /// Unstakes tokens from an [Arrow].
72    #[access_control(ctx.accounts.validate())]
73    pub fn unstake_internal(ctx: Context<UnstakeInternal>, amount: u64) -> Result<()> {
74        ctx.accounts.unstake_internal(amount)
75    }
76
77    /// Withdraws vendor tokens from an [Arrow] into an account.
78    ///
79    /// **IMPORTANT**: A user should take care to ensure that this is called in the same transaction
80    /// as [unstake_internal], otherwise someone else can withdraw their tokens.
81    #[access_control(ctx.accounts.validate())]
82    pub fn withdraw_vendor_tokens(ctx: Context<WithdrawVendorTokens>, amount: u64) -> Result<()> {
83        ctx.accounts.withdraw_vendor_tokens(amount)
84    }
85
86    /// Claims tokens, keeping them within the vault.
87    /// Fees are not removed from the vault at this time.
88    #[access_control(ctx.accounts.validate())]
89    pub fn claim(ctx: Context<Claim>) -> Result<()> {
90        ctx.accounts.claim()
91    }
92
93    /// Withdraws all claimed rewards to the beneficiary.
94    /// One should call [claim] first.
95    #[access_control(ctx.accounts.validate())]
96    pub fn withdraw_rewards_to_beneficiary(
97        ctx: Context<WithdrawRewardsToBeneficiary>,
98    ) -> Result<()> {
99        ctx.accounts.withdraw_rewards_to_beneficiary()
100    }
101}
102
103// --------------------------------
104// Context Structs
105// --------------------------------
106
107/// Accounts for [arrow_sunny::new_arrow].
108#[derive(Accounts)]
109pub struct NewArrow<'info> {
110    /// The [Arrow].
111    #[account(
112        init,
113        seeds = [
114            b"arrow".as_ref(),
115            arrow_mint.key().to_bytes().as_ref()
116        ],
117        bump,
118        space = 8 + Arrow::LEN,
119        payer = payer
120    )]
121    pub arrow: Box<Account<'info, Arrow>>,
122
123    /// [Mint] of the [Arrow].
124    /// One may choose a [Mint] that uniquely describes their pool
125    /// via `solana-keygen grind`.
126    pub arrow_mint: Box<Account<'info, Mint>>,
127
128    /// Payer of the initialization.
129    #[account(mut)]
130    pub payer: Signer<'info>,
131
132    /// The recipient of the [Arrow]'s rewards.
133    /// CHECK: arbitrary
134    pub beneficiary: UncheckedAccount<'info>,
135
136    /// The Sunny pool.
137    pub pool: Box<Account<'info, Pool>>,
138    /// Sunny vault
139    #[account(mut)]
140    pub vault: SystemAccount<'info>,
141    /// The [Mint] which is staked into pools.
142    pub vendor_mint: Box<Account<'info, Mint>>,
143
144    /// Sunny.
145    pub sunny_program: Program<'info, sunny_anchor::program::SunnyAnchor>,
146    /// System program.
147    pub system_program: Program<'info, System>,
148}
149
150/// Accounts for [arrow_sunny::init_arrow_vendor_miner] and [arrow_sunny::init_arrow_internal_miner].
151#[derive(Accounts)]
152pub struct InitArrowMiner<'info> {
153    /// The [Arrow].
154    #[account(mut)]
155    pub arrow: Box<Account<'info, Arrow>>,
156    /// Payer of the initialization.
157    #[account(mut)]
158    pub payer: Signer<'info>,
159
160    /// The Sunny pool.
161    pub pool: Box<Account<'info, Pool>>,
162    /// The Sunny vault.
163    pub vault: Box<Account<'info, Vault>>,
164
165    /// The miner to create.
166    pub miner: InitMiner<'info>,
167
168    /// Mine program.
169    pub mine_program: Program<'info, quarry_mine::program::QuarryMine>,
170    /// Sunny program.
171    pub sunny_program: Program<'info, sunny_anchor::program::SunnyAnchor>,
172    /// System program.
173    pub system_program: Program<'info, System>,
174    /// Token program.
175    pub token_program: Program<'info, Token>,
176}
177
178/// Miner accounts used in [arrow_sunny::init_arrow_vendor_miner] and [arrow_sunny::init_arrow_internal_miner].
179#[derive(Accounts)]
180pub struct InitMiner<'info> {
181    /// Rewarder
182    pub rewarder: Box<Account<'info, quarry_mine::Rewarder>>,
183    /// The [quarry_mine::Quarry] to stake into.
184    #[account(mut)]
185    pub quarry: Box<Account<'info, quarry_mine::Quarry>>,
186    /// The miner. This is unchecked as it should not be initialized.
187    #[account(mut)]
188    pub miner: SystemAccount<'info>,
189    /// Account holding the miner's tokens
190    /// This should be an ATA of the miner, otherwise
191    /// Sunny will throw an exception.
192    pub miner_vault: Box<Account<'info, TokenAccount>>,
193    /// Mint of the token
194    pub token_mint: Box<Account<'info, Mint>>,
195}
196
197/// Accounts for [arrow_sunny::deposit_vendor].
198#[derive(Accounts)]
199pub struct DepositVendor<'info> {
200    /// Arrow
201    pub arrow: Box<Account<'info, Arrow>>,
202    /// Arrow staking accounts
203    pub arrow_stake: ArrowStake<'info>,
204
205    /// Depositor's staked tokens
206    #[account(mut)]
207    pub depositor_staked_tokens: Box<Account<'info, TokenAccount>>,
208
209    /// Vault's vendor tokens
210    #[account(mut)]
211    pub vault_vendor_token_account: Box<Account<'info, TokenAccount>>,
212
213    /// Vendor stake accounts.
214    pub vendor_stake: StakeCommon<'info>,
215
216    /// Sunny pool
217    #[account(mut)]
218    pub pool: Box<Account<'info, Pool>>,
219    /// Sunny vault
220    #[account(mut)]
221    pub vault: Box<Account<'info, Vault>>,
222
223    /// token program
224    pub token_program: Program<'info, Token>,
225    /// Mine program
226    pub mine_program: Program<'info, quarry_mine::program::QuarryMine>,
227    /// Sunny program
228    pub sunny_program: Program<'info, sunny_anchor::program::SunnyAnchor>,
229    /// Clock sysvar
230    pub clock: Sysvar<'info, Clock>,
231}
232
233/// Accounts for [arrow_sunny::stake_internal].
234#[derive(Accounts)]
235pub struct StakeInternal<'info> {
236    /// Arrow
237    pub arrow: Box<Account<'info, Arrow>>,
238
239    /// Internal mint
240    #[account(mut)]
241    pub internal_mint: Box<Account<'info, Mint>>,
242    /// Vault internal tokens
243    #[account(mut)]
244    pub vault_internal_token_account: Box<Account<'info, TokenAccount>>,
245
246    /// Internal stake
247    pub internal_stake: StakeCommon<'info>,
248    /// Sunny Pool
249    #[account(mut)]
250    pub pool: Box<Account<'info, Pool>>,
251    /// Sunny Vault
252    #[account(mut)]
253    pub vault: Box<Account<'info, Vault>>,
254
255    /// token program
256    pub token_program: Program<'info, Token>,
257    /// mine program
258    pub mine_program: Program<'info, quarry_mine::program::QuarryMine>,
259    /// sunny program
260    pub sunny_program: Program<'info, sunny_anchor::program::SunnyAnchor>,
261    /// clock sysvar
262    pub clock: Sysvar<'info, Clock>,
263}
264
265/// Accounts for [arrow_sunny::unstake_internal].
266#[derive(Accounts)]
267pub struct UnstakeInternal<'info> {
268    /// Arrow staking accounts
269    pub arrow_stake: ArrowStake<'info>,
270    /// Staking accounts
271    pub stake: StakeInternal<'info>,
272}
273
274/// Common accounts for minting/burning arrows
275#[derive(Accounts)]
276pub struct ArrowStake<'info> {
277    /// Mint of the arrow.
278    #[account(mut)]
279    pub arrow_mint: Box<Account<'info, Mint>>,
280    /// Depositor creating arrows.
281    pub depositor: Signer<'info>,
282    /// Arrow tokens of the depositor
283    #[account(mut)]
284    pub depositor_arrow_tokens: Box<Account<'info, TokenAccount>>,
285}
286
287/// Common accounts for staking actions.
288/// We do not deserialize the accounts here to save on compute units,
289/// but also because we just check equality anyway.
290#[derive(Accounts)]
291pub struct StakeCommon<'info> {
292    /// Rewarder
293    /// CHECK: equality
294    pub rewarder: UncheckedAccount<'info>,
295    /// Quarry
296    /// CHECK: equality
297    #[account(mut)]
298    pub quarry: UncheckedAccount<'info>,
299    /// Miner
300    /// CHECK: equality
301    #[account(mut)]
302    pub miner: UncheckedAccount<'info>,
303    /// Miner vault
304    /// CHECK: equality
305    #[account(mut)]
306    pub miner_vault: UncheckedAccount<'info>,
307}
308
309/// Accounts for [arrow_sunny::withdraw_vendor_tokens].
310#[derive(Accounts)]
311pub struct WithdrawVendorTokens<'info> {
312    /// Stake accounts
313    pub stake: DepositVendor<'info>,
314    /// Sunny pool fee account for the vendor tokens.
315    #[account(mut)]
316    pub sunny_pool_fee_destination: Account<'info, TokenAccount>,
317}
318
319/// Accounts for [arrow_sunny::claim].
320#[derive(Accounts)]
321pub struct Claim<'info> {
322    /// The [Arrow] we are claiming.
323    pub arrow: Box<Account<'info, Arrow>>,
324
325    /// Vault account holding rewards tokens
326    #[account(mut)]
327    pub vault_rewards_token_account: Box<Account<'info, TokenAccount>>,
328
329    // Quarry Mine accounts
330    /// This account just gets equality checked, so we don't need to deserialize it.
331    /// CHECK: equality
332    #[account(mut)]
333    pub claim_fee_token_account: UncheckedAccount<'info>,
334    /// This account is a dummy and doesn't get used. It must have the
335    /// Quarry mint, but this is checked by Sunny.
336    /// CHECK: equality
337    #[account(mut)]
338    pub stake_token_account: UncheckedAccount<'info>,
339    /// Staking accounts
340    pub stake: StakeCommon<'info>,
341
342    // Quarry Mint wrapper accounts
343    // These are checked by Sunny, so we do not check them here to save
344    // compute units.
345    /// Quarry mint wrapper
346    /// CHECK: compute units
347    #[account(mut)]
348    pub mint_wrapper: UncheckedAccount<'info>,
349    /// Quarry minter
350    #[account(mut)]
351    pub minter: Box<Account<'info, quarry_mint_wrapper::Minter>>,
352    /// Mint of the rewards token.
353    #[account(mut)]
354    pub rewards_token_mint: Box<Account<'info, Mint>>,
355
356    // Sunny accounts
357    // equality checked, so no need to deserialize
358    /// Sunny pool
359    /// CHECK: equality
360    #[account(mut)]
361    pub pool: UncheckedAccount<'info>,
362    /// Sunny vault
363    /// CHECK: equality
364    #[account(mut)]
365    pub vault: UncheckedAccount<'info>,
366
367    // Programs and sysvars
368    /// Quarry mine program
369    pub mine_program: Program<'info, quarry_mine::program::QuarryMine>,
370    /// Mint wrapper program
371    pub mint_wrapper_program: Program<'info, quarry_mint_wrapper::program::QuarryMintWrapper>,
372    /// Sunny program
373    pub sunny_program: Program<'info, sunny_anchor::program::SunnyAnchor>,
374    /// Token program
375    pub token_program: Program<'info, Token>,
376    /// Clock sysvar
377    pub clock: Sysvar<'info, Clock>,
378}
379
380/// Accounts for [arrow_sunny::withdraw_rewards_to_beneficiary].
381#[derive(Accounts)]
382pub struct WithdrawRewardsToBeneficiary<'info> {
383    /// The [Arrow] we are claiming.
384    pub arrow: Box<Account<'info, Arrow>>,
385
386    // Rewards token accounts
387    /// Beneficiary.
388    #[account(mut)]
389    pub beneficiary_account: Box<Account<'info, TokenAccount>>,
390    /// Arrow protocol fees ATA
391    #[account(mut)]
392    pub arrow_fee_account: Box<Account<'info, TokenAccount>>,
393    /// Sunny pool fees ATA
394    #[account(mut)]
395    pub sunny_pool_fee_account: Box<Account<'info, TokenAccount>>,
396    /// Arrow staging ATA
397    #[account(mut)]
398    pub arrow_staging_account: Box<Account<'info, TokenAccount>>,
399    /// Vault rewards tokens ATA
400    #[account(mut)]
401    pub vault_rewards_token_account: Box<Account<'info, TokenAccount>>,
402
403    // Sunny accounts
404    /// Sunny pool.
405    pub pool: Box<Account<'info, Pool>>,
406    /// Sunny vault.
407    #[account(mut)]
408    pub vault: Box<Account<'info, Vault>>,
409
410    /// Sunny program
411    pub sunny_program: Program<'info, sunny_anchor::program::SunnyAnchor>,
412    /// Token program
413    pub token_program: Program<'info, Token>,
414}
415
416#[error_code]
417pub enum ErrorCode {
418    #[msg("Invalid rewards mint.")]
419    InvalidRewardsMint,
420
421    #[msg("The Arrow's mint's authority must be the arrow.", offset = 10)]
422    NewArrowMintAuthorityInvalid,
423    #[msg("The Arrow's freeze authority must be the arrow.")]
424    NewArrowFreezeAuthorityInvalid,
425    #[msg("Arrow mint must have zero supply")]
426    NewArrowNonZeroSupply,
427    #[msg("Arrow decimals must match the staked token")]
428    NewArrowDecimalMismatch,
429
430    #[msg("Miners already initialized.", offset = 20)]
431    InitArrowMinersAlreadyInitialized,
432}