crate_token/
lib.rs

1//! Crate Token.
2#![deny(rustdoc::all)]
3#![allow(rustdoc::missing_doc_code_examples)]
4
5mod account_validators;
6mod macros;
7
8pub mod events;
9pub mod state;
10
11use anchor_lang::prelude::*;
12use anchor_lang::solana_program;
13use anchor_spl::token::{self, Mint, Token, TokenAccount};
14use static_pubkey::static_pubkey;
15use vipers::prelude::*;
16
17use events::*;
18pub use state::*;
19
20declare_id!("CRATwLpu6YZEeiVq9ajjxs61wPQ9f29s1UoQR9siJCRs");
21
22/// Address where fees are sent to.
23pub static FEE_TO_ADDRESS: Pubkey = static_pubkey!("AAqAKWdsUPepSgXf7Msbp1pQ7yCPgYkBvXmNfTFBGAqp");
24
25/// Issuance fee as a portion of the crate's fee, in bps.
26pub static ISSUE_FEE_BPS: u16 = 2_000;
27
28/// Withdraw fee as a portion of the crate's fee, in bps.
29pub static WITHDRAW_FEE_BPS: u16 = 2_000;
30
31/// Maximum fee for anything.
32pub const MAX_FEE_BPS: u16 = 10_000;
33
34/// [crate_token] program.
35#[program]
36pub mod crate_token {
37    use super::*;
38
39    /// Provisions a new Crate.
40    #[access_control(ctx.accounts.validate())]
41    pub fn new_crate(ctx: Context<NewCrate>, _bump: u8) -> Result<()> {
42        let info = &mut ctx.accounts.crate_token;
43        info.mint = ctx.accounts.crate_mint.key();
44        info.bump = unwrap_bump!(ctx, "crate_token");
45
46        info.fee_to_setter = ctx.accounts.fee_to_setter.key();
47        info.fee_setter_authority = ctx.accounts.fee_setter_authority.key();
48        info.issue_authority = ctx.accounts.issue_authority.key();
49        info.withdraw_authority = ctx.accounts.withdraw_authority.key();
50        info.author_fee_to = ctx.accounts.author_fee_to.key();
51
52        info.issue_fee_bps = 0;
53        info.withdraw_fee_bps = 0;
54
55        emit!(NewCrateEvent {
56            issue_authority: ctx.accounts.issue_authority.key(),
57            withdraw_authority: ctx.accounts.withdraw_authority.key(),
58            crate_key: ctx.accounts.crate_token.key(),
59        });
60
61        Ok(())
62    }
63
64    /// Set the issue fee.
65    /// Only the `fee_setter_authority` can call this.
66    #[access_control(ctx.accounts.validate())]
67    pub fn set_issue_fee(ctx: Context<SetFees>, issue_fee_bps: u16) -> Result<()> {
68        invariant!(issue_fee_bps <= MAX_FEE_BPS, MaxFeeExceeded);
69        let crate_token = &mut ctx.accounts.crate_token;
70        crate_token.issue_fee_bps = issue_fee_bps;
71        Ok(())
72    }
73
74    /// Set the withdraw fee.
75    /// Only the `fee_setter_authority` can call this.
76    #[access_control(ctx.accounts.validate())]
77    pub fn set_withdraw_fee(ctx: Context<SetFees>, withdraw_fee_bps: u16) -> Result<()> {
78        invariant!(withdraw_fee_bps <= MAX_FEE_BPS, MaxFeeExceeded);
79        let crate_token = &mut ctx.accounts.crate_token;
80        crate_token.withdraw_fee_bps = withdraw_fee_bps;
81        Ok(())
82    }
83
84    /// Set the next recipient of the fees.
85    /// Only the `fee_to_setter` can call this.
86    #[access_control(ctx.accounts.validate())]
87    pub fn set_fee_to(ctx: Context<SetFeeTo>) -> Result<()> {
88        let crate_token = &mut ctx.accounts.crate_token;
89        crate_token.author_fee_to = ctx.accounts.author_fee_to.key();
90        Ok(())
91    }
92
93    /// Sets who can change who sets the fees.
94    /// Only the `fee_to_setter` can call this.
95    #[access_control(ctx.accounts.validate())]
96    pub fn set_fee_to_setter(ctx: Context<SetFeeToSetter>) -> Result<()> {
97        let crate_token = &mut ctx.accounts.crate_token;
98        crate_token.fee_to_setter = ctx.accounts.next_fee_to_setter.key();
99        Ok(())
100    }
101
102    /// Issues Crate tokens.
103    /// Only the `issue_authority` can call this.
104    #[access_control(ctx.accounts.validate())]
105    pub fn issue(ctx: Context<Issue>, amount: u64) -> Result<()> {
106        // Do nothing if there is a zero amount.
107        if amount == 0 {
108            return Ok(());
109        }
110
111        let seeds: &[&[u8]] = gen_crate_signer_seeds!(ctx.accounts.crate_token);
112        let crate_token = &ctx.accounts.crate_token;
113        let state::Fees {
114            amount,
115            author_fee,
116            protocol_fee,
117        } = crate_token.apply_issue_fee(amount)?;
118
119        token::mint_to(
120            CpiContext::new_with_signer(
121                ctx.accounts.token_program.to_account_info(),
122                token::MintTo {
123                    mint: ctx.accounts.crate_mint.to_account_info(),
124                    to: ctx.accounts.mint_destination.to_account_info(),
125                    authority: ctx.accounts.crate_token.to_account_info(),
126                },
127                &[seeds],
128            ),
129            amount,
130        )?;
131
132        if author_fee > 0 {
133            token::mint_to(
134                CpiContext::new_with_signer(
135                    ctx.accounts.token_program.to_account_info(),
136                    token::MintTo {
137                        mint: ctx.accounts.crate_mint.to_account_info(),
138                        to: ctx.accounts.author_fee_destination.to_account_info(),
139                        authority: ctx.accounts.crate_token.to_account_info(),
140                    },
141                    &[seeds],
142                ),
143                author_fee,
144            )?;
145        }
146
147        if protocol_fee > 0 {
148            token::mint_to(
149                CpiContext::new_with_signer(
150                    ctx.accounts.token_program.to_account_info(),
151                    token::MintTo {
152                        mint: ctx.accounts.crate_mint.to_account_info(),
153                        to: ctx.accounts.protocol_fee_destination.to_account_info(),
154                        authority: ctx.accounts.crate_token.to_account_info(),
155                    },
156                    &[seeds],
157                ),
158                protocol_fee,
159            )?;
160        }
161
162        emit!(IssueEvent {
163            crate_key: ctx.accounts.crate_token.key(),
164            destination: ctx.accounts.mint_destination.key(),
165            amount,
166            author_fee,
167            protocol_fee
168        });
169
170        Ok(())
171    }
172
173    /// Withdraws Crate tokens.
174    /// Only the `withdraw_authority` can call this.
175    #[access_control(ctx.accounts.validate())]
176    pub fn withdraw(ctx: Context<Withdraw>, amount: u64) -> Result<()> {
177        // Do nothing if there is a zero amount.
178        if amount == 0 {
179            return Ok(());
180        }
181
182        let token_program = ctx.accounts.token_program.to_account_info();
183        let seeds = gen_crate_signer_seeds!(ctx.accounts.crate_token);
184        let signer_seeds: &[&[&[u8]]] = &[seeds];
185        let crate_token = &ctx.accounts.crate_token;
186        let state::Fees {
187            amount,
188            author_fee,
189            protocol_fee,
190        } = crate_token.apply_withdraw_fee(amount)?;
191
192        // share
193        token::transfer(
194            CpiContext::new_with_signer(
195                token_program.clone(),
196                token::Transfer {
197                    from: ctx.accounts.crate_underlying.to_account_info(),
198                    to: ctx.accounts.withdraw_destination.to_account_info(),
199                    authority: ctx.accounts.crate_token.to_account_info(),
200                },
201                signer_seeds,
202            ),
203            amount,
204        )?;
205
206        if author_fee > 0 {
207            token::transfer(
208                CpiContext::new_with_signer(
209                    token_program.clone(),
210                    token::Transfer {
211                        from: ctx.accounts.crate_underlying.to_account_info(),
212                        to: ctx.accounts.author_fee_destination.to_account_info(),
213                        authority: ctx.accounts.crate_token.to_account_info(),
214                    },
215                    signer_seeds,
216                ),
217                author_fee,
218            )?;
219        }
220
221        if protocol_fee > 0 {
222            token::transfer(
223                CpiContext::new_with_signer(
224                    token_program.clone(),
225                    token::Transfer {
226                        from: ctx.accounts.crate_underlying.to_account_info(),
227                        to: ctx.accounts.protocol_fee_destination.to_account_info(),
228                        authority: ctx.accounts.crate_token.to_account_info(),
229                    },
230                    signer_seeds,
231                ),
232                protocol_fee,
233            )?;
234        }
235
236        emit!(WithdrawEvent {
237            crate_key: ctx.accounts.crate_token.key(),
238            token: ctx.accounts.crate_underlying.mint,
239            destination: ctx.accounts.withdraw_destination.key(),
240            amount,
241            author_fee,
242            protocol_fee,
243        });
244
245        Ok(())
246    }
247}
248
249// --------------------------------
250// Context Structs
251// --------------------------------
252
253/// Accounts for [crate_token::new_crate].
254#[derive(Accounts)]
255pub struct NewCrate<'info> {
256    /// Information about the crate.
257    #[account(
258        init,
259        seeds = [
260            b"CrateToken".as_ref(),
261            crate_mint.key().to_bytes().as_ref()
262        ],
263        bump,
264        space = 8 + CrateToken::LEN,
265        payer = payer
266    )]
267    pub crate_token: Account<'info, CrateToken>,
268
269    /// [Mint] of the [CrateToken].
270    pub crate_mint: Account<'info, Mint>,
271
272    /// The authority that can change who fees go to.
273    /// CHECK: Arbitrary input.
274    pub fee_to_setter: UncheckedAccount<'info>,
275
276    /// The authority that can set fees.
277    /// CHECK: Arbitrary input.
278    pub fee_setter_authority: UncheckedAccount<'info>,
279
280    /// The authority that can issue new [CrateToken] tokens.
281    /// CHECK: Arbitrary input.
282    pub issue_authority: UncheckedAccount<'info>,
283
284    /// The authority that can redeem the [CrateToken] token underlying.
285    /// CHECK: Arbitrary input.
286    pub withdraw_authority: UncheckedAccount<'info>,
287
288    /// Owner of the author fee accounts.
289    /// CHECK: Arbitrary input.
290    pub author_fee_to: UncheckedAccount<'info>,
291
292    /// Payer of the crate initialization.
293    #[account(mut)]
294    pub payer: Signer<'info>,
295
296    /// System program.
297    pub system_program: Program<'info, System>,
298}
299
300/// Accounts for [crate_token::set_issue_fee] and [crate_token::set_withdraw_fee].
301#[derive(Accounts)]
302#[instruction(bump: u8)]
303pub struct SetFees<'info> {
304    /// Information about the crate.
305    #[account(mut)]
306    pub crate_token: Account<'info, CrateToken>,
307
308    /// Account that can set the fees.
309    pub fee_setter: Signer<'info>,
310}
311
312/// Accounts for [crate_token::set_fee_to].
313#[derive(Accounts)]
314#[instruction(bump: u8)]
315pub struct SetFeeTo<'info> {
316    /// Information about the crate.
317    #[account(mut)]
318    pub crate_token: Account<'info, CrateToken>,
319    /// Account that can set the fee recipient.
320    pub fee_to_setter: Signer<'info>,
321    /// Who the fees go to.
322    /// CHECK: Arbitrary input.
323    pub author_fee_to: UncheckedAccount<'info>,
324}
325
326/// Accounts for [crate_token::set_fee_to_setter].
327#[derive(Accounts)]
328#[instruction(bump: u8)]
329pub struct SetFeeToSetter<'info> {
330    /// Information about the crate.
331    #[account(mut)]
332    pub crate_token: Account<'info, CrateToken>,
333    /// Account that can set the fee recipient.
334    pub fee_to_setter: Signer<'info>,
335    /// Who will be able to change the fees next.
336    /// CHECK: Arbitrary input.
337    pub next_fee_to_setter: UncheckedAccount<'info>,
338}
339
340/// Accounts for [crate_token::issue].
341#[derive(Accounts)]
342pub struct Issue<'info> {
343    /// Information about the crate.
344    pub crate_token: Account<'info, CrateToken>,
345
346    /// [Mint] of the [CrateToken].
347    #[account(mut)]
348    pub crate_mint: Account<'info, Mint>,
349
350    /// Authority of the account issuing Crate tokens.
351    pub issue_authority: Signer<'info>,
352
353    /// Destination of the minted tokens.
354    #[account(mut)]
355    pub mint_destination: Account<'info, TokenAccount>,
356
357    /// Destination of the author fee tokens.
358    #[account(mut)]
359    pub author_fee_destination: Account<'info, TokenAccount>,
360
361    /// Destination of the protocol fee tokens.
362    #[account(mut)]
363    pub protocol_fee_destination: Account<'info, TokenAccount>,
364
365    /// [Token] program.
366    pub token_program: Program<'info, Token>,
367}
368
369/// Accounts for [crate_token::withdraw].
370#[derive(Accounts)]
371pub struct Withdraw<'info> {
372    /// Information about the crate.
373    pub crate_token: Account<'info, CrateToken>,
374
375    /// Crate-owned account of the tokens
376    #[account(mut)]
377    pub crate_underlying: Account<'info, TokenAccount>,
378
379    /// Authority that can withdraw.
380    pub withdraw_authority: Signer<'info>,
381
382    /// Destination of the withdrawn tokens.
383    #[account(mut)]
384    pub withdraw_destination: Account<'info, TokenAccount>,
385
386    /// Destination of the author fee tokens.
387    #[account(mut)]
388    pub author_fee_destination: Account<'info, TokenAccount>,
389
390    /// Destination of the protocol fee tokens.
391    #[account(mut)]
392    pub protocol_fee_destination: Account<'info, TokenAccount>,
393
394    /// [Token] program.
395    pub token_program: Program<'info, Token>,
396}
397
398#[error_code]
399/// Error codes.
400pub enum ErrorCode {
401    #[msg("Maximum fee exceeded.")]
402    MaxFeeExceeded,
403    #[msg("Freeze authority must either be the issuer or the Crate itself.")]
404    InvalidFreezeAuthority,
405}
406
407#[cfg(test)]
408mod tests {
409    use super::*;
410    #[test]
411    fn test_fee_to_address() {
412        let (key, bump) = Pubkey::find_program_address(&[b"CrateFees"], &crate::ID);
413        assert_eq!(key, FEE_TO_ADDRESS);
414        assert_eq!(bump, 254);
415    }
416}