Skip to content

Commit

Permalink
enable bubblegum anchor tests
Browse files Browse the repository at this point in the history
- update package.json and tsconfig.json
- cleanup bubblegum test file and imports
- enable anchor test --skip-lint to run after anchor workspace members are built
  • Loading branch information
jshiohaha authored and danenbm committed Aug 10, 2022
1 parent c3f8b7a commit 1a68c2e
Show file tree
Hide file tree
Showing 13 changed files with 4,889 additions and 547 deletions.
10 changes: 7 additions & 3 deletions Anchor.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ cluster = "localnet"
wallet = "~/.config/solana/id.json"

[programs.localnet]
mpl_auction_house="hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk"
auction_house="hausS13jsjafwWwGqZTUQRmWyvyxn9EQpqMwV1PBBmk"
bubblegum="BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY"

[[test.genesis]]
Expand Down Expand Up @@ -39,7 +39,8 @@ members = [
"token-metadata/program",
"auction-house/program",
"fixed-price-sale/program",
"bubblegum/program"
"bubblegum/program",
"candy-wrapper/program"
]

[test.validator]
Expand All @@ -54,8 +55,11 @@ address = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"
[[test.validator.clone]]
address = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"

[[test.validator.clone]]
address = "GRoLLzvxpxxu2PGNJMMeZPyMxjAUH9pKqxGXV9DGiceU"

[test]
startup_wait = 10000

[scripts]
test = "yarn run ts-mocha -t 1000000 tests/**/*-test.ts"
test = "yarn run ts-mocha -p ./tsconfig.build.json -t 1000000 ./**/js/test/*-test.ts"
2 changes: 1 addition & 1 deletion auction-house/js/test/account.auction-house.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AccountInfo, Keypair, PublicKey } from '@solana/web3.js';
import { AuctionHouse, AuctionHouseArgs } from 'src/generated';
import { AuctionHouse, AuctionHouseArgs } from '../src/generated';
import test from 'tape';
import spok from 'spok';

Expand Down
57 changes: 50 additions & 7 deletions bubblegum/js/package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,57 @@
{
"name": "bubblegum",
"version": "1.0.0",
"name": "@metaplex-foundation/mpl-bubblegum",
"version": "0.0.1",
"description": "SDK for MPL Bubblegum contract",
"main": "index.ts",
"main": "dist/src/mpl-bubblegum.js",
"types": "dist/src/mpl-bubblegum.d.ts",
"scripts": {
"check:publish-ready": "yarn build && yarn test",
"preversion": "yarn check:publish-ready",
"postversion": "git add package.json && git commit -m \"chore: update $npm_package_name to v$npm_package_version\" && git tag $npm_package_name@$npm_package_version",
"prepublish": "yarn check:publish-ready",
"postpublish": "git push origin && git push origin --tags",
"build:docs": "typedoc",
"build": "rimraf dist && tsc -p tsconfig.json",
"test": "tape dist/test/*.js",
"api:gen": "DEBUG='(solita|rustbin):(info|error)' solita",
"lint": "eslint \"{src,test}/**/*.ts\" --format stylish",
"fix:lint": "yarn lint --fix",
"prettier": "prettier \"{src,test}/**/*.ts\" --check",
"fix:prettier": "prettier --write src/",
"fix": "yarn fix:lint && yarn fix:prettier"
},
"files": [
"/dist"
],
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org"
},
"keywords": [
"nft",
"metaplex",
"solana",
"blockchain"
],
"homepage": "https://metaplex.com",
"repository": "https://github.com/metaplex-foundation/metaplex-program-library.git",
"author": "Metaplex Maintainers <[email protected]>",
"license": "Apache-2.0",
"dependencies": {
"@metaplex-foundation/rustbin": "^0.3.1",
"@metaplex-foundation/solita": "^0.12.2"
"@metaplex-foundation/beet": "^0.1.0",
"@metaplex-foundation/beet-solana": "^0.1.1",
"@solana/web3.js": "^1.35.1",
"@sorend-solana/gummyroll": "^0.0.6",
"@sorend-solana/utils": "^0.0.5"
},
"scripts": {
"api:gen": "DEBUG='(solita|rustbin):(info|error)' solita"
"devDependencies": {
"@metaplex-foundation/rustbin": "^0.3.1",
"@metaplex-foundation/solita": "^0.12.2",
"@types/tape": "^4.13.2",
"eslint": "^8.3.0",
"rimraf": "^3.0.2",
"spok": "^1.4.3",
"tape": "^5.5.2",
"typescript": "^4.6.2"
}
}
150 changes: 50 additions & 100 deletions bubblegum/js/src/convenience.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import { BN } from "@project-serum/anchor";
import {
TransactionInstruction,
PublicKey,
Connection,
AccountInfo,
} from "@solana/web3.js";
import { keccak_256 } from "js-sha3";
import { Creator, TreeConfig, MintRequest, PROGRAM_ID } from "./generated";
import {
CANDY_WRAPPER_PROGRAM_ID,
bufferToArray,
num16ToBuffer,
} from "../../utils";
import {
PROGRAM_ID as GUMMYROLL_PROGRAM_ID,
createAllocTreeIx,
} from "../../gummyroll";
import { createCreateTreeInstruction } from "./generated";
import { assert } from "chai";
import { BN } from '@project-serum/anchor';
import { TransactionInstruction, PublicKey, Connection } from '@solana/web3.js';
import { keccak_256 } from 'js-sha3';
import { Creator, TreeConfig, MintRequest, PROGRAM_ID } from './generated';
import { CANDY_WRAPPER_PROGRAM_ID, bufferToArray, num16ToBuffer } from '@sorend-solana/utils';
import { createAllocTreeIx, PROGRAM_ID as GUMMYROLL_PROGRAM_ID } from '@sorend-solana/gummyroll';

