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

Fixes to set_and_verify_collection #43

Merged
merged 10 commits into from
Sep 28, 2023
2 changes: 1 addition & 1 deletion .github/workflows/deploy-program.yml
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ jobs:
if [ "${{ inputs.program }}" == "bubblegum" ]; then
echo ${{ secrets.BUBBLEGUM_DEPLOY_KEY }} > ./deploy-key.json
echo ${{ secrets.BUBBLEGUM_ID }} > ./program-id.json
echo PROGRAM_NAME="mpl_bubblegum" >> $GITHUB_ENV
echo PROGRAM_NAME="bubblegum" >> $GITHUB_ENV
fi

- name: Bump program version
Expand Down
2 changes: 1 addition & 1 deletion clients/js-solita/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"postpublish": "git push origin && git push origin --tags",
"build:docs": "typedoc",
"build": "rimraf dist && tsc -p tsconfig.json",
"start-validator": "solana-test-validator -ud --quiet --reset -c cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK -c 4VTQredsAmr1yzRJugLV6Mt6eu6XMeCwdkZ73wwVMWHv -c noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV -c 3RHkdjCwWyK2firrwFQGvXCxbUpBky1GTmb9EDK9hUnX -c metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s -c PwDiXFxQsGra4sFFTT8r1QWRMd4vfumiWC1jfWNfdYT --bpf-program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY ../../programs/.bin/mpl_bubblegum.so",
"start-validator": "solana-test-validator -ud --quiet --reset -c cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK -c 4VTQredsAmr1yzRJugLV6Mt6eu6XMeCwdkZ73wwVMWHv -c noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV -c 3RHkdjCwWyK2firrwFQGvXCxbUpBky1GTmb9EDK9hUnX -c metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s -c PwDiXFxQsGra4sFFTT8r1QWRMd4vfumiWC1jfWNfdYT --bpf-program BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY ../../programs/.bin/bubblegum.so",
"run-tests": "jest tests --detectOpenHandles",
"test": "start-server-and-test start-validator http://localhost:8899/health run-tests",
"api:gen": "DEBUG='(solita|rustbin):(info|error)' solita",
Expand Down
127 changes: 85 additions & 42 deletions clients/js-solita/tests/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
ConcurrentMerkleTreeAccount,
SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
SPL_NOOP_PROGRAM_ID,
ValidDepthSizePair
ValidDepthSizePair,
} from '@solana/spl-account-compression';

