Spaces:
Runtime error
Runtime error
File size: 6,122 Bytes
0b3cd21 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | import { parse } from "@solidity-parser/parser";
import { tool } from "langchain";
import { z } from "zod";
import {
analyzeFunction,
buildCommentBlocks,
type ContractAnalysis,
extractSolcVersion,
findCommentFor,
generateBriefMarkdown,
generateFullMarkdown,
paramToString,
type RenderOptions,
typeToString,
} from "./utils.ts";
export const analyzeSolidityFile = async (
soliditySource: string,
mode: "full" | "short",
filePath?: string,
importance?: number,
): Promise<string> => {
let ast: any;
try {
ast = parse(soliditySource, { tolerant: true, loc: true, range: true });
} catch (e: any) {
return `# Parse Error\n\nFailed to parse Solidity source: ${e.message as string}`;
}
const comments = buildCommentBlocks(soliditySource);
const imports: string[] = [];
const contracts: ContractAnalysis[] = [];
for (const node of ast.children ?? []) {
if (node.type === "ImportDirective") imports.push(node.path as string);
}
for (const node of ast.children ?? []) {
if (node.type !== "ContractDefinition") continue;
const contractComment = node.loc ? findCommentFor(node.loc.start.line, comments) : undefined;
const contract: ContractAnalysis = {
name: node.name as string,
kind: (node.kind as string) ?? "contract",
baseContracts: (node.baseContracts ?? []).map(
(bc: any) => (bc.baseName?.namePath ?? bc.baseName?.name ?? "?") as string,
),
usingFor: [],
stateVars: [],
events: [],
errors: [],
modifiers: [],
functions: [],
natspec: contractComment?.natspec,
};
const stateVarNames = new Set<string>();
for (const member of node.subNodes ?? []) {
const memberComment = member.loc ? findCommentFor(member.loc.start.line, comments) : undefined;
switch (member.type) {
case "StateVariableDeclaration":
for (const v of member.variables ?? []) {
stateVarNames.add(v.name as string);
contract.stateVars.push({
name: v.name as string,
type: typeToString(v.typeName),
visibility: (v.visibility as string) ?? "internal",
constant: (v.isDeclaredConst as boolean) ?? false,
immutable: (v.isImmutable as boolean) ?? false,
natspec: memberComment?.natspec,
});
}
break;
case "EventDefinition": {
const params = (member.parameters ?? []).map((p: any) => {
const indexed = p.isIndexed ? "indexed " : "";
const name = p.name ? ` ${p.name as string}` : "";
return `${indexed}${typeToString(p.typeName)}${name}`;
});
contract.events.push({
name: member.name as string,
params,
anonymous: (member.isAnonymous as boolean) ?? false,
natspec: memberComment?.natspec,
});
break;
}
case "CustomErrorDefinition":
contract.errors.push({
name: member.name as string,
params: (member.parameters ?? []).map((p: any) => paramToString(p)),
natspec: memberComment?.natspec,
});
break;
case "ModifierDefinition":
contract.modifiers.push({
name: member.name as string,
params: (member.parameters ?? []).map(paramToString),
natspec: memberComment?.natspec,
});
break;
case "FunctionDefinition": {
const { internalCalls, externalCalls, stateReads, stateWrites } = analyzeFunction(member, stateVarNames);
contract.functions.push({
name: (member.name as string) ?? "",
isConstructor: (member.isConstructor as boolean) ?? false,
isReceive: (member.isReceiveEther as boolean) ?? false,
isFallback: (member.isFallback as boolean) ?? false,
visibility: (member.visibility as string) ?? "internal",
mutability: (member.stateMutability as string) ?? "nonpayable",
params: (member.parameters ?? []).map(paramToString),
returns: (member.returnParameters ?? []).map(paramToString),
modifiers: (member.modifiers ?? []).map((m: any) => m.name as string),
internalCalls,
externalCalls,
stateReads,
stateWrites,
natspec: memberComment?.natspec,
});
break;
}
case "UsingForDeclaration": {
const forType = member.typeName ? typeToString(member.typeName) : "*";
if (member.libraryName) {
contract.usingFor.push(`\`${member.libraryName as string}\` for \`${forType}\``);
} else {
const fns = (member.functions ?? [])
.map((f: any) => (f.typeName?.namePath ?? f.typeName?.name ?? f.path ?? "?") as string)
.join(", ");
contract.usingFor.push(`{${fns}} for \`${forType}\``);
}
break;
}
}
}
contracts.push(contract);
}
const opts: RenderOptions = {
filePath,
importance,
lineCount: soliditySource.split("\n").length,
solcVersion: extractSolcVersion(soliditySource),
};
return mode === "short"
? generateBriefMarkdown(imports, contracts, opts)
: generateFullMarkdown(imports, contracts, opts);
};
export const solidityAnalyzerTool = tool(async ({ solidityFile, mode }) => analyzeSolidityFile(solidityFile, mode), {
name: "solidity_analyzer",
description:
"Parse a Solidity source file and generate a markdown report. Use mode='short' for a compact summary (meta, external calls, function table). Use mode='full' for the complete report including storage, events, errors, per-function call graph, recursion detection, and state variable touchpoints.",
schema: z.object({
solidityFile: z.string().describe("The full Solidity source code to analyze."),
mode: z
.enum(["full", "short"])
.default("full")
.describe("Report verbosity. 'short' saves tokens; 'full' provides the complete analysis."),
}),
});
|