Skip to content

Commit

Permalink
feat(ci): carve out storage layout CI
Browse files Browse the repository at this point in the history
Since the storage layout CI needs the API keys to run, it does not work
in PRs originating from forked repos. To avoid those unnecessary errors,
this PR splits the workflow into two parts: the first that generates the
compiled layouts and the second that fetches the deployed layouts and
compares them.

Such a split is possible because the second type of workflow runs in a
trusted context, and hence, the API key is accessible to it.
  • Loading branch information
MaxMustermann2 committed Nov 21, 2024
1 parent 46f9927 commit 4439bae
Show file tree
Hide file tree
Showing 12 changed files with 663 additions and 287 deletions.
82 changes: 82 additions & 0 deletions .github/actions/composite-action-pr-status-comment/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Update CI Status Comment
description: Posts or updates a status comment on a pull request.
inputs:
workflow-conclusion:
description: 'The conclusion of the parent workflow (success, failure, or running).'
required: true
workflow-url:
description: 'The URL to the parent workflow run.'
required: true
workflow-name:
description: 'The name of the CI workflow that triggered this update.'
required: true
github-token:
description: 'The GitHub token to use for making API requests.'
required: true
runs:
using: "composite"
steps:
- name: Echo Workflow Details
shell: bash
run: |
echo "Workflow conclusion: ${{ inputs.workflow-conclusion }}"
echo "Workflow name: ${{ inputs.workflow-name }}"
echo "Workflow URL: ::add-mask::${{ inputs.workflow-url }}"
- name: Comment on Pull Requests
uses: actions/github-script@v6
with:
github-token: ${{ inputs.github-token }}
script: |
const workflowConclusion = "${{ inputs.workflow-conclusion }}";
const workflowRunUrl = "${{ inputs.workflow-url }}";
const workflowName = "${{ inputs.workflow-name }}";
const pullRequests = context.payload.workflow_run.pull_requests;
if (pullRequests.length === 0) {
console.log('No pull requests associated with this workflow run.');
} else {
for (const pr of pullRequests) {
try {
const existingComments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
});
const existingComment = existingComments.data.find(comment =>
comment.body.includes(`The ${workflowName} workflow`)
);
let commentBody;
if (workflowConclusion === 'running') {
commentBody = `🔄 The ${workflowName} workflow is currently running. Check the [workflow run](${workflowRunUrl}) for progress.`;
} else if (workflowConclusion === 'failure') {
commentBody = `⚠️ The ${workflowName} workflow has failed! Check the [workflow run](${workflowRunUrl}) for details.`;
} else if (workflowConclusion === 'success') {
commentBody = `✅ The ${workflowName} workflow has completed successfully. Check the [workflow run](${workflowRunUrl}) for details.`;
} else if (workflowConclusion === 'skipped') {
commentBody = `⏭️ The ${workflowName} workflow was skipped. Check the [workflow run](${workflowRunUrl}) for details.`;
} else {
commentBody = `❓ The ${workflowName} workflow has completed with an unknown status. Check the [workflow run](${workflowRunUrl}) for details.`;
}
if (existingComment) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existingComment.id,
body: commentBody,
});
} else {
await github.rest.issues.createComment({
issue_number: pr.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: commentBody,
});
}
} catch (error) {
console.error(`Failed to comment on PR #${pr.number}: ${error.message}`);
}
}
}
171 changes: 171 additions & 0 deletions .github/workflows/compare-layouts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
name: Compare Storage Layouts

on:
workflow_run:
workflows: ["Forge CI"]
types:
- completed

jobs:
# The actual job to compare the storage layouts.
compare_storage_layouts:
runs-on: ubuntu-latest

permissions:
contents: read
# comment.yml inherits these permissions, so we need `write` here.
pull-requests: write
# to mark the commit with the status
statuses: write

env:
ALCHEMY_API_KEY: ${{ secrets.ALCHEMY_API_KEY }}
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