import {
Expand All @@ -30,19 +30,21 @@ import {
TokenProgramVersion,
TokenStandard,
Creator,
createSetDecompressibleStateInstruction,
DecompressibleState,
} from '../src/generated';
import { getLeafAssetId, computeDataHash, computeCreatorHash, computeCompressedNFTHash } from '../src/mpl-bubblegum';
import { BN } from 'bn.js';
import { PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID } from "@metaplex-foundation/mpl-token-metadata";
import {
ASSOCIATED_TOKEN_PROGRAM_ID,
TOKEN_PROGRAM_ID
} from "@solana/spl-token";
getLeafAssetId,
computeDataHash,
computeCreatorHash,
computeCompressedNFTHash,
} from '../src/mpl-bubblegum';
import { BN } from 'bn.js';
import { PROGRAM_ID as TOKEN_METADATA_PROGRAM_ID } from '@metaplex-foundation/mpl-token-metadata';
import { ASSOCIATED_TOKEN_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token';

function keypairFromSeed(seed: string) {
const expandedSeed = Uint8Array.from(
Buffer.from(`${seed}`),
);
const expandedSeed = Uint8Array.from(Buffer.from(`${seed}`));
return Keypair.fromSeed(expandedSeed.slice(0, 32));
}

Expand All @@ -69,16 +71,19 @@ async function setupTreeWithCompressedNFT(
compressedNFT: MetadataArgs,
depthSizePair: ValidDepthSizePair = {
maxDepth: 14,
maxBufferSize: 64
}
maxBufferSize: 64,
},
): Promise<{
merkleTree: PublicKey;
}> {
const payer = payerKeypair.publicKey;

const merkleTreeKeypair = Keypair.generate();
const merkleTree = merkleTreeKeypair.publicKey;
const space = getConcurrentMerkleTreeAccountSize(depthSizePair.maxDepth, depthSizePair.maxBufferSize);
const space = getConcurrentMerkleTreeAccountSize(
depthSizePair.maxDepth,
depthSizePair.maxBufferSize,
);
const allocTreeIx = SystemProgram.createAccount({
fromPubkey: payer,
newAccountPubkey: merkleTree,
Expand Down Expand Up @@ -107,6 +112,17 @@ async function setupTreeWithCompressedNFT(
BUBBLEGUM_PROGRAM_ID,
);

const setDecompressibleStateIx = createSetDecompressibleStateInstruction(
{
treeAuthority,
treeCreator: payer,
},
{
decompressableState: DecompressibleState.Enabled,
},
BUBBLEGUM_PROGRAM_ID,
);

const mintIx = createMintV1Instruction(
{
merkleTree,
Expand All @@ -123,7 +139,11 @@ async function setupTreeWithCompressedNFT(
},
);

const tx = new Transaction().add(allocTreeIx).add(createTreeIx).add(mintIx);
const tx = new Transaction()
.add(allocTreeIx)
.add(createTreeIx)
.add(setDecompressibleStateIx)
.add(mintIx);
tx.feePayer = payer;
await sendAndConfirmTransaction(connection, tx, [merkleTreeKeypair, payerKeypair], {
commitment: 'confirmed',
Expand Down Expand Up @@ -158,7 +178,10 @@ describe('Bubblegum tests', () => {
sellerFeeBasisPoints: 0,
isMutable: false,
};
await setupTreeWithCompressedNFT(connection, payerKeypair, compressedNFT, { maxDepth: 14, maxBufferSize: 64 });
await setupTreeWithCompressedNFT(connection, payerKeypair, compressedNFT, {
maxDepth: 14,
maxBufferSize: 64,
});
});

describe('Unit test compressed NFT instructions', () => {
Expand All @@ -174,7 +197,7 @@ describe('Bubblegum tests', () => {
share: 45,
verified: false,
},
]
];
const originalCompressedNFT = makeCompressedNFT('test', 'TST', creators);
beforeEach(async () => {
await connection.requestAirdrop(payer, LAMPORTS_PER_SOL);
Expand All @@ -185,7 +208,7 @@ describe('Bubblegum tests', () => {
{
maxDepth: 14,
maxBufferSize: 64,
}
},
);
merkleTree = result.merkleTree;
});
Expand All @@ -197,15 +220,12 @@ describe('Bubblegum tests', () => {
// Verify leaf exists.
const leafIndex = new BN.BN(0);
const assetId = await getLeafAssetId(merkleTree, leafIndex);
const verifyLeafIx = createVerifyLeafIx(
merkleTree,
{
root: account.getCurrentRoot(),
leaf: computeCompressedNFTHash(assetId, payer, payer, leafIndex, originalCompressedNFT),
leafIndex: 0,
proof: [],
}
);
const verifyLeafIx = createVerifyLeafIx(merkleTree, {
root: account.getCurrentRoot(),
leaf: computeCompressedNFTHash(assetId, payer, payer, leafIndex, originalCompressedNFT),
leafIndex: 0,
proof: [],
});
const tx = new Transaction().add(verifyLeafIx);
const txId = await sendAndConfirmTransaction(connection, tx, [payerKeypair], {
commitment: 'confirmed',
Expand Down Expand Up @@ -240,7 +260,7 @@ describe('Bubblegum tests', () => {
dataHash: Array.from(computeDataHash(originalCompressedNFT)),
creatorHash: Array.from(computeCreatorHash(originalCompressedNFT.creators)),
nonce: 0,
index: 0
index: 0,
},
);

Expand Down Expand Up @@ -268,16 +288,21 @@ describe('Bubblegum tests', () => {
dataHash: Array.from(computeDataHash(originalCompressedNFT)),
creatorHash: Array.from(computeCreatorHash(originalCompressedNFT.creators)),
nonce: 0,
index: 0
index: 0,
},
);

const burnTx = new Transaction().add(burnIx);
burnTx.feePayer = payer;
const burnTxId = await sendAndConfirmTransaction(connection, burnTx, [payerKeypair, newLeafOwnerKeypair], {
commitment: 'confirmed',
skipPreflight: true,
});
const burnTxId = await sendAndConfirmTransaction(
connection,
burnTx,
[payerKeypair, newLeafOwnerKeypair],
{
commitment: 'confirmed',
skipPreflight: true,
},
);

console.log('NFT burn tx:', burnTxId);
});
Expand All @@ -292,7 +317,11 @@ describe('Bubblegum tests', () => {
);
const nonce = new BN.BN(0);
const [voucher] = PublicKey.findProgramAddressSync(
[Buffer.from('voucher', 'utf8'), merkleTree.toBuffer(), Uint8Array.from(nonce.toArray('le', 8))],
[
Buffer.from('voucher', 'utf8'),
merkleTree.toBuffer(),
Uint8Array.from(nonce.toArray('le', 8)),
],
BUBBLEGUM_PROGRAM_ID,
);

Expand All @@ -311,7 +340,7 @@ describe('Bubblegum tests', () => {
dataHash: Array.from(computeDataHash(originalCompressedNFT)),
creatorHash: Array.from(computeCreatorHash(originalCompressedNFT.creators)),
nonce,
index: 0
index: 0,
},
);

Expand All @@ -326,7 +355,11 @@ describe('Bubblegum tests', () => {

// Decompress.
const [mint] = PublicKey.findProgramAddressSync(
[Buffer.from('asset', 'utf8'), merkleTree.toBuffer(), Uint8Array.from(nonce.toArray('le', 8))],
[
Buffer.from('asset', 'utf8'),
merkleTree.toBuffer(),
Uint8Array.from(nonce.toArray('le', 8)),
],
BUBBLEGUM_PROGRAM_ID,
);
const [tokenAccount] = PublicKey.findProgramAddressSync(
Expand All @@ -335,14 +368,19 @@ describe('Bubblegum tests', () => {
);
const [mintAuthority] = PublicKey.findProgramAddressSync(
[mint.toBuffer()],
BUBBLEGUM_PROGRAM_ID
BUBBLEGUM_PROGRAM_ID,
);
const [metadata] = PublicKey.findProgramAddressSync(
[Buffer.from('metadata', 'utf8'), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer()],
TOKEN_METADATA_PROGRAM_ID,
);
const [masterEdition] = PublicKey.findProgramAddressSync(
[Buffer.from('metadata', 'utf8'), TOKEN_METADATA_PROGRAM_ID.toBuffer(), mint.toBuffer(), Buffer.from('edition', 'utf8')],
[
Buffer.from('metadata', 'utf8'),
TOKEN_METADATA_PROGRAM_ID.toBuffer(),
mint.toBuffer(),
Buffer.from('edition', 'utf8'),
],
TOKEN_METADATA_PROGRAM_ID,
);

Expand All @@ -361,16 +399,21 @@ describe('Bubblegum tests', () => {
logWrapper: SPL_NOOP_PROGRAM_ID,
},
{
metadata: originalCompressedNFT
metadata: originalCompressedNFT,
},
);

const decompressTx = new Transaction().add(decompressIx);
decompressTx.feePayer = payer;
const decompressTxId = await sendAndConfirmTransaction(connection, decompressTx, [payerKeypair], {
commitment: 'confirmed',
skipPreflight: true,
});
const decompressTxId = await sendAndConfirmTransaction(
connection,
decompressTx,
[payerKeypair],
{
commitment: 'confirmed',
skipPreflight: true,
},
);

console.log('NFT decompress tx:', decompressTxId);
});
Expand Down
64 changes: 62 additions & 2 deletions clients/js/test/mintToCollectionV1.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
} from '../src';
import { createTree, createUmi } from './_setup';

test('it can mint an NFT from a collection', async (t) => {
test('it can mint an NFT from a collection (collection unverified in passed-in metadata)', async (t) => {
// Given an empty Bubblegum tree.
const umi = await createUmi();
const merkleTree = await createTree(umi);
Expand All @@ -45,7 +45,7 @@ test('it can mint an NFT from a collection', async (t) => {
isCollection: true,
}).sendAndConfirm(umi);

// When we mint a new NFT from the tree using the following metadata.
// When we mint a new NFT from the tree using the following metadata, with collection unverified.
const metadata: MetadataArgsArgs = {
name: 'My NFT',
uri: 'https://example.com/my-nft.json',
Expand Down Expand Up @@ -83,6 +83,66 @@ test('it can mint an NFT from a collection', async (t) => {
t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(leaf));
});

test('it can mint an NFT from a collection (collection verified in passed-in metadata)', 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);
t.is(merkleTreeAccount.tree.sequenceNumber, 0n);
t.is(merkleTreeAccount.tree.activeIndex, 0n);
t.is(merkleTreeAccount.tree.bufferSize, 1n);
t.is(merkleTreeAccount.tree.rightMostPath.index, 0);
t.is(merkleTreeAccount.tree.rightMostPath.leaf, defaultPublicKey());

// And a Collection NFT.
const collectionMint = generateSigner(umi);
await createNft(umi, {
mint: collectionMint,
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,
}).sendAndConfirm(umi);

// Then a new leaf was added to the merkle tree.
merkleTreeAccount = await fetchMerkleTree(umi, merkleTree);
t.is(merkleTreeAccount.tree.sequenceNumber, 1n);
t.is(merkleTreeAccount.tree.activeIndex, 1n);
t.is(merkleTreeAccount.tree.bufferSize, 2n);
t.is(merkleTreeAccount.tree.rightMostPath.index, 1);

// And the hash of the metadata matches the new leaf.
const leaf = hashLeaf(umi, {
merkleTree,
owner: leafOwner,
leafIndex: 0,
metadata: {
...metadata,
collection: some({ key: collectionMint.publicKey, verified: true }),
},
});
t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(leaf));
});

test('it can mint an NFT from a collection using a collection delegate', async (t) => {
// Given an empty Bubblegum tree.
const umi = await createUmi();
Expand Down
Loading
Loading