Skip to content

Commit

Permalink
Merge pull request #13 from olasunkanmi-SE/mint-nft
Browse files Browse the repository at this point in the history
feat(nft): Add rate limiting for NFT minting
  • Loading branch information
olasunkanmi-SE authored Sep 30, 2024
2 parents c69eb32 + b1056be commit 5ac1a4c
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 4 deletions.
2 changes: 2 additions & 0 deletions school/programs/school/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ pub enum NFTError {
InvalidSymbolLength,
#[msg("Invalid URI")]
InvalidURI,
#[msg("Rate limit exceeded")]
RateLimitExceeded,
}
41 changes: 37 additions & 4 deletions school/programs/school/src/instructions/mintnft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use mpl_token_metadata::{
use crate::{
constants::{NftMetaDataAttributes, MAX_NAME_LENGTH, MAX_SYMBOL_LENGTH},
error::NFTError,
RateLimit,
};
/// Defines the core functionality for creating and managing NFTs.
///
Expand All @@ -23,11 +24,12 @@ use crate::{
/// Implementors should ensure proper handling of Solana-specific NFT standards
/// and account structures.
pub trait NFTCreator<'info> {
fn mint_nft_token(&self) -> Result<()>;
fn mint_nft_token(&mut self) -> Result<()>;
fn create_nft_metadata(&self, name: &str, symbol: &str, uri: &str) -> Result<()>;
fn create_nft_master_edition(&self) -> Result<()>;
fn create_meta_data_accounts(&self) -> CreateMetadataAccountsV3<'info>;
fn create_master_edition_account(&self) -> CreateMasterEditionV3<'info>;
fn enforce_rate_limit(&mut self) -> Result<()>;
}

impl<'info> NFTCreator<'info> for Context<'_, '_, '_, 'info, InitNFT<'info>> {
Expand All @@ -38,7 +40,8 @@ impl<'info> NFTCreator<'info> for Context<'_, '_, '_, 'info, InitNFT<'info>> {
/// of 1 ensures the uniqueness of the NFT, adhering to the standard practice for NFT creation on Solana.
/// Note: This function assumes that the mint account is properly initialized for an NFT
/// (i.e., with decimals set to 0 and a supply limit of 1).
fn mint_nft_token(&self) -> Result<()> {
fn mint_nft_token(&mut self) -> Result<()> {
self.enforce_rate_limit()?;
let cpi_context = CpiContext::new(
self.accounts.token_program.to_account_info(),
MintTo {
Expand All @@ -51,6 +54,32 @@ impl<'info> NFTCreator<'info> for Context<'_, '_, '_, 'info, InitNFT<'info>> {
Ok(())
}

/// Enforces a rate limit on minting operations.
///
/// This function implements a sliding window rate limit:
/// - Allows up to 5 mints per hour
/// - Resets the count if more than an hour has passed since the last mint
///
/// # Errors
///
/// Returns `NFTError::RateLimitExceeded` if the rate limit is exceeded.
/// May also return errors from clock operations.
fn enforce_rate_limit(&mut self) -> Result<()> {
let clock = Clock::get()?;
let current_time = clock.unix_timestamp;

if current_time - self.accounts.rate_limit.last_mint_time < 3600 {
if self.accounts.rate_limit.mint_count >= 5 {
return Err(NFTError::RateLimitExceeded.into());
}
} else {
self.accounts.rate_limit.mint_count = 0;
}
self.accounts.rate_limit.last_mint_time = current_time;
self.accounts.rate_limit.mint_count += 1;
Ok(())
}

/// Constructs a CreateMetadataAccountsV3 struct for metadata account creation.
///
/// This function prepares the necessary account information for creating
Expand Down Expand Up @@ -178,13 +207,17 @@ pub struct InitNFT<'info> {
/// CHECK: Address is derived using a known PDA
#[account(mut, address=MetaDataAccount::find_pda(&mint.key()).0)]
pub metadata_account: AccountInfo<'info>,
#[account(mut, address= MasterEdition::find_pda(&mint.key()).0)]
/// CHECK: Address is derived using a known PDA
#[account(mut, address= MasterEdition::find_pda(&mint.key()).0)]
pub master_edition_account: AccountInfo<'info>,
pub associated_token_program: Program<'info, AssociatedToken>,
pub token_program: Program<'info, Token>,
pub token_metadata_program: Program<'info, Metadata>,
pub system_program: Program<'info, System>,
#[account(
mut, seeds= [b"rate_limit", authority.key().as_ref()], bump
)]
pub rate_limit: Account<'info, RateLimit>,
pub rent: Sysvar<'info, Rent>,
}

Expand All @@ -203,7 +236,7 @@ fn validate_nft_meta_data_attributes(props: NftMetaDataAttributes) -> Result<()>
require!(!name.is_empty(), NFTError::EmptyAttribute);
require!(!symbol.is_empty(), NFTError::EmptyAttribute);
require!(!uri.is_empty(), NFTError::EmptyAttribute);
require!(!name.len() <= MAX_NAME_LENGTH, NFTError::InvalidNameLength);
require!(name.len() <= MAX_NAME_LENGTH, NFTError::InvalidNameLength);
require!(
!symbol.len() <= MAX_SYMBOL_LENGTH,
NFTError::InvalidSymbolLength
Expand Down
2 changes: 2 additions & 0 deletions school/programs/school/src/state/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod ratelimit;
pub mod school;

pub use ratelimit::*;
pub use school::*;
6 changes: 6 additions & 0 deletions school/programs/school/src/state/ratelimit.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use anchor_lang::prelude::*;
#[account]
pub struct RateLimit {
pub last_mint_time: i64,
pub mint_count: u64,
}

0 comments on commit 5ac1a4c

Please sign in to comment.