Skip to content

fix(ci): use PR addrs to compare layouts #149

fix(ci): use PR addrs to compare layouts

fix(ci): use PR addrs to compare layouts #149

Workflow file for this run

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 }}