diff --git a/.github/workflows/build-programs.yml b/.github/workflows/build-programs.yml index ff38565..eab3fe2 100644 --- a/.github/workflows/build-programs.yml +++ b/.github/workflows/build-programs.yml @@ -7,6 +7,8 @@ on: type: string solana: type: string + git_ref: + type: string workflow_dispatch: inputs: rust: @@ -29,7 +31,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV @@ -59,7 +63,7 @@ jobs: - name: Upload program builds uses: actions/upload-artifact@v4 with: - name: program-builds + name: program-builds-${{ inputs.git_ref }} # First wildcard ensures exported paths are consistently under the programs folder. path: ./program*/.bin/*.so include-hidden-files: true diff --git a/.github/workflows/build-rust-client.yml b/.github/workflows/build-rust-client.yml index 21b3c1f..1008cbe 100644 --- a/.github/workflows/build-rust-client.yml +++ b/.github/workflows/build-rust-client.yml @@ -7,6 +7,8 @@ on: type: string solana: type: string + git_ref: + type: string workflow_dispatch: inputs: rust: @@ -29,7 +31,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 0000000..81b0c91 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,139 @@ +name: Create release + +on: + workflow_dispatch: + inputs: + program: + description: Program + required: true + default: bubblegum + type: choice + options: + - bubblegum + bump: + description: Version bump + required: true + default: patch + type: choice + options: + - patch + - minor + - major + git_ref: + description: Commit hash or branch to create release + required: false + type: string + default: main + + +env: + CACHE: true + +jobs: + build_programs: + name: Programs + uses: ./.github/workflows/build-programs.yml + secrets: inherit + with: + git_ref: ${{ inputs.git_ref }} + + test_programs: + name: Programs + uses: ./.github/workflows/test-programs.yml + secrets: inherit + with: + program_matrix: '["mpl-${{ inputs.program }}"]' + git_ref: ${{ inputs.git_ref }} + + test_js: + name: JS client + needs: build_programs + uses: ./.github/workflows/test-js-client.yml + secrets: inherit + with: + git_ref: ${{ inputs.git_ref }} + + test_rust: + name: Rust client + needs: build_programs + uses: ./.github/workflows/test-rust-client.yml + secrets: inherit + with: + git_ref: ${{ inputs.git_ref }} + + create_release: + name: Create program release + runs-on: ubuntu-latest + needs: test_js + permissions: + contents: write + steps: + - name: Git checkout + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} + - name: Bump Program Version + run: | + git fetch --tags --all + VERSION=`git tag -l --sort -version:refname "release/${{ inputs.program }}@*" | head -n 1 | sed 's|release/${{ inputs.program }}@||'` + 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}" + + echo PROGRAM_VERSION="${PROGRAM_VERSION}" >> $GITHUB_ENV + + - name: Download Program Builds + uses: actions/download-artifact@v4 + with: + name: program-builds-${{ inputs.git_ref }} + + - name: Identify Program + run: | + echo PROGRAM_NAME="mpl_core" >> $GITHUB_ENV + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: release/${{ inputs.program }}@${{ env.PROGRAM_VERSION }} + release_name: ${{ inputs.program }} v${{ env.PROGRAM_VERSION }} + body: | + Release ${{ inputs.program }} v${{ env.PROGRAM_VERSION }} + draft: false + prerelease: false + + - name: Upload Release Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./programs/.bin/${{ env.PROGRAM_NAME }}.so + asset_name: ${{ env.PROGRAM_NAME }}.so + asset_content_type: application/octet-stream + + # - name: Update latest tag + # uses: actions/github-script@v5 + # with: + # script: | + # github.rest.git.createRef({ + # owner: context.repo.owner, + # repo: context.repo.repo, + # ref: 'refs/tags/release/${{ inputs.program }}@latest', + # sha: '${{ github.sha }}' + # }); \ No newline at end of file diff --git a/.github/workflows/deploy-js-client.yml b/.github/workflows/deploy-js-client.yml index d89b465..e1c5e4d 100644 --- a/.github/workflows/deploy-js-client.yml +++ b/.github/workflows/deploy-js-client.yml @@ -50,7 +50,7 @@ jobs: contents: write steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.SVC_TOKEN }} @@ -115,7 +115,7 @@ jobs: url: ${{ steps.deploy.outputs.url }} steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.ref }} diff --git a/.github/workflows/deploy-program.yml b/.github/workflows/deploy-program.yml index 0c06573..b721446 100644 --- a/.github/workflows/deploy-program.yml +++ b/.github/workflows/deploy-program.yml @@ -3,6 +3,11 @@ name: Deploy Program on: workflow_dispatch: inputs: + git_ref: + description: Release tag (release/bubblegum@0.13.0) or commit to deploy + required: true + type: string + default: release/bubblegum@0.13.0 program: description: Program required: true @@ -18,54 +23,80 @@ on: options: - devnet - mainnet-beta - publish_crate: - description: Release cargo crate + - sonic-devnet + - sonic-testnet + - eclipse-mainnet + - eclipse-devnet + dry_run: + description: Dry run + required: false type: boolean default: false - bump: - description: Version bump - required: true - default: patch - type: choice - options: - - patch - - minor - - major env: CACHE: true jobs: + check_tag: + name: 'Check tag' + runs-on: ubuntu-latest + outputs: + program: ${{ steps.set_program.outputs.program }} + type: ${{ steps.set_program.outputs.type }} + steps: + - name: Check tag + id: set_program + run: | + echo program="bubblegum" >> $GITHUB_OUTPUT + if [[ "${{ inputs.git_ref }}" =~ ^release/bubblegum@* ]]; then + echo type="release" >> $GITHUB_OUTPUT + else + echo type="ref" >> $GITHUB_OUTPUT + fi build_programs: name: Programs uses: ./.github/workflows/build-programs.yml secrets: inherit + needs: check_tag + if: needs.check_tag.outputs.type == 'ref' + with: + git_ref: ${{ inputs.git_ref }} test_programs: name: Programs - needs: build_programs uses: ./.github/workflows/test-programs.yml secrets: inherit + needs: [build_programs, check_tag] + if: needs.check_tag.outputs.type == 'ref' with: program_matrix: '["${{ inputs.program }}"]' + git_ref: ${{ inputs.git_ref }} test_js: name: JS client - needs: test_programs + needs: [build_programs, check_tag] uses: ./.github/workflows/test-js-client.yml secrets: inherit + if: needs.check_tag.outputs.type == 'ref' + with: + git_ref: ${{ inputs.git_ref }} deploy_program: name: Program / Deploy runs-on: ubuntu-latest - needs: test_js + needs: [check_tag, test_js, test_programs] permissions: contents: write + if: | + always() + && (needs.test_js.result == 'success' || needs.test_js.result == 'skipped') + && (needs.test_programs.result == 'success' || needs.test_programs.result == 'skipped') steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.SVC_TOKEN }} + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV @@ -101,94 +132,163 @@ jobs: - name: Set RPC run: | + # We do this if waterfall because github actions does not allow dynamic access to secrets if [ "${{ inputs.cluster }}" == "devnet" ]; then echo RPC=${{ secrets.DEVNET_RPC }} >> $GITHUB_ENV - else + elif [ "${{ inputs.cluster }}" == "mainnet-beta" ]; then echo RPC=${{ secrets.MAINNET_RPC }} >> $GITHUB_ENV + elif [ "${{ inputs.cluster }}" == "sonic-devnet" ]; then + echo RPC=${{ secrets.SONIC_DEVNET_RPC }} >> $GITHUB_ENV + elif [ "${{ inputs.cluster }}" == "sonic-testnet" ]; then + echo RPC=${{ secrets.SONIC_TESTNET_RPC }} >> $GITHUB_ENV + elif [ "${{ inputs.cluster }}" == "eclipse-devnet" ]; then + echo RPC=${{ secrets.ECLIPSE_DEVNET_RPC }} >> $GITHUB_ENV + elif [ "${{ inputs.cluster }}" == "eclipse-testnet" ]; then + echo RPC=${{ secrets.ECLIPSE_TESTNET_RPC }} >> $GITHUB_ENV + elif [ "${{ inputs.cluster }}" == "eclipse-mainnet" ]; then + echo RPC=${{ secrets.ECLIPSE_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="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 + echo PROGRAM_NAME="bubblegum" >> $GITHUB_ENV + echo ${{ secrets.BUBBLEGUM_ID }} > ./program-id.json + + if [[ "${{ inputs.cluster }}" == "sonic"* ]]; then + echo ${{ secrets.BUBBLEGUM_SONIC_DEPLOY_KEY }} > ./deployer-key.json + echo DEPLOY_TYPE="direct" >> $GITHUB_ENV + elif [[ "${{ inputs.cluster }}" == "eclipse"* ]]; then + echo ${{ secrets.BUBBLEGUM_ECLIPSE_DEPLOY_KEY }} > ./deployer-key.json + echo DEPLOY_TYPE="direct" >> $GITHUB_ENV + elif [[ "${{ inputs.cluster }}" == "devnet" ]]; then + echo DEPLOY_TYPE="squads" >> $GITHUB_ENV + echo SQUADS_MULTISIG="Gs6jZWxXFvmZtBcyYr6fBXX5ikwRTemBDS4f6kFuB31U" >> $GITHUB_ENV + echo SQUADS_VAULT="Fsxr5WVKZZoeb7xgwTWRHymSRVGY9vk7m5B5GPu1KU59" >> $GITHUB_ENV + echo SQUADS_PROGRAM_INDEX="2" >> $GITHUB_ENV + elif [[ "${{ inputs.cluster }}" == "mainnet-beta" ]]; then + echo DEPLOY_TYPE="squads" >> $GITHUB_ENV + echo SQUADS_MULTISIG="EADFTJ9b6yPAqyRjhz1scirvWhggMSWTt1BgBjKgm5wT" >> $GITHUB_ENV + echo SQUADS_VAULT="bfQVv6niKVgEURYqQ1beJmiEQQN7MrvLRvk3mZGFubb" >> $GITHUB_ENV + echo SQUADS_PROGRAM_INDEX="2" >> $GITHUB_ENV 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 + echo "Invalid cluster: ${{ inputs.cluster }}" + exit 1 + fi - name: Download program builds uses: actions/download-artifact@v4 + if: needs.check_tag.outputs.type == 'ref' with: - name: program-builds + name: program-builds-${{ inputs.git_ref }} - name: Deploy program + if: github.event.inputs.dry_run == 'false' && env.DEPLOY_TYPE == 'direct' run: | - echo "Deploying ${{ inputs.program }} to ${{ inputs.cluster }}" + echo "Deploying ${{ needs.check_tag.outputs.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 \ + -k ./deployer-key.json \ --max-sign-attempts 100 \ --use-rpc + + rm ./deployer-key.json + rm ./program-id.json - - name: Upgrade IDL - working-directory: ./programs/${{ inputs.program }} - run: | - jq 'del(.metadata?) | del(.. | .docs?)' ../../idls/`echo "${{ inputs.program }}" | tr - _`.json > ./idl.json + - name: Download release asset + uses: actions/github-script@v5 + id: get_release + if: needs.check_tag.outputs.type == 'release' + with: + script: | + const tag = "${{ inputs.git_ref }}"; + const assetName = "${{ env.PROGRAM_NAME }}.so"; - anchor idl upgrade -f ./idl.json \ - --provider.cluster ${{ env.RPC }} \ - --provider.wallet ../../deploy-key.json \ - `solana address -k ../../program-id.json` + // Fetch the release associated with the tag + const release = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: tag + }); - rm ../../deploy-key.json - rm ../../program-id.json - rm ./idl.json + if (release.status !== 200) { + throw new Error(`Failed to fetch release for tag ${tag}`); + } + + const asset = release.data.assets.find(asset => asset.name === assetName); + if (!asset) { + throw new Error(`Asset ${assetName} not found in release tagged ${tag}`); + } + + core.setOutput("url", asset.url); + + - name: Download the Selected Asset + if: needs.check_tag.outputs.type == 'release' + run: | + mkdir -p ${{ github.workspace }}/programs/.bin + curl -L -o ${{ github.workspace }}/programs/.bin/${{ env.PROGRAM_NAME }}.so \ + -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ + -H "Accept: application/octet-stream" \ + "${{ steps.get_release.outputs.url }}" - - name: Version program - working-directory: ./programs/${{ inputs.program }}/program - if: github.event.inputs.publish_crate == 'true' && github.event.inputs.cluster == 'mainnet-beta' + - name: Deploy squads buffer + if: github.event.inputs.dry_run == 'false' && env.DEPLOY_TYPE == 'squads' run: | - git stash - git config user.name "${{ env.COMMIT_USER_NAME }}" - git config user.email "${{ env.COMMIT_USER_EMAIL }}" + echo "Deploying buffer for ${{ inputs.program }} on ${{ inputs.cluster }}" + echo ${{ secrets.SQUADS_BOT_KEY }} > ./submitter-key.json - cargo login ${{ secrets.CRATES_TOKEN }} - cargo release ${{ inputs.bump }} --no-confirm --no-push --no-tag --no-publish --execute + BUFFER=$(solana program write-buffer -u ${{ env.RPC }} -k ./submitter-key.json --max-sign-attempts 100 --use-rpc ./programs/.bin/${{ env.PROGRAM_NAME }}.so | awk '{print $2}') + echo "Buffer: $BUFFER" - git reset --soft HEAD~1 - git stash pop + solana program set-buffer-authority $BUFFER \ + --new-buffer-authority ${{ env.SQUADS_VAULT }} \ + -k ./submitter-key.json \ + -u ${{ env.RPC }} + + rm ./submitter-key.json + + echo "BUFFER=$BUFFER" >> $GITHUB_ENV + + - name: Create Squads proposal + if: github.event.inputs.dry_run == 'false' && env.DEPLOY_TYPE == 'squads' + uses: metaplex-foundation/squads-program-upgrade@main + with: + network-url: ${{ env.RPC }} + program-multisig: ${{ env.SQUADS_MULTISIG }} + program-id: 'BGUMAp9Gq7iTEuizy4pqaxsTyUCBK68MDfK752saRPUY' + program-index: ${{ env.SQUADS_PROGRAM_INDEX }} + buffer: ${{ env.BUFFER }} + spill-address: 'botTxAkJhuCtNNn9xsH8fHJjzTkcN6XD4dR3R5hkzV2' + authority: ${{ env.SQUADS_VAULT }} + name: 'Deploy ${{ inputs.git_ref }}' + keypair: ${{ secrets.SQUADS_BOT_KEY }} - - 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' + - name: Create env tag + uses: actions/github-script@v7 + if: github.event.inputs.dry_run == 'false' with: - commit_message: "chore: ${{ inputs.program }} version ${{ env.PROGRAM_VERSION }}" - tagging_message: v${{ env.PROGRAM_VERSION }}@${{ inputs.cluster }} + github-token: ${{ secrets.GH_TAGGER_TOKEN }} + script: | + const refData = await github.rest.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'tags/${{ inputs.git_ref }}' + }); + if (refData.status !== 200) { + throw new Error('Failed to fetch existing tag'); + } + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'refs/tags/${{ needs.check_tag.outputs.program }}-${{ inputs.cluster }}', + sha: refData.data.object.sha + }).catch(err => { + if (err.status !== 422) throw err; + github.rest.git.updateRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'tags/${{ needs.check_tag.outputs.program }}-${{ inputs.cluster }}', + sha: refData.data.object.sha + }); + }) diff --git a/.github/workflows/deploy-rust-client.yml b/.github/workflows/deploy-rust-client.yml index 74a4f2b..88c46b8 100644 --- a/.github/workflows/deploy-rust-client.yml +++ b/.github/workflows/deploy-rust-client.yml @@ -55,7 +55,7 @@ jobs: contents: write steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: token: ${{ secrets.SVC_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 73820d8..f70ec31 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -20,7 +20,7 @@ jobs: rust_client: ${{ steps.changes.outputs.rust_client }} steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV @@ -62,7 +62,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV diff --git a/.github/workflows/test-js-client.yml b/.github/workflows/test-js-client.yml index bac8d4a..b34199b 100644 --- a/.github/workflows/test-js-client.yml +++ b/.github/workflows/test-js-client.yml @@ -2,6 +2,9 @@ name: Test JS client on: workflow_call: + inputs: + git_ref: + type: string env: CACHE: true @@ -15,7 +18,9 @@ jobs: node: ["18.x", "20.x"] steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV @@ -26,6 +31,7 @@ jobs: node: ${{ matrix.node }} solana: ${{ env.SOLANA_VERSION }} cache: ${{ env.CACHE }} + artifacts: program-builds-${{ inputs.git_ref }} - name: Install dependencies uses: metaplex-foundation/actions/install-node-dependencies@v1 @@ -49,7 +55,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV diff --git a/.github/workflows/test-programs.yml b/.github/workflows/test-programs.yml index 7b78da6..5c32676 100644 --- a/.github/workflows/test-programs.yml +++ b/.github/workflows/test-programs.yml @@ -5,6 +5,8 @@ on: inputs: program_matrix: type: string + git_ref: + type: string env: CACHE: true @@ -18,7 +20,9 @@ jobs: program: ${{ fromJson(inputs.program_matrix) }} steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV diff --git a/.github/workflows/test-rust-client.yml b/.github/workflows/test-rust-client.yml index 23c5d06..bf2cc38 100644 --- a/.github/workflows/test-rust-client.yml +++ b/.github/workflows/test-rust-client.yml @@ -5,6 +5,8 @@ on: inputs: program_matrix: type: string + git_ref: + type: string env: CACHE: true @@ -15,7 +17,9 @@ jobs: runs-on: ubuntu-latest-16-cores steps: - name: Git checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 + with: + ref: ${{ inputs.git_ref }} - name: Load environment variables run: cat .github/.env >> $GITHUB_ENV @@ -34,7 +38,7 @@ jobs: - name: Download program builds uses: actions/download-artifact@v4 with: - name: program-builds + name: program-builds-${{ inputs.git_ref }} - name: Run tests shell: bash