Skip to content
Draft
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
17 changes: 9 additions & 8 deletions clients/js/test/collectV2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
publicKey,
sol,
addAmounts,
lamports,
} from '@metaplex-foundation/umi';
import test from 'ava';
import { collectV2, findTreeConfigPda } from '../src';
Expand All @@ -26,8 +27,8 @@ test('it can mint a compressed NFT and collect', async (t) => {

// The tree config has the rent amount.
let treeConfigBalance = await umi.rpc.getBalance(treeConfig);
const rentAmount = 0.00155904;
t.deepEqual(treeConfigBalance, sol(rentAmount));
const rentAmount = sol(0.00155904);
t.deepEqual(treeConfigBalance, rentAmount);

// When we mint a new NFT from the tree.
const leafOwnerA = generateSigner(umi);
Expand All @@ -48,8 +49,8 @@ test('it can mint a compressed NFT and collect', async (t) => {

// And tree config has the `mintV2` fee added.
treeConfigBalance = await umi.rpc.getBalance(treeConfig);
let collectAmount = 0.00009;
t.deepEqual(treeConfigBalance, sol(rentAmount + collectAmount));
let collectAmount = lamports(900);
t.deepEqual(treeConfigBalance, addAmounts(rentAmount, collectAmount));

// When leafOwnerA transfers the NFT to leafOwnerB.
const leafOwnerB = generateSigner(umi);
Expand Down Expand Up @@ -78,8 +79,8 @@ test('it can mint a compressed NFT and collect', async (t) => {

// And tree config has the `transferV2` fee added.
treeConfigBalance = await umi.rpc.getBalance(treeConfig);
collectAmount = collectAmount + 0.000006;
t.deepEqual(treeConfigBalance, sol(rentAmount + collectAmount));
collectAmount = addAmounts(collectAmount, lamports(60));
t.deepEqual(treeConfigBalance, addAmounts(rentAmount, collectAmount));

// When the destination account is airdropped the exact rent amount.
const destination = publicKey('2dgJVPC5fjLTBTmMvKDRig9JJUGK2Fgwr3EHShFxckhv');
Expand All @@ -91,12 +92,12 @@ test('it can mint a compressed NFT and collect', async (t) => {

// Then the tree config has only the rent amount.
treeConfigBalance = await umi.rpc.getBalance(treeConfig);
t.deepEqual(treeConfigBalance, sol(rentAmount));
t.deepEqual(treeConfigBalance, rentAmount);

// And the destination has the fee amount.
const destinationBalance = await umi.rpc.getBalance(destination);
t.deepEqual(
destinationBalance,
addAmounts(originalDestinationBalance, sol(collectAmount))
addAmounts(originalDestinationBalance, collectAmount)
);
});
261 changes: 259 additions & 2 deletions clients/js/test/createTree.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,81 @@
import { generateSigner, publicKey } from '@metaplex-foundation/umi';
import { createAccount } from '@metaplex-foundation/mpl-toolbox';
import {
generateSigner,
publicKey,
Context,
Signer,
TransactionBuilder,
transactionBuilder,
PublicKey,
} from '@metaplex-foundation/umi';
import test from 'ava';
import { TreeConfig, createTree, fetchTreeConfigFromSeeds } from '../src';
import {
TreeConfig,
createTree,
createTreeConfig,
fetchTreeConfigFromSeeds,
getCompressionProgramsForV1Ixs,
safeFetchTreeConfigFromSeeds,
} from '../src';
import { createUmi } from './_setup';
import {
SPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
getMerkleTreeSize,
} from '@metaplex-foundation/spl-account-compression';
import {
MPL_NOOP_PROGRAM_ID,
MPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
} from '@metaplex-foundation/mpl-account-compression';

const createTreeWithSpecificMerkleOwner = async (
context: Parameters<typeof createAccount>[0] &
Parameters<typeof createTreeConfig>[0] &
Pick<Context, 'rpc'>,
input: Omit<Parameters<typeof createTreeConfig>[1], 'merkleTree'> & {
merkleTree: Signer;
merkleTreeSize?: number;
canopyDepth?: number;
merkleTreeOwner?: PublicKey;
}
): Promise<TransactionBuilder> => {
const space =
input.merkleTreeSize ??
getMerkleTreeSize(input.maxDepth, input.maxBufferSize, input.canopyDepth);
const lamports = await context.rpc.getRent(space);

let programId;
if (input.compressionProgram) {
programId = Array.isArray(input.compressionProgram)
? input.compressionProgram[0]
: input.compressionProgram;
} else {
programId = context.programs.getPublicKey(
'splAccountCompression',
SPL_ACCOUNT_COMPRESSION_PROGRAM_ID
);
}

return (
transactionBuilder()
// Create the empty Merkle tree account.
.add(
createAccount(context, {
payer: input.payer ?? context.payer,
newAccount: input.merkleTree,
lamports,
space,
programId: input.merkleTreeOwner ? input.merkleTreeOwner : programId,
})
)
// Create the tree config.
.add(
createTreeConfig(context, {
...input,
merkleTree: input.merkleTree.publicKey,
})
)
);
};

test('it can create a Bubblegum tree', async (t) => {
// Given a brand new merkle tree signer.
Expand Down Expand Up @@ -60,3 +134,186 @@ test('it can create a Bubblegum tree using a newer size', async (t) => {
isPublic: false,
});
});

test('it can create a Bubblegum tree using mpl-account-compression and mpl-noop', async (t) => {
// Given a brand new merkle tree signer.
const umi = await createUmi();
const merkleTree = generateSigner(umi);

// For these tests, make sure `getCompressionPrograms` doesn't return spl programs.
const { logWrapper, compressionProgram } =
await getCompressionProgramsForV1Ixs(umi);
t.is(logWrapper, MPL_NOOP_PROGRAM_ID);
t.is(compressionProgram, MPL_ACCOUNT_COMPRESSION_PROGRAM_ID);

// When we create a tree at this address.
const builder = await createTree(umi, {
merkleTree,
maxDepth: 14,
maxBufferSize: 64,
...(await getCompressionProgramsForV1Ixs(umi)),
});
await builder.sendAndConfirm(umi);

// Then an account exists at the merkle tree address.
t.true(await umi.rpc.accountExists(merkleTree.publicKey));

// And a tree config was created with the correct data.
const treeConfig = await fetchTreeConfigFromSeeds(umi, {
merkleTree: merkleTree.publicKey,
});
t.like(treeConfig, <TreeConfig>{
treeCreator: publicKey(umi.identity),
treeDelegate: publicKey(umi.identity),
totalMintCapacity: 2n ** 14n,
numMinted: 0n,
isPublic: false,
});
});

test('it cannot create a Bubblegum tree using invalid logWrapper with spl-account-compression', async (t) => {
// Given a brand new merkle tree signer.
const umi = await createUmi();
const merkleTree = generateSigner(umi);

// When we create a tree at this address.
const builder = await createTree(umi, {
merkleTree,
maxDepth: 14,
maxBufferSize: 64,
logWrapper: generateSigner(umi).publicKey,
});

const promise = builder.sendAndConfirm(umi);

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

// And an account does not exist at the merkle tree address.
t.false(await umi.rpc.accountExists(merkleTree.publicKey));

// And a tree config was not created with the correct data.
const treeConfig = await safeFetchTreeConfigFromSeeds(umi, {
merkleTree: merkleTree.publicKey,
});
t.is(treeConfig, null);
});

test('it cannot create a Bubblegum tree using invalid logWrapper with mpl-account-compression', async (t) => {
// Given a brand new merkle tree signer.
const umi = await createUmi();
const merkleTree = generateSigner(umi);

// When we create a tree at this address.
const builder = await createTree(umi, {
merkleTree,
maxDepth: 14,
maxBufferSize: 64,
logWrapper: generateSigner(umi).publicKey,
compressionProgram: MPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
});

const promise = builder.sendAndConfirm(umi);

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

// And an account does not exist at the merkle tree address.
t.false(await umi.rpc.accountExists(merkleTree.publicKey));

// And a tree config was not created with the correct data.
const treeConfig = await safeFetchTreeConfigFromSeeds(umi, {
merkleTree: merkleTree.publicKey,
});
t.is(treeConfig, null);
});

test('it cannot create a Bubblegum tree when compression program does not match tree owned by spl-account-compression', async (t) => {
// Given a brand new merkle tree signer.
const umi = await createUmi();
const merkleTree = generateSigner(umi);

// When we create a tree at this address.
const builder = await createTreeWithSpecificMerkleOwner(umi, {
merkleTree,
maxDepth: 14,
maxBufferSize: 64,
compressionProgram: generateSigner(umi).publicKey,
merkleTreeOwner: umi.programs.getPublicKey(
'splAccountCompression',
SPL_ACCOUNT_COMPRESSION_PROGRAM_ID
),
});

const promise = builder.sendAndConfirm(umi);

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

// And an account does not exist at the merkle tree address.
t.false(await umi.rpc.accountExists(merkleTree.publicKey));

// And a tree config was not created with the correct data.
const treeConfig = await safeFetchTreeConfigFromSeeds(umi, {
merkleTree: merkleTree.publicKey,
});
t.is(treeConfig, null);
});

test('it cannot create a Bubblegum tree when compression program does not match tree owned by mpl-account-compression', async (t) => {
// Given a brand new merkle tree signer.
const umi = await createUmi();
const merkleTree = generateSigner(umi);

// When we create a tree at this address.
const builder = await createTreeWithSpecificMerkleOwner(umi, {
merkleTree,
maxDepth: 14,
maxBufferSize: 64,
logWrapper: MPL_NOOP_PROGRAM_ID,
compressionProgram: generateSigner(umi).publicKey,
merkleTreeOwner: MPL_ACCOUNT_COMPRESSION_PROGRAM_ID,
});

const promise = builder.sendAndConfirm(umi);

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

// And an account does not exist at the merkle tree address.
t.false(await umi.rpc.accountExists(merkleTree.publicKey));

// And a tree config was not created with the correct data.
const treeConfig = await safeFetchTreeConfigFromSeeds(umi, {
merkleTree: merkleTree.publicKey,
});
t.is(treeConfig, null);
});

test('it cannot create a Bubblegum tree with incorrect Merkle tree owner', async (t) => {
// Given a brand new merkle tree signer.
const umi = await createUmi();
const merkleTree = generateSigner(umi);

// When we create a tree at this address.
const builder = await createTreeWithSpecificMerkleOwner(umi, {
merkleTree,
maxDepth: 14,
maxBufferSize: 64,
merkleTreeOwner: generateSigner(umi).publicKey,
});

const promise = builder.sendAndConfirm(umi);

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

// And an account does not exist at the merkle tree address.
t.false(await umi.rpc.accountExists(merkleTree.publicKey));

// And a tree config was not created with the correct data.
const treeConfig = await safeFetchTreeConfigFromSeeds(umi, {
merkleTree: merkleTree.publicKey,
});
t.is(treeConfig, null);
});
Loading