- Update dbis_core, cross-chain-pmm-lps, explorer-monorepo, metamask-integration, pr-workspace/chains - Omit embedded publish git dirs and empty placeholders from index Made-with: Cursor
455 lines
19 KiB
Bash
Executable File
455 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Audit live gas-native rollout prerequisites and print the next deploy/config steps.
|
|
# Usage:
|
|
# bash scripts/verify/check-gas-rollout-deployment-matrix.sh
|
|
# bash scripts/verify/check-gas-rollout-deployment-matrix.sh --json
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
|
export PROJECT_ROOT
|
|
|
|
# shellcheck source=/dev/null
|
|
source "$PROJECT_ROOT/scripts/lib/load-project-env.sh" >/dev/null 2>&1 || true
|
|
|
|
OUTPUT_JSON=0
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
--json) OUTPUT_JSON=1 ;;
|
|
*)
|
|
echo "Unknown argument: $arg" >&2
|
|
exit 2
|
|
;;
|
|
esac
|
|
done
|
|
|
|
command -v node >/dev/null 2>&1 || {
|
|
echo "[FAIL] Missing required command: node" >&2
|
|
exit 1
|
|
}
|
|
command -v cast >/dev/null 2>&1 || {
|
|
echo "[FAIL] Missing required command: cast" >&2
|
|
exit 1
|
|
}
|
|
|
|
OUTPUT_JSON="$OUTPUT_JSON" node <<'NODE'
|
|
const path = require('path');
|
|
const { execFileSync } = require('child_process');
|
|
|
|
const loader = require(path.join(process.env.PROJECT_ROOT, 'config/token-mapping-loader.cjs'));
|
|
const contracts = require(path.join(process.env.PROJECT_ROOT, 'config/contracts-loader.cjs'));
|
|
const outputJson = process.env.OUTPUT_JSON === '1';
|
|
const active = loader.loadGruTransportActiveJson() || {};
|
|
const families = loader.getGasAssetFamilies();
|
|
const pairs = loader.getActiveTransportPairs().filter((pair) => pair.assetClass === 'gas_native');
|
|
const reserveVerifiers = active.reserveVerifiers || {};
|
|
const deployedGenericVerifierAddress = contracts.getContractAddress(138, 'CWAssetReserveVerifier') || '';
|
|
const configuredGasPairs = Array.isArray(active.transportPairs)
|
|
? active.transportPairs.filter((pair) => pair && pair.assetClass === 'gas_native')
|
|
: [];
|
|
const deferredGasPairs = configuredGasPairs.filter((pair) => pair.active === false).length;
|
|
|
|
const rpcEnvByChain = {
|
|
138: 'RPC_URL_138',
|
|
1: 'ETHEREUM_MAINNET_RPC',
|
|
10: 'OPTIMISM_MAINNET_RPC',
|
|
25: 'CRONOS_RPC_URL',
|
|
56: 'BSC_RPC_URL',
|
|
100: 'GNOSIS_MAINNET_RPC',
|
|
137: 'POLYGON_MAINNET_RPC',
|
|
42161: 'ARBITRUM_MAINNET_RPC',
|
|
42220: 'CELO_MAINNET_RPC',
|
|
43114: 'AVALANCHE_RPC_URL',
|
|
8453: 'BASE_MAINNET_RPC',
|
|
1111: 'WEMIX_RPC',
|
|
};
|
|
|
|
function resolveConfigRef(ref) {
|
|
if (!ref || typeof ref !== 'object') return '';
|
|
if (typeof ref.address === 'string' && ref.address.trim()) return ref.address.trim();
|
|
if (typeof ref.env === 'string' && process.env[ref.env]) return process.env[ref.env];
|
|
return '';
|
|
}
|
|
|
|
function hasCode(address, rpcUrl) {
|
|
if (!address || !/^0x[a-fA-F0-9]{40}$/.test(address) || !rpcUrl) return null;
|
|
try {
|
|
const out = execFileSync('cast', ['code', address, '--rpc-url', rpcUrl], {
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
}).trim();
|
|
return out !== '' && out !== '0x';
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function callRead(address, rpcUrl, signature, args = []) {
|
|
if (!address || !/^0x[a-fA-F0-9]{40}$/.test(address) || !rpcUrl) {
|
|
return { ok: false, skipped: true, value: null, error: 'missing_rpc_or_address' };
|
|
}
|
|
try {
|
|
const out = execFileSync(
|
|
'cast',
|
|
['call', address, signature, ...args.map((arg) => String(arg)), '--rpc-url', rpcUrl],
|
|
{
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
}
|
|
).trim();
|
|
return { ok: true, skipped: false, value: out, error: null };
|
|
} catch (error) {
|
|
const stderr = error && error.stderr ? String(error.stderr).trim() : '';
|
|
const stdout = error && error.stdout ? String(error.stdout).trim() : '';
|
|
return {
|
|
ok: false,
|
|
skipped: false,
|
|
value: null,
|
|
error: stderr || stdout || (error && error.message ? String(error.message).trim() : 'cast_call_failed'),
|
|
};
|
|
}
|
|
}
|
|
|
|
function parseDestinationConfig(rawValue) {
|
|
if (typeof rawValue !== 'string') {
|
|
return { receiverBridge: null, enabled: null };
|
|
}
|
|
const match = rawValue.match(/^\((0x[a-fA-F0-9]{40}),\s*(true|false)\)$/);
|
|
if (!match) {
|
|
return { receiverBridge: null, enabled: null };
|
|
}
|
|
return {
|
|
receiverBridge: match[1],
|
|
enabled: match[2] === 'true',
|
|
};
|
|
}
|
|
|
|
function classifyL1BridgeCapability(probe) {
|
|
const adminReadable = probe.admin.ok;
|
|
const destinationReadable = probe.destination.ok;
|
|
const accountingReadable =
|
|
probe.reserveVerifier.ok &&
|
|
probe.supportedCanonicalToken.ok &&
|
|
probe.maxOutstanding.ok &&
|
|
probe.outstandingMinted.ok &&
|
|
probe.totalOutstanding.ok &&
|
|
probe.lockedBalance.ok;
|
|
|
|
if (adminReadable && destinationReadable && accountingReadable) return 'full_accounting';
|
|
if (adminReadable && destinationReadable) return 'partial_destination_only';
|
|
if (adminReadable) return 'admin_only';
|
|
if (probe.hasCode === false) return 'missing';
|
|
return 'unknown_or_incompatible';
|
|
}
|
|
|
|
function probeL1Bridge(address, rpcUrl, canonicalAddress, destinationChainSelector) {
|
|
const admin = callRead(address, rpcUrl, 'admin()(address)');
|
|
const destination =
|
|
canonicalAddress && destinationChainSelector
|
|
? callRead(address, rpcUrl, 'destinations(address,uint64)((address,bool))', [
|
|
canonicalAddress,
|
|
destinationChainSelector,
|
|
])
|
|
: { ok: false, skipped: true, value: null, error: 'missing_selector_or_token' };
|
|
const reserveVerifier = callRead(address, rpcUrl, 'reserveVerifier()(address)');
|
|
const supportedCanonicalToken = canonicalAddress
|
|
? callRead(address, rpcUrl, 'supportedCanonicalToken(address)(bool)', [canonicalAddress])
|
|
: { ok: false, skipped: true, value: null, error: 'missing_token' };
|
|
const maxOutstanding =
|
|
canonicalAddress && destinationChainSelector
|
|
? callRead(address, rpcUrl, 'maxOutstanding(address,uint64)(uint256)', [canonicalAddress, destinationChainSelector])
|
|
: { ok: false, skipped: true, value: null, error: 'missing_selector_or_token' };
|
|
const outstandingMinted =
|
|
canonicalAddress && destinationChainSelector
|
|
? callRead(address, rpcUrl, 'outstandingMinted(address,uint64)(uint256)', [
|
|
canonicalAddress,
|
|
destinationChainSelector,
|
|
])
|
|
: { ok: false, skipped: true, value: null, error: 'missing_selector_or_token' };
|
|
const totalOutstanding = canonicalAddress
|
|
? callRead(address, rpcUrl, 'totalOutstanding(address)(uint256)', [canonicalAddress])
|
|
: { ok: false, skipped: true, value: null, error: 'missing_token' };
|
|
const lockedBalance = canonicalAddress
|
|
? callRead(address, rpcUrl, 'lockedBalance(address)(uint256)', [canonicalAddress])
|
|
: { ok: false, skipped: true, value: null, error: 'missing_token' };
|
|
const destinationConfig = parseDestinationConfig(destination.value);
|
|
const readableViews = [
|
|
['admin', admin.ok],
|
|
['destinations', destination.ok],
|
|
['reserveVerifier', reserveVerifier.ok],
|
|
['supportedCanonicalToken', supportedCanonicalToken.ok],
|
|
['maxOutstanding', maxOutstanding.ok],
|
|
['outstandingMinted', outstandingMinted.ok],
|
|
['totalOutstanding', totalOutstanding.ok],
|
|
['lockedBalance', lockedBalance.ok],
|
|
]
|
|
.filter(([, readable]) => readable)
|
|
.map(([label]) => label);
|
|
|
|
return {
|
|
capability: classifyL1BridgeCapability({
|
|
hasCode: hasCode(address, rpcUrl),
|
|
admin,
|
|
destination,
|
|
reserveVerifier,
|
|
supportedCanonicalToken,
|
|
maxOutstanding,
|
|
outstandingMinted,
|
|
totalOutstanding,
|
|
lockedBalance,
|
|
}),
|
|
readableViews,
|
|
destinationConfigured: destinationConfig.enabled,
|
|
destinationReceiverBridge: destinationConfig.receiverBridge,
|
|
reserveVerifier,
|
|
supportedCanonicalToken,
|
|
maxOutstanding,
|
|
outstandingMinted,
|
|
totalOutstanding,
|
|
lockedBalance,
|
|
};
|
|
}
|
|
|
|
function describeMirrorName(pair) {
|
|
if (pair.familyKey === 'eth_mainnet') return 'Wrapped cETH Mainnet';
|
|
if (pair.familyKey === 'eth_l2') return 'Wrapped cETHL2';
|
|
return `Wrapped ${pair.canonicalSymbol}`;
|
|
}
|
|
|
|
function shellQuote(value) {
|
|
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
}
|
|
|
|
const rows = pairs.map((pair) => {
|
|
const destinationRpcEnv = rpcEnvByChain[pair.destinationChainId] || null;
|
|
const destinationRpcUrl = destinationRpcEnv ? process.env[destinationRpcEnv] || '' : '';
|
|
const chain138RpcUrl = process.env[rpcEnvByChain[138]] || '';
|
|
const reserveVerifier = pair.reserveVerifierKey ? reserveVerifiers[pair.reserveVerifierKey] || null : null;
|
|
const reserveVerifierEnvKey =
|
|
reserveVerifier &&
|
|
reserveVerifier.verifierRef &&
|
|
typeof reserveVerifier.verifierRef.env === 'string' &&
|
|
reserveVerifier.verifierRef.env.trim()
|
|
? reserveVerifier.verifierRef.env.trim()
|
|
: '';
|
|
|
|
const l1BridgeAddress = pair.runtimeL1BridgeAddress || resolveConfigRef(pair.peer?.l1Bridge);
|
|
const l2BridgeAddress = pair.runtimeL2BridgeAddress || resolveConfigRef(pair.peer?.l2Bridge);
|
|
const reserveVerifierAddress = pair.runtimeReserveVerifierAddress || resolveConfigRef(reserveVerifier?.verifierRef);
|
|
const reserveVaultAddress = pair.runtimeReserveVaultAddress || resolveConfigRef(reserveVerifier?.vaultRef);
|
|
const destinationChainSelector = typeof pair.destinationChainSelector === 'string' ? pair.destinationChainSelector : '';
|
|
|
|
const canonicalCodeLive = hasCode(pair.canonicalAddress, chain138RpcUrl);
|
|
const mirroredCodeLive = hasCode(pair.mirroredAddress, destinationRpcUrl);
|
|
const l1BridgeLive = hasCode(l1BridgeAddress, chain138RpcUrl);
|
|
const l2BridgeLive = hasCode(l2BridgeAddress, destinationRpcUrl);
|
|
const reserveVerifierLive = hasCode(reserveVerifierAddress, chain138RpcUrl);
|
|
const reserveVaultLive = reserveVaultAddress ? hasCode(reserveVaultAddress, chain138RpcUrl) : null;
|
|
const l1BridgeProbe = probeL1Bridge(l1BridgeAddress, chain138RpcUrl, pair.canonicalAddress, destinationChainSelector);
|
|
const destinationReceiverMatchesRuntimeL2 =
|
|
!!l1BridgeProbe.destinationReceiverBridge &&
|
|
!!l2BridgeAddress &&
|
|
l1BridgeProbe.destinationReceiverBridge.toLowerCase() === l2BridgeAddress.toLowerCase();
|
|
|
|
const actions = [];
|
|
if (canonicalCodeLive !== true) {
|
|
actions.push({
|
|
type: 'deployCanonical',
|
|
command:
|
|
`GAS_FAMILY=${shellQuote(pair.familyKey)} forge script script/deploy/DeployGasCanonicalTokens.s.sol:DeployGasCanonicalTokens --rpc-url "$RPC_URL_138" --broadcast`,
|
|
});
|
|
}
|
|
if (mirroredCodeLive !== true) {
|
|
actions.push({
|
|
type: 'deployMirror',
|
|
command:
|
|
`CW_BRIDGE_ADDRESS="$${pair.peer?.l2Bridge?.env || 'CW_BRIDGE_MISSING'}" ` +
|
|
`CW_TOKEN_NAME=${shellQuote(describeMirrorName(pair))} ` +
|
|
`CW_TOKEN_SYMBOL=${shellQuote(pair.mirroredSymbol)} ` +
|
|
`CW_TOKEN_DECIMALS=18 ` +
|
|
`forge script script/deploy/DeploySingleCWToken.s.sol:DeploySingleCWToken --rpc-url "$${destinationRpcEnv || 'RPC_MISSING'}" --broadcast`,
|
|
});
|
|
}
|
|
if (!l1BridgeAddress) {
|
|
actions.push({ type: 'setEnv', command: 'export CHAIN138_L1_BRIDGE=<live_chain138_l1_bridge>' });
|
|
}
|
|
if (!l2BridgeAddress) {
|
|
actions.push({ type: 'setEnv', command: `export ${pair.peer?.l2Bridge?.env || 'CW_BRIDGE_TARGET'}=<live_destination_bridge>` });
|
|
}
|
|
if (destinationChainSelector && l1BridgeProbe.destinationConfigured !== true && l2BridgeAddress) {
|
|
actions.push({
|
|
type: 'configureL1Destination',
|
|
command:
|
|
`cast send "$CHAIN138_L1_BRIDGE" "configureDestination(address,uint64,address,bool)" ` +
|
|
`${pair.canonicalAddress} ${destinationChainSelector} ${l2BridgeAddress} true ` +
|
|
'--rpc-url "$RPC_URL_138" --private-key "$PRIVATE_KEY"',
|
|
});
|
|
}
|
|
if (!reserveVerifierAddress && deployedGenericVerifierAddress && reserveVerifierEnvKey) {
|
|
actions.push({
|
|
type: 'setVerifierEnv',
|
|
command: `export ${reserveVerifierEnvKey}=${deployedGenericVerifierAddress}`,
|
|
});
|
|
} else if (!reserveVerifierAddress) {
|
|
actions.push({
|
|
type: 'deployVerifier',
|
|
command:
|
|
'CW_ASSET_RESERVE_VAULT=<live_vault> CW_ASSET_RESERVE_SYSTEM=<live_reserve_system_or_blank> ' +
|
|
'forge script script/DeployCWAssetReserveVerifier.s.sol:DeployCWAssetReserveVerifier --rpc-url "$RPC_URL_138" --broadcast',
|
|
});
|
|
}
|
|
if (!pair.runtimeMaxOutstandingValue) {
|
|
actions.push({ type: 'setCap', command: `export ${pair.maxOutstanding?.env || 'CW_MAX_OUTSTANDING_TARGET'}=<per_lane_cap_raw>` });
|
|
}
|
|
if (!pair.runtimeOutstandingValue || !pair.runtimeEscrowedValue || pair.runtimeTreasuryBackedValue == null) {
|
|
actions.push({ type: 'setSupplyAccounting', command: 'export CW_GAS_OUTSTANDING_*=... CW_GAS_ESCROWED_*=... CW_GAS_TREASURY_BACKED_*=...' });
|
|
}
|
|
if (l1BridgeProbe.capability !== 'full_accounting' && l1BridgeLive === true) {
|
|
actions.push({
|
|
type: 'auditL1BridgeAbi',
|
|
command:
|
|
`cast call "$CHAIN138_L1_BRIDGE" "admin()(address)" --rpc-url "$RPC_URL_138" && ` +
|
|
`cast call "$CHAIN138_L1_BRIDGE" "destinations(address,uint64)((address,bool))" ${pair.canonicalAddress} ${destinationChainSelector || 0} --rpc-url "$RPC_URL_138"`,
|
|
});
|
|
}
|
|
|
|
return {
|
|
key: pair.key,
|
|
familyKey: pair.familyKey,
|
|
chainId: pair.destinationChainId,
|
|
chainName: pair.destinationChainName,
|
|
destinationChainSelector: destinationChainSelector || null,
|
|
canonicalSymbol: pair.canonicalSymbol,
|
|
mirroredSymbol: pair.mirroredSymbol,
|
|
canonicalAddress: pair.canonicalAddress,
|
|
mirroredAddress: pair.mirroredAddress,
|
|
l1BridgeAddress,
|
|
l2BridgeAddress,
|
|
reserveVerifierAddress,
|
|
deployedGenericVerifierAddress: deployedGenericVerifierAddress || null,
|
|
reserveVerifierEnvKey: reserveVerifierEnvKey || null,
|
|
reserveVaultAddress,
|
|
canonicalCodeLive,
|
|
mirroredCodeLive,
|
|
l1BridgeLive,
|
|
l2BridgeLive,
|
|
reserveVerifierLive,
|
|
reserveVaultLive,
|
|
l1BridgeCapability: l1BridgeProbe.capability,
|
|
l1BridgeReadableViews: l1BridgeProbe.readableViews,
|
|
l1BridgeProbeErrors: {
|
|
reserveVerifier: l1BridgeProbe.reserveVerifier.ok ? null : l1BridgeProbe.reserveVerifier.error,
|
|
supportedCanonicalToken: l1BridgeProbe.supportedCanonicalToken.ok ? null : l1BridgeProbe.supportedCanonicalToken.error,
|
|
maxOutstanding: l1BridgeProbe.maxOutstanding.ok ? null : l1BridgeProbe.maxOutstanding.error,
|
|
outstandingMinted: l1BridgeProbe.outstandingMinted.ok ? null : l1BridgeProbe.outstandingMinted.error,
|
|
totalOutstanding: l1BridgeProbe.totalOutstanding.ok ? null : l1BridgeProbe.totalOutstanding.error,
|
|
lockedBalance: l1BridgeProbe.lockedBalance.ok ? null : l1BridgeProbe.lockedBalance.error,
|
|
},
|
|
destinationConfiguredOnL1: l1BridgeProbe.destinationConfigured,
|
|
destinationReceiverBridgeOnL1: l1BridgeProbe.destinationReceiverBridge,
|
|
destinationReceiverMatchesRuntimeL2,
|
|
runtimeReady: pair.runtimeReady === true,
|
|
runtimeMissingRequirements: pair.runtimeMissingRequirements || [],
|
|
actions,
|
|
};
|
|
});
|
|
|
|
const uniqueL1BridgeRows = Array.from(
|
|
new Map(
|
|
rows
|
|
.filter((row) => row.l1BridgeAddress)
|
|
.map((row) => [String(row.l1BridgeAddress).toLowerCase(), row])
|
|
).values()
|
|
);
|
|
|
|
const summary = {
|
|
gasFamilies: families.length,
|
|
transportPairs: rows.length,
|
|
configuredTransportPairs: configuredGasPairs.length,
|
|
deferredTransportPairs: deferredGasPairs,
|
|
canonicalContractsLive: rows.filter((row) => row.canonicalCodeLive === true).length,
|
|
mirroredContractsLive: rows.filter((row) => row.mirroredCodeLive === true).length,
|
|
l1BridgeRefsLoaded: rows.filter((row) => row.l1BridgeAddress).length,
|
|
l2BridgeRefsLoaded: rows.filter((row) => row.l2BridgeAddress).length,
|
|
verifierRefsLoaded: rows.filter((row) => row.reserveVerifierAddress).length,
|
|
runtimeReadyPairs: rows.filter((row) => row.runtimeReady).length,
|
|
pairsWithL1DestinationConfigured: rows.filter((row) => row.destinationConfiguredOnL1 === true).length,
|
|
pairsWithL1ReceiverMatchingRuntimeL2: rows.filter((row) => row.destinationReceiverMatchesRuntimeL2 === true).length,
|
|
l1BridgesObserved: uniqueL1BridgeRows.length,
|
|
l1BridgesWithFullAccountingIntrospection: uniqueL1BridgeRows.filter(
|
|
(row) => row.l1BridgeCapability === 'full_accounting'
|
|
).length,
|
|
l1BridgesWithPartialDestinationIntrospection: uniqueL1BridgeRows.filter(
|
|
(row) => row.l1BridgeCapability === 'partial_destination_only'
|
|
).length,
|
|
deployedGenericVerifierAddress: deployedGenericVerifierAddress || null,
|
|
};
|
|
|
|
if (outputJson) {
|
|
console.log(JSON.stringify({ summary, rows }, null, 2));
|
|
process.exit(0);
|
|
}
|
|
|
|
console.log('=== Gas Rollout Deployment Matrix ===');
|
|
console.log(`Gas families: ${summary.gasFamilies}`);
|
|
console.log(`Active transport pairs: ${summary.transportPairs}`);
|
|
console.log(`Deferred transport pairs: ${summary.deferredTransportPairs}`);
|
|
console.log(`Canonical contracts live on 138: ${summary.canonicalContractsLive}/${summary.transportPairs}`);
|
|
console.log(`Mirrored contracts live on destination chains: ${summary.mirroredContractsLive}/${summary.transportPairs}`);
|
|
console.log(`Loaded L1 bridge refs: ${summary.l1BridgeRefsLoaded}/${summary.transportPairs}`);
|
|
console.log(`Loaded L2 bridge refs: ${summary.l2BridgeRefsLoaded}/${summary.transportPairs}`);
|
|
console.log(`Loaded verifier refs: ${summary.verifierRefsLoaded}/${summary.transportPairs}`);
|
|
console.log(`Runtime-ready pairs: ${summary.runtimeReadyPairs}/${summary.transportPairs}`);
|
|
console.log(`L1 destinations configured: ${summary.pairsWithL1DestinationConfigured}/${summary.transportPairs}`);
|
|
console.log(
|
|
`L1 destination receivers matching runtime L2 bridges: ${summary.pairsWithL1ReceiverMatchingRuntimeL2}/${summary.transportPairs}`
|
|
);
|
|
console.log(
|
|
`Observed L1 bridges with full accounting introspection: ${summary.l1BridgesWithFullAccountingIntrospection}/${summary.l1BridgesObserved}`
|
|
);
|
|
console.log(
|
|
`Observed L1 bridges with destination-only introspection: ${summary.l1BridgesWithPartialDestinationIntrospection}/${summary.l1BridgesObserved}`
|
|
);
|
|
if (summary.deployedGenericVerifierAddress) {
|
|
console.log(`Deployed generic verifier on 138: ${summary.deployedGenericVerifierAddress}`);
|
|
}
|
|
|
|
for (const row of rows) {
|
|
const parts = [
|
|
`${row.chainId} ${row.chainName}`,
|
|
`${row.familyKey}`,
|
|
`${row.canonicalSymbol}->${row.mirroredSymbol}`,
|
|
`selector=${row.destinationChainSelector || 'unset'}`,
|
|
`canonical=${row.canonicalCodeLive === true ? 'live' : row.canonicalCodeLive === false ? 'missing' : 'unknown'}`,
|
|
`mirror=${row.mirroredCodeLive === true ? 'live' : row.mirroredCodeLive === false ? 'missing' : 'unknown'}`,
|
|
`l1=${row.l1BridgeAddress ? 'set' : 'unset'}`,
|
|
`l2=${row.l2BridgeAddress ? 'set' : 'unset'}`,
|
|
`verifier=${row.reserveVerifierAddress ? 'set' : 'unset'}`,
|
|
`l1cap=${row.l1BridgeCapability}`,
|
|
`l1dest=${row.destinationConfiguredOnL1 === true ? 'wired' : row.destinationConfiguredOnL1 === false ? 'missing' : 'unknown'}`,
|
|
];
|
|
console.log(`- ${parts.join(' | ')}`);
|
|
if (row.destinationReceiverBridgeOnL1) {
|
|
console.log(
|
|
` l1 destination receiver: ${row.destinationReceiverBridgeOnL1}${row.destinationReceiverMatchesRuntimeL2 ? ' (matches runtime L2)' : ' (differs from runtime L2)'}`
|
|
);
|
|
}
|
|
if (row.l1BridgeReadableViews.length > 0) {
|
|
console.log(` l1 readable views: ${row.l1BridgeReadableViews.join(', ')}`);
|
|
}
|
|
const failedProbeViews = Object.entries(row.l1BridgeProbeErrors)
|
|
.filter(([, error]) => typeof error === 'string' && error)
|
|
.map(([label]) => label);
|
|
if (failedProbeViews.length > 0) {
|
|
console.log(` l1 probe gaps: ${failedProbeViews.join(', ')}`);
|
|
}
|
|
if (row.runtimeMissingRequirements.length > 0) {
|
|
console.log(` missing: ${row.runtimeMissingRequirements.join(', ')}`);
|
|
}
|
|
for (const action of row.actions.slice(0, 3)) {
|
|
console.log(` next ${action.type}: ${action.command}`);
|
|
}
|
|
}
|
|
NODE
|