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

Use Data delegate #107

Merged
merged 3 commits into from
Sep 5, 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
272 changes: 271 additions & 1 deletion clients/js/test/updateMetadata.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { createNft } from '@metaplex-foundation/mpl-token-metadata';
import {
createNft,
delegateDataV1,
TokenStandard,
findMetadataDelegateRecordPda,
MetadataDelegateRole,
delegateCollectionV1,
approveCollectionAuthority,
findCollectionAuthorityRecordPda,
} from '@metaplex-foundation/mpl-token-metadata';
import {
defaultPublicKey,
generateSigner,
Expand Down Expand Up @@ -307,6 +316,267 @@ test('it can update metadata using collection update authority when collection i
t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf));
});

test('it can update metadata using old collection authority when collection is verified', async (t) => {
// Given an empty Bubblegum tree.
const umi = await createUmi();
const merkleTree = await createTree(umi);
const leafOwner = generateSigner(umi).publicKey;
let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree);

// And a Collection NFT.
const collectionMint = generateSigner(umi);
const collectionAuthority = generateSigner(umi);
await createNft(umi, {
mint: collectionMint,
authority: collectionAuthority,
name: 'My Collection',
uri: 'https://example.com/my-collection.json',
sellerFeeBasisPoints: percentAmount(5.5), // 5.5%
isCollection: true,
}).sendAndConfirm(umi);

// When we mint a new NFT from the tree using the following metadata, with collection verified.
const metadata: MetadataArgsArgs = {
name: 'My NFT',
uri: 'https://example.com/my-nft.json',
sellerFeeBasisPoints: 550, // 5.5%
collection: {
key: collectionMint.publicKey,
verified: true,
},
creators: [],
};
await mintToCollectionV1(umi, {
leafOwner,
merkleTree,
metadata,
collectionMint: collectionMint.publicKey,
collectionAuthority,
}).sendAndConfirm(umi);

// When we approve a collection authority record.
const newCollectionAuthority = generateSigner(umi);
let collectionAuthorityRecordPda = findCollectionAuthorityRecordPda(umi, {
mint: collectionMint.publicKey,
collectionAuthority: newCollectionAuthority.publicKey,
});

await approveCollectionAuthority(umi, {
collectionAuthorityRecord: collectionAuthorityRecordPda,
newCollectionAuthority: newCollectionAuthority.publicKey,
updateAuthority: collectionAuthority,
mint: collectionMint.publicKey,
}).sendAndConfirm(umi);

// And when metadata is updated.
const updateArgs: UpdateArgsArgs = {
name: some('New name'),
uri: some('https://updated-example.com/my-nft.json'),
};
await updateMetadata(umi, {
leafOwner,
merkleTree,
root: getCurrentRoot(merkleTreeAccount.tree),
nonce: 0,
index: 0,
currentMetadata: metadata,
proof: [],
updateArgs,
authority: newCollectionAuthority,
collectionMint: collectionMint.publicKey,
collectionAuthorityRecordPda,
}).sendAndConfirm(umi);

// Then the leaf was updated in the merkle tree.
const updatedLeaf = hashLeaf(umi, {
merkleTree,
owner: leafOwner,
leafIndex: 0,
metadata: {
...metadata,
name: 'New name',
uri: 'https://updated-example.com/my-nft.json',
},
});
merkleTreeAccount = await fetchMerkleTree(umi, merkleTree);
t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf));
});

test('it can update metadata using collection data delegate when collection is verified', async (t) => {
// Given an empty Bubblegum tree.
const umi = await createUmi();
const merkleTree = await createTree(umi);
const leafOwner = generateSigner(umi).publicKey;
let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree);

// And a Collection NFT.
const collectionMint = generateSigner(umi);
const collectionAuthority = generateSigner(umi);
await createNft(umi, {
mint: collectionMint,
authority: collectionAuthority,
name: 'My Collection',
uri: 'https://example.com/my-collection.json',
sellerFeeBasisPoints: percentAmount(5.5), // 5.5%
isCollection: true,
}).sendAndConfirm(umi);

// When we mint a new NFT from the tree using the following metadata, with collection verified.
const metadata: MetadataArgsArgs = {
name: 'My NFT',
uri: 'https://example.com/my-nft.json',
sellerFeeBasisPoints: 550, // 5.5%
collection: {
key: collectionMint.publicKey,
verified: true,
},
creators: [],
};
await mintToCollectionV1(umi, {
leafOwner,
merkleTree,
metadata,
collectionMint: collectionMint.publicKey,
collectionAuthority,
}).sendAndConfirm(umi);

// When we approve a data delegate.
const dataDelegate = generateSigner(umi);
await delegateDataV1(umi, {
mint: collectionMint.publicKey,
authority: collectionAuthority,
delegate: dataDelegate.publicKey,
tokenStandard: TokenStandard.NonFungible,
}).sendAndConfirm(umi);

let delegateRecordPda = findMetadataDelegateRecordPda(umi, {
mint: collectionMint.publicKey,
delegateRole: MetadataDelegateRole.Data,
delegate: dataDelegate.publicKey,
updateAuthority: collectionAuthority.publicKey,
});

// And when metadata is updated.
const updateArgs: UpdateArgsArgs = {
name: some('New name'),
uri: some('https://updated-example.com/my-nft.json'),
};
await updateMetadata(umi, {
leafOwner,
merkleTree,
root: getCurrentRoot(merkleTreeAccount.tree),
nonce: 0,
index: 0,
currentMetadata: metadata,
proof: [],
updateArgs,
authority: dataDelegate,
collectionMint: collectionMint.publicKey,
collectionAuthorityRecordPda: delegateRecordPda,
}).sendAndConfirm(umi);

// Then the leaf was updated in the merkle tree.
const updatedLeaf = hashLeaf(umi, {
merkleTree,
owner: leafOwner,
leafIndex: 0,
metadata: {
...metadata,
name: 'New name',
uri: 'https://updated-example.com/my-nft.json',
},
});
merkleTreeAccount = await fetchMerkleTree(umi, merkleTree);
t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf));
});

