Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e80040c
base refactor
lemunozm Feb 17, 2026
6c9aa5d
standalone script
lemunozm Feb 18, 2026
6e9550a
adapt CI and add new job
lemunozm Feb 18, 2026
1a1ceed
use envconfig in layerzero fork
lemunozm Feb 18, 2026
3ff8384
use live for the layerzero fork test
lemunozm Feb 18, 2026
b39e982
fix imports
lemunozm Feb 18, 2026
b3627ed
fix test
lemunozm Feb 18, 2026
f29d7e7
base implementation
lemunozm Feb 19, 2026
011c783
minor reorder in EnvConfig
lemunozm Feb 19, 2026
3d4a8e4
temp test changes
lemunozm Feb 19, 2026
bcb9091
fixes
lemunozm Feb 19, 2026
621a647
mask secrets
lemunozm Feb 19, 2026
7d097b5
better error and network processing
lemunozm Feb 19, 2026
0a7a013
fixes
lemunozm Feb 19, 2026
d8c5d7b
add (new) tag
lemunozm Feb 19, 2026
5c49429
reduce parallelism to avoid reach api rate limits
lemunozm Feb 19, 2026
9910a18
factorize job work
lemunozm Feb 19, 2026
4a1fdb6
add extra delay to avoid rate issues
lemunozm Feb 19, 2026
ee131f6
compile only src
lemunozm Feb 19, 2026
fff0e18
compilation per job
lemunozm Feb 19, 2026
965caf2
fix
lemunozm Feb 19, 2026
92298bb
add extra fix
lemunozm Feb 19, 2026
5246ccf
add extra time gap
lemunozm Feb 19, 2026
3bdde6e
remove logs on failure
lemunozm Feb 19, 2026
bfdd66f
remove temp test additions
lemunozm Feb 19, 2026
caf137f
Merge remote-tracking branch 'origin/main' into ci-verifier
lemunozm Feb 23, 2026
43b3973
add load_secrets.py permissions
lemunozm Feb 23, 2026
61bc4c7
move gcloud secret part to each job
lemunozm Feb 23, 2026
c30e70a
remove changes in EnvConfig to avoid conflicts
lemunozm Feb 24, 2026
af09160
simplify jobs and avoid tarball
lemunozm Mar 18, 2026
c61e482
fix minor errors
lemunozm Mar 19, 2026
9b7cd48
Merge branch 'main' into ci-verifier
gpmayorga Mar 30, 2026
7379538
fix deploy commit
lemunozm Apr 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,12 @@ jobs:
version: v1.4.4
- name: Load secrets
run: python3 script/deploy/load_secrets.py
- name: Mask secrets
run: |
while IFS='=' read -r key value; do
[[ -z "$key" || "$key" == \#* || -z "$value" ]] && continue
echo "::add-mask::$value"
done < .env
- name: Run fork and spell tests
run: forge test --match-path "test/integration/{fork,spell}/**/*.sol" -vv
env:
Expand Down Expand Up @@ -216,6 +222,14 @@ jobs:
CI_MODE: true
run: |
script/deploy/setup.sh
- name: Load secrets
run: python3 script/deploy/load_secrets.py
- name: Mask secrets
run: |
while IFS='=' read -r key value; do
[[ -z "$key" || "$key" == \#* || -z "$value" ]] && continue
echo "::add-mask::$value"
done < .env
Comment on lines +227 to +232
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is a especial addition to the CI jobs that uses secrets that avoid leak those secret in some logs by error

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Doesn't Github upload the tarball of an action as an artifact that's publicly available, including the secrets in the .env? Also, we don't need the PRIVATE_KEY for verification.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good question 🤔, then that would be something to fix...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Very good catch @wischli! Now here (61bc4c7), I've moved the loading secret part to each individual job, so the tarball never has an .env file.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Unless you specify a file upload on the job the files are normally lost.
The private key is accessible to any job in this repository that calls the Google cloud login step whether we put it in an environment file or not, it doesn't really matter

- name: Install dependencies (forge)
run: |
forge install -j 0 --shallow --color auto
Expand Down
83 changes: 83 additions & 0 deletions .github/workflows/verify-factory-contracts.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
name: Verify Factory Contracts

on:
workflow_dispatch:
inputs:
source_version:
description: "Git tag of the deployed source code (e.g. v3.1.0). src/ will be checked out from this tag so forge verify-contract uses matching bytecode."
required: true
default: "v3.1.0"

jobs:
setup:
runs-on: ubuntu-latest
permissions:
contents: "read"
outputs:
networks: ${{ steps.list.outputs.networks }}
steps:
- uses: actions/checkout@v3

- name: List networks from env/*.json
id: list
run: |
networks=$(ls env/*.json | xargs -n1 basename | sed 's/.json//' | grep -v anvil | jq -R -s -c 'split("\n") | map(select(length > 0))')
echo "networks=$networks" >> "$GITHUB_OUTPUT"
echo "Discovered networks: $networks"

verify:
needs: setup
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: "read"
id-token: "write"
strategy:
Comment thread
gpmayorga marked this conversation as resolved.
fail-fast: false
max-parallel: 3
matrix:
network: ${{ fromJson(needs.setup.outputs.networks) }}

name: "${{ matrix.network }}"

steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
submodules: recursive

- name: Checkout src/ from deployed version
run: git checkout ${{ inputs.source_version }} -- src/

- id: "auth"
uses: "google-github-actions/auth@6fc4af4b145ae7821d527454aa9bd537d1f2dc5f" # v2.1.7
with:
workload_identity_provider: ${{ secrets.GCP_WIP }}
service_account: ${{ secrets.GCP_SA }}
create_credentials_file: true
cleanup_credentials: true

- name: "Set up Cloud SDK"
uses: "google-github-actions/setup-gcloud@6189d56e4096ee891640bb02ac264be376592d6a" # v2.1.2
with:
version: ">= 363.0.0"

- name: Load secrets
run: python3 script/deploy/load_secrets.py

- name: Mask secrets
run: |
while IFS='=' read -r key value; do
[[ -z "$key" || "$key" == \#* || -z "$value" ]] && continue
echo "::add-mask::$value"
done < .env

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: v1.4.4

- name: Verify ${{ matrix.network }}
env:
NETWORK: ${{ matrix.network }}
run: forge script script/VerifyFactoryContracts.s.sol --skip test
2 changes: 1 addition & 1 deletion env/hyper-evm.json
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@
},
"deploymentInfo": {
"deploy:protocol": {
"gitCommit": "d9db839cc",
"gitCommit": "c4dcd1ae",
"timestamp": "2026-01-28T15:19:10Z",
"suffix": "",
"startBlock": 25782262
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,28 +31,31 @@ struct AddressesToVerify {
enum VerificationStatus {
NotDeployed,
NotVerified,
Verified
AlreadyVerified,
NewlyVerified
}

struct VerificationResult {
string name;
VerificationStatus status;
}

contract ValidateContractsFromFactories is Script {
contract VerifyFactoryContracts is Script {
using stdJson for string;
using JsonUtils for *;

error VerificationFailed(uint256 notVerified);

EnvConfig config;
GraphQLQuery indexer;

constructor() {
config = Env.load(vm.envString("NETWORK"));
indexer = new GraphQLQuery(config.network.graphQLApi());
}

function run() public {
vm.createSelectFork(config.network.rpcUrl());
indexer = new GraphQLQuery(config.network.graphQLApi());

AddressesToVerify memory addr = _fetchAddresses();

Expand All @@ -66,7 +69,7 @@ contract ValidateContractsFromFactories is Script {
results[5] = _verifyContract(addr.onOfframpManager, "OnOfframpManager");
results[6] = _verifyContract(addr.merkleProofManager, "MerkleProofManager");

// Log summary
// Log summary and revert if any contract failed verification
_logSummary(results);
}

Expand All @@ -82,7 +85,7 @@ contract ValidateContractsFromFactories is Script {
string memory orderBy =
string.concat("orderBy: ", "createdAt".asJsonString(), ", orderDirection: ", "desc".asJsonString());

string memory centrifugeIdValue = vm.toString(config.network.centrifugeId);
string memory centrifugeIdValue = vm.toString(config.network.centrifugeId).asJsonString();

// forgefmt: disable-next-item
string memory json = indexer.queryGraphQL(string.concat(
Expand Down Expand Up @@ -128,8 +131,8 @@ contract ValidateContractsFromFactories is Script {
}

VerificationStatus status = _verificationStatus(contractAddress);
if (status == VerificationStatus.Verified) {
result.status = VerificationStatus.Verified;
if (status == VerificationStatus.AlreadyVerified) {
result.status = VerificationStatus.AlreadyVerified;
return result;
}

Expand All @@ -151,12 +154,18 @@ contract ValidateContractsFromFactories is Script {
" --constructor-args ",
vm.toString(constructorArgs),
" --watch",
" >/dev/tty 2>&1" //Redirect to terminal directly
" 2>&1 || true" // Capture output; exit 0 so ffi doesn't revert
);

try vm.ffi(cmd) returns (bytes memory) {
result.status = VerificationStatus.Verified;
} catch {
// Wait before hitting the Etherscan API again via forge verify-contract
vm.sleep(400);

bytes memory output = vm.ffi(cmd);
if (_containsSubstring(output, "successfully verified")) {
result.status = VerificationStatus.NewlyVerified;
} else if (_containsSubstring(output, "already verified")) {
result.status = VerificationStatus.AlreadyVerified;
} else {
result.status = VerificationStatus.NotVerified;
}
}
Expand All @@ -166,6 +175,9 @@ contract ValidateContractsFromFactories is Script {
return VerificationStatus.NotDeployed;
}

// Rate-limit Etherscan API calls to avoid 3/sec limit
vm.sleep(400);

string[] memory cmd = new string[](3);
cmd[0] = "bash";
cmd[1] = "-c";
Expand All @@ -192,7 +204,7 @@ contract ValidateContractsFromFactories is Script {
return VerificationStatus.NotVerified;
}

return VerificationStatus.Verified;
return VerificationStatus.AlreadyVerified;
}

/// @notice Get constructor args by reading from contract storage
Expand Down Expand Up @@ -263,6 +275,21 @@ contract ValidateContractsFromFactories is Script {
return abi.encode(m.poolId(), m.contractUpdater());
}

function _containsSubstring(bytes memory data, bytes memory needle) internal pure returns (bool) {
if (data.length < needle.length) return false;
for (uint256 i = 0; i <= data.length - needle.length; i++) {
bool found = true;
for (uint256 j = 0; j < needle.length; j++) {
if (data[i + j] != needle[j]) {
found = false;
break;
}
}
if (found) return true;
}
return false;
}

// ANSI color codes
string constant GREEN = "\x1b[32m";
string constant RED = "\x1b[31m";
Expand All @@ -282,7 +309,10 @@ contract ValidateContractsFromFactories is Script {

for (uint256 i = 0; i < results.length; i++) {
string memory statusStr;
if (results[i].status == VerificationStatus.Verified) {
if (results[i].status == VerificationStatus.NewlyVerified) {
statusStr = string.concat(GREEN, "[OK]", RESET, " Verified (new)");
verified++;
} else if (results[i].status == VerificationStatus.AlreadyVerified) {
statusStr = string.concat(GREEN, "[OK]", RESET, " Verified");
verified++;
} else if (results[i].status == VerificationStatus.NotVerified) {
Expand All @@ -303,5 +333,9 @@ contract ValidateContractsFromFactories is Script {
console.log(string.concat("Not Deployed: ", vm.toString(notDeployed)));
console.log("----------------------------------------");
console.log("");

if (notVerified > 0) {
revert VerificationFailed(notVerified);
}
}
}
Loading