Skip to content
Merged
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
151 changes: 151 additions & 0 deletions .github/workflows/_deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
name: Reusable protected deployment

on:
workflow_call:
inputs:
deploy_env:
required: true
type: string
deploy_metadata:
required: true
type: boolean
force:
required: false
default: false
type: boolean
release_tag:
required: false
default: ""
type: string
source_ref:
required: false
default: ""
type: string
allow_placeholder_metadata:
required: false
default: false
type: boolean

permissions:
contents: write

concurrency:
group: contracts-${{ inputs.deploy_env }}
cancel-in-progress: false

jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.deploy_env }}
env:
DEPLOY_ENV: ${{ inputs.deploy_env }}
RPC_URL: ${{ secrets.RPC_URL }}
ETHERSCAN_API_KEY: ${{ secrets.EXPLORER_API_KEY }}
Deployment__RpcUrl: ${{ secrets.RPC_URL }}
Deployment__PrivateKey: ${{ secrets.DEPLOYER_PRIVATE_KEY }}
Deployment__AdminAddress: ${{ vars.ADMIN_ADDRESS }}
Deployment__MinterAddress: ${{ vars.MINTER_ADDRESS }}
Deployment__StakingOwnerAddress: ${{ vars.STAKING_OWNER_ADDRESS }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
ref: ${{ inputs.source_ref || github.sha }}
- name: Record exact checked-out source commit
run: echo "Deployment__SourceCommit=$(git rev-parse HEAD)" >> "$GITHUB_ENV"
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
cache: npm
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: "8.0.x"
- run: npm ci
- run: npm run compile
- run: dotnet build SmartContract.sln --configuration Release
- name: Validate production artwork and generate selected metadata
if: inputs.deploy_metadata
shell: bash
env:
ALLOW_PLACEHOLDER_METADATA: ${{ inputs.allow_placeholder_metadata }}
run: |
validation_args=()
if [[ "$ALLOW_PLACEHOLDER_METADATA" == "true" ]]; then validation_args+=(--allow-placeholders); fi
node scripts/validate-nft-assets.js --env "$DEPLOY_ENV" --manifest-only "${validation_args[@]}"
node scripts/generate-nft-assets.js --env "$DEPLOY_ENV" --force
chain_key="$(node -p "require('./deployments/config/${DEPLOY_ENV}/nft-manifest.json').chainKey")"
node scripts/validate-nft-assets.js --env "$DEPLOY_ENV" --chain-key "$chain_key" --output-only "${validation_args[@]}"
echo "CHAIN_KEY=$chain_key" >> "$GITHUB_ENV"
- name: Validate metadata configuration without publishing
if: ${{ ! inputs.deploy_metadata }}
run: node scripts/validate-nft-assets.js --env "$DEPLOY_ENV" --manifest-only --allow-placeholders
- name: Stage selected chain output
if: inputs.deploy_metadata
shell: bash
run: |
mkdir -p "site/$CHAIN_KEY"
cp -R "nft-assets/$CHAIN_KEY/." "site/$CHAIN_KEY/"
cp nft-assets/_headers nft-assets/_redirects site/
- name: Upload selected metadata to Cloudflare Pages
if: inputs.deploy_metadata
uses: cloudflare/wrangler-action@9acf94ace14e7dc412b076f2c5c20b8ce93c79cd # v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ vars.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy site --project-name=${{ vars.CF_PAGES_PROJECT }} --branch=${{ inputs.deploy_env }}
- name: Install selected deployment configuration
shell: bash
run: cp "deployments/config/$DEPLOY_ENV/deployment-params.json" "src/Tricksfor.Blockchain.Booster.Deploy/bin/Release/net8.0/appsettings.$DEPLOY_ENV.json"
- name: Deploy or resume contracts
shell: bash
env:
FORCE_DEPLOY: ${{ inputs.force }}
run: |
force_arg=""
if [[ "$FORCE_DEPLOY" == "true" ]]; then force_arg="--force"; fi
dotnet src/Tricksfor.Blockchain.Booster.Deploy/bin/Release/net8.0/Tricksfor.Blockchain.Booster.Deploy.dll deploy $force_arg
- name: Verify with synchronized Hardhat build information
if: vars.VERIFY_ENABLED == 'true'
shell: bash
env:
NETWORK_KEY: ${{ vars.NETWORK_KEY }}
run: |
nft_manifest="deployments/$DEPLOY_ENV/TricksforBoosterNFT.json"
staking_manifest="deployments/$DEPLOY_ENV/TricksforBoosterStaking.json"
nft_address="$(jq -r .address "$nft_manifest")"
staking_address="$(jq -r .address "$staking_manifest")"
mapfile -t nft_args < <(jq -r '.constructorArgs[]' "$nft_manifest")
mapfile -t staking_args < <(jq -r '.constructorArgs[]' "$staking_manifest")
npx hardhat verify --build-profile production --network "$NETWORK_KEY" "$nft_address" "${nft_args[@]}" | tee "deployments/$DEPLOY_ENV/nft-verification.txt"
npx hardhat verify --build-profile production --network "$NETWORK_KEY" "$staking_address" "${staking_args[@]}" | tee "deployments/$DEPLOY_ENV/staking-verification.txt"
- name: Run state-changing smoke test
run: dotnet src/Tricksfor.Blockchain.Booster.Deploy/bin/Release/net8.0/Tricksfor.Blockchain.Booster.Deploy.dll smoke
- name: Publish deployment summary
shell: bash
run: |
nft_manifest="deployments/$DEPLOY_ENV/TricksforBoosterNFT.json"
staking_manifest="deployments/$DEPLOY_ENV/TricksforBoosterStaking.json"
smoke_result="deployments/$DEPLOY_ENV/smoke-result.json"
{
echo "## $DEPLOY_ENV deployment"
echo
echo "| Contract | Address | Transaction | Gas used |"
echo "|---|---|---|---:|"
jq -r '"| NFT | `\(.address)` | `\(.transactionHash)` | \(.gasUsed) |"' "$nft_manifest"
jq -r '"| Staking | `\(.address)` | `\(.transactionHash)` | \(.gasUsed) |"' "$staking_manifest"
echo
echo "### Smoke result"
jq -r '"Token `\(.tokenId)` ended with owner `\(.finalOwner)` and staked=`\(.finalStaked)`.\n\nTokenStaked: `\(.tokenStaked | tostring)`\n\nTokenUnstaked: `\(.tokenUnstaked | tostring)`\n\nReads: `\(.readResults | tostring)`"' "$smoke_result"
} >> "$GITHUB_STEP_SUMMARY"
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: deployment-${{ inputs.deploy_env }}-${{ github.run_id }}
path: deployments/${{ inputs.deploy_env }}/
if-no-files-found: error
- name: Attach permanent records to release
if: inputs.release_tag != '' && success()
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.release_tag }}
run: gh release upload "$RELEASE_TAG" "deployments/${DEPLOY_ENV}/"* --clobber
136 changes: 136 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: CI

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
contracts:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
cache: npm
- run: npm ci
- run: npm run artifacts:check
- run: npm test
- run: npm run test:coverage
- name: npm production vulnerability audit
run: npm audit --omit=dev --audit-level=high
- name: Capture development-tool advisory report
shell: bash
run: npm audit --json > npm-audit-development.json || test -s npm-audit-development.json
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: npm-development-audit
path: npm-audit-development.json