test('it cannot update metadata using collection collection delegate when collection is verified', async (t) => {
// Given an empty Bubblegum tree.
const umi = await createUmi();
const merkleTree = await createTree(umi);
const leafOwner = generateSigner(umi).publicKey;
let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree);

// And a Collection NFT.
const collectionMint = generateSigner(umi);
const collectionAuthority = generateSigner(umi);
await createNft(umi, {
mint: collectionMint,
authority: collectionAuthority,
name: 'My Collection',
uri: 'https://example.com/my-collection.json',
sellerFeeBasisPoints: percentAmount(5.5), // 5.5%
isCollection: true,
}).sendAndConfirm(umi);

// When we mint a new NFT from the tree using the following metadata, with collection verified.
const metadata: MetadataArgsArgs = {
name: 'My NFT',
uri: 'https://example.com/my-nft.json',
sellerFeeBasisPoints: 550, // 5.5%
collection: {
key: collectionMint.publicKey,
verified: true,
},
creators: [],
};
await mintToCollectionV1(umi, {
leafOwner,
merkleTree,
metadata,
collectionMint: collectionMint.publicKey,
collectionAuthority,
}).sendAndConfirm(umi);

// When we approve a data delegate.
const collectionDelegate = generateSigner(umi);
await delegateCollectionV1(umi, {
mint: collectionMint.publicKey,
authority: collectionAuthority,
delegate: collectionDelegate.publicKey,
tokenStandard: TokenStandard.NonFungible,
}).sendAndConfirm(umi);

let delegate_record_pda = findMetadataDelegateRecordPda(umi, {
mint: collectionMint.publicKey,
delegateRole: MetadataDelegateRole.Collection,
delegate: collectionDelegate.publicKey,
updateAuthority: collectionAuthority.publicKey,
});

// And when metadata is updated.
const updateArgs: UpdateArgsArgs = {
name: some('New name'),
uri: some('https://updated-example.com/my-nft.json'),
};
const promise = updateMetadata(umi, {
leafOwner,
merkleTree,
root: getCurrentRoot(merkleTreeAccount.tree),
nonce: 0,
index: 0,
currentMetadata: metadata,
proof: [],
updateArgs,
authority: collectionDelegate,
collectionMint: collectionMint.publicKey,
collectionAuthorityRecordPda: delegate_record_pda,
}).sendAndConfirm(umi);

// Then we expect a program error.
await t.throwsAsync(promise, { name: 'InvalidDelegateRecord' });

// And the leaf was not updated in the merkle tree.
const notUpdatedLeaf = hashLeaf(umi, {
merkleTree,
owner: leafOwner,
leafIndex: 0,
metadata,
});
merkleTreeAccount = await fetchMerkleTree(umi, merkleTree);
t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(notUpdatedLeaf));
});

test('it can update metadata using the getAssetWithProof helper with verified collection', async (t) => {
// Given we increase the timeout for this test.
t.timeout(20000);
Expand Down
3 changes: 2 additions & 1 deletion programs/bubblegum/program/src/asserts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ pub fn assert_has_collection_authority(
mint: &Pubkey,
collection_authority: &Pubkey,
delegate_record: Option<&AccountInfo>,
metadata_delegate_role: MetadataDelegateRole,
) -> Result<()> {
// Mint is the correct one for the metadata account.
if collection_data.mint != *mint {
Expand All @@ -112,7 +113,7 @@ pub fn assert_has_collection_authority(
let (ca_pda, ca_bump) = CollectionAuthorityRecord::find_pda(mint, collection_authority);
let (md_pda, md_bump) = MetadataDelegateRecord::find_pda(
mint,
MetadataDelegateRole::Collection,
metadata_delegate_role,
&collection_data.update_authority,
collection_authority,
);
Expand Down
2 changes: 2 additions & 0 deletions programs/bubblegum/program/src/processor/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anchor_lang::prelude::*;
use mpl_token_metadata::types::MetadataDelegateRole;
use solana_program::{account_info::AccountInfo, pubkey::Pubkey};
use spl_account_compression::wrap_application_data_v1;

Expand Down Expand Up @@ -197,6 +198,7 @@ fn process_collection_verification_mpl_only<'info>(
collection_mint.key,
collection_authority.key,
collection_authority_record,
MetadataDelegateRole::Collection,
)?;

// Update collection in metadata args. Note since this is a mutable reference,
Expand Down
2 changes: 2 additions & 0 deletions programs/bubblegum/program/src/processor/update_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use anchor_lang::prelude::*;
use mpl_token_metadata::types::MetadataDelegateRole;
use spl_account_compression::{program::SplAccountCompression, wrap_application_data_v1, Noop};

use crate::{
Expand Down Expand Up @@ -85,6 +86,7 @@ fn assert_authority_matches_collection<'info>(
collection_mint.key,
collection_authority.key,
collection_authority_record.as_ref(),
MetadataDelegateRole::Data,
)?;

Ok(())
Expand Down
Loading
Loading