fix(ci): use PR addrs to compare layouts #149
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
name: Forge CI | |
on: | |
merge_group: | |
pull_request: | |
push: | |
branches: | |
- main | |
- release/** | |
tags: | |
- "*" | |
jobs: | |
setup: | |
# A full job can be used as a reusable workflow but not a step. | |
uses: ./.github/workflows/reusable-foundry-setup.yml | |
with: | |
# The below line does not accept environment variables, | |
# so it becomes the single source of truth for the version. | |
foundry-version: nightly | |
build: | |
# Caching is slow; takes about 3 minutes. | |
timeout-minutes: 15 | |
runs-on: ubuntu-latest | |
needs: setup | |
outputs: | |
# The cache-key only contains the version name. It is only used so that the name does not | |
# need to be repeated everywhere; instead setting the `foundry-version` above suffices. | |
cache-key: ${{ needs.setup.outputs.cache-key }} | |
# Github's cache actions are a bit weird to deal with. It wouldn't let me restore the | |
# binaries to /usr/bin, so I restored them to the original location and added it to PATH. | |
# This output will let us carry it to other jobs. | |
installation-dir: ${{ needs.setup.outputs.installation-dir }} | |
steps: | |
- name: Restore cached Foundry toolchain | |
uses: actions/cache/restore@v3 | |
with: | |
path: ${{ needs.setup.outputs.installation-dir }} | |
key: ${{ needs.setup.outputs.cache-key }} | |
- name: Add Foundry to PATH | |
run: echo "${{ needs.setup.outputs.installation-dir }}" >> "$GITHUB_PATH" | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
with: | |
submodules: recursive | |
- name: Build | |
run: forge build | |
- name: Cache build | |
uses: actions/cache/save@v3 | |
with: | |
path: | | |
./lib | |
./out | |
./cache | |
./broadcast | |
key: build-${{ github.event.pull_request.head.sha || github.event.after || github.sha }} | |
test: | |
# Takes less than 30s | |
timeout-minutes: 5 | |
runs-on: ubuntu-latest | |
needs: build | |
steps: | |
- name: Restore cached Foundry toolchain | |
uses: actions/cache/restore@v3 | |
with: | |
path: ${{ needs.build.outputs.installation-dir }} | |
key: ${{ needs.build.outputs.cache-key }} | |
- name: Add Foundry to PATH | |
run: echo "${{ needs.build.outputs.installation-dir }}" >> "$GITHUB_PATH" | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Restore `forge build` results | |
uses: actions/cache/restore@v3 | |
with: | |
path: | | |
./lib | |
./out | |
./cache | |
./broadcast | |
key: build-${{ github.event.pull_request.head.sha || github.event.after || github.sha }} | |
- name: Clear out the `etherscan` section in `foundry.toml` for missing env vars | |
run: sed -i '/\[etherscan\]/,/^\[/ s/^/#/' foundry.toml | |
- name: Run tests | |
env: | |
FOUNDRY_PROFILE: test | |
run: forge test | |
- name: Set test snapshot as summary | |
env: | |
FOUNDRY_PROFILE: test | |
NO_COLOR: 1 | |
run: forge snapshot >> "$GITHUB_STEP_SUMMARY" | |
format: | |
# Takes less than 30s | |
timeout-minutes: 5 | |
runs-on: ubuntu-latest | |
needs: build | |
steps: | |
- name: Restore cached Foundry toolchain | |
uses: actions/cache/restore@v3 | |
with: | |
path: ${{ needs.build.outputs.installation-dir }} | |
key: ${{ needs.build.outputs.cache-key }} | |
- name: Add Foundry to PATH | |
run: echo "${{ needs.build.outputs.installation-dir }}" >> "$GITHUB_PATH" | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Restore `forge build` results | |
uses: actions/cache/restore@v3 | |
with: | |
path: | | |
./lib | |
./out | |
./cache | |
./broadcast | |
key: build-${{ github.event.pull_request.head.sha || github.event.after || github.sha }} | |
- name: Check formatting | |
run: forge fmt --check | |
check-contract-deployments: | |
# Takes less than 60s | |
timeout-minutes: 10 | |
runs-on: ubuntu-latest | |
needs: build | |
steps: | |
- name: Restore cached Foundry toolchain | |
uses: actions/cache/restore@v3 | |
with: | |
path: ${{ needs.build.outputs.installation-dir }} | |
key: ${{ needs.build.outputs.cache-key }} | |
- name: Add Foundry to PATH | |
run: echo "${{ needs.build.outputs.installation-dir }}" >> "$GITHUB_PATH" | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Validate deployedContracts.json and prepare artifact | |
run: | | |
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') | |
validate_address() { | |
local address=$1 | |
if [ -z "$address" ]; then | |
echo "Validation failed: Address is empty" | |
exit 1 | |
fi | |
if [ "$(cast 2a $address)" != "$address" ]; then | |
echo "Validation failed: $address is not a valid Ethereum checksum address" | |
exit 1 | |
fi | |
} | |
# Check each address | |
echo "Validating bootstrap address..." | |
validate_address "$bootstrap" | |
echo "Validating clientGateway address..." | |
validate_address "$clientGateway" | |
echo "Validating vault address..." | |
validate_address "$vault" | |
echo "Validating rewardVault address..." | |
validate_address "$rewardVault" | |
echo "Validating capsule address..." | |
validate_address "$capsule" | |
# Prepare JSON for artifact. Instead of using the file within the repo, | |
# we create an artifact from the PR because the `compare-layouts` workflow | |
# runs on the base branch and doesn't have access to the PR's files. | |
# Given the presence of the artifact, technically, the above validation | |
# could be offloaded to the `compare-layouts` workflow, but this is a | |
# basic short circuit to avoid running the `compare-layouts` workflow | |
# if the PR has invalid addresses. Other validations to include, if | |
# possible, would be the verification of the contract on Etherscan; | |
# however, we cannot do that in the PR context without leaking the | |
# Etherscan API key. | |
jq -n \ | |
--arg bootstrap "$bootstrap" \ | |
--arg clientGateway "$clientGateway" \ | |
--arg vault "$vault" \ | |
--arg rewardVault "$rewardVault" \ | |
--arg capsule "$capsule" \ | |
'{ | |
bootstrap: $bootstrap, | |
clientGateway: $clientGateway, | |
vault: $vault, | |
rewardVault: $rewardVault, | |
capsule: $capsule | |
}' > validatedContracts.json | |
echo "Validation passed: All fields are non-empty and valid Ethereum checksum addresses" | |
- name: Upload validated contracts artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
name: validated-contracts-${{ github.event.pull_request.head.sha || github.event.after || github.sha }} | |
path: validatedContracts.json | |
extract-storage-layout: | |
# Takes less than 30 seconds per matrix member | |
timeout-minutes: 5 | |
runs-on: ubuntu-latest | |
needs: build | |
outputs: | |
storage-layout-file: ${{ steps.generate-storage-layout.outputs.output-file }} | |
artifact-name: ${{ steps.generate-storage-layout.outputs.artifact-name }} | |
strategy: | |
matrix: | |
include: | |
- contract: ExocoreGateway | |
base: true | |
- contract: Bootstrap | |
base: false | |
- contract: ClientChainGateway | |
base: false | |
- contract: RewardVault | |
base: false | |
- contract: Vault | |
base: false | |
- contract: ExocoreGateway | |
base: false | |
- contract: ExoCapsule | |
base: false | |
steps: | |
- name: Restore cached Foundry toolchain | |
uses: actions/cache/restore@v3 | |
with: | |
path: ${{ needs.build.outputs.installation-dir }} | |
key: ${{ needs.build.outputs.cache-key }} | |
- name: Add Foundry to PATH | |
run: echo "${{ needs.build.outputs.installation-dir }}" >> "$GITHUB_PATH" | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
if: ${{ !matrix.base }} | |
- name: Checkout base branch of repository | |
uses: actions/checkout@v4 | |
with: | |
ref: ${{ github.event.pull_request.base.ref || github.event.before }} | |
if: ${{ matrix.base }} | |
- name: Restore `forge build` results | |
uses: actions/cache/restore@v3 | |
if: ${{ !matrix.base }} | |
with: | |
path: | | |
./lib | |
./out | |
./cache | |
./broadcast | |
key: build-${{ github.event.pull_request.head.sha || github.event.after || github.sha }} | |
- name: Restore `lib` folder | |
uses: actions/cache/restore@v3 | |
if: ${{ matrix.base }} | |
with: | |
path: | | |
./lib | |
key: build-${{ github.event.pull_request.head.sha || github.event.after || github.sha }} | |
- name: Generate storage layout file | |
id: generate-storage-layout | |
run: | | |
artifact_name="compiled-layout-${{ matrix.contract }}" | |
if [ "${{ matrix.base }}" = "true" ]; then | |
output_file="${{ matrix.contract }}.base.json" | |
artifact_name="${artifact_name}-base" | |
else | |
output_file="${{ matrix.contract }}.current.json" | |
artifact_name="${artifact_name}-current" | |
fi | |
artifact_name="${artifact_name}-${{ github.event.pull_request.head.sha || github.event.after || github.sha }}" | |
forge inspect --json src/core/${{ matrix.contract }}.sol:${{ matrix.contract }} storage-layout > $output_file | |
echo "output-file=$output_file" >> "$GITHUB_OUTPUT" | |
echo "artifact-name=$artifact_name" >> "$GITHUB_OUTPUT" | |
- name: Upload storage layout file as an artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
path: ${{ steps.generate-storage-layout.outputs.output-file }} | |
name: ${{ steps.generate-storage-layout.outputs.artifact-name }} | |
combine-storage-layouts: | |
# Takes less than 10 seconds | |
timeout-minutes: 5 | |
runs-on: ubuntu-latest | |
needs: | |
- extract-storage-layout | |
steps: | |
- name: Download artifacts | |
uses: actions/download-artifact@v4 | |
# No name means all artifacts created by this workflow are downloaded | |
# within their respective subfolders (paths) inside the provided path (`combined`). | |
with: | |
path: combined | |
- name: Zip up the compiled layouts | |
run: zip -j compiled-layouts.zip combined/*/*.json | |
- name: Upload the compiled layouts file as an artifact | |
uses: actions/upload-artifact@v4 | |
with: | |
path: compiled-layouts.zip | |
name: compiled-layouts-${{ github.event.pull_request.head.sha || github.event.after || github.sha }} |