diff --git a/.github/workflows/healthCheckForNewNetworkDeployment.yml b/.github/workflows/healthCheckForNewNetworkDeployment.yml new file mode 100644 index 000000000..7f96b2ba7 --- /dev/null +++ b/.github/workflows/healthCheckForNewNetworkDeployment.yml @@ -0,0 +1,112 @@ +name: Health Check for New Network Deployment + +# - designed to perform health checks for newly added networks +# - triggers on pull requests and first checks if the config/networks.json file was modified +# - validates that each new network has corresponding deployment and state configuration files +# - runs network-specific health checks +# - any required file is missing or a health check fails, the action exits with an error + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + +jobs: + check-new-network-health: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check if config/networks.json was changed in this branch + id: check-file-change + run: | + if git diff --name-only origin/main...HEAD | grep -q "config/networks.json"; then + echo "config/networks.json has been modified in this branch" + echo "CONTINUE=true" >> $GITHUB_ENV + else + echo "No changes in config/networks.json detected in this branch" + echo "CONTINUE=false" >> $GITHUB_ENV + fi + + - name: Detect Newly Added Networks + if: env.CONTINUE == 'true' + id: detect-changes + run: | + echo "Comparing config/networks.json with the previous commit..." + git fetch origin main --depth=1 || echo "No previous commit found." + + if git show origin/main:config/networks.json > /dev/null 2>&1; then + OLD_NETWORKS=$(git show origin/main:config/networks.json | jq 'keys') + else + echo "❌ Error: No previous networks.json found. Expected existing network configuration." + exit 1 + fi + + NEW_NETWORKS=$(jq 'keys' config/networks.json) + + ADDED_NETWORKS=$(jq -n --argjson old "$OLD_NETWORKS" --argjson new "$NEW_NETWORKS" '$new - $old') + + echo "Added networks: $ADDED_NETWORKS" + + if [[ "$ADDED_NETWORKS" == "[]" ]]; then + echo "No new networks detected." + echo "SKIP_CHECK=true" >> $GITHUB_ENV + else + echo "New networks detected: $ADDED_NETWORKS" + echo "added_networks=$(echo $ADDED_NETWORKS | jq -c .)" >> $GITHUB_ENV + fi + + - name: Validate Network Deployment Files + if: env.CONTINUE == 'true' && env.SKIP_CHECK != 'true' + run: | + echo "Validating required files for new networks..." + for network in $(echo $added_networks | jq -r '.[]'); do + echo "🔍 Checking files for network: $network" + + # Check if network exists in _targetState.json + if ! jq -e 'has("'"$network"'")' script/deploy/_targetState.json > /dev/null; then + echo "❌ Error: Network '$network' not found in script/deploy/_targetState.json" + exit 1 + else + echo "✅ Confirmed: Network '$network' exists in script/deploy/_targetState.json" + fi + + # Check if deployments/{network}.json file exists + if [[ ! -f "deployments/$network.json" ]]; then + echo "❌ Error: Missing deployment file: deployments/$network.json" + exit 1 + else + echo "✅ Confirmed: Deployment file: deployments/$network.json exists" + fi + done + + - name: Install Bun + if: env.CONTINUE == 'true' && env.SKIP_CHECK != 'true' + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install Foundry (provides cast) + if: env.CONTINUE == 'true' && env.SKIP_CHECK != 'true' + uses: foundry-rs/foundry-toolchain@v1 + + - name: Install dependencies + if: env.CONTINUE == 'true' && env.SKIP_CHECK != 'true' + run: bun install + + - name: Run Health Checks on New Networks + if: env.CONTINUE == 'true' && env.SKIP_CHECK != 'true' + run: | + echo "Running health check for new networks..." + set -e + for network in $(echo $added_networks | jq -r '.[]'); do + echo "🔍 Checking network: $network" + if bun run script/deploy/healthCheck.ts --network "$network"; then + echo "✅ $network is fine." + else + echo "❌ Health check failed for $network. Exiting..." + exit 1 + fi + done diff --git a/script/deploy/healthCheck.ts b/script/deploy/healthCheck.ts index 6fa7f7540..389003988 100644 --- a/script/deploy/healthCheck.ts +++ b/script/deploy/healthCheck.ts @@ -1,6 +1,6 @@ // @ts-nocheck import { consola } from 'consola' -import { $, spinner } from 'zx' +import { $ } from 'zx' import { defineCommand, runMain } from 'citty' import * as path from 'path' import * as fs from 'fs' @@ -24,8 +24,6 @@ import { coreFacets, pauserWallet } from '../../config/global.json' const SAFE_THRESHOLD = 3 -const louperCmd = 'louper-cli' - const corePeriphery = [ 'ERC20Proxy', 'Executor', @@ -48,26 +46,8 @@ const main = defineCommand({ }, }, async run({ args }) { - if ((await $`${louperCmd}`.exitCode) !== 0) { - const answer = await consola.prompt( - 'Louper CLI is required but not installed. Would you like to install it now?', - { - type: 'confirm', - } - ) - if (answer) { - await spinner( - 'Installing...', - () => $`npm install -g @mark3labs/louper-cli` - ) - } else { - consola.error('Louper CLI is required to run this script') - process.exit(1) - } - } - const { network } = args - const deployedContracts = await import( + const { default: deployedContracts } = await import( `../../deployments/${network.toLowerCase()}.json` ) const targetStateJson = await import( @@ -161,16 +141,43 @@ const main = defineCommand({ let registeredFacets: string[] = [] try { - const facetsResult = - await $`${louperCmd} inspect diamond -a ${diamondAddress} -n ${network} --json` - registeredFacets = JSON.parse(facetsResult.stdout).facets.map( - (f: { name: string }) => f.name - ) + if (networksConfig[network.toLowerCase()].rpcUrl) { + const rpcUrl: string = networksConfig[network.toLowerCase()].rpcUrl + const facetsResult = + await $`cast call ${diamondAddress} "facets() returns ((address,bytes4[])[])" --rpc-url ${rpcUrl}` + const rawString = facetsResult.stdout + + const jsonCompatibleString = rawString + .replace(/\(/g, '[') + .replace(/\)/g, ']') + .replace(/0x[0-9a-fA-F]+/g, '"$&"') + + const onChainFacets = JSON.parse(jsonCompatibleString) + + if (Array.isArray(onChainFacets)) { + // mapping on-chain facet addresses to names in config + const configFacetsByAddress = Object.fromEntries( + Object.entries(deployedContracts).map(([name, address]) => { + return [address.toLowerCase(), name] + }) + ) + + const onChainFacetAddresses = onChainFacets.map(([address]) => + address.toLowerCase() + ) + + const configuredFacetAddresses = Object.keys(configFacetsByAddress) + + registeredFacets = onChainFacets.map(([address]) => { + return configFacetsByAddress[address.toLowerCase()] + }) + } + } else { + throw new Error('Failed to get rpc from network config file') + } } catch (error) { - consola.warn( - 'Unable to parse louper output - skipping facet registration check' - ) - consola.debug('Error:', error) + consola.warn('Unable to parse output - skipping facet registration check') + consola.warn('Error:', error) } for (const facet of [...coreFacets, ...nonCoreFacets]) { @@ -531,6 +538,7 @@ const main = defineCommand({ finish() } else { logError('No dexs configured') + finish() } }, }) @@ -592,8 +600,10 @@ const checkIsDeployed = async ( const finish = () => { if (errors.length) { consola.error(`${errors.length} Errors found in deployment`) + process.exit(1) } else { consola.success('Deployment checks passed') + process.exit(0) } }