Automated system for running verification checks on Euler oracle adapters across multiple chains.
This system periodically checks the health and accuracy of price oracle adapters by running a two-step pipeline:
- Fetches metadata from oracle providers (Chainlink, Redstone, Pyth)
- Retrieves all deployed oracle routers from the factory
- Indexes historical adapters from those routers
- Fetches bytecode and configuration for each adapter
- Indexes all assets referenced by the adapters
- Runs a series of verification checks on each adapter:
- Existence and bytecode verification
- Asset consistency checks
- Oracle-specific checks (e.g., Chainlink heartbeat, Pyth staleness)
- Generates a JSON report with results for each adapter
- Clone the repository with submodules:
git clone --recurse-submodules https://github.com/euler-xyz/oracle-checks.git
cd oracle-checks
- Install dependencies:
npm install
- Configure environment:
- Copy
.env.example
to.env
- Add RPC URLs for each chain:
RPC_URL_1=your-ethereum-rpc RPC_URL_137=your-polygon-rpc RPC_URL_42161=your-arbitrum-rpc
Locally:
npm run start
The checks will:
- Run for each configured chain
- Process adapters in batches of 50
- Save results to
data/<chainId>/results.json
The checks run automatically via three triggers:
- Schedule: Every 6 hours via cron (
0 */6 * * *
) - Push: On every push to the main branch
- Manual: Can be triggered from the Actions tab using workflow_dispatch
The GitHub Action:
- Checks out the repository with submodules
- Updates the euler-interfaces submodule to latest
- Sets up Node.js and environment
- Runs the checks
- Commits and pushes any changes to the results
- Go to repository Settings > Secrets and Variables > Actions
- Create a new repository secret called
RPC_URLS
with JSON of RPC endpoints:
{
"1": "https://eth-mainnet.example.com/...",
"137": "https://polygon-mainnet.example.com/...",
"42161": "https://arbitrum-one.example.com/..."
}
- Ensure GitHub Actions has write permissions:
- Go to Settings > Actions > General
- Under "Workflow permissions", select "Read and write permissions"
-
Add the chain's RPC URL to your environment:
RPC_URL_<chainId>=your-rpc-url
-
Update
chainConfigs.ts
with the chain configuration:[chainId]: { publicClient: getPublicClient(chainId), oracleRouterFactory: "0x...", // Factory address fromBlock: 1234567n, // Starting block for indexing fallbackAssets: [...], // Recognized assets minPushHeartbeatBuffer: 60n, // For Chainlink checks pythStalenessLowerBound: 60n, // For Pyth checks pythStalenessUpperBound: 120n, otherRecognizedAggregatorV3Feeds: [...], metadataHashes: { // Allowed bytecode hashes per adapter }, }
-
Add the RPC URL to the GitHub Actions secret in JSON format
-
Create a new check function in
src/checks/
:export function newCheck(params: CheckParams): CheckResult { return { name: "Check Name", pass: boolean, message: string, }; }
-
Export the check from
src/checks/index.ts
-
Add the check to
runChecks.ts
in the appropriate section:- Generic adapter checks
- Chainlink-specific checks
- Pyth-specific checks
- Or create a new section for a new oracle type
Results are saved to chain-specific files:
data/1/results.json
for Ethereumdata/137/results.json
for Polygondata/42161/results.json
for Arbitrum
Each file contains a mapping of adapter addresses to their check results.
src/config.ts
- Chain-specific configurationssrc/runChecks.ts
- Core check logicsrc/index.ts
- Entry point that runs checks for all chains
The system uses chainConfigs.ts
to define settings for each supported chain. Each chain configuration includes:
{
// Public client instance for the chain
publicClient: PublicClient,
// Block number to start indexing from
// Usually set to deployment block of first oracle router
fromBlock: bigint,
// Factory contract that deploys oracle routers
oracleRouterFactory: Address,
// List of designator (e.g. USD) and non-standard assets (e.g. MKR)
fallbackAssets: Asset[],
// Mapping of adapter names to their allowed bytecode hashes
metadataHashes: Record<string, string[]>,
}
{
// Push Settings
// Minimum buffer time (in seconds) that maxStaleness should exceed heartbeat
minPushHeartbeatBuffer: number,
// Additional recognized Aggregator V3 feeds (Chainlink and RedStone are included by default)
otherRecognizedAggregatorV3Feeds: {
[address: Address]: {
provider: string,
description: string
}
},
// Pyth Settings
// Acceptable range for maxStaleness parameter (in seconds)
pythStalenessLowerBound: number,
pythStalenessUpperBound: number,
}
[mainnet.id]: {
publicClient: getClient(mainnet),
fromBlock: 20541273n,
oracleRouterFactory: "0x...",
fallbackAssets: [...],
metadataHashes,
// Push settings
minPushHeartbeatBuffer: 3600,
otherRecognizedAggregatorV3Feeds: {
"0x056339C044055819E8Db84E71f5f2E1F536b2E5b": {
provider: "Midas",
description: "Midas mTBILL/USD Oracle"
},
// ...other feeds
},
// Pyth settings
pythStalenessLowerBound: 30,
pythStalenessUpperBound: 300,
}
Each chain has its own configuration tuned for its specific characteristics like block time and oracle update frequencies.
GPL-3.0-only