forked from ExocoreNetwork/exocore-contracts
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ci): carve out storage layout CI
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
1 parent
46f9927
commit 4439bae
Showing
12 changed files
with
663 additions
and
287 deletions.
There are no files selected for viewing
82 changes: 82 additions & 0 deletions
82
.github/actions/composite-action-pr-status-comment/action.yml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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}`); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.