steps:
# Log the workflow trigger details for debugging.
- name: Echo workflow trigger details
run: |
echo "Workflow run event: ${{ github.event.workflow_run.event }}"
echo "Workflow run conclusion: ${{ github.event.workflow_run.conclusion }}"
echo "Workflow run name: ${{ github.event.workflow_run.name }}"
echo "Workflow run URL: ${{ github.event.workflow_run.html_url }}"
echo "Commit SHA: ${{ github.event.workflow_run.head_commit.id }}"
echo "Workflow Run ID: ${{ github.event.workflow_run.id }}"
# For the composite action to be available, we must checkout the repository.
# Later, the repository is used to run the compareLayouts.js script.
- name: Checkout repository
uses: actions/checkout@v3
# The CI status needs to be posted explicitly because this
# workflow is not triggered by a pull request, and hence,
# the status is not visible under the "Checks" tab.
- name: Post CI Status
if: ${{ github.event.workflow_run.event == 'pull_request' }}
uses: ./.github/actions/composite-action-pr-status-comment
with:
workflow-conclusion: ${{ github.event.workflow_run.conclusion == 'failure' && 'skipped' || 'running' }}
workflow-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
workflow-name: "Storage layout comparison"
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Restore the cached output file
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: actions/cache/restore@v3
with:
path: res.txt
key: cache-${{ github.sha }}
fail-on-cache-miss: true
- name: Load the outputs from the cached file
if: ${{ github.event.workflow_run.conclusion == 'success' }}
id: load-outputs
run: |
echo "cache-key=$(cat res.txt | head -1)" >> "$GITHUB_OUTPUT"
echo "installation-dir=$(cat res.txt | tail -1)" >> "$GITHUB_OUTPUT"
# The toolchain is needed to run `cast storage`
- name: Restore cached Foundry toolchain
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: actions/cache/restore@v3
with:
key: ${{ steps.load-outputs.outputs.cache-key }}
path: ${{ steps.load-outputs.outputs.installation-dir }}
- name: Add Foundry to PATH
if: ${{ github.event.workflow_run.conclusion == 'success' }}
run: echo "${{ steps.load-outputs.outputs.installation-dir }}" >> $GITHUB_PATH
- name: Fetch the deployed layouts
if: ${{ github.event.workflow_run.conclusion == 'success' }}
run: |
set -e
data=$(cat script/deployedContracts.json)
bootstrap=$(echo "$data" | jq -r '.clientChain.bootstrapLogic // empty')
clientGateway=$(echo "$data" | jq -r '.clientChain.clientGatewayLogic // empty')
vault=$(echo "$data" | jq -r '.clientChain.vaultImplementation // empty')
rewardVault=$(echo "$data" | jq -r '.clientChain.rewardVaultImplementation // empty')
capsule=$(echo "$data" | jq -r '.clientChain.capsuleImplementation // empty')
pwd=$(pwd)
cd /tmp
# Create an array of contract names and addresses
declare -A contracts=(
["Bootstrap"]="$bootstrap"
["ClientChainGateway"]="$clientGateway"
["Vault"]="$vault"
["RewardVault"]="$rewardVault"
["ExoCapsule"]="$capsule"
)
# Iterate over the array and run `cast storage` for each contract
for contract in "${!contracts[@]}"; do
address=${contracts[$contract]}
if [[ -n $address ]]; then
echo "Processing $contract at address $address"
cast storage --json "$address" --rpc-url "https://eth-sepolia.g.alchemy.com/v2/$ALCHEMY_API_KEY" \
--etherscan-api-key "$ETHERSCAN_API_KEY" > "$contract.deployed.json"
mv "$contract.deployed.json" "$pwd"
else
echo "Skipping $contract as no address is provided"
fi
done
cd "$pwd"
# Restore the layouts from the cache
- name: Restore the layout files from the previous job
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: actions/cache/restore@v3
with:
path: storage-layouts.zip
key: storage-layouts-${{ github.sha }}
- name: Extract the restored layouts
if: ${{ github.event.workflow_run.conclusion == 'success' }}
run: unzip storage-layouts.zip
- name: Set up Node.js
if: ${{ github.event.workflow_run.conclusion == 'success' }}
uses: actions/setup-node@v2
with:
node-version: '18'
- name: Clear npm cache
if: ${{ github.event.workflow_run.conclusion == 'success' }}
run: npm cache clean --force
- name: Install the required dependency
if: ${{ github.event.workflow_run.conclusion == 'success' }}
run: npm install @openzeppelin/upgrades-core
- name: Compare the layouts
if: ${{ github.event.workflow_run.conclusion == 'success' }}
id: compare_layouts
run: |
node script/compareLayouts.js
# Even if this fails, the CI status should be updated.
continue-on-error: true
- name: Comment or update CI Status
# Only update the status if
# 1. The workflow run was triggered by a pull request
# 2. The triggering workflow run was successful
# We skip the case wherein the triggering workflow failed because
# it was already handled by the "Post CI Status" step.
if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' }}
uses: ./.github/actions/composite-action-pr-status-comment
with:
workflow-conclusion: ${{ steps.compare_layouts.outcome == 'success' && 'success' || 'failure' }}
workflow-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
workflow-name: "Storage layout comparison"
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Update parent commit status
if: always()
# if the outcome is not set, it will post failure
run: |
outcome=$([[ "${{ steps.compare_layouts.outcome }}" == "success" ]] && echo "success" || echo "failure")
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
-d '{
"state": "'${outcome}'",
"context": "Compare Storage Layouts",
"description": "Storage layout comparison results.",
"target_url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}' \
https://api.github.com/repos/${{ github.repository }}/statuses/${{ github.event.workflow_run.head_commit.id }}
- name: Exit with the correct code
if: always()
# if the outcome is not set, it will exit 1
run: |
if [[ "${{ steps.compare_layouts.outcome }}" == "success" ]]; then
exit 0
else
exit 1
fi
87 changes: 0 additions & 87 deletions .github/workflows/compare_deployed_storage_layout.py

This file was deleted.

Loading

0 comments on commit 4439bae

Please sign in to comment.