Skip to content
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

MET-129: Restrict token standard on bubblegum #87

Merged
merged 4 commits into from
Feb 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think its clearer to just call it TokenStandard since that is the type and that's how its been used here and in other places.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to rename it because assert.rs already contains TokenStandard from mpl_token_metadata::types. These types are different.

};
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
Loading