From 6e8179484413cd8a50ee794c0a49cfef91e1fcb6 Mon Sep 17 00:00:00 2001 From: Noah Gundotra Date: Thu, 13 Oct 2022 11:52:16 -0400 Subject: [PATCH] Account Compression: JS SDK release 0.1.1 (#3680) * ac: beet* as deps, not peerDeps * ac: expose depth size pairs, add canopyDepth to CMTAccount getters * ac: add tests for CMT Account * ac: update README.md * ac: update lockfile --- account-compression/sdk/README.md | 41 ++++++++++-- account-compression/sdk/package.json | 10 +-- .../accounts/ConcurrentMerkleTreeAccount.ts | 12 ++-- account-compression/sdk/src/utils/index.ts | 38 ++++++++++- .../sdk/tests/accountCompression.test.ts | 3 - account-compression/sdk/tests/utils.ts | 64 +++++++++---------- account-compression/sdk/yarn.lock | 19 ++++++ 7 files changed, 135 insertions(+), 52 deletions(-) diff --git a/account-compression/sdk/README.md b/account-compression/sdk/README.md index 52e12c0d204..42486e91991 100644 --- a/account-compression/sdk/README.md +++ b/account-compression/sdk/README.md @@ -1,15 +1,42 @@ -# Account Compression SDK +# `@solana/spl-account-compression` -Currently contains a typescript SDK to interact with on-chain SPL ConcurrentMerkleTrees. +A TypeScript library for interacting with SPL Account Compression and SPL NoOp. -Used to test Account Compression program. +## Install -# Setting up the Solita-based Typescript SDK +```shell +npm install --save @solana/spl-account-compression @solana/web3.js +``` + +__OR__ + +```shell +yarn add @solana/spl-account-compression @solana/web3.js +``` + + +## Examples + +* Solana Program Library [tests](https://github.com/solana-labs/solana-program-library/tree/master/account-compression/sdk/tests) + +* Metaplex Program Library Compressed NFT [tests](https://github.com/metaplex-foundation/metaplex-program-library/tree/master/bubblegum/js/tests) + +## Information + +This on-chain program provides an interface for composing smart-contracts to create and use SPL ConcurrentMerkleTrees. The primary application of using SPL ConcurrentMerkleTrees is to make edits to off-chain data with on-chain verification. + +This program is targeted towards supporting [Metaplex Compressed NFTs](https://github.com/metaplex-foundation/metaplex-program-library/tree/master/bubblegum) and may be subject to change. + +Note: Using this program requires an indexer to parse transaction information and write relevant information to an off-chain database. + +A **rough draft** of the whitepaper for SPL ConcurrentMerkleTree's can be found [here](https://drive.google.com/file/d/1BOpa5OFmara50fTvL0VIVYjtg-qzHCVc/view). + +## Build from Source + +0. Install dependencies with `yarn`. 1. Generate the Solita SDK with `yarn solita`. 2. Then build the SDK with `yarn build`. -# Testing the SDK - -Run `yarn test`. Expect `jest` to detect an open handle that prevents it from exiting tests naturally. \ No newline at end of file +3. Run tests with `yarn test`. (Expect `jest` to detect an open handle that prevents it from exiting naturally) \ No newline at end of file diff --git a/account-compression/sdk/package.json b/account-compression/sdk/package.json index 2605a550ed9..e174b6649fe 100644 --- a/account-compression/sdk/package.json +++ b/account-compression/sdk/package.json @@ -1,7 +1,7 @@ { "name": "@solana/spl-account-compression", "description": "SPL Account Compression Program JS API", - "version": "0.1.0", + "version": "0.1.1", "author": "Solana Maintainers ", "repository": { "url": "https://github.com/solana-labs/solana-program-library", @@ -37,7 +37,9 @@ "start-validator": "solana-test-validator --reset --quiet --bpf-program cmtDvXumGCrqC1Age74AVPhSRVXJMd8PJS91L8KbNCK ../target/deploy/spl_account_compression.so --bpf-program noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV ../target/deploy/spl_noop.so", "run-tests": "jest tests --detectOpenHandles", "run-tests:events": "jest tests/events --detectOpenHandles", + "run-tests:accounts": "jest tests/accounts --detectOpenHandles", "test:events": "start-server-and-test start-validator http://localhost:8899/health run-tests:events", + "test:accounts": "start-server-and-test start-validator http://localhost:8899/health run-tests:accounts", "test": "start-server-and-test start-validator http://localhost:8899/health run-tests" }, "dependencies": { @@ -46,11 +48,11 @@ "borsh": "^0.7.0" }, "peerDependencies": { - "@metaplex-foundation/beet": "^0.6.1", - "@metaplex-foundation/beet-solana": "^0.6.1", "@solana/web3.js": "^1.50.1" }, "devDependencies": { + "@metaplex-foundation/beet": "^0.7.1", + "@metaplex-foundation/beet-solana": "^0.4.0", "@metaplex-foundation/rustbin": "^0.3.1", "@metaplex-foundation/solita": "0.15.2", "@project-serum/anchor": "^0.25.0", @@ -68,4 +70,4 @@ "typescript": "=4.7.4", "typescript-collections": "^1.3.3" } -} +} \ No newline at end of file diff --git a/account-compression/sdk/src/accounts/ConcurrentMerkleTreeAccount.ts b/account-compression/sdk/src/accounts/ConcurrentMerkleTreeAccount.ts index a2b1943f18c..7e87ec0afe5 100644 --- a/account-compression/sdk/src/accounts/ConcurrentMerkleTreeAccount.ts +++ b/account-compression/sdk/src/accounts/ConcurrentMerkleTreeAccount.ts @@ -1,4 +1,4 @@ -import type { PublicKey, Connection } from "@solana/web3.js"; +import type { PublicKey, Connection, Commitment, GetAccountInfoConfig } from "@solana/web3.js"; import * as borsh from "borsh"; import * as BN from 'bn.js'; import * as beet from '@metaplex-foundation/beet'; @@ -35,8 +35,8 @@ export class ConcurrentMerkleTreeAccount { return deserializeConcurrentMerkleTree(buffer); } - static async fromAccountAddress(connection: Connection, publicKey: PublicKey): Promise { - const account = await connection.getAccountInfo(publicKey); + static async fromAccountAddress(connection: Connection, publicKey: PublicKey, commitmentOrConfig?: Commitment | GetAccountInfoConfig): Promise { + const account = await connection.getAccountInfo(publicKey, commitmentOrConfig); if (!account) { throw new Error("CMT account data unexpectedly null!"); } @@ -79,9 +79,13 @@ export class ConcurrentMerkleTreeAccount { return new BN.BN(this.tree.sequenceNumber).toNumber(); } + getCanopyDepth(): number { + return getCanopyDepth(this.canopy.canopyBytes.length); + } + }; -function getCanopyDepth(canopyByteLength: number): number { +export function getCanopyDepth(canopyByteLength: number): number { if (canopyByteLength === 0) { return 0; } diff --git a/account-compression/sdk/src/utils/index.ts b/account-compression/sdk/src/utils/index.ts index 1e799d42341..60a206c792e 100644 --- a/account-compression/sdk/src/utils/index.ts +++ b/account-compression/sdk/src/utils/index.ts @@ -3,4 +3,40 @@ import { } from "@solana/web3.js"; export const SPL_NOOP_ADDRESS = "noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV"; -export const SPL_NOOP_PROGRAM_ID = new PublicKey(SPL_NOOP_ADDRESS); \ No newline at end of file +export const SPL_NOOP_PROGRAM_ID = new PublicKey(SPL_NOOP_ADDRESS); + +type DepthSizePair = { + maxDepth: number, + maxBufferSize: number +}; + +const allPairs: number[][] = [ + [3, 8], + [5, 8], + [14, 64], + [14, 256], + [14, 1024], + [14, 2048], + [20, 64], + [20, 256], + [20, 1024], + [20, 2048], + [24, 64], + [24, 256], + [24, 512], + [24, 1024], + [24, 2048], + [26, 512], + [26, 1024], + [26, 2048], + [30, 512], + [30, 1024], + [30, 2048], +]; + +export const ALL_DEPTH_SIZE_PAIRS: DepthSizePair[] = allPairs.map((pair) => { + return { + maxDepth: pair[0], + maxBufferSize: pair[1] + } +}) \ No newline at end of file diff --git a/account-compression/sdk/tests/accountCompression.test.ts b/account-compression/sdk/tests/accountCompression.test.ts index d95deb0690e..f6f860f5339 100644 --- a/account-compression/sdk/tests/accountCompression.test.ts +++ b/account-compression/sdk/tests/accountCompression.test.ts @@ -3,10 +3,7 @@ import { BN } from 'bn.js'; import { AnchorProvider } from "@project-serum/anchor"; import { Connection, - Signer, Keypair, - PublicKey, - Transaction, TransactionInstruction, } from "@solana/web3.js"; import { assert } from "chai"; diff --git a/account-compression/sdk/tests/utils.ts b/account-compression/sdk/tests/utils.ts index 748a1ef8ddb..ad70121b61b 100644 --- a/account-compression/sdk/tests/utils.ts +++ b/account-compression/sdk/tests/utils.ts @@ -23,35 +23,6 @@ import { Tree, } from "./merkleTree"; -export async function assertCMTProperties( - connection: Connection, - expectedMaxDepth: number, - expectedMaxBufferSize: number, - expectedAuthority: PublicKey, - expectedRoot: Buffer, - onChainCMTKey: PublicKey -) { - const onChainCMT = await ConcurrentMerkleTreeAccount.fromAccountAddress(connection, onChainCMTKey); - - assert( - onChainCMT.getMaxDepth() === expectedMaxDepth, - `Max depth does not match ${onChainCMT.getMaxDepth()}, expected ${expectedMaxDepth}`, - ); - assert( - onChainCMT.getMaxBufferSize() === expectedMaxBufferSize, - `Max buffer size does not match ${onChainCMT.getMaxBufferSize()}, expected ${expectedMaxBufferSize}`, - ); - assert( - onChainCMT.getAuthority().equals(expectedAuthority), - "Failed to write auth pubkey", - ); - assert( - onChainCMT.getCurrentRoot().equals(expectedRoot), - "On chain root does not match root passed in instruction", - ); -} - - /// Wait for a transaction of a certain id to confirm and optionally log its messages export async function confirmAndLogTx(provider: AnchorProvider, txId: string, verbose: boolean = false) { const tx = await provider.connection.confirmTransaction(txId, "confirmed"); @@ -98,6 +69,7 @@ export async function execute( return txid; } + export async function createTreeOnChain( provider: AnchorProvider, payer: Keypair, @@ -152,15 +124,41 @@ export async function createTreeOnChain( appendIxs = appendIxs.slice(5,); } } + return [cmtKeypair, tree]; +} - await assertCMTProperties( +export async function createEmptyTreeOnChain( + provider: AnchorProvider, + payer: Keypair, + maxDepth: number, + maxSize: number, + canopyDepth: number = 0, +): Promise { + const cmtKeypair = Keypair.generate(); + const allocAccountIx = await createAllocTreeIx( provider.connection, - maxDepth, maxSize, + maxDepth, + canopyDepth, payer.publicKey, - tree.root, cmtKeypair.publicKey ); - return [cmtKeypair, tree]; + let ixs = [ + allocAccountIx, + createInitEmptyMerkleTreeIx( + payer, + cmtKeypair.publicKey, + maxDepth, + maxSize, + ) + ]; + + let txId = await execute(provider, ixs, [ + payer, + cmtKeypair, + ]); + await confirmAndLogTx(provider, txId as string); + + return cmtKeypair; } \ No newline at end of file diff --git a/account-compression/sdk/yarn.lock b/account-compression/sdk/yarn.lock index 82a011774d5..9b43eb97ec1 100644 --- a/account-compression/sdk/yarn.lock +++ b/account-compression/sdk/yarn.lock @@ -620,6 +620,16 @@ bs58 "^5.0.0" debug "^4.3.4" +"@metaplex-foundation/beet-solana@^0.4.0": + version "0.4.0" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/beet-solana/-/beet-solana-0.4.0.tgz#52891e78674aaa54e0031f1bca5bfbc40de12e8d" + integrity sha512-B1L94N3ZGMo53b0uOSoznbuM5GBNJ8LwSeznxBxJ+OThvfHQ4B5oMUqb+0zdLRfkKGS7Q6tpHK9P+QK0j3w2cQ== + dependencies: + "@metaplex-foundation/beet" ">=0.1.0" + "@solana/web3.js" "^1.56.2" + bs58 "^5.0.0" + debug "^4.3.4" + "@metaplex-foundation/beet@>=0.1.0", "@metaplex-foundation/beet@^0.6.1": version "0.6.1" resolved "https://registry.yarnpkg.com/@metaplex-foundation/beet/-/beet-0.6.1.tgz#6331bdde0648bf2cae6f9e482f8e3552db05d69f" @@ -629,6 +639,15 @@ bn.js "^5.2.0" debug "^4.3.3" +"@metaplex-foundation/beet@^0.7.1": + version "0.7.1" + resolved "https://registry.yarnpkg.com/@metaplex-foundation/beet/-/beet-0.7.1.tgz#0975314211643f87b5f6f3e584fa31abcf4c612c" + integrity sha512-hNCEnS2WyCiYyko82rwuISsBY3KYpe828ubsd2ckeqZr7tl0WVLivGkoyA/qdiaaHEBGdGl71OpfWa2rqL3DiA== + dependencies: + ansicolors "^0.3.2" + bn.js "^5.2.0" + debug "^4.3.3" + "@metaplex-foundation/rustbin@^0.3.0", "@metaplex-foundation/rustbin@^0.3.1": version "0.3.1" resolved "https://registry.yarnpkg.com/@metaplex-foundation/rustbin/-/rustbin-0.3.1.tgz#bbcd61e8699b73c0b062728c6f5e8d52e8145042"