From c203069e3b99c30c5e6a68176cca2c47bd59b101 Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Wed, 27 Sep 2023 14:57:25 -0400 Subject: [PATCH 1/8] Removing deploy capabilities --- .github/workflows/deploy-js-client.yml | 131 --------------- .github/workflows/deploy-program.yml | 194 ----------------------- .github/workflows/deploy-rust-client.yml | 91 ----------- 3 files changed, 416 deletions(-) delete mode 100644 .github/workflows/deploy-js-client.yml delete mode 100644 .github/workflows/deploy-program.yml delete mode 100644 .github/workflows/deploy-rust-client.yml diff --git a/.github/workflows/deploy-js-client.yml b/.github/workflows/deploy-js-client.yml deleted file mode 100644 index d89b4655..00000000 --- a/.github/workflows/deploy-js-client.yml +++ /dev/null @@ -1,131 +0,0 @@ -name: Deploy JS Client - -on: - workflow_dispatch: - inputs: - bump: - description: Version bump - required: true - default: patch - type: choice - options: - - patch - - minor - - major - - prerelease - - prepatch - - preminor - - premajor - tag: - description: NPM Tag (and preid for pre-releases) - required: true - type: string - default: latest - create_release: - description: Create a GitHub release - required: true - type: boolean - default: true - -env: - CACHE: true - -jobs: - build_programs: - name: Programs - uses: ./.github/workflows/build-programs.yml - secrets: inherit - - test_js: - name: JS client - needs: build_programs - uses: ./.github/workflows/test-js-client.yml - secrets: inherit - - deploy_js: - name: JS client / Deploy - runs-on: ubuntu-latest - needs: test_js - permissions: - contents: write - steps: - - name: Git checkout - uses: actions/checkout@v3 - with: - token: ${{ secrets.SVC_TOKEN }} - - - name: Load environment variables - run: cat .github/.env >> $GITHUB_ENV - - - name: Install Node.js - uses: metaplex-foundation/actions/install-node-with-pnpm@v1 - with: - version: ${{ env.NODE_VERSION }} - cache: ${{ env.CACHE }} - - - name: Install dependencies - uses: metaplex-foundation/actions/install-node-dependencies@v1 - with: - folder: ./clients/js - cache: ${{ env.CACHE }} - key: clients-js - - - name: Build - working-directory: ./clients/js - run: pnpm build - - - name: Bump - id: bump - working-directory: ./clients/js - run: | - if [ "${{ startsWith(inputs.bump, 'pre') }}" == "true" ]; then - pnpm version ${{ inputs.bump }} --preid ${{ inputs.tag }} --no-git-tag-version - else - pnpm version ${{ inputs.bump }} --no-git-tag-version - fi - echo "new_version=$(pnpm pkg get version | sed 's/"//g')" >> $GITHUB_OUTPUT - - - name: Set publishing config - run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}" - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - - - name: Publish - working-directory: ./clients/js - run: pnpm publish --no-git-checks --tag ${{ inputs.tag }} - - - name: Commit and tag new version - uses: stefanzweifel/git-auto-commit-action@v4 - with: - commit_message: Deploy JS client v${{ steps.bump.outputs.new_version }} - tagging_message: js@v${{ steps.bump.outputs.new_version }} - - - name: Create GitHub release - if: github.event.inputs.create_release == 'true' - uses: ncipollo/release-action@v1 - with: - tag: js@v${{ steps.bump.outputs.new_version }} - - deploy_js_docs: - name: JS client / Deploy docs - runs-on: ubuntu-latest - needs: deploy_js - environment: - name: js-client-documentation - url: ${{ steps.deploy.outputs.url }} - steps: - - name: Git checkout - uses: actions/checkout@v3 - with: - ref: ${{ github.ref }} - - - name: Load environment variables - run: cat .github/.env >> $GITHUB_ENV - - - name: Deploy to Vercel - id: deploy - env: - VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} - VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} - working-directory: ./clients/js - run: echo "url=$(vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }})" | tee $GITHUB_OUTPUT diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml deleted file mode 100644 index bee294a3..00000000 --- a/.github/workflows/deploy-program.yml +++ /dev/null @@ -1,194 +0,0 @@ -name: Deploy Program - -on: - workflow_dispatch: - inputs: - program: - description: Program - required: true - default: bubblegum - type: choice - options: - - bubblegum - cluster: - description: Cluster environment - required: true - default: devnet - type: choice - options: - - devnet - - mainnet-beta - publish_crate: - description: Release cargo crate - type: boolean - default: false - bump: - description: Version bump - required: true - default: patch - type: choice - options: - - patch - - minor - - major - -env: - CACHE: true - -jobs: - build_programs: - name: Programs - uses: ./.github/workflows/build-programs.yml - secrets: inherit - - test_programs: - name: Programs - needs: build_programs - uses: ./.github/workflows/test-programs.yml - secrets: inherit - with: - program_matrix: '["${{ inputs.program }}"]' - - test_js: - name: JS client - needs: test_programs - uses: ./.github/workflows/test-js-client.yml - secrets: inherit - - deploy_program: - name: Program / Deploy - runs-on: ubuntu-latest - needs: test_js - permissions: - contents: write - steps: - - name: Git checkout - uses: actions/checkout@v3 - with: - token: ${{ secrets.SVC_TOKEN }} - - - name: Load environment variables - run: cat .github/.env >> $GITHUB_ENV - - - name: Install Rust - uses: metaplex-foundation/actions/install-rust@v1 - with: - toolchain: "1.70.0" - - - name: Install Solana - uses: metaplex-foundation/actions/install-solana@v1 - with: - version: "1.14.22" - cache: ${{ env.CACHE }} - - - name: Install Anchor CLI - uses: metaplex-foundation/actions/install-anchor-cli@v1 - with: - version: ${{ env.ANCHOR_VERSION }} - cache: ${{ env.CACHE }} - - - - name: Install cargo-release - uses: metaplex-foundation/actions/install-cargo-release@v1 - if: github.event.inputs.publish_crate == 'true' - with: - cache: ${{ env.CACHE }} - - - - name: Install cargo-release - uses: metaplex-foundation/actions/install-cargo-release@v1 - if: github.event.inputs.publish_crate == 'true' - with: - cache: ${{ env.CACHE }} - - - name: Set RPC - run: | - if [ "${{ inputs.cluster }}" == "devnet" ]; then - echo RPC=${{ secrets.DEVNET_RPC }} >> $GITHUB_ENV - else - echo RPC=${{ secrets.MAINNET_RPC }} >> $GITHUB_ENV - fi - - - name: Identify program - run: | - 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 - fi - - - name: Bump program version - run: | - IDL_NAME=`echo "${{ inputs.program }}" | tr - _` - VERSION=`jq '.version' ./idls/${IDL_NAME}.json | sed 's/"//g'` - MAJOR=`echo ${VERSION} | cut -d. -f1` - MINOR=`echo ${VERSION} | cut -d. -f2` - PATCH=`echo ${VERSION} | cut -d. -f3` - - if [ "${{ inputs.bump }}" == "major" ]; then - MAJOR=$((MAJOR + 1)) - MINOR=0 - PATCH=0 - elif [ "${{ inputs.bump }}" == "minor" ]; then - MINOR=$((MINOR + 1)) - PATCH=0 - else - PATCH=$((PATCH + 1)) - fi - - PROGRAM_VERSION="${MAJOR}.${MINOR}.${PATCH}" - - cp ./idls/${IDL_NAME}.json ./idls/${IDL_NAME}-previous.json - jq ".version = \"${PROGRAM_VERSION}\"" ./idls/${IDL_NAME}-previous.json > ./idls/${IDL_NAME}.json - rm ./idls/${IDL_NAME}-previous.json - - echo PROGRAM_VERSION="${PROGRAM_VERSION}" >> $GITHUB_ENV - - - name: Download program builds - uses: actions/download-artifact@v3 - with: - name: program-builds - - - name: Deploy program - run: | - echo "Deploying ${{ inputs.program }} to ${{ inputs.cluster }}" - - solana -v program deploy ./programs/.bin/${{ env.PROGRAM_NAME }}.so \ - -u ${{ env.RPC }} \ - --program-id ./program-id.json \ - -k ./deploy-key.json - - - name: Upgrade IDL - working-directory: ./programs/${{ inputs.program }} - run: | - jq 'del(.metadata?) | del(.. | .docs?)' ../../idls/`echo "${{ inputs.program }}" | tr - _`.json > ./idl.json - - anchor idl upgrade -f ./idl.json \ - --provider.cluster ${{ env.RPC }} \ - --provider.wallet ../../deploy-key.json \ - `solana address -k ../../program-id.json` - - rm ../../deploy-key.json - rm ../../program-id.json - rm ./idl.json - - - name: Version program - working-directory: ./programs/${{ inputs.program }}/program - if: github.event.inputs.publish_crate == 'true' && github.event.inputs.cluster == 'mainnet-beta' - run: | - git stash - git config user.name "${{ env.COMMIT_USER_NAME }}" - git config user.email "${{ env.COMMIT_USER_EMAIL }}" - - cargo login ${{ secrets.CRATES_TOKEN }} - cargo release ${{ inputs.bump }} --no-confirm --no-push --no-tag --no-publish --execute - - git reset --soft HEAD~1 - git stash pop - - - name: Commit and tag new version - uses: stefanzweifel/git-auto-commit-action@v4 - if: github.event.inputs.publish_crate == 'true' && github.event.inputs.cluster == 'mainnet-beta' - with: - commit_message: "chore: ${{ inputs.program }} version ${{ env.PROGRAM_VERSION }}" - tagging_message: v${{ env.PROGRAM_VERSION }}@${{ inputs.cluster }} diff --git a/.github/workflows/deploy-rust-client.yml b/.github/workflows/deploy-rust-client.yml deleted file mode 100644 index 46922d6a..00000000 --- a/.github/workflows/deploy-rust-client.yml +++ /dev/null @@ -1,91 +0,0 @@ -name: Deploy Rust Client - -on: - workflow_dispatch: - inputs: - level: - description: Level - required: true - default: patch - type: choice - options: - - patch - - minor - - major - - rc - - beta - - alpha - - release - - version - version: - description: Version - required: false - type: string - dry_run: - description: Dry run - required: true - default: true - type: boolean - -env: - CACHE: true - -jobs: - build_programs: - name: Programs - uses: ./.github/workflows/build-programs.yml - secrets: inherit - - build_rust_client: - name: Rust Client - uses: ./.github/workflows/build-rust-client.yml - secrets: inherit - - test_rust_client: - name: Rust Client - needs: [build_programs, build_rust_client] - uses: ./.github/workflows/test-rust-client.yml - secrets: inherit - - publish_crate: - name: Rust Client / Publish Crate - runs-on: ubuntu-latest - needs: test_rust_client - permissions: - contents: write - steps: - - name: Git checkout - uses: actions/checkout@v3 - with: - token: ${{ secrets.SVC_TOKEN }} - - - name: Load environment variables - run: cat .github/.env >> $GITHUB_ENV - - - name: Install Rust - uses: metaplex-foundation/actions/install-rust@v1 - with: - toolchain: "1.70.0" - - - name: Install cargo-release - uses: metaplex-foundation/actions/install-cargo-release@v1 - with: - cache: ${{ env.CACHE }} - - - name: Publish Crate - working-directory: ./clients/rust - run: | - if [ "${{ inputs.level }}" == "version" ]; then - BUMP=${{ inputs.version }} - else - BUMP=${{ inputs.level }} - fi - - if [ "${{ inputs.dry_run }}" == "false" ]; then - OPTIONS="--no-publish --no-confirm --execute" - git config user.name ${{ env.COMMIT_USER_NAME }} - git config user.email ${{ env.COMMIT_USER_EMAIL }} - fi - - cargo login ${{ secrets.CRATES_TOKEN }} - cargo release $BUMP $OPTIONS From 9f6832c8b4913e187ea0287ead4c097ab00a6b7b Mon Sep 17 00:00:00 2001 From: blockiosaurus Date: Wed, 27 Sep 2023 16:57:51 -0400 Subject: [PATCH 2/8] Adding back program deploy. --- .github/workflows/deploy-program.yml | 194 +++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 .github/workflows/deploy-program.yml diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml new file mode 100644 index 00000000..bee294a3 --- /dev/null +++ b/.github/workflows/deploy-program.yml @@ -0,0 +1,194 @@ +name: Deploy Program + +on: + workflow_dispatch: + inputs: + program: + description: Program + required: true + default: bubblegum + type: choice + options: + - bubblegum + cluster: + description: Cluster environment + required: true + default: devnet + type: choice + options: + - devnet + - mainnet-beta + publish_crate: + description: Release cargo crate + type: boolean + default: false + bump: + description: Version bump + required: true + default: patch + type: choice + options: + - patch + - minor + - major + +env: + CACHE: true + +jobs: + build_programs: + name: Programs + uses: ./.github/workflows/build-programs.yml + secrets: inherit + + test_programs: + name: Programs + needs: build_programs + uses: ./.github/workflows/test-programs.yml + secrets: inherit + with: + program_matrix: '["${{ inputs.program }}"]' + + test_js: + name: JS client + needs: test_programs + uses: ./.github/workflows/test-js-client.yml + secrets: inherit + + deploy_program: + name: Program / Deploy + runs-on: ubuntu-latest + needs: test_js + permissions: + contents: write + steps: + - name: Git checkout + uses: actions/checkout@v3 + with: + token: ${{ secrets.SVC_TOKEN }} + + - name: Load environment variables + run: cat .github/.env >> $GITHUB_ENV + + - name: Install Rust + uses: metaplex-foundation/actions/install-rust@v1 + with: + toolchain: "1.70.0" + + - name: Install Solana + uses: metaplex-foundation/actions/install-solana@v1 + with: + version: "1.14.22" + cache: ${{ env.CACHE }} + + - name: Install Anchor CLI + uses: metaplex-foundation/actions/install-anchor-cli@v1 + with: + version: ${{ env.ANCHOR_VERSION }} + cache: ${{ env.CACHE }} + + + - name: Install cargo-release + uses: metaplex-foundation/actions/install-cargo-release@v1 + if: github.event.inputs.publish_crate == 'true' + with: + cache: ${{ env.CACHE }} + + + - name: Install cargo-release + uses: metaplex-foundation/actions/install-cargo-release@v1 + if: github.event.inputs.publish_crate == 'true' + with: + cache: ${{ env.CACHE }} + + - name: Set RPC + run: | + if [ "${{ inputs.cluster }}" == "devnet" ]; then + echo RPC=${{ secrets.DEVNET_RPC }} >> $GITHUB_ENV + else + echo RPC=${{ secrets.MAINNET_RPC }} >> $GITHUB_ENV + fi + + - name: Identify program + run: | + 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 + fi + + - name: Bump program version + run: | + IDL_NAME=`echo "${{ inputs.program }}" | tr - _` + VERSION=`jq '.version' ./idls/${IDL_NAME}.json | sed 's/"//g'` + MAJOR=`echo ${VERSION} | cut -d. -f1` + MINOR=`echo ${VERSION} | cut -d. -f2` + PATCH=`echo ${VERSION} | cut -d. -f3` + + if [ "${{ inputs.bump }}" == "major" ]; then + MAJOR=$((MAJOR + 1)) + MINOR=0 + PATCH=0 + elif [ "${{ inputs.bump }}" == "minor" ]; then + MINOR=$((MINOR + 1)) + PATCH=0 + else + PATCH=$((PATCH + 1)) + fi + + PROGRAM_VERSION="${MAJOR}.${MINOR}.${PATCH}" + + cp ./idls/${IDL_NAME}.json ./idls/${IDL_NAME}-previous.json + jq ".version = \"${PROGRAM_VERSION}\"" ./idls/${IDL_NAME}-previous.json > ./idls/${IDL_NAME}.json + rm ./idls/${IDL_NAME}-previous.json + + echo PROGRAM_VERSION="${PROGRAM_VERSION}" >> $GITHUB_ENV + + - name: Download program builds + uses: actions/download-artifact@v3 + with: + name: program-builds + + - name: Deploy program + run: | + echo "Deploying ${{ inputs.program }} to ${{ inputs.cluster }}" + + solana -v program deploy ./programs/.bin/${{ env.PROGRAM_NAME }}.so \ + -u ${{ env.RPC }} \ + --program-id ./program-id.json \ + -k ./deploy-key.json + + - name: Upgrade IDL + working-directory: ./programs/${{ inputs.program }} + run: | + jq 'del(.metadata?) | del(.. | .docs?)' ../../idls/`echo "${{ inputs.program }}" | tr - _`.json > ./idl.json + + anchor idl upgrade -f ./idl.json \ + --provider.cluster ${{ env.RPC }} \ + --provider.wallet ../../deploy-key.json \ + `solana address -k ../../program-id.json` + + rm ../../deploy-key.json + rm ../../program-id.json + rm ./idl.json + + - name: Version program + working-directory: ./programs/${{ inputs.program }}/program + if: github.event.inputs.publish_crate == 'true' && github.event.inputs.cluster == 'mainnet-beta' + run: | + git stash + git config user.name "${{ env.COMMIT_USER_NAME }}" + git config user.email "${{ env.COMMIT_USER_EMAIL }}" + + cargo login ${{ secrets.CRATES_TOKEN }} + cargo release ${{ inputs.bump }} --no-confirm --no-push --no-tag --no-publish --execute + + git reset --soft HEAD~1 + git stash pop + + - name: Commit and tag new version + uses: stefanzweifel/git-auto-commit-action@v4 + if: github.event.inputs.publish_crate == 'true' && github.event.inputs.cluster == 'mainnet-beta' + with: + commit_message: "chore: ${{ inputs.program }} version ${{ env.PROGRAM_VERSION }}" + tagging_message: v${{ env.PROGRAM_VERSION }}@${{ inputs.cluster }} From cfde6c85ad1d44138699dda5e81deac34c0c6754 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Wed, 27 Sep 2023 15:26:22 -0700 Subject: [PATCH 3/8] Update collection verification checks --- .../src/processor/mint_to_collection.rs | 1 - .../bubblegum/program/src/processor/mod.rs | 38 ++++++++++--------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/programs/bubblegum/program/src/processor/mint_to_collection.rs b/programs/bubblegum/program/src/processor/mint_to_collection.rs index ea9a3ca9..97334108 100644 --- a/programs/bubblegum/program/src/processor/mint_to_collection.rs +++ b/programs/bubblegum/program/src/processor/mint_to_collection.rs @@ -116,7 +116,6 @@ pub(crate) fn mint_to_collection_v1( &token_metadata_program, &mut message, true, - None, )?; process_mint_v1( diff --git a/programs/bubblegum/program/src/processor/mod.rs b/programs/bubblegum/program/src/processor/mod.rs index 57b2b040..76cc8bf0 100644 --- a/programs/bubblegum/program/src/processor/mod.rs +++ b/programs/bubblegum/program/src/processor/mod.rs @@ -166,7 +166,6 @@ fn process_collection_verification_mpl_only<'info>( token_metadata_program: &AccountInfo<'info>, message: &mut MetadataArgs, verify: bool, - new_collection: Option, ) -> Result<()> { // See if a collection authority record PDA was provided. let collection_authority_record = if collection_authority_record_pda.key() == crate::id() { @@ -189,24 +188,8 @@ fn process_collection_verification_mpl_only<'info>( BubblegumError::IncorrectOwner ); - // If new collection was provided, set it in the NFT metadata. - if new_collection.is_some() { - message.collection = new_collection.map(|key| metaplex_adapter::Collection { - verified: false, // Set to true below. - key, - }); - } - // If the NFT has collection data, we set it to the correct value after doing some validation. if let Some(collection) = &mut message.collection { - // Don't verify already verified items, or unverify unverified items, otherwise for sized - // collections we end up with invalid size data. - if verify && collection.verified { - return Err(BubblegumError::AlreadyVerified.into()); - } else if !verify && !collection.verified { - return Err(BubblegumError::AlreadyUnverified.into()); - } - // Collection verify assert from token-metadata program. assert_collection_verify_is_valid( &Some(collection.adapt()), @@ -308,6 +291,26 @@ fn process_collection_verification<'info>( return Err(BubblegumError::DataHashMismatch.into()); } + // Check existing collection. Don't verify already-verified items, or unverify unverified + // items, otherwise for sized collections we end up with invalid size data. Also, we don't + // allow a new collection (via `set_and_verify_collection`) to overwrite an already-verified + // item. + if let Some(collection) = &message.collection { + if verify && collection.verified { + return Err(BubblegumError::AlreadyVerified.into()); + } else if !verify && !collection.verified { + return Err(BubblegumError::AlreadyUnverified.into()); + } + } + + // If new collection was provided (via `set_and_verify_collection`), set it in the metadata. + if new_collection.is_some() { + message.collection = new_collection.map(|key| metaplex_adapter::Collection { + verified: false, // Will be set to true later. + key, + }); + } + // Note this call mutates message. process_collection_verification_mpl_only( collection_metadata, @@ -320,7 +323,6 @@ fn process_collection_verification<'info>( &token_metadata_program, &mut message, verify, - new_collection, )?; // Calculate new data hash. From 5e404aaf84e2ad6c8cefc686bdd750314158548c Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Thu, 28 Sep 2023 12:51:47 -0700 Subject: [PATCH 4/8] Add Umi tests around collection verification --- clients/js/test/mintToCollectionV1.test.ts | 64 +++++++++++++- .../js/test/setAndVerifyCollection.test.ts | 83 +++++++++++++++++++ clients/js/test/unverifyCollection.test.ts | 62 ++++++++++++++ clients/js/test/verifyCollection.test.ts | 65 +++++++++++++++ 4 files changed, 272 insertions(+), 2 deletions(-) diff --git a/clients/js/test/mintToCollectionV1.test.ts b/clients/js/test/mintToCollectionV1.test.ts index a0d43123..a4fb9e97 100644 --- a/clients/js/test/mintToCollectionV1.test.ts +++ b/clients/js/test/mintToCollectionV1.test.ts @@ -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); @@ -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', @@ -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(); diff --git a/clients/js/test/setAndVerifyCollection.test.ts b/clients/js/test/setAndVerifyCollection.test.ts index d8e618ac..7aa54559 100644 --- a/clients/js/test/setAndVerifyCollection.test.ts +++ b/clients/js/test/setAndVerifyCollection.test.ts @@ -113,3 +113,86 @@ test('it cannot set and verify the collection if the tree creator or delegate do // Then we expect a program error. await t.throwsAsync(promise, { name: 'UpdateAuthorityIncorrect' }); }); + +test('it cannot set and verify the collection if there is already a verified collection', async (t) => { + // Given a first Collection NFT. + const umi = await createUmi(); + const firstCollectionMint = generateSigner(umi); + const firstCollectionAuthority = generateSigner(umi); + await createNft(umi, { + mint: firstCollectionMint, + authority: firstCollectionAuthority, + name: 'My Collection 1', + uri: 'https://example.com/my-collection-1.json', + sellerFeeBasisPoints: percentAmount(5.5), // 5.5% + isCollection: true, + }).sendAndConfirm(umi); + + // And a tree with a minted NFT that has a verified collection of that first Collection NFT. + const treeCreator = await generateSignerWithSol(umi); + const merkleTree = await createTree(umi, { treeCreator }); + let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + const leafOwner = generateSigner(umi).publicKey; + const { metadata, leafIndex } = await mint(umi, { + merkleTree, + treeCreatorOrDelegate: treeCreator, + leafOwner, + }); + await setAndVerifyCollection(umi, { + leafOwner, + treeCreatorOrDelegate: treeCreator, + collectionMint: firstCollectionMint.publicKey, + collectionAuthority: firstCollectionAuthority, + merkleTree, + root: getCurrentRoot(merkleTreeAccount.tree), + nonce: leafIndex, + index: leafIndex, + metadata, + }).sendAndConfirm(umi); + const firstCollectionVerifiedMetadata = { + ...metadata, + collection: { + key: firstCollectionMint.publicKey, + verified: true, + }, + }; + + // And then given a second Collection NFT. + const secondCollectionMint = generateSigner(umi); + const secondCollectionAuthority = generateSigner(umi); + await createNft(umi, { + mint: secondCollectionMint, + authority: secondCollectionAuthority, + name: 'My Collection 2', + uri: 'https://example.com/my-collection-2.json', + sellerFeeBasisPoints: percentAmount(5.5), // 5.5% + isCollection: true, + }).sendAndConfirm(umi); + + // When the second collection authority attempts to set and verify the second collection. + let promise = setAndVerifyCollection(umi, { + leafOwner, + treeCreatorOrDelegate: treeCreator, + collectionMint: secondCollectionMint.publicKey, + collectionAuthority: secondCollectionAuthority, + merkleTree, + root: getCurrentRoot(merkleTreeAccount.tree), + nonce: leafIndex, + index: leafIndex, + metadata: firstCollectionVerifiedMetadata, + proof: [], + }).sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'AlreadyVerified' }); + + // And the leaf was not updated in the merkle tree. + const notUpdatedLeaf = hashLeaf(umi, { + merkleTree, + owner: leafOwner, + leafIndex, + metadata: firstCollectionVerifiedMetadata, + }); + merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(notUpdatedLeaf)); +}); diff --git a/clients/js/test/unverifyCollection.test.ts b/clients/js/test/unverifyCollection.test.ts index 32e76c4d..f122f0ee 100644 --- a/clients/js/test/unverifyCollection.test.ts +++ b/clients/js/test/unverifyCollection.test.ts @@ -80,3 +80,65 @@ test('it can unverify the collection of a minted compressed NFT', async (t) => { merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); }); + +test('it cannot unverify the collection if it is already unverified', async (t) => { + // Given a Collection NFT. + const umi = await createUmi(); + 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); + + // And a tree with a minted NFT that has an unverified collection. + const merkleTree = await createTree(umi); + let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + const leafOwner = generateSigner(umi).publicKey; + const { metadata, leafIndex } = await mint(umi, { + merkleTree, + leafOwner, + metadata: { + collection: { + key: collectionMint.publicKey, + verified: false, + }, + }, + }); + + // When the collection authority attempts to unverify the collection. + const promise = unverifyCollection(umi, { + leafOwner, + collectionMint: collectionMint.publicKey, + collectionAuthority, + merkleTree, + root: getCurrentRoot(merkleTreeAccount.tree), + nonce: leafIndex, + index: leafIndex, + metadata, + proof: [], + }).sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'AlreadyUnverified' }); + + // And the leaf was not updated in the merkle tree. + const notUpdatedLeaf = hashLeaf(umi, { + merkleTree, + owner: leafOwner, + leafIndex, + metadata: { + ...metadata, + collection: { + key: collectionMint.publicKey, + verified: false, + }, + }, + }); + merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(notUpdatedLeaf)); +}); diff --git a/clients/js/test/verifyCollection.test.ts b/clients/js/test/verifyCollection.test.ts index 8acfa919..d43caafb 100644 --- a/clients/js/test/verifyCollection.test.ts +++ b/clients/js/test/verifyCollection.test.ts @@ -9,6 +9,7 @@ import { fetchMerkleTree, getCurrentRoot, hashLeaf, + setAndVerifyCollection, verifyCollection, } from '../src'; import { createTree, createUmi, mint } from './_setup'; @@ -71,3 +72,67 @@ test('it can verify the collection of a minted compressed NFT', async (t) => { merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(updatedLeaf)); }); + +test('it cannot verify the collection if it is already verified', async (t) => { + // Given a Collection NFT. + const umi = await createUmi(); + 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); + + // And a tree with a minted NFT that has a verified collection. + const merkleTree = await createTree(umi); + let merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + const leafOwner = generateSigner(umi).publicKey; + const { metadata, leafIndex } = await mint(umi, { merkleTree, leafOwner }); + await setAndVerifyCollection(umi, { + leafOwner, + collectionMint: collectionMint.publicKey, + collectionAuthority, + merkleTree, + root: getCurrentRoot(merkleTreeAccount.tree), + nonce: leafIndex, + index: leafIndex, + metadata, + }).sendAndConfirm(umi); + const verifiedMetadata = { + ...metadata, + collection: { + key: collectionMint.publicKey, + verified: true, + }, + }; + + // When the collection authority attempts to verify the collection. + const promise = verifyCollection(umi, { + leafOwner, + collectionMint: collectionMint.publicKey, + collectionAuthority, + merkleTree, + root: getCurrentRoot(merkleTreeAccount.tree), + nonce: leafIndex, + index: leafIndex, + metadata: verifiedMetadata, + proof: [], + }).sendAndConfirm(umi); + + // Then we expect a program error. + await t.throwsAsync(promise, { name: 'AlreadyVerified' }); + + // And the leaf was not updated in the merkle tree. + const notUpdatedLeaf = hashLeaf(umi, { + merkleTree, + owner: leafOwner, + leafIndex, + metadata: verifiedMetadata, + }); + merkleTreeAccount = await fetchMerkleTree(umi, merkleTree); + t.is(merkleTreeAccount.tree.rightMostPath.leaf, publicKey(notUpdatedLeaf)); +}); From 5ecfa3e3dfecb2fb927f4a3b16a0cdf48e3728f4 Mon Sep 17 00:00:00 2001 From: Fernando Otero Date: Thu, 28 Sep 2023 21:34:28 +0100 Subject: [PATCH 5/8] Add deprecated log message (#2) --- programs/bubblegum/program/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/programs/bubblegum/program/src/lib.rs b/programs/bubblegum/program/src/lib.rs index 4883a79e..03bc3690 100644 --- a/programs/bubblegum/program/src/lib.rs +++ b/programs/bubblegum/program/src/lib.rs @@ -180,6 +180,7 @@ pub mod bubblegum { ctx: Context, decompressable_state: DecompressibleState, ) -> Result<()> { + msg!("Deprecated: please use `set_decompressible_state` instead"); processor::set_decompressible_state(ctx, decompressable_state) } From 26d7e9333f5e59a5ef4d0debac812742676c4af0 Mon Sep 17 00:00:00 2001 From: Fernando Otero Date: Thu, 28 Sep 2023 21:35:44 +0100 Subject: [PATCH 6/8] Set decompressible on tree creation (#3) * Fix binary name * Set decompressible on tree creation --- clients/js-solita/package.json | 2 +- clients/js-solita/tests/main.test.ts | 127 ++++++++++++++++++--------- 2 files changed, 86 insertions(+), 43 deletions(-) diff --git a/clients/js-solita/package.json b/clients/js-solita/package.json index 16c4d418..a1de41b3 100644 --- a/clients/js-solita/package.json +++ b/clients/js-solita/package.json @@ -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", diff --git a/clients/js-solita/tests/main.test.ts b/clients/js-solita/tests/main.test.ts index c70c4074..269f4ba7 100644 --- a/clients/js-solita/tests/main.test.ts +++ b/clients/js-solita/tests/main.test.ts @@ -15,7 +15,7 @@ import { ConcurrentMerkleTreeAccount, SPL_ACCOUNT_COMPRESSION_PROGRAM_ID, SPL_NOOP_PROGRAM_ID, - ValidDepthSizePair + ValidDepthSizePair, } from '@solana/spl-account-compression'; import { @@ -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)); } @@ -69,8 +71,8 @@ async function setupTreeWithCompressedNFT( compressedNFT: MetadataArgs, depthSizePair: ValidDepthSizePair = { maxDepth: 14, - maxBufferSize: 64 - } + maxBufferSize: 64, + }, ): Promise<{ merkleTree: PublicKey; }> { @@ -78,7 +80,10 @@ async function setupTreeWithCompressedNFT( 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, @@ -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, @@ -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', @@ -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', () => { @@ -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); @@ -185,7 +208,7 @@ describe('Bubblegum tests', () => { { maxDepth: 14, maxBufferSize: 64, - } + }, ); merkleTree = result.merkleTree; }); @@ -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', @@ -240,7 +260,7 @@ describe('Bubblegum tests', () => { dataHash: Array.from(computeDataHash(originalCompressedNFT)), creatorHash: Array.from(computeCreatorHash(originalCompressedNFT.creators)), nonce: 0, - index: 0 + index: 0, }, ); @@ -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); }); @@ -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, ); @@ -311,7 +340,7 @@ describe('Bubblegum tests', () => { dataHash: Array.from(computeDataHash(originalCompressedNFT)), creatorHash: Array.from(computeCreatorHash(originalCompressedNFT.creators)), nonce, - index: 0 + index: 0, }, ); @@ -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( @@ -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, ); @@ -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); }); From d99cb7ae5ff67f092ce6ed0fccf52c60262a5637 Mon Sep 17 00:00:00 2001 From: febo Date: Thu, 28 Sep 2023 22:04:30 +0100 Subject: [PATCH 7/8] Fix binary name --- .github/workflows/deploy-program.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index bee294a3..e2943e82 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -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 From e8318bb2a3c02a75c51986f0e867d9afb10737a3 Mon Sep 17 00:00:00 2001 From: Michael Danenberg <56533526+danenbm@users.noreply.github.com> Date: Thu, 28 Sep 2023 14:58:38 -0700 Subject: [PATCH 8/8] Revert "Removing deploy capabilities" This reverts commit c203069e3b99c30c5e6a68176cca2c47bd59b101. --- .github/workflows/deploy-js-client.yml | 131 +++++++++++++++++++++++ .github/workflows/deploy-rust-client.yml | 91 ++++++++++++++++ 2 files changed, 222 insertions(+) create mode 100644 .github/workflows/deploy-js-client.yml create mode 100644 .github/workflows/deploy-rust-client.yml diff --git a/.github/workflows/deploy-js-client.yml b/.github/workflows/deploy-js-client.yml new file mode 100644 index 00000000..d89b4655 --- /dev/null +++ b/.github/workflows/deploy-js-client.yml @@ -0,0 +1,131 @@ +name: Deploy JS Client + +on: + workflow_dispatch: + inputs: + bump: + description: Version bump + required: true + default: patch + type: choice + options: + - patch + - minor + - major + - prerelease + - prepatch + - preminor + - premajor + tag: + description: NPM Tag (and preid for pre-releases) + required: true + type: string + default: latest + create_release: + description: Create a GitHub release + required: true + type: boolean + default: true + +env: + CACHE: true + +jobs: + build_programs: + name: Programs + uses: ./.github/workflows/build-programs.yml + secrets: inherit + + test_js: + name: JS client + needs: build_programs + uses: ./.github/workflows/test-js-client.yml + secrets: inherit + + deploy_js: + name: JS client / Deploy + runs-on: ubuntu-latest + needs: test_js + permissions: + contents: write + steps: + - name: Git checkout + uses: actions/checkout@v3 + with: + token: ${{ secrets.SVC_TOKEN }} + + - name: Load environment variables + run: cat .github/.env >> $GITHUB_ENV + + - name: Install Node.js + uses: metaplex-foundation/actions/install-node-with-pnpm@v1 + with: + version: ${{ env.NODE_VERSION }} + cache: ${{ env.CACHE }} + + - name: Install dependencies + uses: metaplex-foundation/actions/install-node-dependencies@v1 + with: + folder: ./clients/js + cache: ${{ env.CACHE }} + key: clients-js + + - name: Build + working-directory: ./clients/js + run: pnpm build + + - name: Bump + id: bump + working-directory: ./clients/js + run: | + if [ "${{ startsWith(inputs.bump, 'pre') }}" == "true" ]; then + pnpm version ${{ inputs.bump }} --preid ${{ inputs.tag }} --no-git-tag-version + else + pnpm version ${{ inputs.bump }} --no-git-tag-version + fi + echo "new_version=$(pnpm pkg get version | sed 's/"//g')" >> $GITHUB_OUTPUT + + - name: Set publishing config + run: pnpm config set '//registry.npmjs.org/:_authToken' "${NODE_AUTH_TOKEN}" + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + - name: Publish + working-directory: ./clients/js + run: pnpm publish --no-git-checks --tag ${{ inputs.tag }} + + - name: Commit and tag new version + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Deploy JS client v${{ steps.bump.outputs.new_version }} + tagging_message: js@v${{ steps.bump.outputs.new_version }} + + - name: Create GitHub release + if: github.event.inputs.create_release == 'true' + uses: ncipollo/release-action@v1 + with: + tag: js@v${{ steps.bump.outputs.new_version }} + + deploy_js_docs: + name: JS client / Deploy docs + runs-on: ubuntu-latest + needs: deploy_js + environment: + name: js-client-documentation + url: ${{ steps.deploy.outputs.url }} + steps: + - name: Git checkout + uses: actions/checkout@v3 + with: + ref: ${{ github.ref }} + + - name: Load environment variables + run: cat .github/.env >> $GITHUB_ENV + + - name: Deploy to Vercel + id: deploy + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + working-directory: ./clients/js + run: echo "url=$(vercel deploy --prod --token=${{ secrets.VERCEL_TOKEN }})" | tee $GITHUB_OUTPUT diff --git a/.github/workflows/deploy-rust-client.yml b/.github/workflows/deploy-rust-client.yml new file mode 100644 index 00000000..46922d6a --- /dev/null +++ b/.github/workflows/deploy-rust-client.yml @@ -0,0 +1,91 @@ +name: Deploy Rust Client + +on: + workflow_dispatch: + inputs: + level: + description: Level + required: true + default: patch + type: choice + options: + - patch + - minor + - major + - rc + - beta + - alpha + - release + - version + version: + description: Version + required: false + type: string + dry_run: + description: Dry run + required: true + default: true + type: boolean + +env: + CACHE: true + +jobs: + build_programs: + name: Programs + uses: ./.github/workflows/build-programs.yml + secrets: inherit + + build_rust_client: + name: Rust Client + uses: ./.github/workflows/build-rust-client.yml + secrets: inherit + + test_rust_client: + name: Rust Client + needs: [build_programs, build_rust_client] + uses: ./.github/workflows/test-rust-client.yml + secrets: inherit + + publish_crate: + name: Rust Client / Publish Crate + runs-on: ubuntu-latest + needs: test_rust_client + permissions: + contents: write + steps: + - name: Git checkout + uses: actions/checkout@v3 + with: + token: ${{ secrets.SVC_TOKEN }} + + - name: Load environment variables + run: cat .github/.env >> $GITHUB_ENV + + - name: Install Rust + uses: metaplex-foundation/actions/install-rust@v1 + with: + toolchain: "1.70.0" + + - name: Install cargo-release + uses: metaplex-foundation/actions/install-cargo-release@v1 + with: + cache: ${{ env.CACHE }} + + - name: Publish Crate + working-directory: ./clients/rust + run: | + if [ "${{ inputs.level }}" == "version" ]; then + BUMP=${{ inputs.version }} + else + BUMP=${{ inputs.level }} + fi + + if [ "${{ inputs.dry_run }}" == "false" ]; then + OPTIONS="--no-publish --no-confirm --execute" + git config user.name ${{ env.COMMIT_USER_NAME }} + git config user.email ${{ env.COMMIT_USER_EMAIL }} + fi + + cargo login ${{ secrets.CRATES_TOKEN }} + cargo release $BUMP $OPTIONS