Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
28 changes: 18 additions & 10 deletions examples/CRISP/enclave.config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,33 @@ chains:
rpc_url: "ws://localhost:8545"
contracts:
e3_program:
address: '0x851356ae760d987E095750cCeb3bC6014560891C'
deploy_block: 31
address: "0x851356ae760d987E095750cCeb3bC6014560891C"
deploy_block: 64
enclave:
address: "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e"
deploy_block: 21
deploy_block: 42
ciphernode_registry:
address: '0xa513E6E4b8f2a923D98304ec87F64353C4D5C853'
deploy_block: 14
address: "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853"
deploy_block: 36
bonding_registry:
address: '0x8A791620dd6260079BF849Dc5567aDC3F2FdC318'
deploy_block: 10
address: "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318"
deploy_block: 39
slashing_manager:
address: '0x5FC8d32690cc91D4c39d9d3abcBD16989F875707'
deploy_block: 10
address: "0x5FC8d32690cc91D4c39d9d3abcBD16989F875707"
deploy_block: 32
fee_token:
address: "0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512"
deploy_block: 6
deploy_block: 17
program:
dev: true
# risc0:
# risc0_dev_mode: 0 # 0 = production (Boundless), 1 = dev mode (fake proofs)
# boundless:
# rpc_url: "https://sepolia.infura.io/v3/YOUR_KEY"
# private_key: "PRIVATE_KEY" # Use env vars for secrets
# pinata_jwt: "PINATA_JWT" # For uploading programs
# program_url: "https://gateway.pinata.cloud/ipfs/QmNMRAB7DW43JSmENfzGmD96G6sqaeBBNfTVrrq5WQae3D" # Pre-uploaded program
# onchain: true # true = onchain requests, false = offchain
nodes:
cn1:
address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
Expand Down
201 changes: 162 additions & 39 deletions packages/enclave-contracts/scripts/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
// without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE.
import fs from "fs";
import yaml from "js-yaml";
import path from "path";

export const deploymentsFile = path.join("deployed_contracts.json");
Expand Down Expand Up @@ -164,7 +163,8 @@ export function areArraysEqual<T>(arr1: T[], arr2: T[]): boolean {
}

