Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions .github/workflows/test-write-program-buffer.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
name: Test write-program-buffer

on:
pull_request:
workflow_dispatch:

jobs:
integration:
name: ${{ matrix.scenario }} (solana ${{ matrix.solana-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
solana-version: ["3.1.14", "4.1.2"]
scenario: [fresh, clamp, authority, nearmax]
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0

- uses: ./setup-all
with:
solana_version: ${{ matrix.solana-version }}

- name: Start local validator
id: validator
uses: ./start-test-validator

- name: Prepare scenario
id: prepare
shell: bash
run: ./write-program-buffer/tests/integration/prepare-${{ matrix.scenario }}.sh

- name: Run write-program-buffer action
id: run
uses: ./write-program-buffer
with:
program-id: ${{ steps.prepare.outputs.program-id }}
program: fixture-${{ matrix.scenario }}
rpc-url: ${{ steps.validator.outputs.rpc-url }}
keypair: ${{ steps.prepare.outputs.keypair }}
buffer-authority-address: ${{ steps.prepare.outputs.buffer-authority }}

- name: Assert scenario
shell: bash
env:
BUFFER: ${{ steps.run.outputs.buffer }}
PROGRAM_ID: ${{ steps.prepare.outputs.program-id }}
DEPLOYER: ${{ steps.prepare.outputs.deployer }}
BUFFER_AUTHORITY: ${{ steps.prepare.outputs.buffer-authority }}
PRE_LEN: ${{ steps.prepare.outputs.pre-len }}
run: ./write-program-buffer/tests/integration/assert-${{ matrix.scenario }}.sh

- name: Dump validator log on failure
if: failure()
shell: bash
run: tail -100 "${{ steps.validator.outputs.ledger-dir }}/validator.log" || true
53 changes: 53 additions & 0 deletions start-test-validator/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: "Start Test Validator"
description: "Starts a local solana-test-validator in the background and waits until it is healthy"

inputs:
ledger-dir:
description: "Ledger directory (defaults to <runner temp>/test-ledger)"
required: false
default: ""
max-attempts:
description: "Maximum health check attempts, roughly one per second"
required: false
default: "60"

outputs:
rpc-url:
description: "RPC URL of the started validator"
value: ${{ steps.start.outputs.rpc-url }}
ledger-dir:
description: "Ledger directory of the started validator"
value: ${{ steps.start.outputs.ledger-dir }}

runs:
using: "composite"
steps:
- name: Start validator
id: start
shell: bash
run: |
RPC_URL="http://127.0.0.1:8899"
LEDGER_DIR="${{ inputs.ledger-dir }}"
if [ -z "$LEDGER_DIR" ]; then
LEDGER_DIR="${RUNNER_TEMP:-/tmp}/test-ledger"
fi

rm -rf "$LEDGER_DIR"
solana-test-validator --reset --quiet --ledger "$LEDGER_DIR" > "${RUNNER_TEMP:-/tmp}/validator-stdout.log" 2>&1 &
echo "Validator started with PID $!"

echo "rpc-url=$RPC_URL" >> $GITHUB_OUTPUT
echo "ledger-dir=$LEDGER_DIR" >> $GITHUB_OUTPUT

- name: Wait for validator health
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
with:
timeout_seconds: 5
max_attempts: ${{ inputs.max-attempts }}
retry_wait_seconds: 1
command: solana cluster-version -u ${{ steps.start.outputs.rpc-url }}

- name: Dump validator log if unhealthy
if: failure()
shell: bash
run: tail -50 "${{ steps.start.outputs.ledger-dir }}/validator.log" 2>/dev/null || true
Binary file not shown.
Binary file not shown.
Binary file added write-program-buffer/tests/fixtures/program-small.so
Binary file not shown.
27 changes: 27 additions & 0 deletions write-program-buffer/tests/integration/assert-authority.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="http://127.0.0.1:8899"
ARTIFACT="target/deploy/fixture-authority.so"

fail() {
echo "ASSERT FAIL: $1" >&2
exit 1
}

[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address"
[ -n "${BUFFER_AUTHORITY:-}" ] || fail "BUFFER_AUTHORITY env not set"
[ -n "${DEPLOYER:-}" ] || fail "DEPLOYER env not set"

BUFFER_INFO=$(solana program show "$BUFFER" -u "$RPC_URL")
echo "$BUFFER_INFO"
AUTHORITY=$(echo "$BUFFER_INFO" | grep "Authority:" | awk '{print $2}')
[ "$AUTHORITY" = "$BUFFER_AUTHORITY" ] || fail "buffer authority is $AUTHORITY, expected $BUFFER_AUTHORITY"
[ "$AUTHORITY" != "$DEPLOYER" ] || fail "buffer authority still equals the deployer"

DUMP="$(mktemp)"
solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER"
cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact"
rm -f "$DUMP"

echo "Authority transfer assertions passed"
26 changes: 26 additions & 0 deletions write-program-buffer/tests/integration/assert-clamp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="http://127.0.0.1:8899"
ARTIFACT="target/deploy/fixture-clamp.so"
MIN_EXTEND_SIZE=10240

fail() {
echo "ASSERT FAIL: $1" >&2
exit 1
}

[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address"
[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set"
[ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set"

POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}')
GROWTH=$((POST_LEN - PRE_LEN))
[ "$GROWTH" -eq "$MIN_EXTEND_SIZE" ] || fail "program grew by $GROWTH bytes, expected exactly $MIN_EXTEND_SIZE"

DUMP="$(mktemp)"
solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER"
cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact"
rm -f "$DUMP"

echo "Clamp regression assertions passed: program grew by exactly $MIN_EXTEND_SIZE bytes"
30 changes: 30 additions & 0 deletions write-program-buffer/tests/integration/assert-fresh.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="http://127.0.0.1:8899"
ARTIFACT="target/deploy/fixture-fresh.so"

fail() {
echo "ASSERT FAIL: $1" >&2
exit 1
}

[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address"
[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set"
[ -n "${DEPLOYER:-}" ] || fail "DEPLOYER env not set"

DUMP="$(mktemp)"
solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER"
cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact"
rm -f "$DUMP"

BUFFER_INFO=$(solana program show "$BUFFER" -u "$RPC_URL")
echo "$BUFFER_INFO"
AUTHORITY=$(echo "$BUFFER_INFO" | grep "Authority:" | awk '{print $2}')
[ "$AUTHORITY" = "$DEPLOYER" ] || fail "buffer authority is $AUTHORITY, expected deployer $DEPLOYER"

if solana program show "$PROGRAM_ID" -u "$RPC_URL" 2>&1 | grep -q "Data Length:"; then
fail "program $PROGRAM_ID unexpectedly exists"
fi

echo "Fresh-program scenario assertions passed"
26 changes: 26 additions & 0 deletions write-program-buffer/tests/integration/assert-nearmax.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="http://127.0.0.1:8899"
ARTIFACT="target/deploy/fixture-nearmax.so"
MAX_PROGRAM_SIZE=10485715

fail() {
echo "ASSERT FAIL: $1" >&2
exit 1
}

[ -n "${BUFFER:-}" ] || fail "action did not output a buffer address"
[ -n "${PROGRAM_ID:-}" ] || fail "PROGRAM_ID env not set"
[ -n "${PRE_LEN:-}" ] || fail "PRE_LEN env not set"

POST_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}')
[ "$POST_LEN" -eq "$MAX_PROGRAM_SIZE" ] || fail "program data length is $POST_LEN, expected the exact maximum $MAX_PROGRAM_SIZE"
echo "Program extended from $PRE_LEN to $POST_LEN (exact headroom of $((POST_LEN - PRE_LEN)) bytes)"

DUMP="$(mktemp)"
solana program dump "$BUFFER" "$DUMP" -u "$RPC_URL" || fail "could not dump buffer $BUFFER"
cmp -s "$ARTIFACT" "$DUMP" || fail "buffer contents differ from artifact"
rm -f "$DUMP"

echo "Near-max extend assertions passed"
39 changes: 39 additions & 0 deletions write-program-buffer/tests/integration/prepare-authority.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="http://127.0.0.1:8899"
FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)"
SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-authority"

mkdir -p "$SCENARIO_DIR" target/deploy

solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/deployer.json" >/dev/null
solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/program-id.json" >/dev/null
solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/authority-target.json" >/dev/null
DEPLOYER=$(solana-keygen pubkey "$SCENARIO_DIR/deployer.json")
PROGRAM_ID=$(solana-keygen pubkey "$SCENARIO_DIR/program-id.json")
AUTHORITY_TARGET=$(solana-keygen pubkey "$SCENARIO_DIR/authority-target.json")

for i in 1 2 3; do
solana airdrop 100 "$DEPLOYER" -u "$RPC_URL" && break
if [ "$i" -eq 3 ]; then
echo "Airdrop failed after 3 attempts" >&2
exit 1
fi
sleep 2
done

solana program deploy "$FIXTURES_DIR/program-small.so" \
--program-id "$SCENARIO_DIR/program-id.json" \
-u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \
--commitment confirmed

cp "$FIXTURES_DIR/program-small.so" target/deploy/fixture-authority.so

echo "Prepared authority scenario: deployer=$DEPLOYER program-id=$PROGRAM_ID authority-target=$AUTHORITY_TARGET"
{
echo "keypair=$(cat "$SCENARIO_DIR/deployer.json")"
echo "deployer=$DEPLOYER"
echo "program-id=$PROGRAM_ID"
echo "buffer-authority=$AUTHORITY_TARGET"
} >> "$GITHUB_OUTPUT"
52 changes: 52 additions & 0 deletions write-program-buffer/tests/integration/prepare-clamp.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="http://127.0.0.1:8899"
FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)"
SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-clamp"

SMALL_SIZE=$(wc -c < "$FIXTURES_DIR/program-small.so" | tr -d ' ')
BIG_SIZE=$(wc -c < "$FIXTURES_DIR/program-big.so" | tr -d ' ')
DELTA=$((BIG_SIZE - SMALL_SIZE))
if [ "$DELTA" -le 0 ] || [ "$DELTA" -ge 10240 ]; then
echo "Fixture drift: big-small delta of $DELTA bytes is outside (0, 10240)" >&2
exit 1
fi

mkdir -p "$SCENARIO_DIR" target/deploy

solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/deployer.json" >/dev/null
solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/program-id.json" >/dev/null
DEPLOYER=$(solana-keygen pubkey "$SCENARIO_DIR/deployer.json")
PROGRAM_ID=$(solana-keygen pubkey "$SCENARIO_DIR/program-id.json")

for i in 1 2 3; do
solana airdrop 100 "$DEPLOYER" -u "$RPC_URL" && break
if [ "$i" -eq 3 ]; then
echo "Airdrop failed after 3 attempts" >&2
exit 1
fi
sleep 2
done

solana program deploy "$FIXTURES_DIR/program-small.so" \
--program-id "$SCENARIO_DIR/program-id.json" \
-u "$RPC_URL" -k "$SCENARIO_DIR/deployer.json" \
--commitment confirmed

PRE_LEN=$(solana program show "$PROGRAM_ID" -u "$RPC_URL" | grep "Data Length:" | awk '{print $3}')
if [ -z "$PRE_LEN" ]; then
echo "Could not read deployed program size" >&2
exit 1
fi

cp "$FIXTURES_DIR/program-big.so" target/deploy/fixture-clamp.so

echo "Prepared clamp scenario: deployer=$DEPLOYER program-id=$PROGRAM_ID pre-len=$PRE_LEN"
{
echo "keypair=$(cat "$SCENARIO_DIR/deployer.json")"
echo "deployer=$DEPLOYER"
echo "program-id=$PROGRAM_ID"
echo "buffer-authority=$DEPLOYER"
echo "pre-len=$PRE_LEN"
} >> "$GITHUB_OUTPUT"
32 changes: 32 additions & 0 deletions write-program-buffer/tests/integration/prepare-fresh.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#!/usr/bin/env bash
set -euo pipefail

RPC_URL="http://127.0.0.1:8899"
FIXTURES_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../fixtures" && pwd)"
SCENARIO_DIR="${RUNNER_TEMP:-/tmp}/scenario-fresh"

mkdir -p "$SCENARIO_DIR" target/deploy

solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/deployer.json" >/dev/null
solana-keygen new -s --no-bip39-passphrase --force -o "$SCENARIO_DIR/program-id.json" >/dev/null
DEPLOYER=$(solana-keygen pubkey "$SCENARIO_DIR/deployer.json")
PROGRAM_ID=$(solana-keygen pubkey "$SCENARIO_DIR/program-id.json")

for i in 1 2 3; do
solana airdrop 100 "$DEPLOYER" -u "$RPC_URL" && break
if [ "$i" -eq 3 ]; then
echo "Airdrop failed after 3 attempts" >&2
exit 1
fi
sleep 2
done

cp "$FIXTURES_DIR/program-small.so" target/deploy/fixture-fresh.so

echo "Prepared fresh scenario: deployer=$DEPLOYER program-id=$PROGRAM_ID"
{
echo "keypair=$(cat "$SCENARIO_DIR/deployer.json")"
echo "deployer=$DEPLOYER"
echo "program-id=$PROGRAM_ID"
echo "buffer-authority=$DEPLOYER"
} >> "$GITHUB_OUTPUT"
Loading
Loading