Some checks failed
CI / Frontend Lint (push) Has been cancelled
CI / Frontend Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Frontend E2E Tests (push) Has been cancelled
CI / Orchestrator Build (push) Has been cancelled
CI / Orchestrator Unit Tests (push) Has been cancelled
CI / Orchestrator E2E (Testcontainers) (push) Has been cancelled
CI / Contracts Compile (push) Has been cancelled
CI / Contracts Test (push) Has been cancelled
Security Scan / Dependency Vulnerability Scan (push) Has been cancelled
Security Scan / OWASP ZAP Scan (push) Has been cancelled
164 lines
5.0 KiB
TypeScript
164 lines
5.0 KiB
TypeScript
/**
|
|
* Helper: compile contracts/NotaryRegistry.sol + its two interfaces
|
|
* + @openzeppelin/contracts Ownable using solc-js in-process.
|
|
*
|
|
* Keeps the E2E suite self-contained — no dependence on a prior
|
|
* `hardhat compile` step, no new workspace wiring.
|
|
*/
|
|
|
|
import { readFileSync } from "fs";
|
|
import { dirname, join, resolve } from "path";
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
|
|
const solc = require("solc");
|
|
|
|
const REPO_ROOT = resolve(__dirname, "..", "..", "..", "..");
|
|
const CONTRACTS_ROOT = join(REPO_ROOT, "contracts");
|
|
const OZ_ROOT = join(CONTRACTS_ROOT, "node_modules", "@openzeppelin");
|
|
|
|
// ethers v6 accepts any JsonFragment-shaped array here. Declaring the
|
|
// element type loosely keeps us decoupled from ethers' private type
|
|
// exports while still being strictly typed against `unknown`.
|
|
export type AbiFragment = Record<string, unknown>;
|
|
|
|
export interface CompiledArtifact {
|
|
abi: AbiFragment[];
|
|
bytecode: string;
|
|
}
|
|
|
|
interface SolcSource {
|
|
content: string;
|
|
}
|
|
|
|
interface SolcInput {
|
|
language: "Solidity";
|
|
sources: Record<string, SolcSource>;
|
|
settings: {
|
|
optimizer: { enabled: true; runs: number };
|
|
outputSelection: Record<string, Record<string, string[]>>;
|
|
};
|
|
}
|
|
|
|
interface SolcOutput {
|
|
errors?: Array<{ severity: "error" | "warning"; formattedMessage: string }>;
|
|
contracts: Record<
|
|
string,
|
|
Record<string, { abi: AbiFragment[]; evm: { bytecode: { object: string } } }>
|
|
>;
|
|
}
|
|
|
|
function readFromRoots(rel: string, roots: string[]): string {
|
|
for (const root of roots) {
|
|
try {
|
|
return readFileSync(join(root, rel), "utf8");
|
|
} catch {
|
|
// try next root
|
|
}
|
|
}
|
|
throw new Error(`Could not resolve import ${rel} against roots ${roots.join(",")}`);
|
|
}
|
|
|
|
function findImports(requestedPath: string): { contents: string } | { error: string } {
|
|
// @openzeppelin/... → contracts/node_modules/@openzeppelin/...
|
|
if (requestedPath.startsWith("@openzeppelin/")) {
|
|
const rel = requestedPath.replace("@openzeppelin/", "");
|
|
try {
|
|
return { contents: readFileSync(join(OZ_ROOT, rel), "utf8") };
|
|
} catch (e) {
|
|
return { error: `Could not read ${requestedPath}: ${(e as Error).message}` };
|
|
}
|
|
}
|
|
// Local ./interfaces/... paths resolve against contracts/
|
|
try {
|
|
return { contents: readFromRoots(requestedPath, [CONTRACTS_ROOT]) };
|
|
} catch (e) {
|
|
return { error: (e as Error).message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively pull in all `import "..."` references starting from
|
|
* NotaryRegistry.sol and return the full `sources` object solc needs.
|
|
*/
|
|
function collectSources(entryPath: string): Record<string, SolcSource> {
|
|
const sources: Record<string, SolcSource> = {};
|
|
const stack: string[] = [entryPath];
|
|
const seen = new Set<string>();
|
|
|
|
while (stack.length > 0) {
|
|
const cur = stack.pop()!;
|
|
if (seen.has(cur)) continue;
|
|
seen.add(cur);
|
|
|
|
let content: string;
|
|
if (cur === entryPath) {
|
|
content = readFileSync(join(CONTRACTS_ROOT, "NotaryRegistry.sol"), "utf8");
|
|
} else {
|
|
const resolved = findImports(cur);
|
|
if ("error" in resolved) {
|
|
throw new Error(`Unresolved import: ${cur} (${resolved.error})`);
|
|
}
|
|
content = resolved.contents;
|
|
}
|
|
sources[cur] = { content };
|
|
|
|
// Parse `import "..."` statements. Interfaces may use relative paths
|
|
// that we normalise back into keys solc expects.
|
|
const importRe = /^\s*import\s+(?:\{[^}]+\}\s+from\s+)?"([^"]+)";/gm;
|
|
let m: RegExpExecArray | null;
|
|
while ((m = importRe.exec(content)) !== null) {
|
|
const rawImport = m[1];
|
|
let normalised: string;
|
|
if (rawImport.startsWith("@openzeppelin/")) {
|
|
normalised = rawImport;
|
|
} else if (rawImport.startsWith("./") || rawImport.startsWith("../")) {
|
|
// Relative import — resolve against the dir of `cur`.
|
|
const curDir = cur.includes("/") ? dirname(cur) : ".";
|
|
const joined = join(curDir, rawImport);
|
|
normalised = joined.startsWith(".") ? joined.slice(2) : joined;
|
|
} else {
|
|
normalised = rawImport;
|
|
}
|
|
if (!seen.has(normalised)) stack.push(normalised);
|
|
}
|
|
}
|
|
|
|
return sources;
|
|
}
|
|
|
|
export function compileNotaryRegistry(): CompiledArtifact {
|
|
const entry = "NotaryRegistry.sol";
|
|
const sources = collectSources(entry);
|
|
|
|
const input: SolcInput = {
|
|
language: "Solidity",
|
|
sources,
|
|
settings: {
|
|
optimizer: { enabled: true, runs: 200 },
|
|
outputSelection: {
|
|
"*": { "*": ["abi", "evm.bytecode.object"] },
|
|
},
|
|
},
|
|
};
|
|
|
|
const output: SolcOutput = JSON.parse(
|
|
solc.compile(JSON.stringify(input), { import: findImports }),
|
|
);
|
|
|
|
const fatal = (output.errors ?? []).filter((e) => e.severity === "error");
|
|
if (fatal.length > 0) {
|
|
const msg = fatal.map((e) => e.formattedMessage).join("\n");
|
|
throw new Error(`solc compile failed:\n${msg}`);
|
|
}
|
|
|
|
const artifact = output.contracts[entry]?.NotaryRegistry;
|
|
if (!artifact) {
|
|
throw new Error("NotaryRegistry not found in solc output");
|
|
}
|
|
|
|
return {
|
|
abi: artifact.abi,
|
|
bytecode: "0x" + artifact.evm.bytecode.object,
|
|
};
|
|
}
|