Skip to content

ci(contracts): add deployment testing workflow for ATS contracts #10

ci(contracts): add deployment testing workflow for ATS contracts

ci(contracts): add deployment testing workflow for ATS contracts #10

name: "100: [FLOW] ATS Deployment Test"
on:
pull_request:
paths:
- "packages/ats/contracts/**"
- ".github/workflows/100-flow-ats-deployment-test.yaml"
workflow_dispatch:
permissions:
contents: read
jobs:
# ==========================================================================
# Job 0: Build contracts (shared by all deployment jobs)
# ==========================================================================
# Installs dependencies and builds contracts once, then caches both
# node_modules and build artifacts for downstream deployment jobs.
#
# Cache strategy:
# - Dependencies: keyed on package-lock.json hash (skip npm ci on hit)
# - Build artifacts: keyed on Solidity source + config hash (skip build on hit)
# - Downstream jobs use actions/cache/restore (read-only)
# ==========================================================================
build:
name: Build Contracts
runs-on: token-studio-linux-large
timeout-minutes: 10
env:
NODE_OPTIONS: "--max-old-space-size=32768"
CONTRACT_SIZER_RUN_ON_COMPILE: "false"
REPORT_GAS: "false"
steps:
- name: Harden Runner
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup NodeJS Environment
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22.20.0
- name: Restore dependencies from cache
id: deps-cache
uses: actions/cache@v4
with:
path: |
node_modules
*/*/node_modules
*/*/*/node_modules
~/.npm
key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json') }}
- name: Install dependencies
if: steps.deps-cache.outputs.cache-hit != 'true'
run: npm ci
- name: Restore build artifacts from cache
id: build-cache
uses: actions/cache@v4
with:
path: |
packages/ats/contracts/artifacts
packages/ats/contracts/build
packages/ats/contracts/typechain-types
packages/ats/contracts/cache
key: ${{ runner.os }}-contracts-build-${{ hashFiles('packages/ats/contracts/contracts/**/*.sol', 'packages/ats/contracts/hardhat.config.ts', 'package-lock.json') }}
- name: Build contracts
if: steps.build-cache.outputs.cache-hit != 'true'
run: npm run ats:contracts:build
# ==========================================================================
# Job 1: Deploy ATS system to a persistent Hardhat node
# ==========================================================================
# Runs the real CLI deployment pipeline (npx tsx scripts/cli/deploySystemWithNewBlr.ts)
# against a Hardhat JSON-RPC node on port 8545.
#
# Env var flow:
# NETWORK=local -> requireNetworkSigner() -> createNetworkSigner("local")
# -> getNetworkConfig("local") returns { jsonRpcUrl: "http://127.0.0.1:8545" }
# -> getPrivateKey("local", 0) reads LOCAL_PRIVATE_KEY_0
# -> new Wallet(privateKey, new JsonRpcProvider(jsonRpcUrl))
# ==========================================================================
hardhat-deployment:
name: Deploy to Hardhat Network
needs: [build]
runs-on: token-studio-linux-large
timeout-minutes: 15
env:
NODE_OPTIONS: "--max-old-space-size=32768"
CONTRACT_SIZER_RUN_ON_COMPILE: "false"
REPORT_GAS: "false"
steps:
- name: Harden Runner
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup NodeJS Environment
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22.20.0
- name: Restore dependencies from cache
uses: actions/cache/restore@v4
with:
path: |
node_modules
*/*/node_modules
*/*/*/node_modules
~/.npm
key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json') }}
fail-on-cache-miss: true
- name: Restore build artifacts from cache
uses: actions/cache/restore@v4
with:
path: |
packages/ats/contracts/artifacts
packages/ats/contracts/build
packages/ats/contracts/typechain-types
packages/ats/contracts/cache
key: ${{ runner.os }}-contracts-build-${{ hashFiles('packages/ats/contracts/contracts/**/*.sol', 'packages/ats/contracts/hardhat.config.ts', 'package-lock.json') }}
fail-on-cache-miss: true
- name: Start Hardhat node
working-directory: packages/ats/contracts
run: |
npx hardhat node &
echo "Hardhat node started in background (PID: $!)"
- name: Wait for Hardhat node
run: |
echo "Waiting for Hardhat JSON-RPC on port 8545..."
for i in $(seq 1 30); do
if curl -sf http://localhost:8545 -X POST \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
> /dev/null 2>&1; then
echo "Hardhat node is ready"
exit 0
fi
echo " Attempt $i/30 - waiting..."
sleep 1
done
echo "::error::Hardhat node failed to start within 30 seconds"
exit 1
- name: Deploy ATS system
working-directory: packages/ats/contracts
env:
# NETWORK=local -> Configuration.endpoints.local.jsonRpc defaults to http://127.0.0.1:8545
NETWORK: local
# Hardhat Account #0 well-known private key (10,000 ETH pre-funded)
LOCAL_PRIVATE_KEY_0: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
run: npx tsx scripts/cli/deploySystemWithNewBlr.ts
- name: Verify deployment output
if: success()
working-directory: packages/ats/contracts
run: |
echo "Checking deployment output files..."
if ls deployments/local/*.json 1> /dev/null 2>&1; then
echo "Deployment output files found:"
ls -la deployments/local/
else
echo "::warning::No deployment output files found (non-fatal)"
fi
# ==========================================================================
# Job 2: Deploy ATS system to a Hiero Solo network
# ==========================================================================
# Runs the same CLI deployment pipeline against a real Hedera network
# deployed locally via Hiero Solo (Kubernetes-based consensus node +
# JSON-RPC relay via hiero-solo-action).
#
# This catches Hedera-specific issues that Hardhat misses:
# - Gas estimation with real HBAR-based gas
# - Transaction confirmation delays
# - Hedera EVM differences
# - Contract ID resolution (0.0.XXXX)
#
# Uses the official hiero-solo-action GitHub Action to provision the network.
# Gracefully skips if Docker is not available on the runner (Solo needs
# Docker for Kubernetes via kind).
#
# Env var flow:
# NETWORK=hedera-local -> requireNetworkSigner() -> createNetworkSigner("hedera-local")
# -> getNetworkConfig("hedera-local") returns { jsonRpcUrl: "http://127.0.0.1:7546" }
# -> getPrivateKey("hedera-local", 0) reads HEDERA_LOCAL_PRIVATE_KEY_0
# -> new Wallet(privateKey, new JsonRpcProvider(jsonRpcUrl))
# ==========================================================================
solo-deployment:
name: Deploy to Hiero Solo Network
needs: [build]
runs-on: token-studio-linux-large
timeout-minutes: 30
env:
NODE_OPTIONS: "--max-old-space-size=32768"
CONTRACT_SIZER_RUN_ON_COMPILE: "false"
REPORT_GAS: "false"
steps:
- name: Harden Runner
uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Setup NodeJS Environment
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: 22.20.0
- name: Check Docker availability
id: docker-check
run: |
if docker info > /dev/null 2>&1; then
echo "available=true" >> "${GITHUB_OUTPUT}"
echo "Docker is available"
docker --version
else
echo "available=false" >> "${GITHUB_OUTPUT}"
echo "::warning::Docker not available - skipping Solo deployment test"
fi
- name: Restore dependencies from cache
if: steps.docker-check.outputs.available == 'true'
uses: actions/cache/restore@v4
with:
path: |
node_modules
*/*/node_modules
*/*/*/node_modules
~/.npm
key: ${{ runner.os }}-deps-${{ hashFiles('package-lock.json') }}
fail-on-cache-miss: true
- name: Restore build artifacts from cache
if: steps.docker-check.outputs.available == 'true'
uses: actions/cache/restore@v4
with:
path: |
packages/ats/contracts/artifacts
packages/ats/contracts/build
packages/ats/contracts/typechain-types
packages/ats/contracts/cache
key: ${{ runner.os }}-contracts-build-${{ hashFiles('packages/ats/contracts/contracts/**/*.sol', 'packages/ats/contracts/hardhat.config.ts', 'package-lock.json') }}
fail-on-cache-miss: true
- name: Setup Hiero Solo Network
id: solo
if: steps.docker-check.outputs.available == 'true'
uses: hiero-ledger/hiero-solo-action@fbca3e7a99ce9aa8a250563a81187abe115e0dad # v0.16.0
with:
installRelay: true
installMirrorNode: true
hbarAmount: 10000000
- name: Register ECDSA deployer with EVM alias
id: extract-key
if: steps.docker-check.outputs.available == 'true' && steps.solo.outcome == 'success'
env:
DER_KEY: ${{ steps.solo.outputs.ecdsaPrivateKey }}
DEPLOYMENT: ${{ steps.solo.outputs.deployment }}
run: |
# Solo outputs DER-encoded ECDSA secp256k1 key.
# The raw 32-byte private key is the last 64 hex characters.
RAW_KEY="0x${DER_KEY: -64}"
echo "key=${RAW_KEY}" >> "${GITHUB_OUTPUT}"
# Derive EVM address for relay readiness check
EVM_ADDR=$(node -e "const{Wallet}=require('ethers');console.log(new Wallet('${RAW_KEY}').address)")
echo "address=${EVM_ADDR}" >> "${GITHUB_OUTPUT}"
echo "Deployer EVM address: ${EVM_ADDR}"
# Solo's action creates ECDSA accounts WITHOUT an EVM alias.
# The JSON-RPC relay needs the alias to resolve EVM addresses.
# Create a new funded account with the same key but with --set-alias,
# which registers the ECDSA-derived EVM address on the Hedera network.
echo "Creating aliased account via Solo CLI..."
solo ledger account create \
--ecdsa-private-key "${DER_KEY}" \
--set-alias \
--hbar-amount 10000000 \
--deployment "${DEPLOYMENT}" \
--dev
- name: Wait for EVM alias indexing
if: steps.docker-check.outputs.available == 'true' && steps.solo.outcome == 'success'
env:
EVM_ADDR: ${{ steps.extract-key.outputs.address }}
run: |
echo "Waiting for relay to recognize ${EVM_ADDR}..."
for i in $(seq 1 60); do
RESULT=$(curl -sf http://localhost:7546 -X POST \
-H "Content-Type: application/json" \
-d "{\"jsonrpc\":\"2.0\",\"method\":\"eth_getBalance\",\"params\":[\"${EVM_ADDR}\",\"latest\"],\"id\":1}" \
2>/dev/null || echo '{}')
# Check for non-zero balance (alias active + funded)
CHECK=$(echo "${RESULT}" | node -e "
const d = require('fs').readFileSync('/dev/stdin','utf8');
try { const r = JSON.parse(d).result;
console.log(r && r !== '0x0' && r !== '0x' ? 'ready' : 'waiting');
} catch(e) { console.log('error'); }
")
if [[ "${CHECK}" == "ready" ]]; then
echo "Relay recognizes account with balance (attempt ${i})"
echo "Balance response: ${RESULT}"
exit 0
fi
echo " Attempt ${i}/60 - status: ${CHECK}"
sleep 5
done
echo "::error::Relay cannot resolve ${EVM_ADDR} after 5 minutes"
exit 1
- name: Deploy ATS system to Hiero Solo
if: steps.docker-check.outputs.available == 'true' && steps.solo.outcome == 'success'
working-directory: packages/ats/contracts
env:
NETWORK: hedera-local
HEDERA_LOCAL_PRIVATE_KEY_0: ${{ steps.extract-key.outputs.key }}
run: npx tsx scripts/cli/deploySystemWithNewBlr.ts
- name: Verify deployment output
if: success() && steps.docker-check.outputs.available == 'true'
working-directory: packages/ats/contracts
run: |
echo "Checking deployment output files..."
if ls deployments/hedera-local/*.json 1> /dev/null 2>&1; then
echo "Deployment output files found:"
ls -la deployments/hedera-local/
else
echo "::warning::No deployment output files found (non-fatal)"
fi