Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LF-12061 Run healthcheck on new network #985

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
104 changes: 104 additions & 0 deletions .github/workflows/healthCheckForNewNetworkDeployment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Health Check for New Network Deployment

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- 'config/networks.json'

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 "No previous networks.json found, assuming all networks are new."
OLD_NETWORKS="[]"
fi

NEW_NETWORKS=$(jq 'keys' config/networks.json)

echo "New Networks: $NEW_NETWORKS"

ADDED_NETWORKS=$(jq -n --argjson old "$OLD_NETWORKS" --argjson new "$NEW_NETWORKS" '$new - $old')

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
fi

# Check if deployments/{network}.json file exists
if [[ ! -f "deployments/$network.json" ]]; then
echo "❌ Error: Missing deployment file: deployments/$network.json"
exit 1
fi
done

- name: Install Bun
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would suggest to move the install bits before the code logic....like first set up the environment, then execute code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put deps installation part after validating whether everything is needed, convinced that this could save some resources for this action. Since it's a small action, the impact is minimal I know. The validation code is actually larger than the logic part cuz we only run healthCheck after it, which might make it seem a bit odd. I can change it if needed! Wdyt?

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
95 changes: 64 additions & 31 deletions script/deploy/healthCheck.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// @ts-nocheck
import { consola } from 'consola'
import { $, spinner } from 'zx'
import { $ } from 'zx'
import { defineCommand, runMain } from 'citty'
import * as chains from 'viem/chains'
import * as path from 'path'
Expand All @@ -26,8 +26,6 @@ import { coreFacets } from '../../config/global.json'

const SAFE_THRESHOLD = 3

const louperCmd = 'louper-cli'

const corePeriphery = [
'ERC20Proxy',
'Executor',
Expand All @@ -50,26 +48,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(
Expand Down Expand Up @@ -163,16 +143,66 @@ 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)

const missingInConfig = onChainFacetAddresses.filter(
(address) => !configFacetsByAddress[address]
)

const missingOnChain = configuredFacetAddresses.filter(
(address) => !onChainFacetAddresses.includes(address)
)

if (missingInConfig.length > 0) {
logError(
`The following facets exist on-chain but are missing in the config: ${JSON.stringify(
missingInConfig
)}`
)
}
if (missingOnChain.length > 0) {
logError(
`The following facets exist in the config but are not deployed on-chain: ${JSON.stringify(
missingOnChain
)}`
)
}

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]) {
Expand Down Expand Up @@ -519,6 +549,7 @@ const main = defineCommand({
finish()
} else {
logError('No dexs configured')
finish()
}
},
})
Expand Down Expand Up @@ -580,8 +611,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)
}
}

Expand Down
Loading