-
Notifications
You must be signed in to change notification settings - Fork 77
Mint Governor Program #407
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
pileks
wants to merge
6
commits into
develop
Choose a base branch
from
pileks/met-6-mint-governor
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+4,203
−2
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
50669b4
scaffolding
pileks b48768f
mint governor instructions 1st pass
pileks 36b338d
mint governor client 1st pass
pileks 2255e61
mint authority tests first pass
pileks 0792831
mint governor - cleanup pass
pileks 5beb60d
Merge remote-tracking branch 'origin/develop' into pileks/met-6-mint-…
pileks File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| [package] | ||
| name = "mint_governor" | ||
| version = "0.7.0" | ||
| description = "Created with Anchor" | ||
| edition = "2021" | ||
|
|
||
| [lib] | ||
| crate-type = ["cdylib", "lib"] | ||
| name = "mint_governor" | ||
|
|
||
| [features] | ||
| no-entrypoint = [] | ||
| no-idl = [] | ||
| no-log-ix-name = [] | ||
| cpi = ["no-entrypoint"] | ||
| default = [] | ||
| production = [] | ||
|
|
||
| [dependencies] | ||
| anchor-lang = { version = "0.29.0", features = ["init-if-needed", "event-cpi"] } | ||
| anchor-spl = "0.29.0" | ||
| solana-security-txt = "1.1.1" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| pub const MINT_GOVERNOR_SEED: &[u8] = b"mint_governor"; | ||
| pub const MINT_AUTHORITY_SEED: &[u8] = b"mint_authority"; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| use super::*; | ||
|
|
||
| #[error_code] | ||
| pub enum MintGovernorError { | ||
| #[msg("Unauthorized: signer is not the admin")] | ||
| UnauthorizedAdmin, | ||
|
|
||
| #[msg("Unauthorized: signer is not the authorized minter")] | ||
| UnauthorizedMinter, | ||
|
|
||
| #[msg("Mint mismatch: mint_governor.mint does not match provided mint")] | ||
| MintMismatch, | ||
|
|
||
| #[msg("Mint limit exceeded: would exceed max_total")] | ||
| MintLimitExceeded, | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| use anchor_lang::prelude::*; | ||
|
|
||
| #[derive(AnchorSerialize, AnchorDeserialize, Clone)] | ||
| pub struct CommonFields { | ||
| pub slot: u64, | ||
| pub unix_timestamp: i64, | ||
| pub mint_governor_seq_num: u64, | ||
| } | ||
|
|
||
| #[event] | ||
| pub struct MintGovernorInitializedEvent { | ||
| pub common: CommonFields, | ||
| pub mint_governor: Pubkey, | ||
| pub mint: Pubkey, | ||
| pub admin: Pubkey, | ||
| pub create_key: Pubkey, | ||
| pub pda_bump: u8, | ||
| } | ||
|
|
||
| #[event] | ||
| pub struct MintAuthorityTransferredEvent { | ||
| pub common: CommonFields, | ||
| pub mint_governor: Pubkey, | ||
| pub mint: Pubkey, | ||
| } | ||
|
|
||
| #[event] | ||
| pub struct MintAuthorityAddedEvent { | ||
| pub common: CommonFields, | ||
| pub mint_governor: Pubkey, | ||
| pub mint_authority: Pubkey, | ||
| pub authorized_minter: Pubkey, | ||
| pub max_total: Option<u64>, | ||
| } | ||
|
|
||
| #[event] | ||
| pub struct TokensMintedEvent { | ||
| pub common: CommonFields, | ||
| pub mint_governor: Pubkey, | ||
| pub mint: Pubkey, | ||
| pub authorized_minter: Pubkey, | ||
| pub destination_ata: Pubkey, | ||
| pub amount: u64, | ||
| pub post_total_minted: u64, | ||
| pub post_mint_supply: u64, | ||
| } | ||
|
|
||
| #[event] | ||
| pub struct MintAuthorityUpdatedEvent { | ||
| pub common: CommonFields, | ||
| pub mint_governor: Pubkey, | ||
| pub mint_authority: Pubkey, | ||
| pub authorized_minter: Pubkey, | ||
| pub max_total: Option<u64>, | ||
| } | ||
|
|
||
| #[event] | ||
| pub struct MintAuthorityRemovedEvent { | ||
| pub common: CommonFields, | ||
| pub mint_governor: Pubkey, | ||
| pub authorized_minter: Pubkey, | ||
| pub total_minted: u64, | ||
| } | ||
|
|
||
| #[event] | ||
| pub struct MintGovernorAdminUpdatedEvent { | ||
| pub common: CommonFields, | ||
| pub mint_governor: Pubkey, | ||
| pub new_admin: Pubkey, | ||
| } | ||
|
|
||
| #[event] | ||
| pub struct MintAuthorityReclaimedEvent { | ||
| pub common: CommonFields, | ||
| pub mint_governor: Pubkey, | ||
| pub mint: Pubkey, | ||
| pub new_authority: Pubkey, | ||
| } |
74 changes: 74 additions & 0 deletions
74
programs/mint_governor/src/instructions/add_mint_authority.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| use anchor_lang::prelude::*; | ||
|
|
||
| use crate::{ | ||
| CommonFields, MintAuthority, MintAuthorityAddedEvent, MintGovernor, MintGovernorError, | ||
| MINT_AUTHORITY_SEED, | ||
| }; | ||
|
|
||
| #[derive(AnchorSerialize, AnchorDeserialize, Clone)] | ||
| pub struct AddMintAuthorityArgs { | ||
| pub max_total: Option<u64>, | ||
| } | ||
|
|
||
| #[derive(Accounts)] | ||
| pub struct AddMintAuthority<'info> { | ||
| #[account(mut)] | ||
| pub mint_governor: Account<'info, MintGovernor>, | ||
|
|
||
| #[account( | ||
| init, | ||
| payer = payer, | ||
| space = 8 + MintAuthority::INIT_SPACE, | ||
| seeds = [MINT_AUTHORITY_SEED, mint_governor.key().as_ref(), authorized_minter.key().as_ref()], | ||
| bump | ||
| )] | ||
| pub mint_authority: Account<'info, MintAuthority>, | ||
|
|
||
| #[account(address = mint_governor.admin @ MintGovernorError::UnauthorizedAdmin)] | ||
| pub admin: Signer<'info>, | ||
|
|
||
| /// CHECK: This is the address receiving minting rights, no validation needed | ||
| pub authorized_minter: UncheckedAccount<'info>, | ||
|
|
||
| #[account(mut)] | ||
| pub payer: Signer<'info>, | ||
|
|
||
| pub system_program: Program<'info, System>, | ||
| } | ||
|
|
||
| impl AddMintAuthority<'_> { | ||
| pub fn validate(&self, _args: &AddMintAuthorityArgs) -> Result<()> { | ||
| Ok(()) | ||
| } | ||
|
|
||
| pub fn handle(ctx: Context<Self>, args: AddMintAuthorityArgs) -> Result<()> { | ||
| let mint_governor = &mut ctx.accounts.mint_governor; | ||
| let mint_authority = &mut ctx.accounts.mint_authority; | ||
|
|
||
| mint_authority.set_inner(MintAuthority { | ||
| mint_governor: mint_governor.key(), | ||
| authorized_minter: ctx.accounts.authorized_minter.key(), | ||
| max_total: args.max_total, | ||
| total_minted: 0, | ||
| bump: ctx.bumps.mint_authority, | ||
| }); | ||
|
|
||
| mint_governor.seq_num += 1; | ||
|
|
||
| let clock = Clock::get()?; | ||
|
|
||
| emit!(MintAuthorityAddedEvent { | ||
| common: CommonFields { | ||
| slot: clock.slot, | ||
| unix_timestamp: clock.unix_timestamp, | ||
| mint_governor_seq_num: mint_governor.seq_num, | ||
| }, | ||
| mint_governor: mint_governor.key(), | ||
| mint_authority: ctx.accounts.mint_authority.key(), | ||
| authorized_minter: ctx.accounts.authorized_minter.key(), | ||
| max_total: args.max_total, | ||
| }); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } |
62 changes: 62 additions & 0 deletions
62
programs/mint_governor/src/instructions/initialize_mint_governor.rs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,62 @@ | ||
| use anchor_lang::prelude::*; | ||
| use anchor_spl::token::Mint; | ||
|
|
||
| use crate::{CommonFields, MintGovernor, MintGovernorInitializedEvent, MINT_GOVERNOR_SEED}; | ||
|
|
||
| #[derive(Accounts)] | ||
| pub struct InitializeMintGovernor<'info> { | ||
| pub mint: Account<'info, Mint>, | ||
|
|
||
| #[account( | ||
| init, | ||
| payer = payer, | ||
| space = 8 + MintGovernor::INIT_SPACE, | ||
| seeds = [MINT_GOVERNOR_SEED, mint.key().as_ref(), create_key.key().as_ref()], | ||
| bump | ||
| )] | ||
| pub mint_governor: Account<'info, MintGovernor>, | ||
|
|
||
| pub create_key: Signer<'info>, | ||
|
|
||
| /// CHECK: This is the future admin, no validation needed | ||
| pub admin: UncheckedAccount<'info>, | ||
|
|
||
| #[account(mut)] | ||
| pub payer: Signer<'info>, | ||
|
|
||
| pub system_program: Program<'info, System>, | ||
| } | ||
|
|
||
| impl InitializeMintGovernor<'_> { | ||
| pub fn validate(&self) -> Result<()> { | ||
| Ok(()) | ||
| } | ||
|
|
||
| pub fn handle(ctx: Context<Self>) -> Result<()> { | ||
| ctx.accounts.mint_governor.set_inner(MintGovernor { | ||
| mint: ctx.accounts.mint.key(), | ||
| admin: ctx.accounts.admin.key(), | ||
| create_key: ctx.accounts.create_key.key(), | ||
| seq_num: 0, | ||
| bump: ctx.bumps.mint_governor, | ||
| }); | ||
|
|
||
| let clock = Clock::get()?; | ||
| let mint_governor = &ctx.accounts.mint_governor; | ||
|
|
||
| emit!(MintGovernorInitializedEvent { | ||
| common: CommonFields { | ||
| slot: clock.slot, | ||
| unix_timestamp: clock.unix_timestamp, | ||
| mint_governor_seq_num: mint_governor.seq_num, | ||
| }, | ||
| mint_governor: mint_governor.key(), | ||
| mint: mint_governor.mint, | ||
| admin: mint_governor.admin, | ||
| create_key: mint_governor.create_key, | ||
| pda_bump: mint_governor.bump, | ||
| }); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| use anchor_lang::prelude::*; | ||
| use anchor_spl::token::{self, Mint, MintTo, Token, TokenAccount}; | ||
|
|
||
| use crate::{ | ||
| CommonFields, MintAuthority, MintGovernor, MintGovernorError, TokensMintedEvent, | ||
| MINT_GOVERNOR_SEED, | ||
| }; | ||
|
|
||
| #[derive(AnchorSerialize, AnchorDeserialize, Clone)] | ||
| pub struct MintTokensArgs { | ||
| pub amount: u64, | ||
| } | ||
|
|
||
| #[derive(Accounts)] | ||
| pub struct MintTokens<'info> { | ||
| #[account(mut)] | ||
| pub mint_governor: Account<'info, MintGovernor>, | ||
|
|
||
| #[account( | ||
| mut, | ||
| has_one = mint_governor | ||
| )] | ||
| pub mint_authority: Account<'info, MintAuthority>, | ||
|
|
||
| #[account( | ||
| mut, | ||
| address = mint_governor.mint @ MintGovernorError::MintMismatch | ||
| )] | ||
| pub mint: Account<'info, Mint>, | ||
|
|
||
| #[account(mut, has_one = mint)] | ||
| pub destination_ata: Account<'info, TokenAccount>, | ||
|
|
||
| #[account(address = mint_authority.authorized_minter @ MintGovernorError::UnauthorizedMinter)] | ||
| pub authorized_minter: Signer<'info>, | ||
|
|
||
| pub token_program: Program<'info, Token>, | ||
| } | ||
|
|
||
| impl MintTokens<'_> { | ||
| pub fn validate(&self, args: &MintTokensArgs) -> Result<()> { | ||
| // Check mint limit if max_total is set | ||
| if let Some(max_total) = self.mint_authority.max_total { | ||
| require_gte!( | ||
| max_total, | ||
| self.mint_authority.total_minted + args.amount, | ||
pileks marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| MintGovernorError::MintLimitExceeded | ||
| ); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| pub fn handle(ctx: Context<Self>, args: MintTokensArgs) -> Result<()> { | ||
| let mint_governor = &mut ctx.accounts.mint_governor; | ||
| let mint_authority = &mut ctx.accounts.mint_authority; | ||
|
|
||
| // Build PDA signer seeds | ||
| let mint_key = mint_governor.mint; | ||
| let create_key = mint_governor.create_key; | ||
| let bump = mint_governor.bump; | ||
| let seeds = &[ | ||
| MINT_GOVERNOR_SEED, | ||
| mint_key.as_ref(), | ||
| create_key.as_ref(), | ||
| &[bump], | ||
| ]; | ||
| let signer_seeds = &[&seeds[..]]; | ||
|
|
||
| // CPI to mint_to with mint_governor PDA as authority | ||
| token::mint_to( | ||
| CpiContext::new_with_signer( | ||
| ctx.accounts.token_program.to_account_info(), | ||
| MintTo { | ||
| mint: ctx.accounts.mint.to_account_info(), | ||
| to: ctx.accounts.destination_ata.to_account_info(), | ||
| authority: mint_governor.to_account_info(), | ||
| }, | ||
| signer_seeds, | ||
| ), | ||
| args.amount, | ||
| )?; | ||
|
|
||
| // Update total_minted | ||
| mint_authority.total_minted += args.amount; | ||
pileks marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Increment seq_num | ||
| mint_governor.seq_num += 1; | ||
pileks marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // Emit event | ||
| let clock = Clock::get()?; | ||
|
|
||
| // Reload mint to get post-mint supply | ||
| ctx.accounts.mint.reload()?; | ||
|
|
||
| emit!(TokensMintedEvent { | ||
| common: CommonFields { | ||
| slot: clock.slot, | ||
| unix_timestamp: clock.unix_timestamp, | ||
| mint_governor_seq_num: mint_governor.seq_num, | ||
| }, | ||
| mint_governor: mint_governor.key(), | ||
| mint: mint_governor.mint, | ||
| authorized_minter: ctx.accounts.authorized_minter.key(), | ||
| destination_ata: ctx.accounts.destination_ata.key(), | ||
| amount: args.amount, | ||
| post_total_minted: mint_authority.total_minted, | ||
| post_mint_supply: ctx.accounts.mint.supply, | ||
| }); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.