import { createCreateTreeInstruction } from './generated';
import { assert } from 'chai';

export async function getBubblegumAuthorityPDA(merkleRollPubKey: PublicKey) {
const [bubblegumAuthorityPDAKey] = await PublicKey.findProgramAddress(
[merkleRollPubKey.toBuffer()],
PROGRAM_ID
PROGRAM_ID,
);
return bubblegumAuthorityPDAKey;
}
Expand All @@ -31,26 +20,23 @@ export async function getDefaultMintRequestPDA(merkleRollPubKey: PublicKey) {
const authority = await getBubblegumAuthorityPDA(merkleRollPubKey);
const [defaultMintRequestKey] = await PublicKey.findProgramAddress(
[merkleRollPubKey.toBuffer(), authority.toBuffer()],
PROGRAM_ID
PROGRAM_ID,
);
return defaultMintRequestKey;
}

export async function getMintRequestPDA(
merkleRollPubKey: PublicKey,
requester: PublicKey
) {
export async function getMintRequestPDA(merkleRollPubKey: PublicKey, requester: PublicKey) {
const [mintAuthorityRequest] = await PublicKey.findProgramAddress(
[merkleRollPubKey.toBuffer(), requester.toBuffer()],
PROGRAM_ID
PROGRAM_ID,
);
return mintAuthorityRequest;
}

export async function getMintRequest(
connection: Connection,
merkleRollPubKey: PublicKey,
requester: PublicKey
requester: PublicKey,
): Promise<MintRequest> {
const requestPda = await getMintRequestPDA(merkleRollPubKey, requester);
return await MintRequest.fromAccountAddress(connection, requestPda);
Expand All @@ -59,97 +45,78 @@ export async function getMintRequest(
export async function assertOnChainMintRequest(
connection: Connection,
expectedState: MintRequest,
mintRequestPDA: PublicKey
mintRequestPDA: PublicKey,
) {
const request = await MintRequest.fromAccountAddress(
connection,
mintRequestPDA
);
const request = await MintRequest.fromAccountAddress(connection, mintRequestPDA);
const { mintAuthority, numMintsApproved, numMintsRequested } = expectedState;
assert(
request.mintAuthority.equals(mintAuthority),
`Request should have mint authority ${mintAuthority.toString()}, but has ${request.mintAuthority.toString()}`
`Request should have mint authority ${mintAuthority.toString()}, but has ${request.mintAuthority.toString()}`,
);
assert(
new BN(request.numMintsApproved).eq(new BN(numMintsApproved)),
`Request should${numMintsApproved > 0 ? "" : " not"} be approved`
`Request should${numMintsApproved > 0 ? '' : ' not'} be approved`,
);
assert(
new BN(request.numMintsRequested).eq(new BN(numMintsRequested)),
`Request should have requested ${numMintsRequested}, but has ${request.numMintsRequested}`
`Request should have requested ${numMintsRequested}, but has ${request.numMintsRequested}`,
);
}

export async function assertOnChainTreeAuthority(
connection: Connection,
expectedState: TreeConfig,
authorityPDA: PublicKey
authorityPDA: PublicKey,
) {
const authority = await TreeConfig.fromAccountAddress(
connection,
authorityPDA
);
const { creator, delegate, totalMintCapacity, numMintsApproved, numMinted } =
expectedState;
const authority = await TreeConfig.fromAccountAddress(connection, authorityPDA);
const { creator, delegate, totalMintCapacity, numMintsApproved, numMinted } = expectedState;
assert(
authority.creator.equals(creator),
`Authority should have creator ${creator.toString()}, but has ${authority.creator.toString()}`
`Authority should have creator ${creator.toString()}, but has ${authority.creator.toString()}`,
);
assert(
authority.delegate.equals(delegate),
`Authority should have delegate ${delegate.toString()}, but has ${authority.delegate.toString()}`
`Authority should have delegate ${delegate.toString()}, but has ${authority.delegate.toString()}`,
);
assert(
new BN(authority.totalMintCapacity).eq(new BN(totalMintCapacity)),
`Authority should have total mint capacity ${totalMintCapacity}, but has ${authority.totalMintCapacity}`
`Authority should have total mint capacity ${totalMintCapacity}, but has ${authority.totalMintCapacity}`,
);
assert(
new BN(authority.numMintsApproved).eq(new BN(numMintsApproved)),
`Authority should have num mints approved ${numMintsApproved}, but has ${authority.numMintsApproved}`
`Authority should have num mints approved ${numMintsApproved}, but has ${authority.numMintsApproved}`,
);
assert(
new BN(authority.numMinted).eq(new BN(numMinted)),
`Authority should have num minted ${numMinted}, but has ${authority.numMinted}`
`Authority should have num minted ${numMinted}, but has ${authority.numMinted}`,
);
}

export async function getNonceCount(
connection: Connection,
tree: PublicKey
): Promise<BN> {
export async function getNonceCount(connection: Connection, tree: PublicKey): Promise<BN> {
const treeAuthority = await getBubblegumAuthorityPDA(tree);
return new BN(
(await TreeConfig.fromAccountAddress(connection, treeAuthority)).numMinted
);
return new BN((await TreeConfig.fromAccountAddress(connection, treeAuthority)).numMinted);
}

export async function getVoucherPDA(
connection: Connection,
tree: PublicKey,
leafIndex: number
leafIndex: number,
): Promise<PublicKey> {
let [voucher] = await PublicKey.findProgramAddress(
[
Buffer.from("voucher", "utf8"),
Buffer.from('voucher', 'utf8'),
tree.toBuffer(),
Uint8Array.from((new BN(leafIndex)).toArray("le", 8)),
Uint8Array.from(new BN(leafIndex).toArray('le', 8)),
],
PROGRAM_ID
PROGRAM_ID,
);
return voucher;
}

export async function getLeafAssetId(
tree: PublicKey,
leafIndex: BN
): Promise<PublicKey> {
export async function getLeafAssetId(tree: PublicKey, leafIndex: BN): Promise<PublicKey> {
let [assetId] = await PublicKey.findProgramAddress(
[
Buffer.from("asset", "utf8"),
tree.toBuffer(),
Uint8Array.from(leafIndex.toArray("le", 8)),
],
PROGRAM_ID
[Buffer.from('asset', 'utf8'), tree.toBuffer(), Uint8Array.from(leafIndex.toArray('le', 8))],
PROGRAM_ID,
);
return assetId;
}
Expand All @@ -161,15 +128,15 @@ export async function getCreateTreeIxs(
canopyDepth: number,
payer: PublicKey,
merkleRoll: PublicKey,
treeCreator: PublicKey
treeCreator: PublicKey,
): Promise<TransactionInstruction[]> {
const allocAccountIx = await createAllocTreeIx(
connection,
maxBufferSize,
maxDepth,
canopyDepth,
payer,
merkleRoll
merkleRoll,
);
const authority = await getBubblegumAuthorityPDA(merkleRoll);
const initGummyrollIx = createCreateTreeInstruction(
Expand All @@ -184,7 +151,7 @@ export async function getCreateTreeIxs(
{
maxDepth,
maxBufferSize,
}
},
);
return [allocAccountIx, initGummyrollIx];
}
Expand All @@ -197,39 +164,27 @@ export function computeMetadataArgsHash(mintIx: TransactionInstruction) {
export function computeDataHash(
sellerFeeBasisPoints: number,
mintIx?: TransactionInstruction,
metadataArgsHash?: number[]
metadataArgsHash?: number[],
) {
// Input validation
if (
typeof mintIx === "undefined" &&
typeof metadataArgsHash === "undefined"
) {
if (typeof mintIx === 'undefined' && typeof metadataArgsHash === 'undefined') {
throw new Error(
"Either the mint NFT instruction or the hash of metadata args must be provided to determine the data hash of the leaf!"
'Either the mint NFT instruction or the hash of metadata args must be provided to determine the data hash of the leaf!',
);
}
if (
typeof mintIx !== "undefined" &&
typeof metadataArgsHash !== "undefined"
) {
if (typeof mintIx !== 'undefined' && typeof metadataArgsHash !== 'undefined') {
throw new Error(
"Only the mint instruction or the hash of metadata args should be specified, not both"
'Only the mint instruction or the hash of metadata args should be specified, not both',
);
}

if (typeof mintIx !== "undefined") {
if (typeof mintIx !== 'undefined') {
metadataArgsHash = computeMetadataArgsHash(mintIx);
}

const sellerFeeBasisPointsNumberArray = bufferToArray(
num16ToBuffer(sellerFeeBasisPoints)
);
const allDataToHash = metadataArgsHash!.concat(
sellerFeeBasisPointsNumberArray
);
const dataHashOfCompressedNFT = bufferToArray(
Buffer.from(keccak_256.digest(allDataToHash))
);
const sellerFeeBasisPointsNumberArray = bufferToArray(num16ToBuffer(sellerFeeBasisPoints));
const allDataToHash = metadataArgsHash!.concat(sellerFeeBasisPointsNumberArray);
const dataHashOfCompressedNFT = bufferToArray(Buffer.from(keccak_256.digest(allDataToHash)));
return dataHashOfCompressedNFT;
}

Expand All @@ -242,13 +197,8 @@ export function computeCreatorHash(creators: Creator[]) {
creator.address.toBuffer(),
Buffer.from([creator.share]),
]);
bufferOfCreatorShares = Buffer.concat([
bufferOfCreatorShares,
Buffer.from([creator.share]),
]);
bufferOfCreatorShares = Buffer.concat([bufferOfCreatorShares, Buffer.from([creator.share])]);
}
let creatorHash = bufferToArray(
Buffer.from(keccak_256.digest(bufferOfCreatorData))
);
let creatorHash = bufferToArray(Buffer.from(keccak_256.digest(bufferOfCreatorData)));
return creatorHash;
}
Loading

0 comments on commit 1a68c2e

Please sign in to comment.