/**
* The function to update the enclave.config.yaml file with the deployed contract addresses
* The function to update the enclave.config.yaml file with the deployed contract addresses.
* Uses line-by-line text manipulation to preserve comments, blank lines, and quote style.
* @param chainToConfig - The chain name to update in the config
* @param pathToConfigFile - The path to the enclave.config.yaml file
* @param contractMapping - A mapping of contract names to config keys
Expand All @@ -175,59 +175,182 @@ export const updateE3Config = (
contractMapping: Record<string, string>,
rpcUrl?: string,
): void => {
const fileContent = fs.readFileSync(pathToConfigFile, "utf8");
const config = yaml.load(fileContent) as EnclaveConfig;
const content = fs.readFileSync(pathToConfigFile, "utf8");
const lines = content.split("\n");

// Find the hardhat chain config
// Find the chain config or create it
let configChain = config.chains.find((chain) => chain.name === chainToConfig);
// Collect deployment data keyed by config key
const updates = new Map<string, { address: string; deployBlock: number }>();
for (const [contractName, configKey] of Object.entries(contractMapping)) {
const deployment = readDeploymentArgs(contractName, chainToConfig);
if (deployment) {
updates.set(configKey, {
address: deployment.address,
deployBlock: deployment.blockNumber ?? 1,
});
}
}

if (updates.size === 0) {
console.log("No deployments found to update.");
return;
}

console.log(`\nUpdating contracts for chain: ${chainToConfig}`);

// State machine to walk through the YAML lines
let inTargetChain = false;
let foundTargetChain = false;
let inContracts = false;
let currentContractKey: string | null = null;
let chainBaseIndent = -1;
let contractsKeyIndent = -1;
let contractEntryIndent = -1;
const foundKeys = new Set<string>();
let lastContractsLine = -1;

for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();

if (trimmed === "" || trimmed.startsWith("#")) {
if (inContracts) lastContractsLine = i;
continue;
}

const indent = line.length - line.trimStart().length;

// Detect chain name entry: ` - name: "chainName"`
const nameMatch = trimmed.match(/^-\s+name:\s*["']?(.+?)["']?\s*$/);
if (nameMatch) {
if (inTargetChain) {
// We've passed the target chain
break;
}

if (nameMatch[1] === chainToConfig) {
inTargetChain = true;
foundTargetChain = true;
chainBaseIndent = indent;
}
continue;
}

// If we hit a top-level key while in the target chain, we've left it
if (inTargetChain && indent <= chainBaseIndent && !trimmed.startsWith("-")) {
break;
}

if (!inTargetChain) continue;

// Detect `contracts:` section
if (trimmed === "contracts:") {
inContracts = true;
contractsKeyIndent = indent;
lastContractsLine = i;
continue;
}

if (!inContracts) continue;

// Check if we've left the contracts section
if (indent <= contractsKeyIndent) {
break;
}

lastContractsLine = i;

if (!configChain) {
// Detect contract key line (e.g., ` enclave:`)
const keyMatch = trimmed.match(/^(\w+):$/);
if (keyMatch && (contractEntryIndent === -1 || indent === contractEntryIndent)) {
currentContractKey = keyMatch[1];
if (contractEntryIndent === -1) contractEntryIndent = indent;
continue;
}

if (!currentContractKey) continue;

// We're inside a contract entry — update address/deploy_block if this contract needs updating
const update = updates.get(currentContractKey);
if (!update) continue;

if (trimmed.startsWith("address:")) {
foundKeys.add(currentContractKey);
const ws = line.match(/^(\s*)/)?.[1] ?? "";
const comment = trimmed.match(/^address:\s*["']?[^#"']*["']?\s*(#.*)$/)?.[1];
lines[i] = `${ws}address: "${update.address}"${comment ? " " + comment : ""}`;
console.log(
`✓ Updated ${currentContractKey}: ${update.address} (block ${update.deployBlock})`,
);
}

if (trimmed.startsWith("deploy_block:")) {
const ws = line.match(/^(\s*)/)?.[1] ?? "";
const comment = trimmed.match(/^deploy_block:\s*\S+\s*(#.*)$/)?.[1];
lines[i] = `${ws}deploy_block: ${update.deployBlock}${comment ? " " + comment : ""}`;
}
}

if (!foundTargetChain) {
// Chain not found — append a new chain block at the end of the chains section
console.log(
`Chain "${chainToConfig}" not found in config. Creating new entry...`,
);

if (!rpcUrl) {
console.warn(
"Warning: No RPC URL provided. You'll need to update it manually in the config.",
);
}

configChain = {
name: chainToConfig,
rpc_url: rpcUrl || `ws://localhost:8545`,
contracts: {},
};

config.chains.push(configChain);
console.log(`✓ Created new chain entry for "${chainToConfig}"`);
}

console.log(`\nUpdating contracts for chain: ${chainToConfig}`);

// Update contract addresses and deploy blocks
for (const [contractName, configKey] of Object.entries(contractMapping)) {
const deployment = readDeploymentArgs(contractName, chainToConfig);
const chainsIdx = lines.findIndex((l) => l.trim() === "chains:");
let insertIdx = lines.length;
if (chainsIdx !== -1) {
for (let i = chainsIdx + 1; i < lines.length; i++) {
const t = lines[i].trim();
if (t === "" || t.startsWith("#")) continue;
if (lines[i].length - lines[i].trimStart().length === 0) {
insertIdx = i;
break;
}
}
}

if (deployment) {
configChain.contracts[configKey as keyof typeof configChain.contracts] = {
address: deployment.address,
deploy_block: deployment.blockNumber ?? 1,
};
const newLines = [
` - name: "${chainToConfig}"`,
` rpc_url: "${rpcUrl || "ws://localhost:8545"}"`,
` contracts:`,
];
for (const [configKey, update] of updates) {
newLines.push(` ${configKey}:`);
newLines.push(` address: "${update.address}"`);
newLines.push(` deploy_block: ${update.deployBlock}`);
console.log(
`✓ Updated ${configKey}: ${deployment.address} (block ${deployment.blockNumber ?? 1})`,
`✓ Added ${configKey}: ${update.address} (block ${update.deployBlock})`,
);
}
}
lines.splice(insertIdx, 0, ...newLines);
} else {
// Insert any contracts that weren't found in the existing config
const missingKeys = [...updates.keys()].filter((k) => !foundKeys.has(k));
if (missingKeys.length > 0 && lastContractsLine !== -1) {
const keyIndent =
contractEntryIndent !== -1 ? contractEntryIndent : contractsKeyIndent + 2;
const valIndent = keyIndent + 2;

// Write updated config back to file
const yamlStr = yaml.dump(config, {
indent: 2,
lineWidth: -1, // Don't wrap lines
quotingType: '"',
forceQuotes: true,
});
const newLines: string[] = [];
for (const configKey of missingKeys) {
const update = updates.get(configKey)!;
newLines.push(`${" ".repeat(keyIndent)}${configKey}:`);
newLines.push(`${" ".repeat(valIndent)}address: "${update.address}"`);
newLines.push(`${" ".repeat(valIndent)}deploy_block: ${update.deployBlock}`);
console.log(
`✓ Added ${configKey}: ${update.address} (block ${update.deployBlock})`,
);
}

lines.splice(lastContractsLine + 1, 0, ...newLines);
}
}

fs.writeFileSync(pathToConfigFile, yamlStr + "\n", "utf8");
fs.writeFileSync(pathToConfigFile, lines.join("\n"), "utf8");
console.log("\n✓ enclave.config.yaml updated successfully!");
};
Loading