security-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
cache: npm
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.12"
- run: npm ci
- run: npm run compile
- run: python -m pip install slither-analyzer==0.11.5
- run: npm run security:prepare
- name: Slither static analysis
run: >-
slither .
--hardhat-ignore-compile
--hardhat-artifacts-directory .slither-artifacts
--exclude-dependencies
--filter-paths 'contracts/test|node_modules'
--fail-medium
--json slither-report.json
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
if: always()
with:
name: slither-report
path: slither-report.json
if-no-files-found: warn

dotnet:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
- uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4
with:
dotnet-version: "8.0.x"
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
cache: npm
- run: npm ci
- run: dotnet restore SmartContract.sln
- run: dotnet build SmartContract.sln --configuration Release --no-restore
- run: dotnet test SmartContract.sln --configuration Release --no-build
- name: NuGet vulnerability audit
shell: bash
run: |
dotnet list SmartContract.sln package --vulnerable --include-transitive | tee /tmp/nuget-audit.txt
if grep -qE 'has the following vulnerable packages|Top-level Package.*Severity' /tmp/nuget-audit.txt; then exit 1; fi
- name: Pack and inspect NuGet artifacts
shell: bash
run: |
package_version="2.0.0-ci.${GITHUB_RUN_NUMBER}"
dotnet pack src/Tricksfor.Blockchain.Booster/Tricksfor.Blockchain.Booster.csproj --configuration Release --no-build --output artifacts/nuget /p:PackageVersion="$package_version"
test "$(find artifacts/nuget -name '*.nupkg' ! -name '*.snupkg' | wc -l)" -eq 1
test "$(find artifacts/nuget -name '*.snupkg' | wc -l)" -eq 1
unzip -l artifacts/nuget/*.nupkg | grep -E 'lib/net8.0/Tricksfor.Blockchain.Booster.(dll|xml)|README.nuget.md'
unzip -l artifacts/nuget/*.snupkg | grep 'lib/net8.0/Tricksfor.Blockchain.Booster.pdb'
unzip -p artifacts/nuget/*.snupkg 'lib/net8.0/Tricksfor.Blockchain.Booster.pdb' | strings | grep -q 'raw.githubusercontent.com/Tricksfor-Organization/SmartContract'
mkdir -p /tmp/tricksfor-package-consumer
dotnet new console --framework net8.0 --output /tmp/tricksfor-package-consumer
dotnet add /tmp/tricksfor-package-consumer package Tricksfor.SmartContracts --version "$package_version" --no-restore
dotnet restore /tmp/tricksfor-package-consumer --source "$GITHUB_WORKSPACE/artifacts/nuget" --source https://api.nuget.org/v3/index.json
printf '%s\n' 'using Tricksfor.Blockchain.Booster.Contracts.Deployment;' 'if (!TricksforBoosterNFTDeployment.BYTECODE.StartsWith("0x") || TricksforBoosterNFTDeployment.ABI.Length == 0) throw new Exception("Missing NFT artifacts");' 'if (!TricksforBoosterStakingDeployment.BYTECODE.StartsWith("0x") || TricksforBoosterStakingDeployment.ABI.Length == 0) throw new Exception("Missing staking artifacts");' > /tmp/tricksfor-package-consumer/Program.cs
dotnet run --project /tmp/tricksfor-package-consumer
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: nuget-ci
path: artifacts/nuget/*

metadata:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: "22"
- name: Validate manifests and common source tree
shell: bash
run: |
for deploy_env in ethereum-sepolia polygon-amoy optimism-sepolia bsc-testnet avalanche-fuji; do
node scripts/validate-nft-assets.js --env "$deploy_env" --manifest-only --allow-placeholders
done
- name: Generate and validate every chain output
shell: bash
run: |
for deploy_env in ethereum-sepolia polygon-amoy optimism-sepolia bsc-testnet avalanche-fuji; do
node scripts/generate-nft-assets.js --env "$deploy_env" --force
chain_key="$(node -p "require('./deployments/config/${deploy_env}/nft-manifest.json').chainKey")"
node scripts/validate-nft-assets.js --env "$deploy_env" --chain-key "$chain_key" --output-only --allow-placeholders
done
node scripts/validate-nft-output.js
node scripts/test-metadata-snapshots.js

workflow-lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- run: docker run --rm -v "${GITHUB_WORKSPACE}:/repo" -w /repo rhysd/actionlint:1.7.7
43 changes: 43 additions & 0 deletions .github/workflows/deploy-mainnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Deploy mainnet

on:
workflow_dispatch:
inputs:
deploy_env:
type: choice
required: true
options: [ethereum-mainnet, polygon-mainnet, optimism-mainnet, bsc-mainnet, avalanche-mainnet]
release_tag:
description: Stable vMAJOR.MINOR.PATCH tag
required: true
type: string
deploy_metadata:
type: boolean
default: true
force:
type: boolean
default: false

permissions:
contents: write

jobs:
stable-tag:
runs-on: ubuntu-latest
steps:
- env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.release_tag }}
run: |
[[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::A stable release tag is required"; exit 1; }
gh release view "$RELEASE_TAG" >/dev/null
deploy:
needs: stable-tag
uses: ./.github/workflows/_deploy.yml
with:
deploy_env: ${{ inputs.deploy_env }}
deploy_metadata: ${{ inputs.deploy_metadata }}
force: ${{ inputs.force }}
release_tag: ${{ inputs.release_tag }}
source_ref: ${{ inputs.release_tag }}
secrets: inherit
28 changes: 28 additions & 0 deletions .github/workflows/deploy-testnet.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: Deploy testnet

on:
workflow_dispatch:
inputs:
deploy_env:
type: choice
required: true
options: [ethereum-sepolia, polygon-amoy, optimism-sepolia, bsc-testnet, avalanche-fuji]
deploy_metadata:
type: boolean
default: true
force:
type: boolean
default: false

permissions:
contents: read

jobs:
deploy:
uses: ./.github/workflows/_deploy.yml
with:
deploy_env: ${{ inputs.deploy_env }}
deploy_metadata: ${{ inputs.deploy_metadata }}
force: ${{ inputs.force }}
allow_placeholder_metadata: true
secrets: inherit
Loading
Loading