Skip to content

Commit

Permalink
Add survey-roles script
Browse files Browse the repository at this point in the history
  • Loading branch information
bbenligiray committed Dec 31, 2024
1 parent 79cadc5 commit d80c95f
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"prettier:check": "prettier --check \"./**/*.{js,ts,md,json,sol}\"",
"prettier": "prettier --write \"./**/*.{js,ts,md,json,sol}\"",
"postpack": "./postpack.sh",
"survey-roles": "hardhat run scripts/survey-roles.ts",
"test": "hardhat test --parallel",
"test:coverage": "hardhat coverage",
"test:extended": "EXTENDED_TEST=TRUE hardhat test --parallel",
Expand Down
123 changes: 123 additions & 0 deletions scripts/survey-roles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import * as fs from 'node:fs';
import { join } from 'node:path';

import { CHAINS } from '@api3/chains';
import { go } from '@api3/promise-utils';
import { config, ethers } from 'hardhat';

import { chainsSupportedByManagerMultisig, chainsSupportedByDapis } from '../data/chain-support.json';

import { goAsyncOptions } from './constants';

// This may need to be tuned for different RPCs
const MAXIMUM_GETLOGS_BLOCK_RANGE = 50_000;

async function surveyRoles(network: string) {
if (!chainsSupportedByDapis.includes(network)) {
return;
}
// The provider must serve all logs for this script to work. The default
// chain RPC URLs don't necessarily support that. In such cases, you can
// override the default RPC URL through .env. See
// https://github.com/api3dao/chains/tree/main?tab=readme-ov-file#hardhatconfignetworks
const provider = new ethers.JsonRpcProvider((config.networks[network] as any).url);
const { address: accessControlRegistryAddress, abi: accessControlRegistryAbi } = JSON.parse(
fs.readFileSync(join('deployments', network, `AccessControlRegistry.json`), 'utf8')
);
const accessControlRegistryInterface = new ethers.Interface(accessControlRegistryAbi);
const blockNumber = await provider.getBlockNumber();
let logs: any[] = [];
let percentage = 0;
for (let fromBlockNumber = 0; fromBlockNumber <= blockNumber; fromBlockNumber += MAXIMUM_GETLOGS_BLOCK_RANGE) {
const goGetLogs = await go(
async () =>
provider.getLogs({
address: accessControlRegistryAddress,
fromBlock: fromBlockNumber,
toBlock:
fromBlockNumber + MAXIMUM_GETLOGS_BLOCK_RANGE > blockNumber
? blockNumber
: fromBlockNumber + MAXIMUM_GETLOGS_BLOCK_RANGE,
}),
goAsyncOptions
);
if (!goGetLogs.success || !goGetLogs.data) {
throw new Error(`${network} AccessControlRegistry logs could not be fetched`);
}
logs = [...logs, ...goGetLogs.data];
if (percentage !== Math.floor((fromBlockNumber * 100) / blockNumber)) {
percentage = Math.floor((fromBlockNumber * 100) / blockNumber);
// eslint-disable-next-line no-console
console.log(`${percentage}%`);
}
}
const parsedLogs = logs.map((log) => accessControlRegistryInterface.parseLog(log));
const roleToGrantees: Record<string, Set<string>> = {};
for (const parsedLog of parsedLogs) {
if (parsedLog!.name === 'RoleGranted') {
if (roleToGrantees[parsedLog!.args[0]]) {
roleToGrantees[parsedLog!.args[0]]!.add(parsedLog!.args[1]);
} else {
roleToGrantees[parsedLog!.args[0]] = new Set([parsedLog!.args[1]]);
}
} else if (parsedLog!.name === 'RoleRevoked') {
roleToGrantees[parsedLog!.args[0]]!.delete(parsedLog!.args[1]);
}
}
for (const [role, grantees] of Object.entries(roleToGrantees)) {
if (parsedLogs.some((parsedLog) => parsedLog!.name === 'InitializedManager' && parsedLog!.args[0] === role)) {
// Manager roles can't dangle so we don't worry about them
continue;
}
const roleInitializationParsedLog = parsedLogs.find(
(parsedLog) => parsedLog!.name === 'InitializedRole' && parsedLog!.args[0] === role
);
// eslint-disable-next-line no-console
console.log(`${roleInitializationParsedLog!.args[2]}: ${[...grantees].join(' ')}`);
// The important roles and expected grantees are:
// - Api3ServerV1: "dAPI name setter" (OwnableCallForwarder and Api3MarketV2)
// - Api3ServerV1OevExtension: "Withdrawer" (none), "Auctioneer" (OwnableCallForwarder and auctioneer EOA)
// - OevAuctionHouse: "Proxy setter" (none), "Withdrawer" (none), "Auctioneer" (OwnableCallForwarder and auctioneer EOA)
// In addition, all admin roles should only be granted to OwnableCallForwarder
}
}

async function main() {
const networks = process.env.NETWORK ? [process.env.NETWORK] : chainsSupportedByManagerMultisig;

const erroredMainnets: string[] = [];
const erroredTestnets: string[] = [];
await Promise.all(
networks.map(async (network) => {
try {
await surveyRoles(network);
} catch (error) {
if (CHAINS.find((chain) => chain.alias === network)?.testnet) {
erroredTestnets.push(network);
} else {
erroredMainnets.push(network);
}
// eslint-disable-next-line no-console
console.error(error, '\n');
}
})
);
if (erroredTestnets.length > 0) {
// eslint-disable-next-line no-console
console.error(`Survey failed on testnets: ${erroredTestnets.join(', ')}`);
}
if (erroredMainnets.length > 0) {
// eslint-disable-next-line no-console
console.error(`Survey failed on: ${erroredMainnets.join(', ')}`);
// eslint-disable-next-line unicorn/no-process-exit
process.exit(1);
}
}

/* eslint-disable */
main()
.then(() => process.exit(0))
.catch((error) => {
console.log(error);
process.exit(1);
});

0 comments on commit d80c95f

Please sign in to comment.