Skip to content

Commit

Permalink
MET-129: Restrict token standard on bubblegum (metaplex-foundation#87)
Browse files Browse the repository at this point in the history
* Prevent non NonFungible from minting

* update clients

* updated tests

* update program's test accordingly to the latest changes regarding TokenStandard
  • Loading branch information
kstepanovdev authored Feb 27, 2024
1 parent c851fb7 commit 75efbe3
Show file tree
Hide file tree
Showing 10 changed files with 109 additions and 11 deletions.
13 changes: 13 additions & 0 deletions clients/js/src/generated/errors/mplBubblegum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,19 @@ export class CreatorDidNotUnverifyError extends ProgramError {
codeToErrorMap.set(0x1797, CreatorDidNotUnverifyError);
nameToErrorMap.set('CreatorDidNotUnverify', CreatorDidNotUnverifyError);

/** InvalidTokenStandard: Only NonFungible standard is supported */
export class InvalidTokenStandardError extends ProgramError {
readonly name: string = 'InvalidTokenStandard';

readonly code: number = 0x1798; // 6040

constructor(program: Program, cause?: Error) {
super('Only NonFungible standard is supported', program, cause);
}
}
codeToErrorMap.set(0x1798, InvalidTokenStandardError);
nameToErrorMap.set('InvalidTokenStandard', InvalidTokenStandardError);

/**
* Attempts to resolve a custom program error from the provided error code.
* @category Errors
Expand Down
57 changes: 56 additions & 1 deletion clients/js/test/mintV1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,16 @@ import {
generateSigner,
none,
publicKey,
some,
} from '@metaplex-foundation/umi';
import test from 'ava';
import { MetadataArgsArgs, fetchMerkleTree, hashLeaf, mintV1 } from '../src';
import {
MetadataArgsArgs,
TokenStandard,
fetchMerkleTree,
hashLeaf,
mintV1,
} from '../src';
import { createTree, createUmi } from './_setup';

test('it can mint an NFT from a Bubblegum tree', async (t) => {
Expand Down Expand Up @@ -46,3 +53,51 @@ test('it can mint an NFT from a Bubblegum tree', async (t) => {
});
t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(leaf));
});

test('it cannot mint an NFT from a Bubblegum tree because token standard is empty', async (t) => {
// Given an empty Bubblegum tree.
const umi = await createUmi();
const merkleTree = await createTree(umi);
const leafOwner = generateSigner(umi).publicKey;

// When we mint a new NFT from the tree using the following metadata.
const metadata: MetadataArgsArgs = {
name: 'My NFT',
uri: 'https://example.com/my-nft.json',
sellerFeeBasisPoints: 500, // 5%
collection: none(),
creators: [],
tokenStandard: none(),
};
const promise = mintV1(umi, {
leafOwner,
merkleTree,
metadata,
}).sendAndConfirm(umi);
// Then we expect a program error because metadata's token standard is empty.
await t.throwsAsync(promise, { name: 'InvalidTokenStandard' });
});

test('it cannot mint an NFT from a Bubblegum tree because token standard is wrong', async (t) => {
// Given an empty Bubblegum tree.
const umi = await createUmi();
const merkleTree = await createTree(umi);
const leafOwner = generateSigner(umi).publicKey;

// When we mint a new NFT from the tree using the following metadata.
const metadata: MetadataArgsArgs = {
name: 'My NFT',
uri: 'https://example.com/my-nft.json',
sellerFeeBasisPoints: 500, // 5%
collection: none(),
creators: [],
tokenStandard: some(TokenStandard.FungibleAsset),
};
const promise = mintV1(umi, {
leafOwner,
merkleTree,
metadata,
}).sendAndConfirm(umi);
// Then we expect a program error because metadata's token standard is FungibleAsset which is wrong.
await t.throwsAsync(promise, { name: 'InvalidTokenStandard' });
});
3 changes: 3 additions & 0 deletions clients/rust/src/generated/errors/mpl_bubblegum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,9 @@ pub enum MplBubblegumError {
/// 6039 (0x1797) - Creator did not unverify the metadata
#[error("Creator did not unverify the metadata")]
CreatorDidNotUnverify,
/// 6040 (0x1798) - Only NonFungible standard is supported
#[error("Only NonFungible standard is supported")]
InvalidTokenStandard,
}

impl solana_program::program_error::PrintProgramError for MplBubblegumError {
Expand Down
5 changes: 5 additions & 0 deletions idls/bubblegum.json
Original file line number Diff line number Diff line change
Expand Up @@ -2269,6 +2269,11 @@
"code": 6039,
"name": "CreatorDidNotUnverify",
"msg": "Creator did not unverify the metadata"
},
{
"code": 6040,
"name": "InvalidTokenStandard",
"msg": "Only NonFungible standard is supported"
}
],
"metadata": {
Expand Down
15 changes: 14 additions & 1 deletion programs/bubblegum/program/src/asserts.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use crate::{error::BubblegumError, state::metaplex_adapter::MetadataArgs, utils::cmp_pubkeys};
use crate::{
error::BubblegumError,
state::metaplex_adapter::{MetadataArgs, TokenStandard as MetadataTokenStandard},
utils::cmp_pubkeys,
};
use anchor_lang::prelude::*;
use mpl_token_metadata::{
accounts::{CollectionAuthorityRecord, Metadata, MetadataDelegateRecord},
Expand Down Expand Up @@ -187,3 +191,12 @@ pub fn assert_collection_membership(

Ok(())
}

/// Assert that the provided MetadataArgs contains info about Token Standard
/// and ensures that it's NonFungible
pub fn assert_metadata_token_standard(metadata: &MetadataArgs) -> Result<()> {
match metadata.token_standard {
Some(MetadataTokenStandard::NonFungible) => Ok(()),
_ => Err(BubblegumError::InvalidTokenStandard.into()),
}
}
2 changes: 2 additions & 0 deletions programs/bubblegum/program/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ pub enum BubblegumError {
PrimarySaleCanOnlyBeFlippedToTrue,
#[msg("Creator did not unverify the metadata")]
CreatorDidNotUnverify,
#[msg("Only NonFungible standard is supported")]
InvalidTokenStandard,
}

// Converts certain Token Metadata errors into Bubblegum equivalents
Expand Down
8 changes: 5 additions & 3 deletions programs/bubblegum/program/src/processor/mint.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
use std::collections::HashSet;

use anchor_lang::prelude::*;
use solana_program::keccak;
use spl_account_compression::{program::SplAccountCompression, wrap_application_data_v1, Noop};
use std::collections::HashSet;

use crate::{
asserts::assert_metadata_is_mpl_compatible,
asserts::{assert_metadata_is_mpl_compatible, assert_metadata_token_standard},
error::BubblegumError,
state::{leaf_schema::LeafSchema, metaplex_adapter::MetadataArgs, TreeConfig},
utils::{append_leaf, get_asset_id},
Expand Down Expand Up @@ -41,6 +40,7 @@ pub(crate) fn mint_v1(ctx: Context<MintV1>, message: MetadataArgs) -> Result<()>
let delegate = ctx.accounts.leaf_delegate.key();
let authority = &mut ctx.accounts.tree_authority;
let merkle_tree = &ctx.accounts.merkle_tree;

if !authority.is_public {
require!(
incoming_tree_delegate == authority.tree_creator
Expand Down Expand Up @@ -107,6 +107,8 @@ pub(crate) fn process_mint_v1<'info>(
}
}

assert_metadata_token_standard(&message)?;

// @dev: seller_fee_basis points is encoded twice so that it can be passed to marketplace
// instructions, without passing the entire, un-hashed MetadataArgs struct
let metadata_args_hash = keccak::hashv(&[message.try_to_vec()?.as_slice()]);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use anchor_lang::prelude::*;

use crate::{
error::BubblegumError, processor::process_collection_verification,
processor::verify_collection::CollectionVerification, state::metaplex_adapter::MetadataArgs,
error::BubblegumError,
processor::{process_collection_verification, verify_collection::CollectionVerification},
state::metaplex_adapter::MetadataArgs,
};

pub(crate) fn set_and_verify_collection<'info>(
Expand Down
6 changes: 4 additions & 2 deletions programs/bubblegum/program/tests/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ pub mod utils;
use anchor_lang::solana_program::instruction::InstructionError;

use solana_program_test::{tokio, BanksClientError};
use solana_sdk::signature::{Keypair, Signer};
use solana_sdk::transaction::TransactionError;
use solana_sdk::{
signature::{Keypair, Signer},
transaction::TransactionError,
};

use crate::utils::Error::BanksClient;
use utils::{
Expand Down
6 changes: 4 additions & 2 deletions programs/bubblegum/program/tests/utils/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use super::{
clone_keypair, digital_asset::DigitalAsset, program_test, tree::Tree, BanksResult, DirtyClone,
Error, LeafArgs, Result,
};
use bubblegum::state::metaplex_adapter::{Collection, Creator, MetadataArgs, TokenProgramVersion};
use bubblegum::state::metaplex_adapter::{
Collection, Creator, MetadataArgs, TokenProgramVersion, TokenStandard as MetadataTokenStandard,
};
use mpl_token_metadata::{
accounts::CollectionAuthorityRecord,
instructions::ApproveCollectionAuthorityBuilder,
Expand Down Expand Up @@ -130,7 +132,7 @@ impl BubblegumTestContext {
primary_sale_happened: false,
is_mutable: false,
edition_nonce: None,
token_standard: None,
token_standard: Some(MetadataTokenStandard::NonFungible),
token_program_version: TokenProgramVersion::Original,
collection: Some(Collection {
verified: false,
Expand Down

0 comments on commit 75efbe3

Please sign in to comment.