diff --git a/.changeset/thirty-beers-attack.md b/.changeset/thirty-beers-attack.md new file mode 100644 index 00000000..96eb3dfa --- /dev/null +++ b/.changeset/thirty-beers-attack.md @@ -0,0 +1,5 @@ +--- +"@perfect-abstractions/compose-cli": minor +--- + +Add compiler AST support for Foundry and Hardhat, introduce Virtual Storage Layout compatibility checks, and add the standalone `compose validate` pipeline with traceable selector and storage diagnostics. diff --git a/cli/src/adapters/foundryAdapter.ts b/cli/src/adapters/foundryAdapter.ts index 2e606897..ab9b3e58 100644 --- a/cli/src/adapters/foundryAdapter.ts +++ b/cli/src/adapters/foundryAdapter.ts @@ -1,11 +1,19 @@ import fs from "node:fs/promises"; import path from "node:path"; import { ComposeContext } from "../context/types"; -import { ConfigOptions, IFrameworkAdapter } from "./interface/IFrameworkAdapter"; +import { + ConfigOptions, + IFrameworkAdapter, + SolidityAstSource, +} from "./interface/IFrameworkAdapter"; import { writeFileIfMissing } from "../utils/files"; import { runCommand } from "../utils/exec"; -import { resolveCatalogSourceForRead } from "../utils/soliditySources"; +import { + composePackageSubpath, + isComposePackagePath, +} from "../utils/soliditySources"; import { CLI_ROOT } from "../utils/cliRoot"; +import { isSourceUnitAst, listJsonFiles, uniqueAstSources } from "../utils/solidityAst"; function ensureTomlSectionSettings( content: string, @@ -59,7 +67,45 @@ const adapter: IFrameworkAdapter = { }, async resolveSoliditySourcePath(ctx: ComposeContext, sourcePath: string): Promise { - return resolveCatalogSourceForRead(sourcePath); + if (path.isAbsolute(sourcePath)) return sourcePath; + + const root = String(ctx.param.projectRoot ?? ""); + if (isComposePackagePath(sourcePath)) { + return path.join(root, "lib", "Compose", "src", composePackageSubpath(sourcePath)); + } + + return path.resolve(root, sourcePath); + }, + + async compileAst(ctx: ComposeContext, sourcePaths: string[]): Promise { + const root = String(ctx.param.projectRoot ?? ""); + const buildPaths = sourcePaths.map((sourcePath) => { + const relativePath = path.relative(root, sourcePath); + return relativePath && !relativePath.startsWith("..") && !path.isAbsolute(relativePath) + ? relativePath.replace(/\\/g, "/") + : sourcePath; + }); + await runCommand("forge", ["build", ...buildPaths, "--ast", "--force"], { cwd: root }); + + const sources: SolidityAstSource[] = []; + const artifactPaths = await listJsonFiles(path.join(root, "out")); + + for (const artifactPath of artifactPaths) { + const artifact = JSON.parse(await fs.readFile(artifactPath, "utf8")) as { + ast?: unknown; + }; + if (!isSourceUnitAst(artifact.ast)) continue; + + const sourceName = artifact.ast.absolutePath ?? path.basename(path.dirname(artifactPath)); + sources.push({ sourceName, ast: artifact.ast }); + } + + const uniqueSources = uniqueAstSources(sources); + if (uniqueSources.length === 0) { + throw new Error("Foundry compilation did not produce any Solidity AST source units."); + } + + return uniqueSources; }, async initProject(ctx: ComposeContext): Promise { diff --git a/cli/src/adapters/hardhatAdapter.ts b/cli/src/adapters/hardhatAdapter.ts index 4dc49a28..c5b22a2a 100644 --- a/cli/src/adapters/hardhatAdapter.ts +++ b/cli/src/adapters/hardhatAdapter.ts @@ -1,12 +1,43 @@ import fs from "node:fs/promises"; import path from "node:path"; import { ComposeContext } from "../context/types"; -import { ConfigOptions, IFrameworkAdapter } from "./interface/IFrameworkAdapter"; +import { + ConfigOptions, + IFrameworkAdapter, + SolidityAstSource, +} from "./interface/IFrameworkAdapter"; import { writeFileIfMissing } from "../utils/files"; import { runCommand } from "../utils/exec"; -import { isComposePackagePath, resolveCatalogSourceForRead } from "../utils/soliditySources"; +import { + composePackageSubpath, + isComposePackagePath, +} from "../utils/soliditySources"; import { ScaffoldingModule } from "../modules/scaffolding/module"; import { CLI_ROOT } from "../utils/cliRoot"; +import { isSourceUnitAst, listJsonFiles, uniqueAstSources } from "../utils/solidityAst"; + +/** Converts a Hardhat compiler source name into its readable filesystem path. */ +export function resolveHardhatAstSourcePath(projectRoot: string, sourceName: string): string { + const segments = sourceName.replace(/\\/g, "/").split("/"); + + if (segments[0] === "project") { + return path.resolve(projectRoot, ...segments.slice(1)); + } + + if (segments[0] === "npm") { + const packageNameIndex = segments[1]?.startsWith("@") ? 2 : 1; + const versionedPackageName = segments[packageNameIndex] ?? ""; + const versionSeparator = versionedPackageName.lastIndexOf("@"); + if (versionSeparator > 0) { + segments[packageNameIndex] = versionedPackageName.slice(0, versionSeparator); + } + return path.resolve(projectRoot, "node_modules", ...segments.slice(1)); + } + + return path.isAbsolute(sourceName) + ? path.normalize(sourceName) + : path.resolve(projectRoot, sourceName); +} /** Framework adapter for Hardhat-based Diamond projects. */ const adapter: IFrameworkAdapter = { @@ -31,7 +62,53 @@ const adapter: IFrameworkAdapter = { }, async resolveSoliditySourcePath(ctx: ComposeContext, sourcePath: string): Promise { - return resolveCatalogSourceForRead(sourcePath); + if (path.isAbsolute(sourcePath)) return sourcePath; + + const root = String(ctx.param.projectRoot ?? ""); + if (isComposePackagePath(sourcePath)) { + return path.join( + root, + "node_modules", + "@perfect-abstractions", + "compose", + composePackageSubpath(sourcePath), + ); + } + + return path.resolve(root, sourcePath); + }, + + async compileAst(ctx: ComposeContext, sourcePaths: string[]): Promise { + const root = String(ctx.param.projectRoot ?? ""); + await Promise.all(sourcePaths.map((sourcePath) => fs.access(sourcePath))); + await runCommand("npx", ["hardhat", "compile", "--force"], { cwd: root }); + + const sources: SolidityAstSource[] = []; + const buildInfoPaths = await listJsonFiles(path.join(root, "artifacts", "build-info")); + + for (const buildInfoPath of buildInfoPaths) { + const buildInfo = JSON.parse(await fs.readFile(buildInfoPath, "utf8")) as { + output?: { sources?: Record }; + sources?: Record; + }; + const compilerSources = buildInfo.output?.sources ?? buildInfo.sources ?? {}; + + for (const [sourceName, compilerSource] of Object.entries(compilerSources)) { + if (isSourceUnitAst(compilerSource.ast)) { + sources.push({ + sourceName: resolveHardhatAstSourcePath(root, sourceName), + ast: compilerSource.ast, + }); + } + } + } + + const uniqueSources = uniqueAstSources(sources); + if (uniqueSources.length === 0) { + throw new Error("Hardhat compilation did not produce any Solidity AST source units."); + } + + return uniqueSources; }, async initProject(ctx: ComposeContext): Promise { diff --git a/cli/src/adapters/interface/IFrameworkAdapter.ts b/cli/src/adapters/interface/IFrameworkAdapter.ts index 2a4c49bd..8a5fcff9 100644 --- a/cli/src/adapters/interface/IFrameworkAdapter.ts +++ b/cli/src/adapters/interface/IFrameworkAdapter.ts @@ -7,6 +7,22 @@ export type ConfigOptions = { installDeps: boolean; }; +/** A Solidity source unit AST returned by a framework compiler. */ +export type SolidityAstSource = { + sourceName: string; + ast: SoliditySourceUnitAst; +}; + +/** Minimal stable shape shared by Solidity compact AST source units. */ +export type SoliditySourceUnitAst = { + id: number; + nodeType: "SourceUnit"; + src: string; + absolutePath?: string; + nodes?: unknown[]; + [key: string]: unknown; +}; + /** Interface for framework-specific project scaffolding adapters. */ export interface IFrameworkAdapter { /** Resolve the framework's Solidity source root inside the generated project. */ @@ -24,6 +40,9 @@ export interface IFrameworkAdapter { /** Compile the project using the framework's build tool. */ compile(projectRoot: string): Promise; + /** Compile explicit Solidity entrypoints and return every unique source unit AST. */ + compileAst(ctx: ComposeContext, sourcePaths: string[]): Promise; + /** * Resolve a catalog Solidity path to a readable local file. * diff --git a/cli/src/comander.ts b/cli/src/comander.ts index 6ec2043b..f7f73773 100644 --- a/cli/src/comander.ts +++ b/cli/src/comander.ts @@ -34,7 +34,8 @@ export function buildProgram(): Command { program .command("validate") - .description("Validate your project (Coming Soon...)") + .description("Validate selectors and virtual storage layouts") + .option("--project-root ", "Compose project directory") program .command("info") @@ -83,6 +84,11 @@ export function parseArgs(argv: string[]): { command: string; flags: Record = { ...opts }; + if (flags.out !== undefined) { + flags.outDir = flags.out; + delete flags.out; + } + // Map the first positional argument to projectName if --name wasn't passed const positionalArgs = commandInstance.args.filter( (arg): arg is string => typeof arg === "string" && arg !== command, diff --git a/cli/src/index.ts b/cli/src/index.ts index 0795da69..8c0239a6 100644 --- a/cli/src/index.ts +++ b/cli/src/index.ts @@ -32,7 +32,10 @@ async function main(): Promise { ctx.param = { ...ctx.param, ...flags }; try { - await EntryPipeline.execute(ctx); + const result = await EntryPipeline.execute(ctx); + if (!result.status.success) { + process.exitCode = 1; + } } catch (error) { exitWithError(error); } diff --git a/cli/src/modules/pipelineBuilder/module.ts b/cli/src/modules/pipelineBuilder/module.ts index 1e16d459..df1788e9 100644 --- a/cli/src/modules/pipelineBuilder/module.ts +++ b/cli/src/modules/pipelineBuilder/module.ts @@ -5,6 +5,7 @@ import { CatalogPipeline } from "../../pipelines/catalogPipeline"; import { BuildPipeline } from "../../pipelines/buildPipeline"; import { RPCPipeline } from "../../pipelines/rpcPipeline"; import { InspectPipeline } from "../../pipelines/inspectPipeline"; +import { ValidatePipeline } from "../../pipelines/validatePipeline"; /** * Pipeline builder module that routes CLI commands to their corresponding pipelines. @@ -39,7 +40,7 @@ export const PipelineBuilderModule = { result: { command: ctx.param.command }, error: null, }; - return ctx; + return ValidatePipeline.execute(ctx); case "info": ctx.state.commandSelected = { success: true, diff --git a/cli/src/modules/scaffolding/module.ts b/cli/src/modules/scaffolding/module.ts index 7f26b231..f76e0069 100644 --- a/cli/src/modules/scaffolding/module.ts +++ b/cli/src/modules/scaffolding/module.ts @@ -184,16 +184,11 @@ export const ScaffoldingModule = { const scaffoldMapState = ctx.state.scaffoldMap as ModuleState<{ entries: ScaffoldMapEntry[] }> | undefined; const entries = scaffoldMapState?.result?.entries ?? []; - const facetScanState = ctx.state.facetScan as ModuleState<{ facets: FacetScanResult[] }> | undefined; - const facetScanResults = facetScanState?.result?.facets ?? []; - const facets: Record = {}; for (const entry of entries) { const relativePath = toPosixPath(path.relative(root, entry.targetPath)); if (entry.origin === "package") { - const scanResult = facetScanResults.find((f) => f.facetName === entry.facetName); - const sourcePath = scanResult?.path ?? entry.contractName; - const packageName = parsePackageName(sourcePath); + const packageName = parsePackageName(entry.targetPath); facets[entry.facetName] = { source: "package", contract: entry.contractName, diff --git a/cli/src/modules/validation/astIdentity.ts b/cli/src/modules/validation/astIdentity.ts new file mode 100644 index 00000000..f4bfff51 --- /dev/null +++ b/cli/src/modules/validation/astIdentity.ts @@ -0,0 +1,21 @@ +/** Matches a resolved source file with the source-unit name emitted by solc. */ +export function matchesAstSource( + compilerSourceName: string, + resolvedSourcePath: string, +): boolean { + let sourceName = normalizePath(compilerSourceName); + let sourcePath = normalizePath(resolvedSourcePath); + + if (/^[a-zA-Z]:\//.test(sourcePath)) { + sourceName = sourceName.toLowerCase(); + sourcePath = sourcePath.toLowerCase(); + } + + if (sourceName === sourcePath) return true; + const relativeSourceName = sourceName.replace(/^\.\//, "").replace(/^\//, ""); + return sourcePath.endsWith(`/${relativeSourceName}`); +} + +function normalizePath(value: string): string { + return value.replace(/\\/g, "/").replace(/\/+$/, ""); +} diff --git a/cli/src/modules/validation/astSelectors.ts b/cli/src/modules/validation/astSelectors.ts new file mode 100644 index 00000000..3ed3b0ab --- /dev/null +++ b/cli/src/modules/validation/astSelectors.ts @@ -0,0 +1,282 @@ +import { SolidityAstSource } from "../../adapters/interface/IFrameworkAdapter"; +import { matchesAstSource } from "./astIdentity"; +import { FacetReference, FacetScanResult, FunctionInfo } from "./types"; + +type AstNode = Record & { + id?: number; + nodeType: string; +}; + +type AstIndex = { + nodesById: Map; + contractsById: Map; + sourceByContractId: Map; +}; + +/** Extracts selector declarations for referenced facets from compiler AST output. */ +export function scanFacetSelectorsFromAst( + sources: SolidityAstSource[], + facets: FacetReference[], +): FacetScanResult[] { + const index = buildAstIndex(sources); + + return facets.map((facet) => { + const matches = [...index.contractsById.values()].filter( + (contract) => contract.name === facet.contractName && + typeof contract.id === "number" && + matchesAstSource( + index.sourceByContractId.get(contract.id) ?? "", + facet.sourcePath, + ), + ); + const identity = `${facet.sourcePath}:${facet.contractName}`; + + if (matches.length !== 1) { + throw new Error( + matches.length === 0 + ? `Facet contract not found in Solidity AST: ${identity}` + : `Facet contract is ambiguous in Solidity AST: ${identity}`, + ); + } + + return scanContract(matches[0], index); + }); +} + +function scanContract(contract: AstNode, index: AstIndex): FacetScanResult { + const linearizedContractIds = numberArray(contract.linearizedBaseContracts); + const contractIds = linearizedContractIds.length > 0 + ? linearizedContractIds + : typeof contract.id === "number" + ? [contract.id] + : []; + const functionsBySignature = new Map(); + const functionSignaturesById = new Map(); + let exportFunction: AstNode | null = null; + + for (const contractId of contractIds) { + const current = index.contractsById.get(contractId); + if (!current) { + continue; + } + + for (const node of childNodes(current, "nodes")) { + if (node.nodeType !== "FunctionDefinition" || node.kind !== "function") { + continue; + } + + if (node.name === "exportSelectors" && exportFunction === null) { + exportFunction = node; + continue; + } + + if (node.visibility !== "public" && node.visibility !== "external") { + continue; + } + + const signature = functionSignature(node, index); + if (typeof node.id === "number") { + functionSignaturesById.set(node.id, signature); + } + if (!functionsBySignature.has(signature) && typeof node.id === "number") { + functionsBySignature.set(signature, { + declarationId: node.id, + info: { + name: stringValue(node.name), + signature, + visibility: node.visibility, + }, + }); + } + } + } + + const functions = [...functionsBySignature.values()].map(({ info }) => info); + const exportedSelectors = exportFunction + ? collectExportedSignatures(exportFunction, functionSignaturesById) + : []; + const functionSignatures = new Set(functions.map((fn) => fn.signature)); + const exportedSet = new Set(exportedSelectors); + const facetName = stringValue(contract.name); + const sourceName = typeof contract.id === "number" + ? index.sourceByContractId.get(contract.id) ?? "" + : ""; + + return { + facetName, + path: sourceName, + functions, + exportedSelectors, + hasExportSelectorsFunction: exportFunction !== null, + missingExports: functions + .filter((fn) => !exportedSet.has(fn.signature)) + .map((fn) => fn.signature), + extraExports: exportedSelectors.filter((signature) => !functionSignatures.has(signature)), + storageLayouts: [], + warnings: [], + }; +} + +function collectExportedSignatures( + exportFunction: AstNode, + functionSignaturesById: Map, +): string[] { + const signatures = new Set(); + + walkAst(exportFunction.body, (node, ancestors) => { + if (node.nodeType === "MemberAccess" && node.memberName === "selector") { + const expression = astNode(node.expression); + const declarationId = expression && typeof expression.referencedDeclaration === "number" + ? expression.referencedDeclaration + : null; + const signature = declarationId === null + ? null + : functionSignaturesById.get(declarationId); + if (signature) { + signatures.add(signature); + } + return; + } + + if ( + node.nodeType === "Literal" && + node.kind === "string" && + ancestors.some(isKeccak256Call) && + ancestors.some(isBytes4Conversion) + ) { + const signature = stringValue(node.value).replace(/\s+/g, ""); + if (signature.includes("(") && signature.endsWith(")")) { + signatures.add(signature); + } + } + }); + + return [...signatures]; +} + +function functionSignature(node: AstNode, index: AstIndex): string { + const parameters = astNode(node.parameters); + const parameterTypes = childNodes(parameters, "parameters") + .map((parameter) => canonicalAbiType(astNode(parameter.typeName), index)); + return `${stringValue(node.name)}(${parameterTypes.join(",")})`; +} + +function canonicalAbiType(type: AstNode | null, index: AstIndex): string { + if (!type) { + return "unknown"; + } + + if (type.nodeType === "ElementaryTypeName") { + const name = stringValue(type.name); + if (name === "uint") return "uint256"; + if (name === "int") return "int256"; + if (name === "address payable") return "address"; + return name; + } + + if (type.nodeType === "ArrayTypeName") { + const baseType = canonicalAbiType(astNode(type.baseType), index); + const length = astNode(type.length); + return `${baseType}[${length ? stringValue(length.value) : ""}]`; + } + + if (type.nodeType === "UserDefinedTypeName") { + const declarationId = typeof type.referencedDeclaration === "number" + ? type.referencedDeclaration + : null; + const declaration = declarationId === null ? null : index.nodesById.get(declarationId); + + if (declaration?.nodeType === "StructDefinition") { + return `(${childNodes(declaration, "members") + .map((member) => canonicalAbiType(astNode(member.typeName), index)) + .join(",")})`; + } + if (declaration?.nodeType === "EnumDefinition") return "uint8"; + if (declaration?.nodeType === "ContractDefinition") return "address"; + if (declaration?.nodeType === "UserDefinedValueTypeDefinition") { + return canonicalAbiType(astNode(declaration.underlyingType), index); + } + } + + if (type.nodeType === "FunctionTypeName") { + return "function"; + } + + const descriptions = astNode(type.typeDescriptions); + return stringValue(descriptions?.typeString).replace(/\s+(memory|storage|calldata)\b/g, ""); +} + +function buildAstIndex(sources: SolidityAstSource[]): AstIndex { + const nodesById = new Map(); + const contractsById = new Map(); + const sourceByContractId = new Map(); + + for (const source of sources) { + walkAst(source.ast, (node) => { + if (typeof node.id === "number") { + nodesById.set(node.id, node); + if (node.nodeType === "ContractDefinition") { + contractsById.set(node.id, node); + sourceByContractId.set(node.id, source.sourceName); + } + } + }); + } + + return { nodesById, contractsById, sourceByContractId }; +} + +function walkAst( + value: unknown, + visitor: (node: AstNode, ancestors: AstNode[]) => void, + ancestors: AstNode[] = [], +): void { + if (Array.isArray(value)) { + for (const child of value) walkAst(child, visitor, ancestors); + return; + } + + const node = astNode(value); + if (!node) return; + + visitor(node, ancestors); + for (const child of Object.values(node)) { + if (child && typeof child === "object") { + walkAst(child, visitor, [...ancestors, node]); + } + } +} + +function isKeccak256Call(node: AstNode): boolean { + if (node.nodeType !== "FunctionCall") return false; + const expression = astNode(node.expression); + return expression?.nodeType === "Identifier" && expression.name === "keccak256"; +} + +function isBytes4Conversion(node: AstNode): boolean { + if (node.nodeType !== "FunctionCall" || node.kind !== "typeConversion") return false; + const expression = astNode(node.expression); + const typeName = expression && astNode(expression.typeName); + return typeName?.nodeType === "ElementaryTypeName" && typeName.name === "bytes4"; +} + +function childNodes(node: AstNode | null, key: string): AstNode[] { + const value = node?.[key]; + return Array.isArray(value) + ? value.map(astNode).filter((child): child is AstNode => child !== null) + : []; +} + +function astNode(value: unknown): AstNode | null { + if (!value || typeof value !== "object" || Array.isArray(value)) return null; + const candidate = value as Record; + return typeof candidate.nodeType === "string" ? candidate as AstNode : null; +} + +function numberArray(value: unknown): number[] { + return Array.isArray(value) ? value.filter((item): item is number => typeof item === "number") : []; +} + +function stringValue(value: unknown): string { + return typeof value === "string" ? value : ""; +} diff --git a/cli/src/modules/validation/module.ts b/cli/src/modules/validation/module.ts index 6e9f9cff..847fbe2d 100644 --- a/cli/src/modules/validation/module.ts +++ b/cli/src/modules/validation/module.ts @@ -1,5 +1,12 @@ import { ComposeContext } from "../../context/types"; -import { SelectorCollisionDeps } from "./types"; +import { SolidityAstSource } from "../../adapters/interface/IFrameworkAdapter"; +import { DiamondValidationScope, FacetReference, SelectorCollisionDeps } from "./types"; +import { matchesAstSource } from "./astIdentity"; +import { scanFacetSelectorsFromAst } from "./astSelectors"; +import { + buildScopedVirtualStorageLayout, + buildVirtualStorageLayout, +} from "./virtualStorageLayout"; import { findIdentifierCollisions, findSelectorCollisions, @@ -11,29 +18,108 @@ import { getIdentifierCollisionValidationState, getSelectorCollisionValidationState, getSelectorExportValidationState, + getVirtualStorageLayoutValidationState, } from "./state"; -import { showReport } from "./output"; +import { showReport, showSuccess } from "./output"; +import { getResolvedFacetSources, resolveFacetSources } from "./sourceResolution"; +import { IFrameworkAdapter } from "../../adapters/interface/IFrameworkAdapter"; /** - * Validates facet scans for selector export correctness and collision-free layouts. - * - * Provides three validation steps that run sequentially in the init pipelines: - * 1. Selector export validation — ensures every public/external function is declared in `exportSelectors()`. - * 2. Selector collision detection — detects duplicate 4-byte selectors across facets. - * 3. Identifier collision detection — detects incompatible storage layouts for the same storage slot. + * Validates facet scans for selector export guidance and collision-free layouts. * - * Each step stores its result as a `ModuleState` in `ctx.state` and sets - * `success: false` when issues are found. + * Compiler AST supplies selector evidence and the source-side virtual storage + * map. Advisory uncertainty is retained while clear selector or storage + * contradictions are blocking. */ export const ValidationModule = { showReport, + showSuccess, getFacetScanState, getSelectorExportValidationState, getSelectorCollisionValidationState, getIdentifierCollisionValidationState, + getVirtualStorageLayoutValidationState, + getResolvedFacetSources, + + /** Resolves selected Compose package facets into compiler input paths. */ + async resolveComposeFacetSources( + ctx: ComposeContext, + adapter: IFrameworkAdapter, + ): Promise { + const sources = await resolveFacetSources(ctx, adapter, "package"); + ctx.state.validationComposeFacetSources = { + success: true, + result: { sources }, + error: null, + }; + return ctx; + }, + + /** Resolves copied project facets into compiler input paths. */ + async resolveProjectFacetSources( + ctx: ComposeContext, + adapter: IFrameworkAdapter, + ): Promise { + const sources = await resolveFacetSources(ctx, adapter, "local"); + ctx.state.validationProjectFacetSources = { + success: true, + result: { sources }, + error: null, + }; + return ctx; + }, + + /** Scans referenced facets from compiler AST and stores selector evidence in facetScan state. */ + scanFacetSelectors( + ctx: ComposeContext, + sources: SolidityAstSource[], + facets: FacetReference[], + ): ComposeContext { + const scannedFacets = scanFacetSelectorsFromAst(sources, facets); + ctx.state.facetScan = { + success: true, + result: { + facets: scannedFacets, + facetCount: scannedFacets.length, + }, + error: null, + }; + return ctx; + }, + + /** Builds and validates the source-side virtual storage layout from compiler AST. */ + buildVirtualStorageLayout( + ctx: ComposeContext, + sources: SolidityAstSource[], + facets: FacetReference[], + scopes?: DiamondValidationScope[], + ): ComposeContext { + const result = scopes + ? buildScopedVirtualStorageLayout(sources, scopes) + : buildVirtualStorageLayout(sources, facets); + const success = result.collisions.length === 0 && result.unsupported.length === 0; + const unsupported = result.collisions.length === 0 && result.unsupported.length > 0; + + ctx.state.validationVirtualStorageLayout = { + success, + result, + error: success + ? null + : { + code: unsupported + ? "VIRTUAL_STORAGE_LAYOUT_UNSUPPORTED" + : "VIRTUAL_STORAGE_COLLISION_DETECTED", + message: unsupported + ? "Storage layout compatibility could not be proven." + : "Selected facets declare incompatible storage layouts.", + nativeError: null, + }, + }; + return ctx; + }, /** - * Returns true when selector export validation has found blocking issues. + * Returns true when selector export validation could not run. * * Keeps validation state reads inside the validation module boundary. */ @@ -62,6 +148,12 @@ export const ValidationModule = { return Boolean(state && !state.success); }, + /** Returns true when source-derived virtual storage layouts collide. */ + hasVirtualStorageLayoutFailure(ctx: ComposeContext): boolean { + const state = getVirtualStorageLayoutValidationState(ctx); + return Boolean(state && !state.success); + }, + /** * Returns true when any validation stage has produced a blocking failure. * @@ -71,17 +163,17 @@ export const ValidationModule = { return ( ValidationModule.hasSelectorExportFailure(ctx) || ValidationModule.hasSelectorCollisionFailure(ctx) || + ValidationModule.hasVirtualStorageLayoutFailure(ctx) || ValidationModule.hasIdentifierCollisionFailure(ctx) ); }, /** - * Validates that every public/external function is exported by `exportSelectors()`. + * Records advisory differences between public/external functions and `exportSelectors()`. * - * Requires `ctx.state.facetScan` to have been populated by - * {@link ScaffoldingModule.scanSelectedFacets}. For each facet, checks that - * every public/external function name appears in the facet's exported - * selector list and that no extra names are declared. + * Requires `ctx.state.facetScan` to have been populated. For each facet, + * checks whether `exportSelectors()` exists and reports missing or extra + * signatures without blocking validation. * * @param ctx - The compose context with facet scan results. * @returns The context with `ctx.state.validationSelectorExports` populated. @@ -103,21 +195,13 @@ export const ValidationModule = { } const issues = findSelectorExportIssues(facetScan.facets); - const success = issues.length === 0; - ctx.state.validationSelectorExports = { - success, + success: true, result: { checkedFacets: facetScan.facetCount, issues, }, - error: success - ? null - : { - code: "SELECTOR_EXPORT_INVALID", - message: "One or more selected facets do not export all intended selectors.", - nativeError: null, - }, + error: null, }; return ctx; @@ -136,7 +220,7 @@ export const ValidationModule = { */ async detectSelectorCollisions( ctx: ComposeContext, - { hashing }: SelectorCollisionDeps, + { hashing, scopes }: SelectorCollisionDeps, ): Promise { const facetScan = getFacetScanResult(ctx); @@ -153,7 +237,18 @@ export const ValidationModule = { return ctx; } - const collisions = findSelectorCollisions(facetScan.facets, hashing); + const collisions = scopes + ? scopes.flatMap((scope) => { + const scopedFacets = facetScan.facets.filter((scannedFacet) => + scope.facets.some((facet) => + facet.contractName === scannedFacet.facetName && + matchesAstSource(scannedFacet.path, facet.sourcePath))); + return findSelectorCollisions(scopedFacets, hashing).map((collision) => ({ + ...collision, + diamondName: scope.diamondName, + })); + }) + : findSelectorCollisions(facetScan.facets, hashing); const success = collisions.length === 0; ctx.state.validationSelectorCollisions = { diff --git a/cli/src/modules/validation/output.ts b/cli/src/modules/validation/output.ts index ce04660e..ffc8923f 100644 --- a/cli/src/modules/validation/output.ts +++ b/cli/src/modules/validation/output.ts @@ -1,19 +1,23 @@ import { ComposeContext } from "../../context/types"; -import { yellow, red } from "../../utils/terminal"; +import { green, yellow, red } from "../../utils/terminal"; import { getFacetScanState, getSelectorExportValidationState, getSelectorCollisionValidationState, getIdentifierCollisionValidationState, + getVirtualStorageLayoutValidationState, } from "./state"; -import { FacetScanWarning } from "./types"; +import { + FacetScanWarning, + StorageVariableReference, + VirtualStorageLayoutRecord, +} from "./types"; /** * Renders validation warnings and fail-fast error reports. * - * Displays facet scan warnings (yellow), then checks for selector export - * issues, selector collisions, and identifier collisions in order. Each - * failure is printed in red with details and the method returns early. + * Displays facet scan and selector export warnings in yellow, then checks + * selector and identifier collisions. Blocking failures are printed in red. * * @param ctx - The compose context with validation state populated. * @returns The context unchanged. @@ -36,25 +40,45 @@ export async function showReport(ctx: ComposeContext): Promise { } const selectorExportValidation = getSelectorExportValidationState(ctx); + const selectorExportIssues = selectorExportValidation?.result?.issues ?? []; - if (selectorExportValidation && !selectorExportValidation.success) { - console.error(red("\nValidation failed")); - console.error(red(selectorExportValidation.error?.message ?? "Validation failed.")); + if (selectorExportIssues.length > 0) { + console.warn(yellow("\nSelector export warnings")); - for (const issue of selectorExportValidation.result?.issues ?? []) { - console.error(`\n${issue.facetName}`); - console.error(` ${issue.path}`); + for (const issue of selectorExportIssues) { + console.warn(`\n${issue.facetName}`); + console.warn(` ${issue.path}`); + + if (issue.missingExportSelectorsFunction) { + console.warn(" Missing exportSelectors() function"); + } if (issue.missingExports.length > 0) { - console.error(` Missing exports: ${issue.missingExports.join(", ")}`); + console.warn(` Missing exports: ${issue.missingExports.join(", ")}`); } if (issue.extraExports.length > 0) { - console.error(` Extra exports: ${issue.extraExports.join(", ")}`); + console.warn(` Extra exports: ${issue.extraExports.join(", ")}`); } } + } - return ctx; + const virtualStorageLayoutValidation = getVirtualStorageLayoutValidationState(ctx); + const virtualStorageLayoutWarnings = virtualStorageLayoutValidation?.result?.warnings ?? []; + + if (virtualStorageLayoutWarnings.length > 0) { + console.warn(yellow("\nVirtual storage warnings")); + for (const warning of virtualStorageLayoutWarnings) { + const scope = warning.diamondName ? `${warning.diamondName} / ` : ""; + if (warning.contractName && warning.storagePath) { + console.warn(yellow(`\n${scope}${warning.contractName}: ${warning.storagePath}`)); + console.warn(yellow(` ${warning.message}`)); + console.warn(yellow(` ${warning.sourceName}`)); + } else { + console.warn(yellow(`\n${scope}${warning.sourceName}`)); + console.warn(yellow(` ${warning.message}`)); + } + } } const selectorCollisionValidation = getSelectorCollisionValidationState(ctx); @@ -64,14 +88,62 @@ export async function showReport(ctx: ComposeContext): Promise { console.error(red(selectorCollisionValidation.error?.message ?? "Validation failed.")); for (const collision of selectorCollisionValidation.result?.collisions ?? []) { - console.error(`\n${collision.selector}`); + const scope = collision.diamondName ? `${collision.diamondName} / ` : ""; + console.error(`\n${scope}${collision.selector}`); for (const owner of collision.owners) { console.error(` ${owner.facetName}: ${owner.signature}`); console.error(` ${owner.path}`); } } - return ctx; + } + + if (virtualStorageLayoutValidation && !virtualStorageLayoutValidation.success) { + const unsupported = virtualStorageLayoutValidation.result?.unsupported ?? []; + const hasCollisions = (virtualStorageLayoutValidation.result?.collisions.length ?? 0) > 0; + + if (hasCollisions) { + console.error(red("\nValidation failed")); + console.error(red(virtualStorageLayoutValidation.error?.message ?? "Validation failed.")); + } + + if (!hasCollisions && unsupported.length === 0) { + console.error(red("\nValidation failed")); + console.error(red(virtualStorageLayoutValidation.error?.message ?? "Validation failed.")); + } + + if (unsupported.length > 0) { + console.warn(yellow("\nValidation incomplete")); + console.warn(yellow("Storage layout compatibility could not be proven.")); + for (const item of unsupported) { + const scope = item.diamondName ? `${item.diamondName} / ` : ""; + console.warn(yellow(`\n${scope}${item.virtualPath}`)); + if (item.variables.length > 0) { + for (const variable of item.variables) printUncertainStorageVariable(variable); + } else { + for (const record of item.records) { + console.warn(yellow(` ${record.contractName}`)); + console.warn(yellow(` ${record.sourceName}`)); + } + } + } + } + + for (const collision of virtualStorageLayoutValidation.result?.collisions ?? []) { + const scope = collision.diamondName ? `${collision.diamondName} / ` : ""; + console.error(`\n${scope}${collision.virtualPath}`); + if (collision.mismatches.length > 0) { + for (const [index, mismatch] of collision.mismatches.entries()) { + if (index > 0) console.error(" " + "─".repeat(48)); + printStorageVariable(mismatch.left); + console.error(""); + printStorageVariable(mismatch.right); + } + } else { + for (const record of collision.records) printStorageRecord(record); + } + } + } const identifierCollisionValidation = getIdentifierCollisionValidationState(ctx); @@ -83,7 +155,7 @@ export async function showReport(ctx: ComposeContext): Promise { for (const collision of identifierCollisionValidation.result?.collisions ?? []) { console.error(`\n${collision.identifier}`); for (const owner of collision.owners) { - console.error(` ${owner.facetName}: [${owner.layout.join(", ")}]`); + console.error(` ${owner.facetName}`); console.error(` ${owner.path}`); } } @@ -91,3 +163,33 @@ export async function showReport(ctx: ComposeContext): Promise { return ctx; } + +/** Prints the command-level success message after all validation stages pass. */ +export function showSuccess(): void { + console.log(green("\nValidation passed.\n")); +} + +function printStorageVariable(variable: StorageVariableReference): void { + const name = variable.structName + ? `${variable.structName}.${variable.variableName}` + : variable.variableName; + console.error(` ${variable.contractName}: ${name}`); + console.error(` Type: ${variable.typeName}`); + console.error(` Storage path: ${variable.storagePath}`); + console.error(` Source: ${variable.sourceName}`); +} + +function printUncertainStorageVariable(variable: StorageVariableReference): void { + const name = variable.structName + ? `${variable.structName}.${variable.variableName}` + : variable.variableName; + console.warn(yellow(` ${variable.contractName}: ${name}`)); + console.warn(yellow(` Type: ${variable.typeName}`)); + console.warn(yellow(` Storage path: ${variable.storagePath}`)); + console.warn(yellow(` Source: ${variable.sourceName}`)); +} + +function printStorageRecord(record: VirtualStorageLayoutRecord): void { + console.error(` ${record.contractName}: ${record.structName ?? "storage layout"}`); + console.error(` ${record.sourceName}`); +} diff --git a/cli/src/modules/validation/project.ts b/cli/src/modules/validation/project.ts new file mode 100644 index 00000000..5e655053 --- /dev/null +++ b/cli/src/modules/validation/project.ts @@ -0,0 +1,159 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { ComposeContext } from "../../context/types"; +import { findFileAncestor } from "../../utils/files"; +import { ResolvedFacetSource } from "./types"; +import { + ComposeProjectFacet, + resolveComposeProjectFacetSources, + resolveUserProjectFacetSources, + UserProjectFacet, +} from "./projectSourceResolution"; + +type ComposeFacetDefinition = { + contract?: unknown; + package?: unknown; + source?: unknown; +}; + +type ComposeDiamondDefinition = { + contract?: unknown; + facets?: Record; +}; + +type ComposeProjectDefinition = { + framework?: unknown; + diamonds?: Record; +}; + +export type ValidationProject = { + diamonds: ValidationDiamond[]; + diamondSourcePaths: string[]; + facetSources: ResolvedFacetSource[]; +}; + +export type ValidationDiamond = { + name: string; + sourcePath: string; + facets: ResolvedFacetSource[]; +}; + +type PendingDiamond = Omit & { + facetIndexes: number[]; +}; + +type PendingFacet = { + kind: "compose" | "user"; + kindIndex: number; +}; + +/** Loads validation inputs from the nearest Compose project definition. */ +export async function loadValidationProject( + ctx: ComposeContext, +): Promise { + const startDirectory = path.resolve(String(ctx.param.projectRoot ?? process.cwd())); + const composeJsonPath = await findFileAncestor(startDirectory, "compose.json"); + if (!composeJsonPath) { + throw new Error( + "compose.json not found. Run 'compose init' first or navigate to a Compose project directory.", + ); + } + + const composeJson = JSON.parse( + await fs.readFile(composeJsonPath, "utf8"), + ) as ComposeProjectDefinition; + const projectRoot = path.dirname(composeJsonPath); + const configuredFramework = String(composeJson.framework ?? ""); + const framework = String(ctx.param.framework ?? configuredFramework); + if (framework !== "foundry" && framework !== "hardhat") { + throw new Error(`Unsupported framework in compose.json: ${framework || "missing"}.`); + } + + const diamondSourcePaths = new Set(); + const pendingDiamonds: PendingDiamond[] = []; + const pendingFacets: PendingFacet[] = []; + const composeFacets: ComposeProjectFacet[] = []; + const userFacets: UserProjectFacet[] = []; + for (const [diamondName, diamond] of Object.entries(composeJson.diamonds ?? {})) { + const diamondReference = typeof diamond.contract === "string" ? diamond.contract : ""; + const diamondSourcePath = diamondReference.split(":")[0]; + if (!diamondSourcePath) { + throw new Error("Every diamond in compose.json must define its generated contract path."); + } + const resolvedDiamondSourcePath = path.resolve(projectRoot, diamondSourcePath); + const diamondFacetIndexes: number[] = []; + diamondSourcePaths.add(resolvedDiamondSourcePath); + + for (const [facetAlias, facet] of Object.entries(diamond.facets ?? {})) { + const contractReference = typeof facet.contract === "string" ? facet.contract : ""; + const separatorIndex = contractReference.lastIndexOf(":"); + const contractName = separatorIndex >= 0 + ? contractReference.slice(separatorIndex + 1) + : facetAlias; + if (!contractName) continue; + + if (facet.source === "package") { + const packageName = typeof facet.package === "string" ? facet.package : ""; + if (!packageName) { + throw new Error(`Package facet ${contractName} is missing its package name.`); + } + diamondFacetIndexes.push(pendingFacets.length); + pendingFacets.push({ kind: "compose", kindIndex: composeFacets.length }); + composeFacets.push({ contractName, packageName }); + } else { + const contractPath = contractReference.split(":")[0]; + if (!contractPath) { + throw new Error(`Local facet ${contractName} is missing its contract path.`); + } + diamondFacetIndexes.push(pendingFacets.length); + pendingFacets.push({ kind: "user", kindIndex: userFacets.length }); + userFacets.push({ contractName, contractPath }); + } + } + + pendingDiamonds.push({ + name: diamondName, + sourcePath: resolvedDiamondSourcePath, + facetIndexes: diamondFacetIndexes, + }); + } + if (pendingFacets.length === 0) { + throw new Error("compose.json does not define any facets to validate."); + } + + const composeFacetSources = await resolveComposeProjectFacetSources(projectRoot, composeFacets); + const userFacetSources = resolveUserProjectFacetSources(projectRoot, userFacets); + const resolvedFacets = pendingFacets.map((facet) => facet.kind === "compose" + ? composeFacetSources[facet.kindIndex] + : userFacetSources[facet.kindIndex]); + const diamonds = pendingDiamonds.map((diamond) => ({ + name: diamond.name, + sourcePath: diamond.sourcePath, + facets: diamond.facetIndexes.map((index) => resolvedFacets[index]), + })); + const facetSources = [...new Map( + resolvedFacets.map((facet) => [`${facet.sourcePath}:${facet.contractName}`, facet]), + ).values()]; + + ctx.param.projectRoot = projectRoot; + ctx.param.framework = framework; + ctx.config.composeJson = composeJson as Record; + ctx.state.validationProject = { + success: true, + result: { + composeJsonPath, + projectRoot, + framework, + diamonds, + diamondSourcePaths: [...diamondSourcePaths], + facetSources, + }, + error: null, + }; + + return { + diamonds, + diamondSourcePaths: [...diamondSourcePaths], + facetSources, + }; +} diff --git a/cli/src/modules/validation/projectSourceResolution.ts b/cli/src/modules/validation/projectSourceResolution.ts new file mode 100644 index 00000000..b28760d2 --- /dev/null +++ b/cli/src/modules/validation/projectSourceResolution.ts @@ -0,0 +1,78 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { ResolvedFacetSource } from "./types"; + +export type ComposeProjectFacet = { + contractName: string; + packageName: string; +}; + +export type UserProjectFacet = { + contractName: string; + contractPath: string; +}; + +/** Resolves package facets from installed Node or Foundry dependencies. */ +export async function resolveComposeProjectFacetSources( + projectRoot: string, + facets: ComposeProjectFacet[], +): Promise { + return Promise.all( + facets.map(async (facet) => ({ + contractName: facet.contractName, + sourcePath: await findPackageFacetSource( + projectRoot, + facet.packageName, + facet.contractName, + ), + })), + ); +} + +/** Resolves user-specific facets from compose.json contract references. */ +export function resolveUserProjectFacetSources( + projectRoot: string, + facets: UserProjectFacet[], +): ResolvedFacetSource[] { + return facets.map((facet) => ({ + contractName: facet.contractName, + sourcePath: path.resolve(projectRoot, facet.contractPath), + })); +} + +async function findPackageFacetSource( + projectRoot: string, + packageName: string, + facetName: string, +): Promise { + const candidates = [ + path.join(projectRoot, "node_modules", packageName), + path.join(projectRoot, "lib", "Compose", "src"), + ]; + const matches: string[] = []; + + for (const candidate of candidates) { + try { + const entries = await fs.readdir(candidate, { recursive: true, withFileTypes: true }); + for (const entry of entries) { + if (entry.isFile() && entry.name === `${facetName}.sol`) { + matches.push(path.join(entry.parentPath, entry.name)); + } + } + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error; + } + } + + const uniqueMatches = [...new Set(matches.map((match) => path.resolve(match)))]; + if (uniqueMatches.length === 0) { + throw new Error(`Package facet source not found: ${packageName}/${facetName}.sol`); + } + if (uniqueMatches.length > 1) { + throw new Error( + `Package facet source is ambiguous: ${packageName}/${facetName}.sol\n${uniqueMatches.join("\n")}`, + ); + } + + return uniqueMatches[0]; +} diff --git a/cli/src/modules/validation/sourceResolution.ts b/cli/src/modules/validation/sourceResolution.ts new file mode 100644 index 00000000..c7365ff3 --- /dev/null +++ b/cli/src/modules/validation/sourceResolution.ts @@ -0,0 +1,42 @@ +import { IFrameworkAdapter } from "../../adapters/interface/IFrameworkAdapter"; +import { ComposeContext, ModuleState } from "../../context/types"; +import { ScaffoldMapEntry } from "../scaffolding/types"; +import { ResolvedFacetSource, ResolvedFacetSourceResult } from "./types"; + +type FacetOrigin = ScaffoldMapEntry["origin"]; + +/** Resolves one scaffold-map origin into framework compilation paths. */ +export async function resolveFacetSources( + ctx: ComposeContext, + adapter: IFrameworkAdapter, + origin: FacetOrigin, +): Promise { + const scaffoldMap = ctx.state.scaffoldMap as + | ModuleState<{ entries: ScaffoldMapEntry[] }> + | undefined; + const entries = scaffoldMap?.result?.entries ?? []; + + return Promise.all( + entries + .filter((entry) => entry.origin === origin) + .map(async (entry) => ({ + contractName: entry.contractName, + sourcePath: await adapter.resolveSoliditySourcePath(ctx, entry.targetPath), + })), + ); +} + +/** Reads and merges package and project facet compilation inputs. */ +export function getResolvedFacetSources(ctx: ComposeContext): ResolvedFacetSource[] { + const composeSources = ctx.state.validationComposeFacetSources as + | ModuleState + | undefined; + const projectSources = ctx.state.validationProjectFacetSources as + | ModuleState + | undefined; + + return [ + ...(composeSources?.result?.sources ?? []), + ...(projectSources?.result?.sources ?? []), + ]; +} diff --git a/cli/src/modules/validation/state.ts b/cli/src/modules/validation/state.ts index 3962a2f0..dd70530e 100644 --- a/cli/src/modules/validation/state.ts +++ b/cli/src/modules/validation/state.ts @@ -5,6 +5,7 @@ import { SelectorExportValidationResult, SelectorCollisionValidationResult, IdentifierCollisionValidationResult, + VirtualStorageLayoutResult, } from "./types"; /** @@ -67,3 +68,12 @@ export function getIdentifierCollisionValidationState( ctx.state.validationIdentifierCollisions as ModuleState | undefined ) ?? null; } + +/** Returns the source-derived virtual storage layout validation state. */ +export function getVirtualStorageLayoutValidationState( + ctx: ComposeContext, +): ModuleState | null { + return ( + ctx.state.validationVirtualStorageLayout as ModuleState | undefined + ) ?? null; +} diff --git a/cli/src/modules/validation/types.ts b/cli/src/modules/validation/types.ts index 673a6e8a..27cf295f 100644 --- a/cli/src/modules/validation/types.ts +++ b/cli/src/modules/validation/types.ts @@ -11,13 +11,26 @@ export type FacetScanResult = { path: string; functions: FunctionInfo[]; exportedSelectors: string[]; + hasExportSelectorsFunction?: boolean; missingExports: string[]; extraExports: string[]; storageLayouts: StorageLayoutInfo[]; + warnings: string[]; }; export type FacetScanResultCollection = { - facets: FacetScanWarning[]; + facets: FacetScanResult[]; +}; + +export type FacetReference = { + contractName: string; + sourcePath: string; +}; + +export type ResolvedFacetSource = FacetReference; + +export type ResolvedFacetSourceResult = { + sources: ResolvedFacetSource[]; }; export type FacetScanStateResult = { @@ -28,6 +41,7 @@ export type FacetScanStateResult = { export type SelectorExportIssue = { facetName: string; path: string; + missingExportSelectorsFunction: boolean; missingExports: string[]; extraExports: string[]; }; @@ -42,10 +56,17 @@ export type SelectorOwner = { export type SelectorCollision = { selector: string; owners: SelectorOwner[]; + diamondName?: string; +}; + +export type DiamondValidationScope = { + diamondName: string; + facets: FacetReference[]; }; export type SelectorCollisionDeps = { hashing: IHashingAdapter; + scopes?: DiamondValidationScope[]; }; export type StorageLayoutInfo = { @@ -89,3 +110,72 @@ export type FacetScanWarning = { path: string; warnings: string[]; }; + +export type VirtualStorageLayoutKind = "normal" | "immutable"; + +export type VirtualStorageLayoutSource = + | "erc8042" + | "erc7201" + | "slot-assignment" + | "implicit-state"; + +export type VirtualStorageLayoutRecord = { + id: string; + virtualPath: string; + kind: VirtualStorageLayoutKind; + codeWidth: 1; + layout: string[]; + serializedLayout: string[]; + slots: number[][]; + source: VirtualStorageLayoutSource; + sourceName: string; + contractName: string; + structName: string | null; + diamondName?: string; +}; + +export type VirtualStorageLayoutWarning = { + sourceName: string; + message: string; + contractName?: string; + storagePath?: string; + diamondName?: string; +}; + +export type StorageVariableReference = { + contractName: string; + structName: string | null; + variableName: string; + typeName: string; + storagePath: string; + sourceName: string; +}; + +export type VirtualStorageLayoutCollision = { + id: string; + virtualPath: string; + reason: string; + records: VirtualStorageLayoutRecord[]; + mismatches: Array<{ + position: number; + left: StorageVariableReference; + right: StorageVariableReference; + }>; + diamondName?: string; +}; + +export type UnsupportedVirtualStorageLayout = { + id: string; + virtualPath: string; + reason: string; + records: VirtualStorageLayoutRecord[]; + variables: StorageVariableReference[]; + diamondName?: string; +}; + +export type VirtualStorageLayoutResult = { + records: VirtualStorageLayoutRecord[]; + warnings: VirtualStorageLayoutWarning[]; + collisions: VirtualStorageLayoutCollision[]; + unsupported: UnsupportedVirtualStorageLayout[]; +}; diff --git a/cli/src/modules/validation/validators.ts b/cli/src/modules/validation/validators.ts index 2a96659f..d1e39e01 100644 --- a/cli/src/modules/validation/validators.ts +++ b/cli/src/modules/validation/validators.ts @@ -20,10 +20,16 @@ export function findSelectorExportIssues(facets: FacetScanResult[]): SelectorExp .map((facet) => ({ facetName: facet.facetName, path: facet.path, + missingExportSelectorsFunction: facet.hasExportSelectorsFunction === false, missingExports: facet.missingExports, extraExports: facet.extraExports, })) - .filter((issue) => issue.missingExports.length > 0 || issue.extraExports.length > 0); + .filter( + (issue) => + issue.missingExportSelectorsFunction || + issue.missingExports.length > 0 || + issue.extraExports.length > 0, + ); } /** diff --git a/cli/src/modules/validation/virtualStorageLayout.ts b/cli/src/modules/validation/virtualStorageLayout.ts new file mode 100644 index 00000000..d3e20bf2 --- /dev/null +++ b/cli/src/modules/validation/virtualStorageLayout.ts @@ -0,0 +1,1305 @@ +import { keccak256, stringToBytes, toHex } from "viem"; +import { SolidityAstSource } from "../../adapters/interface/IFrameworkAdapter"; +import { matchesAstSource } from "./astIdentity"; +import { + DiamondValidationScope, + FacetReference, + StorageVariableReference, + UnsupportedVirtualStorageLayout, + VirtualStorageLayoutCollision, + VirtualStorageLayoutRecord, + VirtualStorageLayoutResult, + VirtualStorageLayoutSource, + VirtualStorageLayoutWarning, +} from "./types"; + +/** Builds independent virtual storage layouts for each diamond. */ +export function buildScopedVirtualStorageLayout( + sources: SolidityAstSource[], + scopes: DiamondValidationScope[], +): VirtualStorageLayoutResult { + const results = scopes.map((scope) => { + const result = buildVirtualStorageLayout(sources, scope.facets); + const records = result.records.map((record) => ({ + ...record, + diamondName: scope.diamondName, + })); + + return { + records, + warnings: result.warnings.map((warning) => ({ + ...warning, + diamondName: scope.diamondName, + })), + collisions: result.collisions.map((collision) => ({ + ...collision, + diamondName: scope.diamondName, + records: collision.records.map((record) => ({ + ...record, + diamondName: scope.diamondName, + })), + })), + unsupported: result.unsupported.map((unsupported) => ({ + ...unsupported, + diamondName: scope.diamondName, + records: unsupported.records.map((record) => ({ + ...record, + diamondName: scope.diamondName, + })), + })), + }; + }); + + return { + records: results.flatMap((result) => result.records), + warnings: results.flatMap((result) => result.warnings), + collisions: results.flatMap((result) => result.collisions), + unsupported: results.flatMap((result) => result.unsupported), + }; +} + +type AstNode = Record & { + id?: number; + nodeType: string; +}; + +type ContainerKind = "mapping" | "dynamic-array" | "fixed-array"; + +type ChildLayout = { + slot: number; + structId: number; + containerKind: ContainerKind; + storagePath: string; +}; + +type TypeAnalysis = { + layout: string[]; + origins: Array; + packBits: number[]; + slotGroups: number[][]; + children: ChildLayout[]; + warnings: TypeAnalysisWarning[]; + boundaryBefore: boolean; + boundaryAfter: boolean; +}; + +type TypeAnalysisWarning = { + storagePath: string; + message: string; +}; + +type StorageVariableOrigin = Omit; + +type AstIndex = { + nodesById: Map; + contractsById: Map; + ownerContractByNodeId: Map; + sourceByNodeId: Map; + initialValueByDeclarationId: Map; +}; + +type StorageRoot = { + id: string; + source: "erc8042" | "erc7201" | "slot-assignment" | "implicit-state"; + sourceName: string; + contractName: string; + structName: string | null; + structId?: number; + fields?: AstNode[]; +}; + +type StorageScope = { + completeContractIds: Set; + routineIds: Set; + referencedDeclarationIds: Set; + rootOwnerIds: Set; +}; + +const CODE = { + bool: "0x01", + enum: "0x02", + address: "0x03", + externalFunction: "0x70", + internalFunction: "0x71", + bytes: "0x72", + string: "0x73", + mapping: "0xf1", + dynamicArray: "0xf2", + fixedArray: "0xf3", + struct: "0xf4", + unknown: "0xfe", + end: "0xff", +} as const; + +const originsByRecord = new WeakMap< + VirtualStorageLayoutRecord, + Array +>(); + +/** Builds compact virtual storage records and detects source-side collisions. */ +export function buildVirtualStorageLayout( + sources: SolidityAstSource[], + facets: FacetReference[], +): VirtualStorageLayoutResult { + const index = buildAstIndex(sources); + const warnings: VirtualStorageLayoutWarning[] = []; + const roots: StorageRoot[] = []; + for (const facet of facets) { + const contract = resolveFacetContract(index, facet); + const contractId = Number(contract.id); + const storageScope = traceFacetStorageScope(index, [contractId]); + const facetRoots = [ + ...findExplicitRoots(index, storageScope), + ...findImplicitRoots(index, [contract]), + ]; + roots.push(...facetRoots); + + if (facetRoots.length === 0) { + warnings.push({ + sourceName: sourceNameFor(contract, index), + message: `${facet.contractName}: no storage pattern found; storage validation skipped for this facet.`, + }); + } + } + const records: VirtualStorageLayoutRecord[] = []; + const seenRoots = new Set(); + + for (const root of roots) { + const rootKey = `${root.id}:${root.sourceName}:${root.structId ?? root.contractName}`; + if (seenRoots.has(rootKey)) continue; + seenRoots.add(rootKey); + + const emitted = emitRecord({ + id: deriveStorageRootId(root.id, root.source), + virtualPath: root.id, + kind: "normal", + fields: root.fields ?? structMembers(root.structId, index), + root, + layoutStructName: root.structName, + storagePath: root.structName ?? root.contractName, + index, + seen: new Set(), + }); + records.push(...emitted.records); + warnings.push(...emitted.warnings); + } + + const comparison = compareVirtualStorageLayouts(records); + return { + records, + warnings, + ...comparison, + }; +} + +/** Finds incompatible records that share the same virtual storage id. */ +export function findVirtualStorageLayoutCollisions( + records: VirtualStorageLayoutRecord[], +): VirtualStorageLayoutCollision[] { + return compareVirtualStorageLayouts(records).collisions; +} + +/** Finds shared layouts whose compatibility cannot be proven from known type codes. */ +export function findUnsupportedVirtualStorageLayouts( + records: VirtualStorageLayoutRecord[], +): UnsupportedVirtualStorageLayout[] { + return compareVirtualStorageLayouts(records).unsupported; +} + +function compareVirtualStorageLayouts( + records: VirtualStorageLayoutRecord[], +): Pick { + const recordsById = new Map(); + for (const record of records) { + const owners = recordsById.get(record.id) ?? []; + owners.push(record); + recordsById.set(record.id, owners); + } + + const collisions: VirtualStorageLayoutCollision[] = []; + const unsupported: UnsupportedVirtualStorageLayout[] = []; + for (const [id, owners] of recordsById) { + if (owners.length <= 1) continue; + + const kinds = new Set(owners.map((record) => record.kind)); + if (kinds.size > 1) { + collisions.push({ + id, + virtualPath: owners[0].virtualPath, + reason: "mixed normal and immutable records", + records: owners, + mismatches: findVariableMismatches(owners), + }); + continue; + } + + const compatibility = kinds.has("immutable") + ? compareImmutableRecords(owners) + : comparePrefixLayouts(owners.map((record) => record.layout)); + + if (compatibility === "collision") { + collisions.push({ + id, + virtualPath: owners[0].virtualPath, + reason: kinds.has("immutable") + ? "immutable layout changed" + : "normal layout is not append-only compatible", + records: owners, + mismatches: findVariableMismatches(owners), + }); + } else if (compatibility === "unsupported") { + unsupported.push({ + id, + virtualPath: owners[0].virtualPath, + reason: "layout contains an unknown storage type", + records: owners, + variables: findUnsupportedVariables(owners), + }); + } + } + + return { collisions, unsupported }; +} + +function emitRecord(options: { + id: string; + virtualPath: string; + kind: VirtualStorageLayoutRecord["kind"]; + fields: AstNode[]; + root: StorageRoot; + layoutStructName: string | null; + storagePath: string; + index: AstIndex; + seen: Set; +}): { records: VirtualStorageLayoutRecord[]; warnings: VirtualStorageLayoutWarning[] } { + const analysis = analyzeFields( + options.fields, + options.index, + options.layoutStructName, + options.storagePath, + ); + const record: VirtualStorageLayoutRecord = { + id: options.id, + virtualPath: options.virtualPath, + kind: options.kind, + codeWidth: 1, + layout: analysis.layout, + serializedLayout: ["0x01", ...analysis.layout], + slots: analysis.slotGroups, + source: options.root.source, + sourceName: options.root.sourceName, + contractName: options.root.contractName, + structName: options.root.structName, + }; + originsByRecord.set(record, analysis.origins); + const records = [record]; + const warnings = analysis.warnings.map((warning) => ({ + sourceName: options.root.sourceName, + contractName: options.root.contractName, + storagePath: warning.storagePath, + message: warning.message, + })); + + for (const child of analysis.children) { + const childPath = buildChildPath(options.virtualPath, child.slot); + const childId = hashVirtualPath(childPath); + const cycleKey = `${childId}:${child.structId}`; + if (options.seen.has(cycleKey)) continue; + + const emitted = emitRecord({ + id: childId, + virtualPath: childPath, + kind: child.containerKind === "mapping" ? "normal" : "immutable", + fields: structMembers(child.structId, options.index), + root: options.root, + layoutStructName: stringValue(options.index.nodesById.get(child.structId)?.name) || null, + storagePath: child.storagePath, + index: options.index, + seen: new Set([...options.seen, cycleKey]), + }); + records.push(...emitted.records); + warnings.push(...emitted.warnings); + } + + return { records, warnings }; +} + +function analyzeFields( + fields: AstNode[], + index: AstIndex, + structName: string | null, + storagePath: string, +): TypeAnalysis { + const layout: string[] = []; + const origins: Array = []; + const slotGroups: number[][] = []; + const children: ChildLayout[] = []; + const warnings: TypeAnalysisWarning[] = []; + let currentSlot: number[] = []; + let usedBits = 0; + + const flushSlot = (): void => { + if (currentSlot.length === 0) return; + slotGroups.push(currentSlot); + currentSlot = []; + usedBits = 0; + }; + + for (const field of fields) { + const fieldName = stringValue(field.name) || ""; + const fieldPath = `${storagePath}.${fieldName}`; + const analysis = analyzeType(astNode(field.typeName), index, { storagePath: fieldPath }); + layout.push(...analysis.layout); + const origin = storageVariableOrigin(field, index, structName, fieldPath); + origins.push(...analysis.origins.map((item) => item ?? origin)); + warnings.push(...analysis.warnings); + + const startsNewSlot = + analysis.boundaryBefore || + analysis.slotGroups.length > 0 || + analysis.packBits.some((bits) => bits === 256); + if (startsNewSlot) flushSlot(); + + const fieldStartSlot = slotGroups.length; + children.push( + ...analysis.children.map((child) => ({ + ...child, + slot: fieldStartSlot + child.slot, + })), + ); + + if (analysis.slotGroups.length > 0) { + slotGroups.push(...analysis.slotGroups.map((slot) => [...slot])); + currentSlot = []; + usedBits = 0; + continue; + } + + for (const bits of analysis.packBits) { + if (bits === 256) { + flushSlot(); + slotGroups.push([256]); + continue; + } + if (usedBits + bits > 256) flushSlot(); + currentSlot.push(bits); + usedBits += bits; + if (usedBits === 256) flushSlot(); + } + + if (analysis.boundaryAfter) flushSlot(); + } + + flushSlot(); + return { + layout, + origins, + packBits: [], + slotGroups, + children, + warnings, + boundaryBefore: false, + boundaryAfter: false, + }; +} + +function analyzeType( + type: AstNode | null, + index: AstIndex, + options: { + storagePath: string; + insideContainer?: boolean; + containerKind?: ContainerKind; + }, +): TypeAnalysis { + if (!type) return unknownType(options.storagePath, "missing AST type node"); + + if (type.nodeType === "Mapping") { + const key = scalarType(astNode(type.keyType), index); + const value = analyzeType(astNode(type.valueType), index, { + storagePath: `${options.storagePath}[key]`, + insideContainer: true, + containerKind: "mapping", + }); + return { + layout: [CODE.mapping, key.code, ...ensureEnd(value.layout)], + origins: [null, null, ...ensureEndOrigins(value)], + packBits: [], + slotGroups: [[256]], + children: value.children, + warnings: [ + ...key.warnings.map((message) => ({ + storagePath: `${options.storagePath}[key]`, + message, + })), + ...value.warnings, + ], + boundaryBefore: false, + boundaryAfter: false, + }; + } + + if (type.nodeType === "ArrayTypeName") { + const lengthNode = astNode(type.length); + const fixedLength = lengthNode ? Number(lengthNode.value) : null; + const containerKind: ContainerKind = fixedLength === null ? "dynamic-array" : "fixed-array"; + const element = analyzeType(astNode(type.baseType), index, { + storagePath: `${options.storagePath}[index]`, + insideContainer: true, + containerKind, + }); + const prefix = fixedLength === null + ? [CODE.dynamicArray] + : [CODE.fixedArray, ...encodeFixedArrayLength(fixedLength)]; + + if (fixedLength === null) { + return { + layout: [...prefix, ...ensureEnd(element.layout)], + origins: [...prefix.map(() => null), ...ensureEndOrigins(element)], + packBits: [], + slotGroups: [[256]], + children: element.children, + warnings: element.warnings, + boundaryBefore: false, + boundaryAfter: false, + }; + } + + return { + layout: [...prefix, ...ensureEnd(element.layout)], + origins: [...prefix.map(() => null), ...ensureEndOrigins(element)], + packBits: [], + slotGroups: repeatFixedArraySlots(fixedLength, element), + children: element.children, + warnings: element.warnings, + boundaryBefore: true, + boundaryAfter: true, + }; + } + + const declaration = referencedDeclaration(type, index); + if (declaration?.nodeType === "StructDefinition") { + const nested = analyzeFields( + childNodes(declaration, "members"), + index, + stringValue(declaration.name) || null, + options.storagePath, + ); + if (options.insideContainer) { + return { + layout: [CODE.end], + origins: [null], + packBits: [], + slotGroups: nested.slotGroups, + children: [{ + slot: 0, + structId: Number(declaration.id), + containerKind: options.containerKind ?? "mapping", + storagePath: options.storagePath, + }], + warnings: nested.warnings, + boundaryBefore: false, + boundaryAfter: false, + }; + } + + return { + layout: [CODE.struct, ...nested.layout, CODE.end], + origins: [null, ...nested.origins, null], + packBits: [], + slotGroups: nested.slotGroups, + children: nested.children, + warnings: nested.warnings, + boundaryBefore: true, + boundaryAfter: true, + }; + } + + const scalar = scalarType(type, index); + return { + layout: [scalar.code], + origins: [null], + packBits: scalar.bits ? [scalar.bits] : [], + slotGroups: scalar.wholeSlot ? [[256]] : [], + children: [], + warnings: scalar.warnings.map((message) => ({ + storagePath: options.storagePath, + message, + })), + boundaryBefore: false, + boundaryAfter: false, + }; +} + +function scalarType( + type: AstNode | null, + index: AstIndex, +): { code: string; bits?: number; wholeSlot?: boolean; warnings: string[] } { + if (!type) return { code: CODE.unknown, wholeSlot: true, warnings: ["missing AST type node"] }; + + const declaration = referencedDeclaration(type, index); + if (declaration?.nodeType === "UserDefinedValueTypeDefinition") { + return scalarType(astNode(declaration.underlyingType), index); + } + if (declaration?.nodeType === "EnumDefinition") { + return { code: CODE.enum, bits: 8, warnings: [] }; + } + if (declaration?.nodeType === "ContractDefinition") { + return { code: CODE.address, bits: 160, warnings: [] }; + } + + if (type.nodeType === "FunctionTypeName") { + if (type.visibility === "external") { + return { code: CODE.externalFunction, bits: 192, warnings: [] }; + } + return { + code: CODE.internalFunction, + warnings: ["internal function storage type uses compiler-specific representation"], + }; + } + + const name = stringValue(type.name); + if (name === "bool") return { code: CODE.bool, bits: 8, warnings: [] }; + if (name === "address" || name === "address payable") { + return { code: CODE.address, bits: 160, warnings: [] }; + } + if (name === "bytes") return { code: CODE.bytes, wholeSlot: true, warnings: [] }; + if (name === "string") return { code: CODE.string, wholeSlot: true, warnings: [] }; + if (name === "byte") return { code: "0x50", bits: 8, warnings: [] }; + + const uintBits = numericSuffix(name, "uint"); + if (uintBits !== null) return numericTypeCode(0x10, uintBits); + const intBits = numericSuffix(name, "int"); + if (intBits !== null) return numericTypeCode(0x30, intBits); + const bytesSize = numericSuffix(name, "bytes", false); + if (bytesSize !== null && bytesSize >= 1 && bytesSize <= 32) { + return { code: hexByte(0x50 + bytesSize - 1), bits: bytesSize * 8, warnings: [] }; + } + + return { + code: CODE.unknown, + wholeSlot: true, + warnings: [`unsupported storage type: ${name || type.nodeType}`], + }; +} + +function findExplicitRoots(index: AstIndex, scope: StorageScope): StorageRoot[] { + const roots: StorageRoot[] = []; + + for (const contractId of scope.rootOwnerIds) { + const contract = index.contractsById.get(contractId); + if (!contract) continue; + const contractName = stringValue(contract.name); + const sourceName = sourceNameFor(contract, index); + + for (const node of childNodes(contract, "nodes")) { + if (node.nodeType === "StructDefinition") { + const annotation = storageAnnotation(node.documentation); + const declarationId = typeof node.id === "number" ? node.id : null; + const belongsToCompleteContract = scope.completeContractIds.has(contractId); + const isReferenced = declarationId !== null && scope.referencedDeclarationIds.has(declarationId); + if (annotation && declarationId !== null && (belongsToCompleteContract || isReferenced)) { + roots.push({ + id: annotation.id, + source: annotation.standard, + sourceName, + contractName, + structName: stringValue(node.name), + structId: node.id, + }); + } + } + + if ( + node.nodeType === "FunctionDefinition" && + typeof node.id === "number" && + scope.routineIds.has(node.id) + ) { + const root = slotAssignmentRoot(node, contract, index); + if (root) roots.push(root); + } + } + } + + return roots; +} + +function findImplicitRoots(index: AstIndex, contracts: AstNode[]): StorageRoot[] { + const roots: StorageRoot[] = []; + + for (const contract of contracts) { + const fields: AstNode[] = []; + const linearized = numberArray(contract.linearizedBaseContracts); + const contractIds = linearized.length > 0 + ? [...linearized].reverse() + : typeof contract.id === "number" + ? [contract.id] + : []; + + for (const contractId of contractIds) { + const current = index.contractsById.get(contractId); + if (!current) continue; + fields.push( + ...childNodes(current, "nodes").filter( + (node) => + node.nodeType === "VariableDeclaration" && + node.stateVariable === true && + node.constant !== true && + node.mutability !== "immutable", + ), + ); + } + + if (fields.length > 0) { + roots.push({ + id: "0x0", + source: "implicit-state", + sourceName: sourceNameFor(contract, index), + contractName: stringValue(contract.name), + structName: null, + fields, + }); + } + } + + return roots; +} + +function slotAssignmentRoot( + fn: AstNode, + contract: AstNode, + index: AstIndex, +): StorageRoot | null { + const returnParameter = childNodes(astNode(fn.returnParameters), "parameters").find( + (parameter) => parameter.storageLocation === "storage", + ); + if (!returnParameter) return null; + + const struct = referencedDeclaration(astNode(returnParameter.typeName), index); + if (struct?.nodeType !== "StructDefinition" || typeof struct.id !== "number") return null; + + let namespace: string | null = null; + walkAst(fn.body, (node) => { + if (namespace || node.nodeType !== "InlineAssembly") return; + const externalReferences = Array.isArray(node.externalReferences) + ? node.externalReferences.filter(isRecord) + : []; + + walkAst(node.AST, (yulNode) => { + if (namespace || yulNode.nodeType !== "YulAssignment") return; + const targets = childNodes(yulNode, "variableNames"); + const expectedTarget = `${stringValue(returnParameter.name)}.slot`; + if (!targets.some((target) => target.name === expectedTarget)) return; + + const value = astNode(yulNode.value); + const reference = externalReferences.find((candidate) => candidate.src === value?.src); + const declarationId = typeof reference?.declaration === "number" ? reference.declaration : null; + if (declarationId !== null) { + namespace = resolveNamespaceDeclaration(declarationId, index, new Set()); + } + }); + }); + + return namespace + ? { + id: namespace, + source: "slot-assignment", + sourceName: sourceNameFor(contract, index), + contractName: stringValue(contract.name), + structName: stringValue(struct.name), + structId: struct.id, + } + : null; +} + +function resolveNamespaceDeclaration( + declarationId: number, + index: AstIndex, + seen: Set, +): string | null { + if (seen.has(declarationId)) return null; + seen.add(declarationId); + + const declaration = index.nodesById.get(declarationId); + const expression = index.initialValueByDeclarationId.get(declarationId) ?? astNode(declaration?.value); + return resolveNamespaceExpression(expression, index, seen); +} + +function resolveNamespaceExpression( + expression: AstNode | null, + index: AstIndex, + seen: Set, +): string | null { + if (!expression) return null; + if (expression.nodeType === "Identifier" && typeof expression.referencedDeclaration === "number") { + return resolveNamespaceDeclaration(expression.referencedDeclaration, index, seen); + } + if (expression.nodeType === "Literal") { + return stringValue(expression.value) || stringValue(expression.hexValue); + } + if (expression.nodeType === "FunctionCall") { + const fn = astNode(expression.expression); + const args = childNodes(expression, "arguments"); + if (fn?.nodeType === "Identifier" && fn.name === "keccak256" && args.length === 1) { + return resolveNamespaceExpression(args[0], index, seen); + } + if (expression.kind === "typeConversion" && args.length === 1) { + return resolveNamespaceExpression(args[0], index, seen); + } + } + return null; +} + +function buildAstIndex(sources: SolidityAstSource[]): AstIndex { + const nodesById = new Map(); + const contractsById = new Map(); + const ownerContractByNodeId = new Map(); + const sourceByNodeId = new Map(); + const initialValueByDeclarationId = new Map(); + + for (const source of sources) { + walkAstWithContract(source.ast, null, (node, ownerContractId) => { + if (typeof node.id === "number") { + nodesById.set(node.id, node); + sourceByNodeId.set(node.id, source.sourceName); + if (node.nodeType === "ContractDefinition") contractsById.set(node.id, node); + if (ownerContractId !== null) ownerContractByNodeId.set(node.id, ownerContractId); + } + + if (node.nodeType === "VariableDeclarationStatement") { + const initialValue = astNode(node.initialValue); + for (const declaration of childNodes(node, "declarations")) { + if (initialValue && typeof declaration.id === "number") { + initialValueByDeclarationId.set(declaration.id, initialValue); + } + } + } + }); + } + + return { + nodesById, + contractsById, + ownerContractByNodeId, + sourceByNodeId, + initialValueByDeclarationId, + }; +} + +function traceFacetStorageScope(index: AstIndex, facetContractIds: number[]): StorageScope { + const completeContractIds = new Set(); + const routineIds = new Set(); + const referencedDeclarationIds = new Set(); + const rootOwnerIds = new Set(); + const storageExecutionOwnerIds = new Set(); + const routineQueue: number[] = []; + + for (const contractId of facetContractIds) { + const contract = index.contractsById.get(contractId); + if (!contract) continue; + + const lineage = numberArray(contract.linearizedBaseContracts); + const lineageIds = lineage.length > 0 + ? lineage + : typeof contract.id === "number" + ? [contract.id] + : []; + lineageIds.forEach((contractId) => { + completeContractIds.add(contractId); + rootOwnerIds.add(contractId); + storageExecutionOwnerIds.add(contractId); + }); + + for (const routineId of facetRuntimeEntryIds(lineageIds, index)) { + routineQueue.push(routineId); + } + } + + while (routineQueue.length > 0) { + const routineId = routineQueue.shift(); + if (routineId === undefined || routineIds.has(routineId)) continue; + + const routine = index.nodesById.get(routineId); + if (!routine || !isRoutine(routine)) continue; + routineIds.add(routineId); + + const routineOwnerId = index.ownerContractByNodeId.get(routineId); + if (routineOwnerId !== undefined) rootOwnerIds.add(routineOwnerId); + + walkAst(routine, (node) => { + const declarationId = typeof node.referencedDeclaration === "number" + ? node.referencedDeclaration + : null; + if (declarationId !== null && declarationId >= 0) { + referencedDeclarationIds.add(declarationId); + const declarationOwnerId = index.ownerContractByNodeId.get(declarationId); + if (declarationOwnerId !== undefined) rootOwnerIds.add(declarationOwnerId); + } + + if (node.nodeType === "ModifierInvocation") { + enqueueModifier(node, index, routineQueue); + } + if (node.nodeType === "FunctionCall") { + enqueueStorageContextCall( + node, + routineOwnerId, + completeContractIds, + storageExecutionOwnerIds, + index, + routineQueue, + ); + } + }); + } + + return { + completeContractIds, + routineIds, + referencedDeclarationIds, + rootOwnerIds, + }; +} + +function resolveFacetContract(index: AstIndex, facet: FacetReference): AstNode { + const matches = [...index.contractsById.values()].filter((contract) => + contract.name === facet.contractName && + typeof contract.id === "number" && + matchesAstSource(sourceNameFor(contract, index), facet.sourcePath)); + const identity = `${facet.sourcePath}:${facet.contractName}`; + + if (matches.length !== 1) { + throw new Error( + matches.length === 0 + ? `Facet contract not found in Solidity AST: ${identity}` + : `Facet contract is ambiguous in Solidity AST: ${identity}`, + ); + } + + return matches[0]; +} + +function facetRuntimeEntryIds(contractIds: number[], index: AstIndex): number[] { + const selectedIds: number[] = []; + const claimedSignatures = new Set(); + + for (const contractId of contractIds) { + const contract = index.contractsById.get(contractId); + if (!contract) continue; + + for (const node of childNodes(contract, "nodes")) { + if (node.nodeType !== "FunctionDefinition" || typeof node.id !== "number") continue; + if (!isRuntimeEntry(node)) continue; + + const signature = routineDispatchKey(node); + if (claimedSignatures.has(signature)) continue; + claimedSignatures.add(signature); + selectedIds.push(node.id); + } + } + + return selectedIds; +} + +function enqueueModifier(node: AstNode, index: AstIndex, queue: number[]): void { + const modifierName = astNode(node.modifierName); + const declarationId = typeof modifierName?.referencedDeclaration === "number" + ? modifierName.referencedDeclaration + : null; + if (declarationId === null) return; + + const declaration = index.nodesById.get(declarationId); + if (declaration?.nodeType === "ModifierDefinition") queue.push(declarationId); +} + +function enqueueStorageContextCall( + call: AstNode, + callerOwnerId: number | undefined, + selectedContractIds: Set, + storageExecutionOwnerIds: Set, + index: AstIndex, + queue: number[], +): void { + const expression = unwrapCallExpression(astNode(call.expression)); + const declarationId = typeof expression?.referencedDeclaration === "number" + ? expression.referencedDeclaration + : null; + if (declarationId === null || declarationId < 0) return; + + const declaration = index.nodesById.get(declarationId); + if (declaration?.nodeType !== "FunctionDefinition") return; + + const calleeOwnerId = index.ownerContractByNodeId.get(declarationId); + if (calleeOwnerId === undefined) { + queue.push(declarationId); + return; + } + + const calleeOwner = index.contractsById.get(calleeOwnerId); + const sharesStorageContext = + calleeOwnerId === callerOwnerId || + selectedContractIds.has(calleeOwnerId) || + storageExecutionOwnerIds.has(calleeOwnerId) || + calleeOwner?.contractKind === "library"; + if (sharesStorageContext) { + storageExecutionOwnerIds.add(calleeOwnerId); + queue.push(declarationId); + } +} + +function unwrapCallExpression(node: AstNode | null): AstNode | null { + let current = node; + while (current?.nodeType === "FunctionCallOptions") { + current = astNode(current.expression); + } + return current; +} + +function isRuntimeEntry(node: AstNode): boolean { + return node.kind === "fallback" || + node.kind === "receive" || + node.visibility === "public" || + node.visibility === "external"; +} + +function isRoutine(node: AstNode): boolean { + return node.nodeType === "FunctionDefinition" || node.nodeType === "ModifierDefinition"; +} + +function routineDispatchKey(node: AstNode): string { + if (typeof node.functionSelector === "string") return node.functionSelector; + if (node.kind === "fallback" || node.kind === "receive") return stringValue(node.kind); + const parameters = childNodes(astNode(node.parameters), "parameters") + .map((parameter) => { + const descriptions = astNode(parameter.typeDescriptions); + return stringValue(descriptions?.typeIdentifier) || stringValue(descriptions?.typeString); + }); + return `${stringValue(node.name)}(${parameters.join(",")})`; +} + +function repeatFixedArraySlots(length: number, element: TypeAnalysis): number[][] { + if (!Number.isInteger(length) || length <= 0) return []; + if (element.slotGroups.length > 0) { + return Array.from({ length }, () => element.slotGroups.map((group) => [...group])).flat(); + } + if (element.packBits.length === 0) return [[256]]; + + const groups: number[][] = []; + let current: number[] = []; + let usedBits = 0; + const flush = (): void => { + if (current.length === 0) return; + groups.push(current); + current = []; + usedBits = 0; + }; + + for (let index = 0; index < length; index++) { + for (const bits of element.packBits) { + if (bits === 256) { + flush(); + groups.push([256]); + } else { + if (usedBits + bits > 256) flush(); + current.push(bits); + usedBits += bits; + if (usedBits === 256) flush(); + } + } + } + flush(); + return groups; +} + +function encodeFixedArrayLength(length: number): string[] { + if (!Number.isInteger(length) || length <= 0) return ["0x00"]; + let hex = length.toString(16); + if (hex.length % 2 === 1) hex = `0${hex}`; + const bytes = hex.match(/.{2}/g) ?? ["00"]; + return [hexByte(bytes.length), ...bytes.map((byte) => `0x${byte}`)]; +} + +function storageAnnotation(value: unknown): { + standard: "erc8042" | "erc7201"; + id: string; +} | null { + const documentation = typeof value === "string" + ? value + : isRecord(value) && typeof value.text === "string" + ? value.text + : ""; + const marker = "@custom:storage-location"; + const markerIndex = documentation.indexOf(marker); + if (markerIndex === -1) return null; + const token = documentation.slice(markerIndex + marker.length).trim().split(/\s+/)[0] ?? ""; + const separator = token.indexOf(":"); + const standard = token.slice(0, separator); + const id = token.slice(separator + 1); + return (standard === "erc8042" || standard === "erc7201") && id + ? { standard, id } + : null; +} + +function numericSuffix(value: string, prefix: string, defaultTo256 = true): number | null { + if (value === prefix && defaultTo256) return 256; + if (!value.startsWith(prefix)) return null; + const suffix = value.slice(prefix.length); + if (!suffix || [...suffix].some((character) => character < "0" || character > "9")) return null; + return Number(suffix); +} + +function numericTypeCode( + base: number, + bits: number, +): { code: string; bits?: number; wholeSlot?: boolean; warnings: string[] } { + if (bits < 8 || bits > 256 || bits % 8 !== 0) { + return { code: CODE.unknown, wholeSlot: true, warnings: [`invalid numeric width: ${bits}`] }; + } + return { code: hexByte(base + bits / 8 - 1), bits, warnings: [] }; +} + +function unknownType(storagePath: string, message: string): TypeAnalysis { + return { + layout: [CODE.unknown], + origins: [null], + packBits: [], + slotGroups: [[256]], + children: [], + warnings: [{ storagePath, message }], + boundaryBefore: false, + boundaryAfter: false, + }; +} + +function referencedDeclaration(type: AstNode | null, index: AstIndex): AstNode | null { + const declarationId = typeof type?.referencedDeclaration === "number" + ? type.referencedDeclaration + : null; + return declarationId === null ? null : index.nodesById.get(declarationId) ?? null; +} + +function structMembers(structId: number | undefined, index: AstIndex): AstNode[] { + if (structId === undefined) return []; + return childNodes(index.nodesById.get(structId) ?? null, "members"); +} + +function sourceNameFor(node: AstNode, index: AstIndex): string { + return typeof node.id === "number" ? index.sourceByNodeId.get(node.id) ?? "" : ""; +} + +/** Hashes a canonical readable virtual storage path with EVM Keccak-256. */ +export function hashVirtualPath(virtualPath: string): string { + return keccak256(stringToBytes(virtualPath)); +} + +/** Derives the physical namespace root for a supported storage convention. */ +export function deriveStorageRootId( + rootIdentifier: string, + source: VirtualStorageLayoutSource, +): string { + if (/^0x[0-9a-fA-F]{1,64}$/.test(rootIdentifier)) { + return `0x${rootIdentifier.slice(2).padStart(64, "0").toLowerCase()}`; + } + if (source === "erc7201") { + const namespaceHash = BigInt(hashVirtualPath(rootIdentifier)); + const alignedHash = BigInt(keccak256(toHex(namespaceHash - 1n, { size: 32 }))) & ~0xffn; + return toHex(alignedHash, { size: 32 }); + } + return hashVirtualPath(rootIdentifier); +} + +function buildChildPath(parentPath: string, slot: number): string { + return `${parentPath}.${slot}`; +} + +function ensureEnd(layout: string[]): string[] { + return layout.at(-1) === CODE.end ? layout : [...layout, CODE.end]; +} + +function ensureEndOrigins(analysis: TypeAnalysis): Array { + return analysis.layout.at(-1) === CODE.end + ? analysis.origins + : [...analysis.origins, null]; +} + +function storageVariableOrigin( + field: AstNode, + index: AstIndex, + structName: string | null, + storagePath: string, +): StorageVariableOrigin { + const descriptions = isRecord(field.typeDescriptions) ? field.typeDescriptions : {}; + const type = astNode(field.typeName); + return { + structName, + variableName: stringValue(field.name) || "", + typeName: stringValue(descriptions.typeString) || stringValue(type?.name) || type?.nodeType || "unknown", + storagePath, + sourceName: sourceNameFor(field, index), + }; +} + +function findVariableMismatches( + records: VirtualStorageLayoutRecord[], +): VirtualStorageLayoutCollision["mismatches"] { + const mismatches: VirtualStorageLayoutCollision["mismatches"] = []; + const seen = new Set(); + for (let leftIndex = 0; leftIndex < records.length; leftIndex += 1) { + for (let rightIndex = leftIndex + 1; rightIndex < records.length; rightIndex += 1) { + const left = records[leftIndex]; + const right = records[rightIndex]; + const length = Math.min(left.layout.length, right.layout.length); + for (let position = 0; position < length; position += 1) { + const leftCode = left.layout[position]; + const rightCode = right.layout[position]; + if ( + leftCode === rightCode || + leftCode === CODE.unknown || + rightCode === CODE.unknown + ) continue; + + const leftOrigin = originsByRecord.get(left)?.[position]; + const rightOrigin = originsByRecord.get(right)?.[position]; + if (!leftOrigin || !rightOrigin) continue; + const key = [ + left.contractName, + leftOrigin.structName, + leftOrigin.variableName, + right.contractName, + rightOrigin.structName, + rightOrigin.variableName, + ].join(":"); + if (seen.has(key)) continue; + seen.add(key); + mismatches.push({ + position, + left: { contractName: left.contractName, ...leftOrigin }, + right: { contractName: right.contractName, ...rightOrigin }, + }); + } + } + } + return mismatches; +} + +function findUnsupportedVariables(records: VirtualStorageLayoutRecord[]): StorageVariableReference[] { + const variables: StorageVariableReference[] = []; + const seen = new Set(); + for (const record of records) { + const origins = originsByRecord.get(record) ?? []; + for (let position = 0; position < record.layout.length; position += 1) { + if (record.layout[position] !== CODE.unknown) continue; + const origin = origins[position]; + if (!origin) continue; + const key = `${record.contractName}:${origin.storagePath}`; + if (seen.has(key)) continue; + seen.add(key); + variables.push({ contractName: record.contractName, ...origin }); + } + } + return variables; +} + +type LayoutCompatibility = "compatible" | "collision" | "unsupported"; + +function comparePrefixLayouts(layouts: string[][]): LayoutCompatibility { + const sorted = [...layouts].sort((left, right) => left.length - right.length); + let unsupported = false; + for (let index = 1; index < sorted.length; index += 1) { + for (let tokenIndex = 0; tokenIndex < sorted[index - 1].length; tokenIndex += 1) { + const compatibility = compareTokens(sorted[index - 1][tokenIndex], sorted[index][tokenIndex]); + if (compatibility === "collision") return "collision"; + if (compatibility === "unsupported") unsupported = true; + } + } + return unsupported ? "unsupported" : "compatible"; +} + +function compareImmutableRecords(records: VirtualStorageLayoutRecord[]): LayoutCompatibility { + let unsupported = false; + for (const right of records.slice(1)) { + const left = records[0]; + if (left.layout.length !== right.layout.length) return "collision"; + for (let index = 0; index < left.layout.length; index += 1) { + const compatibility = compareTokens(left.layout[index], right.layout[index]); + if (compatibility === "collision") return "collision"; + if (compatibility === "unsupported") unsupported = true; + } + if ( + left.slots.length !== right.slots.length || + !left.slots.every((slot, index) => arraysEqual(slot, right.slots[index])) + ) { + return "collision"; + } + } + return unsupported ? "unsupported" : "compatible"; +} + +function compareTokens(left: string, right: string | undefined): LayoutCompatibility { + if (right === undefined) return "collision"; + if (left === CODE.unknown || right === CODE.unknown) return "unsupported"; + return left === right ? "compatible" : "collision"; +} + +function arraysEqual(left: T[], right: T[]): boolean { + return left.length === right.length && left.every((value, index) => value === right[index]); +} + +function walkAst(value: unknown, visitor: (node: AstNode) => void): void { + if (Array.isArray(value)) { + for (const child of value) walkAst(child, visitor); + return; + } + const node = astNode(value); + if (!node) return; + visitor(node); + for (const child of Object.values(node)) { + if (child && typeof child === "object") walkAst(child, visitor); + } +} + +function walkAstWithContract( + value: unknown, + ownerContractId: number | null, + visitor: (node: AstNode, ownerContractId: number | null) => void, +): void { + if (Array.isArray(value)) { + for (const child of value) walkAstWithContract(child, ownerContractId, visitor); + return; + } + const node = astNode(value); + if (!node) return; + const nextOwner = node.nodeType === "ContractDefinition" && typeof node.id === "number" + ? node.id + : ownerContractId; + visitor(node, nextOwner); + for (const child of Object.values(node)) { + if (child && typeof child === "object") walkAstWithContract(child, nextOwner, visitor); + } +} + +function childNodes(node: AstNode | null, key: string): AstNode[] { + const value = node?.[key]; + return Array.isArray(value) + ? value.map(astNode).filter((child): child is AstNode => child !== null) + : []; +} + +function astNode(value: unknown): AstNode | null { + return isRecord(value) && typeof value.nodeType === "string" ? value as AstNode : null; +} + +function numberArray(value: unknown): number[] { + return Array.isArray(value) ? value.filter((item): item is number => typeof item === "number") : []; +} + +function isRecord(value: unknown): value is Record { + return Boolean(value) && typeof value === "object" && !Array.isArray(value); +} + +function stringValue(value: unknown): string { + return typeof value === "string" ? value : ""; +} + +function hexByte(value: number): string { + return `0x${value.toString(16).padStart(2, "0")}`; +} diff --git a/cli/src/pipelines/initPipeline.ts b/cli/src/pipelines/initPipeline.ts index 0d563232..7df547fc 100644 --- a/cli/src/pipelines/initPipeline.ts +++ b/cli/src/pipelines/initPipeline.ts @@ -68,27 +68,38 @@ export const InitPipeline = { await adapter.writeConfig(ctx, { compilerVersion, projectName, installDeps }); ctx = ScaffoldingModule.recordScaffoldMap(ctx, scaffoldMapEntries); - ctx = await ScaffoldingModule.scanSelectedFacets(ctx, adapter); - ctx = await ValidationModule.validateSelectorExports(ctx); - - if (ValidationModule.hasSelectorExportFailure(ctx)) { - ctx = await ValidationModule.showReport(ctx); - return ctx; - } - - ctx = await ValidationModule.detectSelectorCollisions(ctx, { - hashing: deps.hashing, - }); - - if (ValidationModule.hasSelectorCollisionFailure(ctx)) { - ctx = await ValidationModule.showReport(ctx); - return ctx; - } + ctx = await ValidationModule.resolveComposeFacetSources(ctx, adapter); + ctx = await ValidationModule.resolveProjectFacetSources(ctx, adapter); + const facetSources = ValidationModule.getResolvedFacetSources(ctx); + const astSources = await adapter.compileAst( + ctx, + facetSources.map((facet) => facet.sourcePath), + ); - ctx = await ValidationModule.detectIdentifierCollisions(ctx); + ctx = ValidationModule.scanFacetSelectors(ctx, astSources, facetSources); + ctx = ValidationModule.buildVirtualStorageLayout(ctx, astSources, facetSources); + ctx = await ValidationModule.validateSelectorExports(ctx); + ctx = await ValidationModule.detectSelectorCollisions(ctx, { hashing: deps.hashing }); + + const selectorCollisions = ValidationModule.getSelectorCollisionValidationState(ctx); + const virtualStorageLayout = ValidationModule.getVirtualStorageLayoutValidationState(ctx); + const validationError = selectorCollisions?.error ?? virtualStorageLayout?.error ?? null; + const validationSuccess = + selectorCollisions?.success === true && virtualStorageLayout?.success === true; + ctx.state.initValidation = { + success: validationSuccess, + result: { checkedFacets: facetSources.length }, + error: validationError, + }; + ctx = await ValidationModule.showReport(ctx); - if (ValidationModule.hasIdentifierCollisionFailure(ctx)) { - ctx = await ValidationModule.showReport(ctx); + if (!validationSuccess) { + ctx.status = { + success: false, + stopped: true, + failedAt: "initValidation", + error: validationError, + }; return ctx; } @@ -114,12 +125,6 @@ export const InitPipeline = { error: null, }; - ctx = await ValidationModule.showReport(ctx); - - if (ValidationModule.hasBlockingFailure(ctx)) { - return ctx; - } - if (ctx.state.initPipeline?.success) { InitModule.showSuccess(ctx); } diff --git a/cli/src/pipelines/validatePipeline.ts b/cli/src/pipelines/validatePipeline.ts new file mode 100644 index 00000000..27f77c33 --- /dev/null +++ b/cli/src/pipelines/validatePipeline.ts @@ -0,0 +1,76 @@ +import { IFrameworkAdapter } from "../adapters/interface/IFrameworkAdapter"; +import { ComposeContext } from "../context/types"; +import { ValidationModule } from "../modules/validation/module"; +import { DependencyKey } from "../resolver/dependencyKey"; +import { DependencyResolver } from "../resolver/dependencyResolver"; +import { loadValidationProject } from "../modules/validation/project"; + +/** Runs source-side validation directly from compiler AST output. */ +export const ValidatePipeline = { + async execute(ctx: ComposeContext): Promise { + const project = await loadValidationProject(ctx); + const framework = String(ctx.param.framework ?? "foundry") as DependencyKey; + const deps = await DependencyResolver.resolve([ + { key: DependencyKey.Hashing }, + { key: framework }, + ]); + const adapter = deps[framework] as IFrameworkAdapter | undefined; + + if (!deps.hashing) { + throw new Error("Hashing dependency was not resolved."); + } + if (!adapter) { + throw new Error(`${framework} adapter was not resolved.`); + } + + const sources = await adapter.compileAst( + ctx, + project.facetSources.map((facet) => facet.sourcePath), + ); + const scopes = project.diamonds.map((diamond) => ({ + diamondName: diamond.name, + facets: diamond.facets, + })); + + ctx = ValidationModule.scanFacetSelectors(ctx, sources, project.facetSources); + ctx = ValidationModule.buildVirtualStorageLayout( + ctx, + sources, + project.facetSources, + scopes, + ); + ctx = await ValidationModule.validateSelectorExports(ctx); + ctx = await ValidationModule.detectSelectorCollisions(ctx, { + hashing: deps.hashing, + scopes, + }); + + const selectorCollisions = ValidationModule.getSelectorCollisionValidationState(ctx); + const virtualStorageLayout = ValidationModule.getVirtualStorageLayoutValidationState(ctx); + const pipelineError = selectorCollisions?.error ?? virtualStorageLayout?.error ?? null; + ctx.state.validatePipeline = { + success: selectorCollisions?.success === true && virtualStorageLayout?.success === true, + result: { + checkedFacets: project.facetSources.length, + }, + error: pipelineError, + }; + + if (!ctx.state.validatePipeline.success) { + ctx.status = { + success: false, + stopped: true, + failedAt: "validatePipeline", + error: pipelineError, + }; + } + + ctx = await ValidationModule.showReport(ctx); + + if (ctx.state.validatePipeline.success) { + ValidationModule.showSuccess(); + } + + return ctx; + }, +}; diff --git a/cli/src/utils/solidityAst.ts b/cli/src/utils/solidityAst.ts new file mode 100644 index 00000000..566ae8e9 --- /dev/null +++ b/cli/src/utils/solidityAst.ts @@ -0,0 +1,59 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { + SolidityAstSource, + SoliditySourceUnitAst, +} from "../adapters/interface/IFrameworkAdapter"; + +/** Recursively lists JSON files below an adapter output directory. */ +export async function listJsonFiles(root: string): Promise { + let entries; + + try { + entries = await fs.readdir(root, { withFileTypes: true }); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === "ENOENT") { + return []; + } + throw error; + } + + const nestedFiles = await Promise.all( + entries.map(async (entry) => { + const entryPath = path.join(root, entry.name); + if (entry.isDirectory()) { + return listJsonFiles(entryPath); + } + return entry.isFile() && entry.name.endsWith(".json") ? [entryPath] : []; + }), + ); + + return nestedFiles.flat().sort(); +} + +/** Returns true when a compiler JSON value is a Solidity source unit AST. */ +export function isSourceUnitAst(value: unknown): value is SoliditySourceUnitAst { + if (!value || typeof value !== "object") { + return false; + } + + const candidate = value as Record; + return ( + candidate.nodeType === "SourceUnit" && + typeof candidate.id === "number" && + typeof candidate.src === "string" + ); +} + +/** Deduplicates source units by compiler source name and returns stable ordering. */ +export function uniqueAstSources(sources: SolidityAstSource[]): SolidityAstSource[] { + const unique = new Map(); + + for (const source of sources) { + if (!unique.has(source.sourceName)) { + unique.set(source.sourceName, source); + } + } + + return [...unique.values()].sort((a, b) => a.sourceName.localeCompare(b.sourceName)); +} diff --git a/cli/test/adapters/IFrameworkAdapter/canonicalAst.ts b/cli/test/adapters/IFrameworkAdapter/canonicalAst.ts new file mode 100644 index 00000000..9537c56e --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/canonicalAst.ts @@ -0,0 +1,22 @@ +import { SoliditySourceUnitAst } from "../../../src/adapters/interface/IFrameworkAdapter"; + +/** Sorts AST object keys recursively so expected fixtures are stable and reviewable. */ +export function canonicalizeAst(ast: SoliditySourceUnitAst): unknown { + return canonicalizeValue(ast); +} + +function canonicalizeValue(value: unknown): unknown { + if (Array.isArray(value)) { + return value.map(canonicalizeValue); + } + + if (value !== null && typeof value === "object") { + return Object.fromEntries( + Object.entries(value) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, child]) => [key, canonicalizeValue(child)]), + ); + } + + return value; +} diff --git a/cli/test/adapters/IFrameworkAdapter/fixtures/normal/Normal.sol b/cli/test/adapters/IFrameworkAdapter/fixtures/normal/Normal.sol new file mode 100644 index 00000000..4890d08e --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/fixtures/normal/Normal.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +contract Normal { + bytes32 constant STORAGE_POSITION = keccak256("evmole.normal"); + uint8 constant SAMPLE_COUNT2 = 7; + + struct InnerRecord { + bytes4 tailId; + uint64 count; + uint8 count2; + } + + struct DirectRecord { + bytes4 headId; + InnerRecord inner; + uint32 total; + } + + struct NormalStorage { + uint256 totalSupply; + bytes4 marker; + DirectRecord record; + mapping(address account => uint256 value) balances; + mapping(address account => mapping(address spender => uint256 value)) allowances; + uint8[] smallValues; + } + + struct NormalSnapshot { + uint256 totalSupply; + bytes4 marker; + bytes4 headId; + bytes4 tailId; + uint64 count; + uint8 count2; + uint32 total; + uint256 balance; + uint256 allowance; + uint8 smallValue; + } + + function getStorage() internal pure returns (NormalStorage storage s) { + bytes32 position = STORAGE_POSITION; + assembly { + s.slot := position + } + } + + function readAll(address account, address spender, uint256 index) external view returns (NormalSnapshot memory snapshot) { + NormalStorage storage s = getStorage(); + snapshot.totalSupply = s.totalSupply; + snapshot.marker = s.marker; + snapshot.headId = s.record.headId; + snapshot.tailId = s.record.inner.tailId; + snapshot.count = s.record.inner.count; + snapshot.count2 = s.record.inner.count2; + snapshot.total = s.record.total; + snapshot.balance = s.balances[account]; + snapshot.allowance = s.allowances[account][spender]; + snapshot.smallValue = s.smallValues[index]; + } + + function setCount2() external { + getStorage().record.inner.count2 = SAMPLE_COUNT2; + } +} diff --git a/cli/test/adapters/IFrameworkAdapter/fixtures/normal/expected/foundry.ast.json b/cli/test/adapters/IFrameworkAdapter/fixtures/normal/expected/foundry.ast.json new file mode 100644 index 00000000..64cf9c53 --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/fixtures/normal/expected/foundry.ast.json @@ -0,0 +1,2447 @@ +{ + "absolutePath": "src/Normal.sol", + "exportedSymbols": { + "Normal": [ + 196 + ] + }, + "id": 197, + "license": "MIT", + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1, + "literals": [ + "solidity", + ">=", + "0.8", + ".30" + ], + "nodeType": "PragmaDirective", + "nodes": [], + "src": "32:25:0" + }, + { + "abstract": false, + "baseContracts": [], + "canonicalName": "Normal", + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "id": 196, + "linearizedBaseContracts": [ + 196 + ], + "name": "Normal", + "nameLocation": "68:6:0", + "nodeType": "ContractDefinition", + "nodes": [ + { + "constant": true, + "id": 6, + "mutability": "constant", + "name": "STORAGE_POSITION", + "nameLocation": "98:16:0", + "nodeType": "VariableDeclaration", + "nodes": [], + "scope": 196, + "src": "81:62:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 2, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "81:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": { + "arguments": [ + { + "hexValue": "65766d6f6c652e6e6f726d616c", + "id": 4, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "127:15:0", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_b4df32537f6767405c9db7d67260e5375218aecdea91f4240ad14000623cbdff", + "typeString": "literal_string \"evmole.normal\"" + }, + "value": "evmole.normal" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_b4df32537f6767405c9db7d67260e5375218aecdea91f4240ad14000623cbdff", + "typeString": "literal_string \"evmole.normal\"" + } + ], + "id": 3, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "117:9:0", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 5, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "117:26:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": true, + "id": 9, + "mutability": "constant", + "name": "SAMPLE_COUNT2", + "nameLocation": "164:13:0", + "nodeType": "VariableDeclaration", + "nodes": [], + "scope": 196, + "src": "149:32:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 7, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "149:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "value": { + "hexValue": "37", + "id": 8, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "180:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_7_by_1", + "typeString": "int_const 7" + }, + "value": "7" + }, + "visibility": "internal" + }, + { + "canonicalName": "Normal.InnerRecord", + "id": 16, + "members": [ + { + "constant": false, + "id": 11, + "mutability": "mutable", + "name": "tailId", + "nameLocation": "224:6:0", + "nodeType": "VariableDeclaration", + "scope": 16, + "src": "217:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 10, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "217:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 13, + "mutability": "mutable", + "name": "count", + "nameLocation": "247:5:0", + "nodeType": "VariableDeclaration", + "scope": 16, + "src": "240:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 12, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "240:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 15, + "mutability": "mutable", + "name": "count2", + "nameLocation": "268:6:0", + "nodeType": "VariableDeclaration", + "scope": 16, + "src": "262:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 14, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "262:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + } + ], + "name": "InnerRecord", + "nameLocation": "195:11:0", + "nodeType": "StructDefinition", + "nodes": [], + "scope": 196, + "src": "188:93:0", + "visibility": "public" + }, + { + "canonicalName": "Normal.DirectRecord", + "id": 24, + "members": [ + { + "constant": false, + "id": 18, + "mutability": "mutable", + "name": "headId", + "nameLocation": "324:6:0", + "nodeType": "VariableDeclaration", + "scope": 24, + "src": "317:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 17, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "317:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 21, + "mutability": "mutable", + "name": "inner", + "nameLocation": "352:5:0", + "nodeType": "VariableDeclaration", + "scope": 24, + "src": "340:17:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage_ptr", + "typeString": "struct Normal.InnerRecord" + }, + "typeName": { + "id": 20, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 19, + "name": "InnerRecord", + "nameLocations": [ + "340:11:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 16, + "src": "340:11:0" + }, + "referencedDeclaration": 16, + "src": "340:11:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage_ptr", + "typeString": "struct Normal.InnerRecord" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 23, + "mutability": "mutable", + "name": "total", + "nameLocation": "374:5:0", + "nodeType": "VariableDeclaration", + "scope": 24, + "src": "367:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + }, + "typeName": { + "id": 22, + "name": "uint32", + "nodeType": "ElementaryTypeName", + "src": "367:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "visibility": "internal" + } + ], + "name": "DirectRecord", + "nameLocation": "294:12:0", + "nodeType": "StructDefinition", + "nodes": [], + "scope": 196, + "src": "287:99:0", + "visibility": "public" + }, + { + "canonicalName": "Normal.NormalStorage", + "id": 45, + "members": [ + { + "constant": false, + "id": 26, + "mutability": "mutable", + "name": "totalSupply", + "nameLocation": "431:11:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "423:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 25, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "423:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 28, + "mutability": "mutable", + "name": "marker", + "nameLocation": "459:6:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "452:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 27, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "452:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 31, + "mutability": "mutable", + "name": "record", + "nameLocation": "488:6:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "475:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage_ptr", + "typeString": "struct Normal.DirectRecord" + }, + "typeName": { + "id": 30, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 29, + "name": "DirectRecord", + "nameLocations": [ + "475:12:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 24, + "src": "475:12:0" + }, + "referencedDeclaration": 24, + "src": "475:12:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage_ptr", + "typeString": "struct Normal.DirectRecord" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 35, + "mutability": "mutable", + "name": "balances", + "nameLocation": "546:8:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "504:50:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "typeName": { + "id": 34, + "keyName": "account", + "keyNameLocation": "520:7:0", + "keyType": { + "id": 32, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "512:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "504:41:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "valueName": "value", + "valueNameLocation": "539:5:0", + "valueType": { + "id": 33, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "531:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 41, + "mutability": "mutable", + "name": "allowances", + "nameLocation": "634:10:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "564:80:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_mapping$_t_address_$_t_uint256_$_$", + "typeString": "mapping(address => mapping(address => uint256))" + }, + "typeName": { + "id": 40, + "keyName": "account", + "keyNameLocation": "580:7:0", + "keyType": { + "id": 36, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "572:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "564:69:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_mapping$_t_address_$_t_uint256_$_$", + "typeString": "mapping(address => mapping(address => uint256))" + }, + "valueName": "", + "valueNameLocation": "-1:-1:-1", + "valueType": { + "id": 39, + "keyName": "spender", + "keyNameLocation": "607:7:0", + "keyType": { + "id": 37, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "599:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "591:41:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "valueName": "value", + "valueNameLocation": "626:5:0", + "valueType": { + "id": 38, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "618:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 44, + "mutability": "mutable", + "name": "smallValues", + "nameLocation": "662:11:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "654:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint8_$dyn_storage_ptr", + "typeString": "uint8[]" + }, + "typeName": { + "baseType": { + "id": 42, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "654:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 43, + "nodeType": "ArrayTypeName", + "src": "654:7:0", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint8_$dyn_storage_ptr", + "typeString": "uint8[]" + } + }, + "visibility": "internal" + } + ], + "name": "NormalStorage", + "nameLocation": "399:13:0", + "nodeType": "StructDefinition", + "nodes": [], + "scope": 196, + "src": "392:288:0", + "visibility": "public" + }, + { + "canonicalName": "Normal.NormalSnapshot", + "id": 66, + "members": [ + { + "constant": false, + "id": 47, + "mutability": "mutable", + "name": "totalSupply", + "nameLocation": "726:11:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "718:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 46, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "718:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 49, + "mutability": "mutable", + "name": "marker", + "nameLocation": "754:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "747:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 48, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "747:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 51, + "mutability": "mutable", + "name": "headId", + "nameLocation": "777:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "770:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 50, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "770:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 53, + "mutability": "mutable", + "name": "tailId", + "nameLocation": "800:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "793:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 52, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "793:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 55, + "mutability": "mutable", + "name": "count", + "nameLocation": "823:5:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "816:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 54, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "816:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 57, + "mutability": "mutable", + "name": "count2", + "nameLocation": "844:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "838:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 56, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "838:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 59, + "mutability": "mutable", + "name": "total", + "nameLocation": "867:5:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "860:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + }, + "typeName": { + "id": 58, + "name": "uint32", + "nodeType": "ElementaryTypeName", + "src": "860:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61, + "mutability": "mutable", + "name": "balance", + "nameLocation": "890:7:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "882:15:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 60, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "882:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 63, + "mutability": "mutable", + "name": "allowance", + "nameLocation": "915:9:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "907:17:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 62, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "907:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 65, + "mutability": "mutable", + "name": "smallValue", + "nameLocation": "940:10:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "934:16:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 64, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "934:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + } + ], + "name": "NormalSnapshot", + "nameLocation": "693:14:0", + "nodeType": "StructDefinition", + "nodes": [], + "scope": 196, + "src": "686:271:0", + "visibility": "public" + }, + { + "body": { + "id": 77, + "nodeType": "Block", + "nodes": [], + "src": "1033:112:0", + "statements": [ + { + "assignments": [ + 73 + ], + "declarations": [ + { + "constant": false, + "id": 73, + "mutability": "mutable", + "name": "position", + "nameLocation": "1051:8:0", + "nodeType": "VariableDeclaration", + "scope": 77, + "src": "1043:16:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 72, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "1043:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 75, + "initialValue": { + "id": 74, + "name": "STORAGE_POSITION", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 6, + "src": "1062:16:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1043:35:0" + }, + { + "AST": { + "nativeSrc": "1097:42:0", + "nodeType": "YulBlock", + "src": "1097:42:0", + "statements": [ + { + "nativeSrc": "1111:18:0", + "nodeType": "YulAssignment", + "src": "1111:18:0", + "value": { + "name": "position", + "nativeSrc": "1121:8:0", + "nodeType": "YulIdentifier", + "src": "1121:8:0" + }, + "variableNames": [ + { + "name": "s.slot", + "nativeSrc": "1111:6:0", + "nodeType": "YulIdentifier", + "src": "1111:6:0" + } + ] + } + ] + }, + "evmVersion": "prague", + "externalReferences": [ + { + "declaration": 73, + "isOffset": false, + "isSlot": false, + "src": "1121:8:0", + "valueSize": 1 + }, + { + "declaration": 70, + "isOffset": false, + "isSlot": true, + "src": "1111:6:0", + "suffix": "slot", + "valueSize": 1 + } + ], + "id": 76, + "nodeType": "InlineAssembly", + "src": "1088:51:0" + } + ] + }, + "id": 78, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getStorage", + "nameLocation": "972:10:0", + "nodeType": "FunctionDefinition", + "nodes": [], + "parameters": { + "id": 67, + "nodeType": "ParameterList", + "parameters": [], + "src": "982:2:0" + }, + "returnParameters": { + "id": 71, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 70, + "mutability": "mutable", + "name": "s", + "nameLocation": "1030:1:0", + "nodeType": "VariableDeclaration", + "scope": 78, + "src": "1008:23:0", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + }, + "typeName": { + "id": 69, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 68, + "name": "NormalStorage", + "nameLocations": [ + "1008:13:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 45, + "src": "1008:13:0" + }, + "referencedDeclaration": 45, + "src": "1008:13:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + } + }, + "visibility": "internal" + } + ], + "src": "1007:25:0" + }, + "scope": 196, + "src": "963:182:0", + "stateMutability": "pure", + "virtual": false, + "visibility": "internal" + }, + { + "body": { + "id": 182, + "nodeType": "Block", + "nodes": [], + "src": "1272:527:0", + "statements": [ + { + "assignments": [ + 92 + ], + "declarations": [ + { + "constant": false, + "id": 92, + "mutability": "mutable", + "name": "s", + "nameLocation": "1304:1:0", + "nodeType": "VariableDeclaration", + "scope": 182, + "src": "1282:23:0", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + }, + "typeName": { + "id": 91, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 90, + "name": "NormalStorage", + "nameLocations": [ + "1282:13:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 45, + "src": "1282:13:0" + }, + "referencedDeclaration": 45, + "src": "1282:13:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + } + }, + "visibility": "internal" + } + ], + "id": 95, + "initialValue": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 93, + "name": "getStorage", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 78, + "src": "1308:10:0", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$__$returns$_t_struct$_NormalStorage_$45_storage_ptr_$", + "typeString": "function () pure returns (struct Normal.NormalStorage storage pointer)" + } + }, + "id": 94, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1308:12:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1282:38:0" + }, + { + "expression": { + "id": 101, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 96, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1330:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 98, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1339:11:0", + "memberName": "totalSupply", + "nodeType": "MemberAccess", + "referencedDeclaration": 47, + "src": "1330:20:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "id": 99, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1353:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 100, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1355:11:0", + "memberName": "totalSupply", + "nodeType": "MemberAccess", + "referencedDeclaration": 26, + "src": "1353:13:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1330:36:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 102, + "nodeType": "ExpressionStatement", + "src": "1330:36:0" + }, + { + "expression": { + "id": 108, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 103, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1376:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 105, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1385:6:0", + "memberName": "marker", + "nodeType": "MemberAccess", + "referencedDeclaration": 49, + "src": "1376:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "id": 106, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1394:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 107, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1396:6:0", + "memberName": "marker", + "nodeType": "MemberAccess", + "referencedDeclaration": 28, + "src": "1394:8:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "src": "1376:26:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "id": 109, + "nodeType": "ExpressionStatement", + "src": "1376:26:0" + }, + { + "expression": { + "id": 116, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 110, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1412:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 112, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1421:6:0", + "memberName": "headId", + "nodeType": "MemberAccess", + "referencedDeclaration": 51, + "src": "1412:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "id": 113, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1430:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 114, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1432:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1430:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 115, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1439:6:0", + "memberName": "headId", + "nodeType": "MemberAccess", + "referencedDeclaration": 18, + "src": "1430:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "src": "1412:33:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "id": 117, + "nodeType": "ExpressionStatement", + "src": "1412:33:0" + }, + { + "expression": { + "id": 125, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 118, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1455:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 120, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1464:6:0", + "memberName": "tailId", + "nodeType": "MemberAccess", + "referencedDeclaration": 53, + "src": "1455:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "expression": { + "id": 121, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1473:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 122, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1475:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1473:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 123, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1482:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1473:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 124, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1488:6:0", + "memberName": "tailId", + "nodeType": "MemberAccess", + "referencedDeclaration": 11, + "src": "1473:21:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "src": "1455:39:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "id": 126, + "nodeType": "ExpressionStatement", + "src": "1455:39:0" + }, + { + "expression": { + "id": 134, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 127, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1504:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 129, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1513:5:0", + "memberName": "count", + "nodeType": "MemberAccess", + "referencedDeclaration": 55, + "src": "1504:14:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "expression": { + "id": 130, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1521:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 131, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1523:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1521:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 132, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1530:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1521:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 133, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1536:5:0", + "memberName": "count", + "nodeType": "MemberAccess", + "referencedDeclaration": 13, + "src": "1521:20:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "src": "1504:37:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "id": 135, + "nodeType": "ExpressionStatement", + "src": "1504:37:0" + }, + { + "expression": { + "id": 143, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 136, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1551:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 138, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1560:6:0", + "memberName": "count2", + "nodeType": "MemberAccess", + "referencedDeclaration": 57, + "src": "1551:15:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "expression": { + "id": 139, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1569:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 140, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1571:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1569:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 141, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1578:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1569:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 142, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1584:6:0", + "memberName": "count2", + "nodeType": "MemberAccess", + "referencedDeclaration": 15, + "src": "1569:21:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "src": "1551:39:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 144, + "nodeType": "ExpressionStatement", + "src": "1551:39:0" + }, + { + "expression": { + "id": 151, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 145, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1600:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 147, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1609:5:0", + "memberName": "total", + "nodeType": "MemberAccess", + "referencedDeclaration": 59, + "src": "1600:14:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "id": 148, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1617:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 149, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1619:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1617:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 150, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1626:5:0", + "memberName": "total", + "nodeType": "MemberAccess", + "referencedDeclaration": 23, + "src": "1617:14:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "src": "1600:31:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "id": 152, + "nodeType": "ExpressionStatement", + "src": "1600:31:0" + }, + { + "expression": { + "id": 160, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 153, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1641:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 155, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1650:7:0", + "memberName": "balance", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "1641:16:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "baseExpression": { + "expression": { + "id": 156, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1660:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 157, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1662:8:0", + "memberName": "balances", + "nodeType": "MemberAccess", + "referencedDeclaration": 35, + "src": "1660:10:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + } + }, + "id": 159, + "indexExpression": { + "id": 158, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 80, + "src": "1671:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1660:19:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1641:38:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 161, + "nodeType": "ExpressionStatement", + "src": "1641:38:0" + }, + { + "expression": { + "id": 171, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 162, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1689:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 164, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1698:9:0", + "memberName": "allowance", + "nodeType": "MemberAccess", + "referencedDeclaration": 63, + "src": "1689:18:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "baseExpression": { + "baseExpression": { + "expression": { + "id": 165, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1710:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 166, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1712:10:0", + "memberName": "allowances", + "nodeType": "MemberAccess", + "referencedDeclaration": 41, + "src": "1710:12:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_mapping$_t_address_$_t_uint256_$_$", + "typeString": "mapping(address => mapping(address => uint256))" + } + }, + "id": 168, + "indexExpression": { + "id": 167, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 80, + "src": "1723:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1710:21:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + } + }, + "id": 170, + "indexExpression": { + "id": 169, + "name": "spender", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 82, + "src": "1732:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1710:30:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1689:51:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 172, + "nodeType": "ExpressionStatement", + "src": "1689:51:0" + }, + { + "expression": { + "id": 180, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 173, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1750:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 175, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1759:10:0", + "memberName": "smallValue", + "nodeType": "MemberAccess", + "referencedDeclaration": 65, + "src": "1750:19:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "baseExpression": { + "expression": { + "id": 176, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1772:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 177, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1774:11:0", + "memberName": "smallValues", + "nodeType": "MemberAccess", + "referencedDeclaration": 44, + "src": "1772:13:0", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint8_$dyn_storage", + "typeString": "uint8[] storage ref" + } + }, + "id": 179, + "indexExpression": { + "id": 178, + "name": "index", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 84, + "src": "1786:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1772:20:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "src": "1750:42:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 181, + "nodeType": "ExpressionStatement", + "src": "1750:42:0" + } + ] + }, + "functionSelector": "4fad3e6d", + "id": 183, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "readAll", + "nameLocation": "1160:7:0", + "nodeType": "FunctionDefinition", + "nodes": [], + "parameters": { + "id": 85, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 80, + "mutability": "mutable", + "name": "account", + "nameLocation": "1176:7:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1168:15:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 79, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1168:7:0", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 82, + "mutability": "mutable", + "name": "spender", + "nameLocation": "1193:7:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1185:15:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 81, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1185:7:0", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 84, + "mutability": "mutable", + "name": "index", + "nameLocation": "1210:5:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1202:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 83, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1202:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "src": "1167:49:0" + }, + "returnParameters": { + "id": 89, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 88, + "mutability": "mutable", + "name": "snapshot", + "nameLocation": "1262:8:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1240:30:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot" + }, + "typeName": { + "id": 87, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 86, + "name": "NormalSnapshot", + "nameLocations": [ + "1240:14:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 66, + "src": "1240:14:0" + }, + "referencedDeclaration": 66, + "src": "1240:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_storage_ptr", + "typeString": "struct Normal.NormalSnapshot" + } + }, + "visibility": "internal" + } + ], + "src": "1239:32:0" + }, + "scope": 196, + "src": "1151:648:0", + "stateMutability": "view", + "virtual": false, + "visibility": "external" + }, + { + "body": { + "id": 194, + "nodeType": "Block", + "nodes": [], + "src": "1835:65:0", + "statements": [ + { + "expression": { + "id": 192, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "expression": { + "expression": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 186, + "name": "getStorage", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 78, + "src": "1845:10:0", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$__$returns$_t_struct$_NormalStorage_$45_storage_ptr_$", + "typeString": "function () pure returns (struct Normal.NormalStorage storage pointer)" + } + }, + "id": 187, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1845:12:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 188, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1858:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1845:19:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 189, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1865:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1845:25:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 190, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1871:6:0", + "memberName": "count2", + "nodeType": "MemberAccess", + "referencedDeclaration": 15, + "src": "1845:32:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 191, + "name": "SAMPLE_COUNT2", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9, + "src": "1880:13:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "src": "1845:48:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 193, + "nodeType": "ExpressionStatement", + "src": "1845:48:0" + } + ] + }, + "functionSelector": "ac5b16dc", + "id": 195, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "setCount2", + "nameLocation": "1814:9:0", + "nodeType": "FunctionDefinition", + "nodes": [], + "parameters": { + "id": 184, + "nodeType": "ParameterList", + "parameters": [], + "src": "1823:2:0" + }, + "returnParameters": { + "id": 185, + "nodeType": "ParameterList", + "parameters": [], + "src": "1835:0:0" + }, + "scope": 196, + "src": "1805:95:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + } + ], + "scope": 197, + "src": "59:1843:0", + "usedErrors": [], + "usedEvents": [] + } + ], + "src": "32:1871:0" +} diff --git a/cli/test/adapters/IFrameworkAdapter/fixtures/normal/expected/hardhat.ast.json b/cli/test/adapters/IFrameworkAdapter/fixtures/normal/expected/hardhat.ast.json new file mode 100644 index 00000000..8d5bc4e9 --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/fixtures/normal/expected/hardhat.ast.json @@ -0,0 +1,2434 @@ +{ + "absolutePath": "project/contracts/Normal.sol", + "exportedSymbols": { + "Normal": [ + 196 + ] + }, + "id": 197, + "license": "MIT", + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1, + "literals": [ + "solidity", + ">=", + "0.8", + ".30" + ], + "nodeType": "PragmaDirective", + "src": "32:25:0" + }, + { + "abstract": false, + "baseContracts": [], + "canonicalName": "Normal", + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "id": 196, + "linearizedBaseContracts": [ + 196 + ], + "name": "Normal", + "nameLocation": "68:6:0", + "nodeType": "ContractDefinition", + "nodes": [ + { + "constant": true, + "id": 6, + "mutability": "constant", + "name": "STORAGE_POSITION", + "nameLocation": "98:16:0", + "nodeType": "VariableDeclaration", + "scope": 196, + "src": "81:62:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 2, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "81:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": { + "arguments": [ + { + "hexValue": "65766d6f6c652e6e6f726d616c", + "id": 4, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "127:15:0", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_b4df32537f6767405c9db7d67260e5375218aecdea91f4240ad14000623cbdff", + "typeString": "literal_string \"evmole.normal\"" + }, + "value": "evmole.normal" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_b4df32537f6767405c9db7d67260e5375218aecdea91f4240ad14000623cbdff", + "typeString": "literal_string \"evmole.normal\"" + } + ], + "id": 3, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "117:9:0", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 5, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "117:26:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": true, + "id": 9, + "mutability": "constant", + "name": "SAMPLE_COUNT2", + "nameLocation": "164:13:0", + "nodeType": "VariableDeclaration", + "scope": 196, + "src": "149:32:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 7, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "149:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "value": { + "hexValue": "37", + "id": 8, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "180:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_7_by_1", + "typeString": "int_const 7" + }, + "value": "7" + }, + "visibility": "internal" + }, + { + "canonicalName": "Normal.InnerRecord", + "id": 16, + "members": [ + { + "constant": false, + "id": 11, + "mutability": "mutable", + "name": "tailId", + "nameLocation": "224:6:0", + "nodeType": "VariableDeclaration", + "scope": 16, + "src": "217:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 10, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "217:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 13, + "mutability": "mutable", + "name": "count", + "nameLocation": "247:5:0", + "nodeType": "VariableDeclaration", + "scope": 16, + "src": "240:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 12, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "240:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 15, + "mutability": "mutable", + "name": "count2", + "nameLocation": "268:6:0", + "nodeType": "VariableDeclaration", + "scope": 16, + "src": "262:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 14, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "262:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + } + ], + "name": "InnerRecord", + "nameLocation": "195:11:0", + "nodeType": "StructDefinition", + "scope": 196, + "src": "188:93:0", + "visibility": "public" + }, + { + "canonicalName": "Normal.DirectRecord", + "id": 24, + "members": [ + { + "constant": false, + "id": 18, + "mutability": "mutable", + "name": "headId", + "nameLocation": "324:6:0", + "nodeType": "VariableDeclaration", + "scope": 24, + "src": "317:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 17, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "317:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 21, + "mutability": "mutable", + "name": "inner", + "nameLocation": "352:5:0", + "nodeType": "VariableDeclaration", + "scope": 24, + "src": "340:17:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage_ptr", + "typeString": "struct Normal.InnerRecord" + }, + "typeName": { + "id": 20, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 19, + "name": "InnerRecord", + "nameLocations": [ + "340:11:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 16, + "src": "340:11:0" + }, + "referencedDeclaration": 16, + "src": "340:11:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage_ptr", + "typeString": "struct Normal.InnerRecord" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 23, + "mutability": "mutable", + "name": "total", + "nameLocation": "374:5:0", + "nodeType": "VariableDeclaration", + "scope": 24, + "src": "367:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + }, + "typeName": { + "id": 22, + "name": "uint32", + "nodeType": "ElementaryTypeName", + "src": "367:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "visibility": "internal" + } + ], + "name": "DirectRecord", + "nameLocation": "294:12:0", + "nodeType": "StructDefinition", + "scope": 196, + "src": "287:99:0", + "visibility": "public" + }, + { + "canonicalName": "Normal.NormalStorage", + "id": 45, + "members": [ + { + "constant": false, + "id": 26, + "mutability": "mutable", + "name": "totalSupply", + "nameLocation": "431:11:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "423:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 25, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "423:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 28, + "mutability": "mutable", + "name": "marker", + "nameLocation": "459:6:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "452:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 27, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "452:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 31, + "mutability": "mutable", + "name": "record", + "nameLocation": "488:6:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "475:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage_ptr", + "typeString": "struct Normal.DirectRecord" + }, + "typeName": { + "id": 30, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 29, + "name": "DirectRecord", + "nameLocations": [ + "475:12:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 24, + "src": "475:12:0" + }, + "referencedDeclaration": 24, + "src": "475:12:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage_ptr", + "typeString": "struct Normal.DirectRecord" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 35, + "mutability": "mutable", + "name": "balances", + "nameLocation": "546:8:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "504:50:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "typeName": { + "id": 34, + "keyName": "account", + "keyNameLocation": "520:7:0", + "keyType": { + "id": 32, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "512:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "504:41:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "valueName": "value", + "valueNameLocation": "539:5:0", + "valueType": { + "id": 33, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "531:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 41, + "mutability": "mutable", + "name": "allowances", + "nameLocation": "634:10:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "564:80:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_mapping$_t_address_$_t_uint256_$_$", + "typeString": "mapping(address => mapping(address => uint256))" + }, + "typeName": { + "id": 40, + "keyName": "account", + "keyNameLocation": "580:7:0", + "keyType": { + "id": 36, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "572:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "564:69:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_mapping$_t_address_$_t_uint256_$_$", + "typeString": "mapping(address => mapping(address => uint256))" + }, + "valueName": "", + "valueNameLocation": "-1:-1:-1", + "valueType": { + "id": 39, + "keyName": "spender", + "keyNameLocation": "607:7:0", + "keyType": { + "id": 37, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "599:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "591:41:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "valueName": "value", + "valueNameLocation": "626:5:0", + "valueType": { + "id": 38, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "618:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 44, + "mutability": "mutable", + "name": "smallValues", + "nameLocation": "662:11:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "654:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint8_$dyn_storage_ptr", + "typeString": "uint8[]" + }, + "typeName": { + "baseType": { + "id": 42, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "654:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 43, + "nodeType": "ArrayTypeName", + "src": "654:7:0", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint8_$dyn_storage_ptr", + "typeString": "uint8[]" + } + }, + "visibility": "internal" + } + ], + "name": "NormalStorage", + "nameLocation": "399:13:0", + "nodeType": "StructDefinition", + "scope": 196, + "src": "392:288:0", + "visibility": "public" + }, + { + "canonicalName": "Normal.NormalSnapshot", + "id": 66, + "members": [ + { + "constant": false, + "id": 47, + "mutability": "mutable", + "name": "totalSupply", + "nameLocation": "726:11:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "718:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 46, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "718:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 49, + "mutability": "mutable", + "name": "marker", + "nameLocation": "754:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "747:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 48, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "747:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 51, + "mutability": "mutable", + "name": "headId", + "nameLocation": "777:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "770:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 50, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "770:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 53, + "mutability": "mutable", + "name": "tailId", + "nameLocation": "800:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "793:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 52, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "793:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 55, + "mutability": "mutable", + "name": "count", + "nameLocation": "823:5:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "816:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 54, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "816:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 57, + "mutability": "mutable", + "name": "count2", + "nameLocation": "844:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "838:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 56, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "838:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 59, + "mutability": "mutable", + "name": "total", + "nameLocation": "867:5:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "860:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + }, + "typeName": { + "id": 58, + "name": "uint32", + "nodeType": "ElementaryTypeName", + "src": "860:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61, + "mutability": "mutable", + "name": "balance", + "nameLocation": "890:7:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "882:15:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 60, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "882:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 63, + "mutability": "mutable", + "name": "allowance", + "nameLocation": "915:9:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "907:17:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 62, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "907:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 65, + "mutability": "mutable", + "name": "smallValue", + "nameLocation": "940:10:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "934:16:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 64, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "934:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + } + ], + "name": "NormalSnapshot", + "nameLocation": "693:14:0", + "nodeType": "StructDefinition", + "scope": 196, + "src": "686:271:0", + "visibility": "public" + }, + { + "body": { + "id": 77, + "nodeType": "Block", + "src": "1033:112:0", + "statements": [ + { + "assignments": [ + 73 + ], + "declarations": [ + { + "constant": false, + "id": 73, + "mutability": "mutable", + "name": "position", + "nameLocation": "1051:8:0", + "nodeType": "VariableDeclaration", + "scope": 77, + "src": "1043:16:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 72, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "1043:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 75, + "initialValue": { + "id": 74, + "name": "STORAGE_POSITION", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 6, + "src": "1062:16:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1043:35:0" + }, + { + "AST": { + "nativeSrc": "1097:42:0", + "nodeType": "YulBlock", + "src": "1097:42:0", + "statements": [ + { + "nativeSrc": "1111:18:0", + "nodeType": "YulAssignment", + "src": "1111:18:0", + "value": { + "name": "position", + "nativeSrc": "1121:8:0", + "nodeType": "YulIdentifier", + "src": "1121:8:0" + }, + "variableNames": [ + { + "name": "s.slot", + "nativeSrc": "1111:6:0", + "nodeType": "YulIdentifier", + "src": "1111:6:0" + } + ] + } + ] + }, + "evmVersion": "prague", + "externalReferences": [ + { + "declaration": 73, + "isOffset": false, + "isSlot": false, + "src": "1121:8:0", + "valueSize": 1 + }, + { + "declaration": 70, + "isOffset": false, + "isSlot": true, + "src": "1111:6:0", + "suffix": "slot", + "valueSize": 1 + } + ], + "id": 76, + "nodeType": "InlineAssembly", + "src": "1088:51:0" + } + ] + }, + "id": 78, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getStorage", + "nameLocation": "972:10:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 67, + "nodeType": "ParameterList", + "parameters": [], + "src": "982:2:0" + }, + "returnParameters": { + "id": 71, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 70, + "mutability": "mutable", + "name": "s", + "nameLocation": "1030:1:0", + "nodeType": "VariableDeclaration", + "scope": 78, + "src": "1008:23:0", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + }, + "typeName": { + "id": 69, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 68, + "name": "NormalStorage", + "nameLocations": [ + "1008:13:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 45, + "src": "1008:13:0" + }, + "referencedDeclaration": 45, + "src": "1008:13:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + } + }, + "visibility": "internal" + } + ], + "src": "1007:25:0" + }, + "scope": 196, + "src": "963:182:0", + "stateMutability": "pure", + "virtual": false, + "visibility": "internal" + }, + { + "body": { + "id": 182, + "nodeType": "Block", + "src": "1272:527:0", + "statements": [ + { + "assignments": [ + 92 + ], + "declarations": [ + { + "constant": false, + "id": 92, + "mutability": "mutable", + "name": "s", + "nameLocation": "1304:1:0", + "nodeType": "VariableDeclaration", + "scope": 182, + "src": "1282:23:0", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + }, + "typeName": { + "id": 91, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 90, + "name": "NormalStorage", + "nameLocations": [ + "1282:13:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 45, + "src": "1282:13:0" + }, + "referencedDeclaration": 45, + "src": "1282:13:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + } + }, + "visibility": "internal" + } + ], + "id": 95, + "initialValue": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 93, + "name": "getStorage", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 78, + "src": "1308:10:0", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$__$returns$_t_struct$_NormalStorage_$45_storage_ptr_$", + "typeString": "function () pure returns (struct Normal.NormalStorage storage pointer)" + } + }, + "id": 94, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1308:12:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1282:38:0" + }, + { + "expression": { + "id": 101, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 96, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1330:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 98, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1339:11:0", + "memberName": "totalSupply", + "nodeType": "MemberAccess", + "referencedDeclaration": 47, + "src": "1330:20:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "id": 99, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1353:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 100, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1355:11:0", + "memberName": "totalSupply", + "nodeType": "MemberAccess", + "referencedDeclaration": 26, + "src": "1353:13:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1330:36:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 102, + "nodeType": "ExpressionStatement", + "src": "1330:36:0" + }, + { + "expression": { + "id": 108, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 103, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1376:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 105, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1385:6:0", + "memberName": "marker", + "nodeType": "MemberAccess", + "referencedDeclaration": 49, + "src": "1376:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "id": 106, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1394:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 107, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1396:6:0", + "memberName": "marker", + "nodeType": "MemberAccess", + "referencedDeclaration": 28, + "src": "1394:8:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "src": "1376:26:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "id": 109, + "nodeType": "ExpressionStatement", + "src": "1376:26:0" + }, + { + "expression": { + "id": 116, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 110, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1412:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 112, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1421:6:0", + "memberName": "headId", + "nodeType": "MemberAccess", + "referencedDeclaration": 51, + "src": "1412:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "id": 113, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1430:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 114, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1432:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1430:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 115, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1439:6:0", + "memberName": "headId", + "nodeType": "MemberAccess", + "referencedDeclaration": 18, + "src": "1430:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "src": "1412:33:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "id": 117, + "nodeType": "ExpressionStatement", + "src": "1412:33:0" + }, + { + "expression": { + "id": 125, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 118, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1455:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 120, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1464:6:0", + "memberName": "tailId", + "nodeType": "MemberAccess", + "referencedDeclaration": 53, + "src": "1455:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "expression": { + "id": 121, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1473:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 122, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1475:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1473:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 123, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1482:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1473:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 124, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1488:6:0", + "memberName": "tailId", + "nodeType": "MemberAccess", + "referencedDeclaration": 11, + "src": "1473:21:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "src": "1455:39:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "id": 126, + "nodeType": "ExpressionStatement", + "src": "1455:39:0" + }, + { + "expression": { + "id": 134, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 127, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1504:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 129, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1513:5:0", + "memberName": "count", + "nodeType": "MemberAccess", + "referencedDeclaration": 55, + "src": "1504:14:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "expression": { + "id": 130, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1521:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 131, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1523:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1521:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 132, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1530:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1521:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 133, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1536:5:0", + "memberName": "count", + "nodeType": "MemberAccess", + "referencedDeclaration": 13, + "src": "1521:20:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "src": "1504:37:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "id": 135, + "nodeType": "ExpressionStatement", + "src": "1504:37:0" + }, + { + "expression": { + "id": 143, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 136, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1551:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 138, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1560:6:0", + "memberName": "count2", + "nodeType": "MemberAccess", + "referencedDeclaration": 57, + "src": "1551:15:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "expression": { + "id": 139, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1569:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 140, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1571:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1569:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 141, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1578:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1569:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 142, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1584:6:0", + "memberName": "count2", + "nodeType": "MemberAccess", + "referencedDeclaration": 15, + "src": "1569:21:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "src": "1551:39:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 144, + "nodeType": "ExpressionStatement", + "src": "1551:39:0" + }, + { + "expression": { + "id": 151, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 145, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1600:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 147, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1609:5:0", + "memberName": "total", + "nodeType": "MemberAccess", + "referencedDeclaration": 59, + "src": "1600:14:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "id": 148, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1617:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 149, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1619:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1617:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 150, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1626:5:0", + "memberName": "total", + "nodeType": "MemberAccess", + "referencedDeclaration": 23, + "src": "1617:14:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "src": "1600:31:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "id": 152, + "nodeType": "ExpressionStatement", + "src": "1600:31:0" + }, + { + "expression": { + "id": 160, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 153, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1641:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 155, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1650:7:0", + "memberName": "balance", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "1641:16:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "baseExpression": { + "expression": { + "id": 156, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1660:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 157, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1662:8:0", + "memberName": "balances", + "nodeType": "MemberAccess", + "referencedDeclaration": 35, + "src": "1660:10:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + } + }, + "id": 159, + "indexExpression": { + "id": 158, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 80, + "src": "1671:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1660:19:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1641:38:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 161, + "nodeType": "ExpressionStatement", + "src": "1641:38:0" + }, + { + "expression": { + "id": 171, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 162, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1689:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 164, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1698:9:0", + "memberName": "allowance", + "nodeType": "MemberAccess", + "referencedDeclaration": 63, + "src": "1689:18:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "baseExpression": { + "baseExpression": { + "expression": { + "id": 165, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1710:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 166, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1712:10:0", + "memberName": "allowances", + "nodeType": "MemberAccess", + "referencedDeclaration": 41, + "src": "1710:12:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_mapping$_t_address_$_t_uint256_$_$", + "typeString": "mapping(address => mapping(address => uint256))" + } + }, + "id": 168, + "indexExpression": { + "id": 167, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 80, + "src": "1723:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1710:21:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + } + }, + "id": 170, + "indexExpression": { + "id": 169, + "name": "spender", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 82, + "src": "1732:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1710:30:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1689:51:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 172, + "nodeType": "ExpressionStatement", + "src": "1689:51:0" + }, + { + "expression": { + "id": 180, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 173, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1750:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 175, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1759:10:0", + "memberName": "smallValue", + "nodeType": "MemberAccess", + "referencedDeclaration": 65, + "src": "1750:19:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "baseExpression": { + "expression": { + "id": 176, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1772:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 177, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1774:11:0", + "memberName": "smallValues", + "nodeType": "MemberAccess", + "referencedDeclaration": 44, + "src": "1772:13:0", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint8_$dyn_storage", + "typeString": "uint8[] storage ref" + } + }, + "id": 179, + "indexExpression": { + "id": 178, + "name": "index", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 84, + "src": "1786:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1772:20:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "src": "1750:42:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 181, + "nodeType": "ExpressionStatement", + "src": "1750:42:0" + } + ] + }, + "functionSelector": "4fad3e6d", + "id": 183, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "readAll", + "nameLocation": "1160:7:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 85, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 80, + "mutability": "mutable", + "name": "account", + "nameLocation": "1176:7:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1168:15:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 79, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1168:7:0", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 82, + "mutability": "mutable", + "name": "spender", + "nameLocation": "1193:7:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1185:15:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 81, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1185:7:0", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 84, + "mutability": "mutable", + "name": "index", + "nameLocation": "1210:5:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1202:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 83, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1202:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "src": "1167:49:0" + }, + "returnParameters": { + "id": 89, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 88, + "mutability": "mutable", + "name": "snapshot", + "nameLocation": "1262:8:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1240:30:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot" + }, + "typeName": { + "id": 87, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 86, + "name": "NormalSnapshot", + "nameLocations": [ + "1240:14:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 66, + "src": "1240:14:0" + }, + "referencedDeclaration": 66, + "src": "1240:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_storage_ptr", + "typeString": "struct Normal.NormalSnapshot" + } + }, + "visibility": "internal" + } + ], + "src": "1239:32:0" + }, + "scope": 196, + "src": "1151:648:0", + "stateMutability": "view", + "virtual": false, + "visibility": "external" + }, + { + "body": { + "id": 194, + "nodeType": "Block", + "src": "1835:65:0", + "statements": [ + { + "expression": { + "id": 192, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "expression": { + "expression": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 186, + "name": "getStorage", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 78, + "src": "1845:10:0", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$__$returns$_t_struct$_NormalStorage_$45_storage_ptr_$", + "typeString": "function () pure returns (struct Normal.NormalStorage storage pointer)" + } + }, + "id": 187, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1845:12:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 188, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1858:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1845:19:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 189, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1865:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1845:25:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 190, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1871:6:0", + "memberName": "count2", + "nodeType": "MemberAccess", + "referencedDeclaration": 15, + "src": "1845:32:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 191, + "name": "SAMPLE_COUNT2", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9, + "src": "1880:13:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "src": "1845:48:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 193, + "nodeType": "ExpressionStatement", + "src": "1845:48:0" + } + ] + }, + "functionSelector": "ac5b16dc", + "id": 195, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "setCount2", + "nameLocation": "1814:9:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 184, + "nodeType": "ParameterList", + "parameters": [], + "src": "1823:2:0" + }, + "returnParameters": { + "id": 185, + "nodeType": "ParameterList", + "parameters": [], + "src": "1835:0:0" + }, + "scope": 196, + "src": "1805:95:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + } + ], + "scope": 197, + "src": "59:1843:0", + "usedErrors": [], + "usedEvents": [] + } + ], + "src": "32:1871:0" +} diff --git a/cli/test/adapters/IFrameworkAdapter/foundryAdapter/foundryAdapter.test.ts b/cli/test/adapters/IFrameworkAdapter/foundryAdapter/foundryAdapter.test.ts new file mode 100644 index 00000000..c5ce7ac2 --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/foundryAdapter/foundryAdapter.test.ts @@ -0,0 +1,39 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { foundryAdapter } from "../../../../src/adapters/foundryAdapter"; +import { canonicalizeAst } from "../canonicalAst"; +import { createFoundryAdapterFixtureHarness } from "./harness"; + +/** Tests Foundry AST compilation and artifact normalization. */ +describe("foundryAdapter.compileAst", () => { + it( + "generates and returns the expected Solidity AST", + async () => { + const harness = await createFoundryAdapterFixtureHarness(); + + try { + const artifactPath = path.join(harness.projectRoot, "out", "Normal.sol", "Normal.json"); + await expect(fs.access(artifactPath)).rejects.toThrow(); + + const result = await foundryAdapter.compileAst(harness.ctx, [ + path.join(harness.projectRoot, "src", "Normal.sol"), + ]); + const normalSource = result.find((source) => source.sourceName === "src/Normal.sol"); + const expected = JSON.parse( + await fs.readFile( + path.join(__dirname, "..", "fixtures", "normal", "expected", "foundry.ast.json"), + "utf8", + ), + ); + + await expect(fs.access(artifactPath)).resolves.toBeUndefined(); + expect(normalSource?.ast.nodeType).toBe("SourceUnit"); + expect(canonicalizeAst(normalSource!.ast)).toEqual(expected); + } finally { + await harness.cleanup(); + } + }, + 30_000, + ); +}); diff --git a/cli/test/adapters/IFrameworkAdapter/foundryAdapter/harness.ts b/cli/test/adapters/IFrameworkAdapter/foundryAdapter/harness.ts new file mode 100644 index 00000000..cd77b398 --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/foundryAdapter/harness.ts @@ -0,0 +1,33 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { ComposeContext } from "../../../../src/context/types"; +import { Context } from "../../../../src/context/context"; + +export type FoundryAdapterFixtureHarness = { + ctx: ComposeContext; + projectRoot: string; + cleanup(): Promise; +}; + +/** Creates an isolated Foundry project from the shared Solidity fixture. */ +export async function createFoundryAdapterFixtureHarness(): Promise { + const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compose-foundry-adapter-")); + const projectTemplateRoot = path.join(__dirname, "project"); + const normalFixturePath = path.join(__dirname, "..", "fixtures", "normal", "Normal.sol"); + const ctx = Context.create(); + + await fs.cp(projectTemplateRoot, projectRoot, { + recursive: true, + filter: (source) => !["cache", "node_modules"].includes(path.basename(source)), + }); + await fs.mkdir(path.join(projectRoot, "src"), { recursive: true }); + await fs.copyFile(normalFixturePath, path.join(projectRoot, "src", "Normal.sol")); + ctx.param.projectRoot = projectRoot; + + return { + ctx, + projectRoot, + cleanup: () => fs.rm(projectRoot, { recursive: true, force: true }), + }; +} diff --git a/cli/test/adapters/IFrameworkAdapter/foundryAdapter/project/.gitignore b/cli/test/adapters/IFrameworkAdapter/foundryAdapter/project/.gitignore new file mode 100644 index 00000000..d8a1d071 --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/foundryAdapter/project/.gitignore @@ -0,0 +1,2 @@ +cache/ +out/ diff --git a/cli/test/adapters/IFrameworkAdapter/foundryAdapter/project/foundry.toml b/cli/test/adapters/IFrameworkAdapter/foundryAdapter/project/foundry.toml new file mode 100644 index 00000000..53d42b50 --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/foundryAdapter/project/foundry.toml @@ -0,0 +1,4 @@ +[profile.default] +src = "src" +out = "out" +solc = "0.8.30" diff --git a/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/hardhatAdapter.test.ts b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/hardhatAdapter.test.ts new file mode 100644 index 00000000..0df1aa58 --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/hardhatAdapter.test.ts @@ -0,0 +1,82 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { beforeAll, describe, expect, it } from "vitest"; +import { + hardhatAdapter, + resolveHardhatAstSourcePath, +} from "../../../../src/adapters/hardhatAdapter"; +import { canonicalizeAst } from "../canonicalAst"; +import { + createHardhatAdapterFixtureHarness, + ensureHardhatFixtureDependencies, +} from "./harness"; + +/** Tests Hardhat AST compilation and build-info normalization. */ +describe("hardhatAdapter.compileAst", () => { + beforeAll(() => ensureHardhatFixtureDependencies(), 120_000); + + it("resolves project and versioned npm source names to readable paths", () => { + const projectRoot = path.resolve("project-root"); + + expect(resolveHardhatAstSourcePath( + projectRoot, + "project/contracts/CounterFacet.sol", + )).toBe(path.join(projectRoot, "contracts", "CounterFacet.sol")); + expect(resolveHardhatAstSourcePath( + projectRoot, + "npm/@perfect-abstractions/compose@0.0.4/diamond/DiamondInspectFacet.sol", + )).toBe(path.join( + projectRoot, + "node_modules", + "@perfect-abstractions", + "compose", + "diamond", + "DiamondInspectFacet.sol", + )); + }); + + it( + "generates and returns a fresh Solidity AST", + async () => { + const harness = await createHardhatAdapterFixtureHarness(); + + try { + const buildInfoRoot = path.join(harness.projectRoot, "artifacts", "build-info"); + await expect(fs.access(buildInfoRoot)).rejects.toThrow(); + + const result = await hardhatAdapter.compileAst(harness.ctx, [ + path.join(harness.projectRoot, "contracts", "Normal.sol"), + ]); + const buildInfoFiles = await fs.readdir(buildInfoRoot); + const normalSource = result.find( + (source) => source.sourceName === path.join( + harness.projectRoot, + "contracts", + "Normal.sol", + ), + ); + const expected = JSON.parse( + await fs.readFile( + path.join(__dirname, "..", "fixtures", "normal", "expected", "hardhat.ast.json"), + "utf8", + ), + ); + + expect(buildInfoFiles.some((file) => file.endsWith(".json"))).toBe(true); + expect(normalSource?.ast.nodeType).toBe("SourceUnit"); + expect(normalSource?.ast.nodes).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + name: "Normal", + nodeType: "ContractDefinition", + }), + ]), + ); + expect(canonicalizeAst(normalSource!.ast)).toEqual(expected); + } finally { + await harness.cleanup(); + } + }, + 30_000, + ); +}); diff --git a/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/harness.ts b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/harness.ts new file mode 100644 index 00000000..b1690209 --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/harness.ts @@ -0,0 +1,50 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { ComposeContext } from "../../../../src/context/types"; +import { Context } from "../../../../src/context/context"; +import { runCommand } from "../../../../src/utils/exec"; + +export type HardhatAdapterFixtureHarness = { + ctx: ComposeContext; + projectRoot: string; + cleanup(): Promise; +}; + +const projectTemplateRoot = path.join(__dirname, "project"); +const normalFixturePath = path.join(__dirname, "..", "fixtures", "normal", "Normal.sol"); + +/** Installs the fixture's locked local Hardhat dependency when needed. */ +export async function ensureHardhatFixtureDependencies(): Promise { + try { + await fs.access(path.join(projectTemplateRoot, "node_modules", "hardhat", "package.json")); + } catch { + await runCommand("npm", ["ci"], { cwd: projectTemplateRoot }); + } +} + +/** Creates an isolated Hardhat project from the shared Solidity fixture. */ +export async function createHardhatAdapterFixtureHarness(): Promise { + const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compose-hardhat-adapter-")); + const ctx = Context.create(); + + await fs.cp(projectTemplateRoot, projectRoot, { + recursive: true, + filter: (source) => + !["artifacts", "cache", "node_modules"].includes(path.basename(source)), + }); + await fs.mkdir(path.join(projectRoot, "contracts"), { recursive: true }); + await fs.copyFile(normalFixturePath, path.join(projectRoot, "contracts", "Normal.sol")); + await fs.symlink( + path.join(projectTemplateRoot, "node_modules"), + path.join(projectRoot, "node_modules"), + process.platform === "win32" ? "junction" : "dir", + ); + ctx.param.projectRoot = projectRoot; + + return { + ctx, + projectRoot, + cleanup: () => fs.rm(projectRoot, { recursive: true, force: true }), + }; +} diff --git a/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/.gitignore b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/.gitignore new file mode 100644 index 00000000..d9691c04 --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/.gitignore @@ -0,0 +1,3 @@ +artifacts/ +cache/ +node_modules/ diff --git a/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/hardhat.config.ts b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/hardhat.config.ts new file mode 100644 index 00000000..6f7d176b --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/hardhat.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "hardhat/config"; + +export default defineConfig({ + solidity: { + version: "0.8.30", + }, +}); diff --git a/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/package-lock.json b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/package-lock.json new file mode 100644 index 00000000..3c08d8fd --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/package-lock.json @@ -0,0 +1,1183 @@ +{ + "name": "compose-hardhat-ast-fixture", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "compose-hardhat-ast-fixture", + "devDependencies": { + "hardhat": "3.8.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@noble/curves": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", + "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.4.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", + "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nomicfoundation/edr": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr/-/edr-0.12.0.tgz", + "integrity": "sha512-dVfrB70L//W05s+s+/c6n52Fct+kVKoXYT+/CKL9ZsNdq/yLr5LaPNsvpVkQHP1JdAiOrubUzA/MwZIk4gRxAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/edr-darwin-arm64": "0.12.0", + "@nomicfoundation/edr-darwin-x64": "0.12.0", + "@nomicfoundation/edr-linux-arm64-gnu": "0.12.0", + "@nomicfoundation/edr-linux-arm64-musl": "0.12.0", + "@nomicfoundation/edr-linux-x64-gnu": "0.12.0", + "@nomicfoundation/edr-linux-x64-musl": "0.12.0", + "@nomicfoundation/edr-win32-x64-msvc": "0.12.0" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-darwin-arm64": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-arm64/-/edr-darwin-arm64-0.12.0.tgz", + "integrity": "sha512-z/8jU2dgZjhY2iLtJ1DGi3t/N2xbmjgok9K3R0f7+UZxSSJ5LbXCFn5So33fVh47RzGzOqEB+Yk4SdyUq2odqw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-darwin-x64": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-darwin-x64/-/edr-darwin-x64-0.12.0.tgz", + "integrity": "sha512-F9RrA60mEtxfKFiGB7QsydoUwzz4ckoustVMFegcIKmjRxRVb1qrRYiAc9oQiKMdJWIZKDYsOpHABJ9Um4U/+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-linux-arm64-gnu": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-gnu/-/edr-linux-arm64-gnu-0.12.0.tgz", + "integrity": "sha512-EtGRZbh1d4BF/1SIG5rVrKQ9R0nuNvPgCYiU5fCmY3bojAFOUf4m7I2ezIhim1vb1QBlXmFaoFNPZFIdMltBGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-linux-arm64-musl": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-arm64-musl/-/edr-linux-arm64-musl-0.12.0.tgz", + "integrity": "sha512-5gWtmKuVfftcO1OUbF/3KTPVSR6klC7RI9Z96G5lDO325jQhYqOG+hkvDPKtM+nbYf+A0veOndghqbUgAX+E4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-linux-x64-gnu": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-gnu/-/edr-linux-x64-gnu-0.12.0.tgz", + "integrity": "sha512-0Ty0fov3/NFL0dIshNTGCi06sjVzsgB7k+n9LAoj+57OsqP9X4e3P5XwjlTSNuyYshv8JdYMHqY+1ZIZ8SHsyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-linux-x64-musl": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-linux-x64-musl/-/edr-linux-x64-musl-0.12.0.tgz", + "integrity": "sha512-e5du3t17vdB3tAY0kl3Ip1cu8CcwiaeJeUC4mMPmL91HDM//AQehqpyIP8upam4AeIl1H6FA3xIUIDA6VOZSxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/edr-win32-x64-msvc": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@nomicfoundation/edr-win32-x64-msvc/-/edr-win32-x64-msvc-0.12.0.tgz", + "integrity": "sha512-wD8YxhFdlY2IXb7uVrSF7VHartFKXEYeiE6ISJOV10Y7YOUE1IzfwB0QOdKtLUepwB+HXis+KRy4h22dWEbT0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@nomicfoundation/hardhat-errors": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-errors/-/hardhat-errors-3.0.20.tgz", + "integrity": "sha512-6wmiB5zkqQ+3d+3Ta/vl0MvlIXdrAwUF+OMX2bi7RbCgBfGbbjBbzQhXmJUuhx6XDMPeswdRjjOChpK07tDNmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/hardhat-utils": "^4.1.5" + } + }, + "node_modules/@nomicfoundation/hardhat-utils": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-utils/-/hardhat-utils-4.1.7.tgz", + "integrity": "sha512-syZ1OqcJUR1l8JD8cP96PPtjn+55GJX6c7q2e0z1WhT6/DjQJZCbOiET1MCkIr6HK6k+ZMfsTR7KbnhrRiL0CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@streamparser/json-node": "^0.0.22", + "env-paths": "^4.0.0", + "ethereum-cryptography": "^2.2.1", + "fast-equals": "^5.4.0", + "json-stream-stringify": "^3.1.7", + "rfdc": "^1.3.1", + "undici": "^6.27.0" + } + }, + "node_modules/@nomicfoundation/hardhat-vendored": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-vendored/-/hardhat-vendored-3.0.4.tgz", + "integrity": "sha512-RO8Otj1FvRvxJmXzkxh1vTwK/+cqSVPYLqY6RrWkmzHEEcxnAwAFsBYdW7xyTEyW/pVbSSNd2gs3aoGdGZaoNA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nomicfoundation/hardhat-zod-utils": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-zod-utils/-/hardhat-zod-utils-3.0.6.tgz", + "integrity": "sha512-DraBttTle+tRi24wkEfTYUrsX/Cdp74ySXcc4MUzMQPBQNUTFx5YyTH7qFhXQdz3V171iqLAQpOAhNcEqwmq9Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/hardhat-errors": "^3.0.13", + "@nomicfoundation/hardhat-utils": "^4.1.2" + }, + "peerDependencies": { + "zod": "^3.23.8" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer/-/solidity-analyzer-0.1.2.tgz", + "integrity": "sha512-q4n32/FNKIhQ3zQGGw5CvPF6GTvDCpYwIf7bEY/dZTZbgfDsHyjJwURxUJf3VQuuJj+fDIFl4+KkBVbw4Ef6jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12" + }, + "optionalDependencies": { + "@nomicfoundation/solidity-analyzer-darwin-arm64": "0.1.2", + "@nomicfoundation/solidity-analyzer-darwin-x64": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-arm64-gnu": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-arm64-musl": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-x64-gnu": "0.1.2", + "@nomicfoundation/solidity-analyzer-linux-x64-musl": "0.1.2", + "@nomicfoundation/solidity-analyzer-win32-x64-msvc": "0.1.2" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-arm64": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-arm64/-/solidity-analyzer-darwin-arm64-0.1.2.tgz", + "integrity": "sha512-JaqcWPDZENCvm++lFFGjrDd8mxtf+CtLd2MiXvMNTBD33dContTZ9TWETwNFwg7JTJT5Q9HEecH7FA+HTSsIUw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-darwin-x64": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-darwin-x64/-/solidity-analyzer-darwin-x64-0.1.2.tgz", + "integrity": "sha512-fZNmVztrSXC03e9RONBT+CiksSeYcxI1wlzqyr0L7hsQlK1fzV+f04g2JtQ1c/Fe74ZwdV6aQBdd6Uwl1052sw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-gnu/-/solidity-analyzer-linux-arm64-gnu-0.1.2.tgz", + "integrity": "sha512-3d54oc+9ZVBuB6nbp8wHylk4xh0N0Gc+bk+/uJae+rUgbOBwQSfuGIbAZt1wBXs5REkSmynEGcqx6DutoK0tPA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-musl": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-arm64-musl/-/solidity-analyzer-linux-arm64-musl-0.1.2.tgz", + "integrity": "sha512-iDJfR2qf55vgsg7BtJa7iPiFAsYf2d0Tv/0B+vhtnI16+wfQeTbP7teookbGvAo0eJo7aLLm0xfS/GTkvHIucA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-gnu": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-gnu/-/solidity-analyzer-linux-x64-gnu-0.1.2.tgz", + "integrity": "sha512-9dlHMAt5/2cpWyuJ9fQNOUXFB/vgSFORg1jpjX1Mh9hJ/MfZXlDdHQ+DpFCs32Zk5pxRBb07yGvSHk9/fezL+g==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-linux-x64-musl": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-linux-x64-musl/-/solidity-analyzer-linux-x64-musl-0.1.2.tgz", + "integrity": "sha512-GzzVeeJob3lfrSlDKQw2bRJ8rBf6mEYaWY+gW0JnTDHINA0s2gPR4km5RLIj1xeZZOYz4zRw+AEeYgLRqB2NXg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@nomicfoundation/solidity-analyzer-win32-x64-msvc": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@nomicfoundation/solidity-analyzer-win32-x64-msvc/-/solidity-analyzer-win32-x64-msvc-0.1.2.tgz", + "integrity": "sha512-Fdjli4DCcFHb4Zgsz0uEJXZ2K7VEO+w5KVv7HmT7WO10iODdU9csC2az4jrhEsRtiR9Gfd74FlG0NYlw1BMdyA==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/@scure/base": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", + "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", + "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.4.0", + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", + "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.4.0", + "@scure/base": "~1.1.6" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@sentry/core": { + "version": "9.47.1", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-9.47.1.tgz", + "integrity": "sha512-KX62+qIt4xgy8eHKHiikfhz2p5fOciXd0Cl+dNzhgPFq8klq4MGMNaf148GB3M/vBqP4nw/eFvRMAayFCgdRQw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@streamparser/json": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@streamparser/json/-/json-0.0.22.tgz", + "integrity": "sha512-b6gTSBjJ8G8SuO3Gbbj+zXbVx8NSs1EbpbMKpzGLWMdkR+98McH9bEjSz3+0mPJf68c5nxa3CrJHp5EQNXM6zQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@streamparser/json-node": { + "version": "0.0.22", + "resolved": "https://registry.npmjs.org/@streamparser/json-node/-/json-node-0.0.22.tgz", + "integrity": "sha512-sJT2ptNRwqB1lIsQrQlCoWk5rF4tif9wDh+7yluAGijJamAhrHGYpFB/Zg3hJeceoZypi74ftXk8DHzwYpbZSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@streamparser/json": "^0.0.22" + } + }, + "node_modules/adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.3.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/env-paths": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-4.0.0.tgz", + "integrity": "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-safe-filename": "^0.1.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, + "node_modules/ethereum-cryptography": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", + "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "1.4.2", + "@noble/hashes": "1.4.0", + "@scure/bip32": "1.4.0", + "@scure/bip39": "1.3.0" + } + }, + "node_modules/fast-equals": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.1.tgz", + "integrity": "sha512-DjlFSM5Pk9cGcL0q5QXl66eGzx0N6szNgaswwc5ZphlBohjTVJSnGgI+rJVOgOi65qUoQnDZN4nDqi33udtydQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/hardhat": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/hardhat/-/hardhat-3.8.0.tgz", + "integrity": "sha512-luWXeLA4dcEoj3QrJhmnf+kRyGn9SrrUN9zf2KlrysXt9U3VIhLYZ8vNIA8HP4fZuvrlKMXHRdLDu0+9Gnmrkg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nomicfoundation/edr": "0.12.0", + "@nomicfoundation/hardhat-errors": "^3.0.15", + "@nomicfoundation/hardhat-utils": "^4.1.3", + "@nomicfoundation/hardhat-vendored": "^3.0.4", + "@nomicfoundation/hardhat-zod-utils": "^3.0.5", + "@nomicfoundation/solidity-analyzer": "^0.1.1", + "@sentry/core": "^9.4.0", + "adm-zip": "^0.4.16", + "chokidar": "^4.0.3", + "enquirer": "^2.3.0", + "ethereum-cryptography": "^2.2.1", + "micro-eth-signer": "^0.14.0", + "p-map": "^7.0.2", + "resolve.exports": "^2.0.3", + "semver": "^7.6.3", + "tsx": "^4.19.3", + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "bin": { + "hardhat": "dist/src/cli.js" + } + }, + "node_modules/is-safe-filename": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-safe-filename/-/is-safe-filename-0.1.1.tgz", + "integrity": "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/json-stream-stringify": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/json-stream-stringify/-/json-stream-stringify-3.1.7.tgz", + "integrity": "sha512-F4MWetLtY42YMaAKw5cV4e47zMD5aOT+tjjQWjX18ACtdkQ5Y/vrcfbcQ107Rh+MXjOCIx4KhW0wPmOvG8iQ5w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=7.10.1" + } + }, + "node_modules/micro-eth-signer": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/micro-eth-signer/-/micro-eth-signer-0.14.0.tgz", + "integrity": "sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.8.1", + "@noble/hashes": "~1.7.1", + "micro-packed": "~0.7.2" + } + }, + "node_modules/micro-eth-signer/node_modules/@noble/curves": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.2.tgz", + "integrity": "sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.2" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-eth-signer/node_modules/@noble/hashes": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.2.tgz", + "integrity": "sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-packed": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/micro-packed/-/micro-packed-0.7.3.tgz", + "integrity": "sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/micro-packed/node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/p-map": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.6.tgz", + "integrity": "sha512-I4Prw6ivkd6p8PiYR1tXASOAOBzIJwu0TB7fqaX0c/8c3QAehNYmX57EijyGGGBt3c/BIowGwV03RVBtXvHEVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/resolve.exports": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", + "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tsx": { + "version": "4.23.12", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.12.tgz", + "integrity": "sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.28.0" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/undici": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", + "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/package.json b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/package.json new file mode 100644 index 00000000..cb496401 --- /dev/null +++ b/cli/test/adapters/IFrameworkAdapter/hardhatAdapter/project/package.json @@ -0,0 +1,8 @@ +{ + "name": "compose-hardhat-ast-fixture", + "private": true, + "type": "module", + "devDependencies": { + "hardhat": "3.8.0" + } +} diff --git a/cli/test/comander.test.ts b/cli/test/comander.test.ts index 65e5bf91..985ab631 100644 --- a/cli/test/comander.test.ts +++ b/cli/test/comander.test.ts @@ -1,6 +1,43 @@ import { describe, expect, it } from "vitest"; import { parseArgs } from "../src/comander"; +describe("validate command arguments", () => { + it("accepts a Compose project root", () => { + expect(parseArgs(["node", "compose", "validate", "--project-root", "./example"])).toEqual({ + command: "validate", + flags: { projectRoot: "./example" }, + }); + }); +}); + +describe("init command arguments", () => { + it("normalizes --out to the project output directory parameter", () => { + expect( + parseArgs([ + "node", + "compose", + "init", + "example", + "--base", + "counter", + "--out", + "./projects", + "--yes", + ]), + ).toEqual({ + command: "init", + flags: { + framework: "foundry", + toolbox: "ethers", + projectName: "example", + base: "counter", + outDir: "./projects", + yes: true, + }, + }); + }); +}); + describe("rpc command", () => { it("parses chain and optional address flags", () => { const result = parseArgs([ diff --git a/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts b/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts new file mode 100644 index 00000000..5a957103 --- /dev/null +++ b/cli/test/modules/pipelineBuilder/pipelineBuilder.test.ts @@ -0,0 +1,22 @@ +import { describe, expect, it, vi } from "vitest"; +import { Context } from "../../../src/context/context"; +import { PipelineBuilderModule } from "../../../src/modules/pipelineBuilder/module"; +import { ValidatePipeline } from "../../../src/pipelines/validatePipeline"; + +describe("PipelineBuilderModule", () => { + it("routes the validate command to ValidatePipeline", async () => { + const ctx = Context.create(); + ctx.param.command = "validate"; + const execute = vi.spyOn(ValidatePipeline, "execute").mockResolvedValue(ctx); + + try { + const result = await PipelineBuilderModule.route(ctx); + + expect(result).toBe(ctx); + expect(execute).toHaveBeenCalledWith(ctx); + expect(ctx.state.commandSelected?.success).toBe(true); + } finally { + execute.mockRestore(); + } + }); +}); diff --git a/cli/test/modules/validation/astSelectors.test.ts b/cli/test/modules/validation/astSelectors.test.ts new file mode 100644 index 00000000..6fe10cbc --- /dev/null +++ b/cli/test/modules/validation/astSelectors.test.ts @@ -0,0 +1,284 @@ +import { describe, expect, it, vi } from "vitest"; +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { + IFrameworkAdapter, + SolidityAstSource, +} from "../../../src/adapters/interface/IFrameworkAdapter"; +import { HashingAdapter } from "../../../src/adapters/hashingAdapter"; +import { Context } from "../../../src/context/context"; +import { ModuleState } from "../../../src/context/types"; +import { ValidationModule } from "../../../src/modules/validation/module"; +import { + FacetScanStateResult, + SelectorExportValidationResult, +} from "../../../src/modules/validation/types"; +import { ValidatePipeline } from "../../../src/pipelines/validatePipeline"; +import { DependencyResolver } from "../../../src/resolver/dependencyResolver"; + +const parameter = (id: number, name: string) => ({ + id, + nodeType: "VariableDeclaration", + typeName: { id: id + 100, name, nodeType: "ElementaryTypeName" }, +}); + +const functionNode = ( + id: number, + name: string, + visibility: "external" | "public", + parameters: unknown[] = [], +) => ({ + id, + kind: "function", + name, + nodeType: "FunctionDefinition", + parameters: { id: id + 200, nodeType: "ParameterList", parameters }, + visibility, +}); + +const selectorReference = (id: number, functionId: number, name: string) => ({ + expression: { + expression: { id: id + 2, name: "this", nodeType: "Identifier" }, + id: id + 1, + memberName: name, + nodeType: "MemberAccess", + referencedDeclaration: functionId, + }, + id, + memberName: "selector", + nodeType: "MemberAccess", +}); + +const astSources: SolidityAstSource[] = [ + { + sourceName: "src/SelectorFacet.sol", + ast: { + id: 1000, + nodeType: "SourceUnit", + src: "0:0:0", + nodes: [ + { + id: 10, + linearizedBaseContracts: [10], + name: "BaseFacet", + nodeType: "ContractDefinition", + nodes: [ + functionNode(11, "foo", "external", [parameter(12, "uint256")]), + functionNode(13, "bar", "public", [parameter(14, "address")]), + ], + }, + { + id: 20, + linearizedBaseContracts: [20, 10], + name: "SelectorFacet", + nodeType: "ContractDefinition", + nodes: [ + functionNode(21, "baz", "external"), + { + body: { + id: 24, + nodeType: "Block", + statements: [ + selectorReference(25, 11, "foo"), + selectorReference(28, 21, "baz"), + ], + }, + id: 22, + kind: "function", + name: "exportSelectors", + nodeType: "FunctionDefinition", + parameters: { id: 23, nodeType: "ParameterList", parameters: [] }, + visibility: "external", + }, + ], + }, + ], + }, + }, +]; + +describe("ValidationModule AST selector scan", () => { + it("uses source path to disambiguate contracts with the same name", () => { + const ctx = Context.create(); + const duplicateSources: SolidityAstSource[] = [ + namedContractSource("src/a/Foo.sol", 3000, "fromA"), + namedContractSource("src/b/Foo.sol", 4000, "fromB"), + ]; + + ValidationModule.scanFacetSelectors(ctx, duplicateSources, [{ + contractName: "Foo", + sourcePath: "src/b/Foo.sol", + }]); + + const scan = ctx.state.facetScan as ModuleState; + expect(scan.result?.facets).toEqual([ + expect.objectContaining({ + facetName: "Foo", + path: "src/b/Foo.sol", + functions: [expect.objectContaining({ signature: "fromB()" })], + }), + ]); + }); + + it("resolves inherited functions and reports missing exports as warnings", async () => { + const ctx = Context.create(); + + ValidationModule.scanFacetSelectors(ctx, astSources, [{ + contractName: "SelectorFacet", + sourcePath: "src/SelectorFacet.sol", + }]); + await ValidationModule.validateSelectorExports(ctx); + + const scan = ctx.state.facetScan as ModuleState; + const facet = scan.result?.facets[0]; + const validation = ctx.state.validationSelectorExports as ModuleState; + + if (!facet) { + throw new Error("SelectorFacet scan result was not produced."); + } + + expect(facet.functions.map((fn) => fn.signature)).toEqual([ + "baz()", + "foo(uint256)", + "bar(address)", + ]); + expect(facet.exportedSelectors).toEqual(["foo(uint256)", "baz()"]); + expect(facet.missingExports).toEqual(["bar(address)"]); + expect(validation.success).toBe(true); + expect(validation.result?.issues).toEqual([ + { + facetName: "SelectorFacet", + path: "src/SelectorFacet.sol", + missingExportSelectorsFunction: false, + missingExports: ["bar(address)"], + extraExports: [], + }, + ]); + expect(ValidationModule.hasSelectorExportFailure(ctx)).toBe(false); + }); + + it("warns when a facet does not declare exportSelectors", async () => { + const ctx = Context.create(); + + ValidationModule.scanFacetSelectors(ctx, astSources, [{ + contractName: "BaseFacet", + sourcePath: "src/SelectorFacet.sol", + }]); + await ValidationModule.validateSelectorExports(ctx); + + const validation = ctx.state.validationSelectorExports as ModuleState; + + expect(validation.success).toBe(true); + expect(validation.result?.issues).toEqual([ + { + facetName: "BaseFacet", + path: "src/SelectorFacet.sol", + missingExportSelectorsFunction: true, + missingExports: ["foo(uint256)", "bar(address)"], + extraExports: [], + }, + ]); + }); + + it("runs from the validate command using compose.json project inputs", async () => { + const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compose-validate-command-")); + const ctx = Context.create(); + const compileAst = vi.fn(async () => astSources); + const adapter = { compileAst } as unknown as IFrameworkAdapter; + const resolveDependencies = vi.spyOn(DependencyResolver, "resolve").mockResolvedValue({ + foundry: adapter, + hashing: HashingAdapter, + }); + const detectSelectorCollisions = vi.spyOn(ValidationModule, "detectSelectorCollisions"); + const buildVirtualStorageLayout = vi.spyOn(ValidationModule, "buildVirtualStorageLayout"); + const output = vi.spyOn(console, "log").mockImplementation(() => undefined); + + try { + await fs.writeFile( + path.join(projectRoot, "compose.json"), + JSON.stringify({ + framework: "foundry", + diamonds: { + Example: { + contract: "src/Diamond.sol:Diamond", + facets: { + SelectorFacet: { + source: "local", + contract: "src/SelectorFacet.sol:SelectorFacet", + }, + }, + }, + }, + }), + "utf8", + ); + ctx.param.command = "validate"; + ctx.param.projectRoot = projectRoot; + + const result = await ValidatePipeline.execute(ctx); + + expect(result.status.success).toBe(true); + expect(result.state.validationProject?.success).toBe(true); + expect(result.state.validatePipeline?.success).toBe(true); + expect(compileAst).toHaveBeenCalledWith(ctx, [ + path.join(projectRoot, "src", "SelectorFacet.sol"), + ]); + expect(detectSelectorCollisions).toHaveBeenCalledWith(ctx, { + hashing: HashingAdapter, + scopes: [{ + diamondName: "Example", + facets: [{ + contractName: "SelectorFacet", + sourcePath: path.join(projectRoot, "src", "SelectorFacet.sol"), + }], + }], + }); + expect(buildVirtualStorageLayout).toHaveBeenCalledWith( + ctx, + astSources, + [{ + contractName: "SelectorFacet", + sourcePath: path.join(projectRoot, "src", "SelectorFacet.sol"), + }], + [{ + diamondName: "Example", + facets: [{ + contractName: "SelectorFacet", + sourcePath: path.join(projectRoot, "src", "SelectorFacet.sol"), + }], + }], + ); + expect(output.mock.calls.flat().some((value) => String(value).includes("Validation passed"))) + .toBe(true); + } finally { + output.mockRestore(); + detectSelectorCollisions.mockRestore(); + buildVirtualStorageLayout.mockRestore(); + resolveDependencies.mockRestore(); + await fs.rm(projectRoot, { recursive: true, force: true }); + } + }); +}); + +function namedContractSource( + sourceName: string, + id: number, + functionName: string, +): SolidityAstSource { + return { + sourceName, + ast: { + id: id + 100, + nodeType: "SourceUnit", + src: "0:0:0", + nodes: [{ + id, + linearizedBaseContracts: [id], + name: "Foo", + nodeType: "ContractDefinition", + nodes: [functionNode(id + 1, functionName, "external")], + }], + }, + }; +} diff --git a/cli/test/modules/validation/fixtures/normal/Normal.sol b/cli/test/modules/validation/fixtures/normal/Normal.sol new file mode 100644 index 00000000..4890d08e --- /dev/null +++ b/cli/test/modules/validation/fixtures/normal/Normal.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.30; + +contract Normal { + bytes32 constant STORAGE_POSITION = keccak256("evmole.normal"); + uint8 constant SAMPLE_COUNT2 = 7; + + struct InnerRecord { + bytes4 tailId; + uint64 count; + uint8 count2; + } + + struct DirectRecord { + bytes4 headId; + InnerRecord inner; + uint32 total; + } + + struct NormalStorage { + uint256 totalSupply; + bytes4 marker; + DirectRecord record; + mapping(address account => uint256 value) balances; + mapping(address account => mapping(address spender => uint256 value)) allowances; + uint8[] smallValues; + } + + struct NormalSnapshot { + uint256 totalSupply; + bytes4 marker; + bytes4 headId; + bytes4 tailId; + uint64 count; + uint8 count2; + uint32 total; + uint256 balance; + uint256 allowance; + uint8 smallValue; + } + + function getStorage() internal pure returns (NormalStorage storage s) { + bytes32 position = STORAGE_POSITION; + assembly { + s.slot := position + } + } + + function readAll(address account, address spender, uint256 index) external view returns (NormalSnapshot memory snapshot) { + NormalStorage storage s = getStorage(); + snapshot.totalSupply = s.totalSupply; + snapshot.marker = s.marker; + snapshot.headId = s.record.headId; + snapshot.tailId = s.record.inner.tailId; + snapshot.count = s.record.inner.count; + snapshot.count2 = s.record.inner.count2; + snapshot.total = s.record.total; + snapshot.balance = s.balances[account]; + snapshot.allowance = s.allowances[account][spender]; + snapshot.smallValue = s.smallValues[index]; + } + + function setCount2() external { + getStorage().record.inner.count2 = SAMPLE_COUNT2; + } +} diff --git a/cli/test/modules/validation/fixtures/normal/expected/hardhat.ast.json b/cli/test/modules/validation/fixtures/normal/expected/hardhat.ast.json new file mode 100644 index 00000000..8d5bc4e9 --- /dev/null +++ b/cli/test/modules/validation/fixtures/normal/expected/hardhat.ast.json @@ -0,0 +1,2434 @@ +{ + "absolutePath": "project/contracts/Normal.sol", + "exportedSymbols": { + "Normal": [ + 196 + ] + }, + "id": 197, + "license": "MIT", + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1, + "literals": [ + "solidity", + ">=", + "0.8", + ".30" + ], + "nodeType": "PragmaDirective", + "src": "32:25:0" + }, + { + "abstract": false, + "baseContracts": [], + "canonicalName": "Normal", + "contractDependencies": [], + "contractKind": "contract", + "fullyImplemented": true, + "id": 196, + "linearizedBaseContracts": [ + 196 + ], + "name": "Normal", + "nameLocation": "68:6:0", + "nodeType": "ContractDefinition", + "nodes": [ + { + "constant": true, + "id": 6, + "mutability": "constant", + "name": "STORAGE_POSITION", + "nameLocation": "98:16:0", + "nodeType": "VariableDeclaration", + "scope": 196, + "src": "81:62:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 2, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "81:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": { + "arguments": [ + { + "hexValue": "65766d6f6c652e6e6f726d616c", + "id": 4, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "127:15:0", + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_b4df32537f6767405c9db7d67260e5375218aecdea91f4240ad14000623cbdff", + "typeString": "literal_string \"evmole.normal\"" + }, + "value": "evmole.normal" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_b4df32537f6767405c9db7d67260e5375218aecdea91f4240ad14000623cbdff", + "typeString": "literal_string \"evmole.normal\"" + } + ], + "id": 3, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "117:9:0", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 5, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "117:26:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + }, + { + "constant": true, + "id": 9, + "mutability": "constant", + "name": "SAMPLE_COUNT2", + "nameLocation": "164:13:0", + "nodeType": "VariableDeclaration", + "scope": 196, + "src": "149:32:0", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 7, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "149:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "value": { + "hexValue": "37", + "id": 8, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "180:1:0", + "typeDescriptions": { + "typeIdentifier": "t_rational_7_by_1", + "typeString": "int_const 7" + }, + "value": "7" + }, + "visibility": "internal" + }, + { + "canonicalName": "Normal.InnerRecord", + "id": 16, + "members": [ + { + "constant": false, + "id": 11, + "mutability": "mutable", + "name": "tailId", + "nameLocation": "224:6:0", + "nodeType": "VariableDeclaration", + "scope": 16, + "src": "217:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 10, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "217:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 13, + "mutability": "mutable", + "name": "count", + "nameLocation": "247:5:0", + "nodeType": "VariableDeclaration", + "scope": 16, + "src": "240:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 12, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "240:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 15, + "mutability": "mutable", + "name": "count2", + "nameLocation": "268:6:0", + "nodeType": "VariableDeclaration", + "scope": 16, + "src": "262:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 14, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "262:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + } + ], + "name": "InnerRecord", + "nameLocation": "195:11:0", + "nodeType": "StructDefinition", + "scope": 196, + "src": "188:93:0", + "visibility": "public" + }, + { + "canonicalName": "Normal.DirectRecord", + "id": 24, + "members": [ + { + "constant": false, + "id": 18, + "mutability": "mutable", + "name": "headId", + "nameLocation": "324:6:0", + "nodeType": "VariableDeclaration", + "scope": 24, + "src": "317:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 17, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "317:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 21, + "mutability": "mutable", + "name": "inner", + "nameLocation": "352:5:0", + "nodeType": "VariableDeclaration", + "scope": 24, + "src": "340:17:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage_ptr", + "typeString": "struct Normal.InnerRecord" + }, + "typeName": { + "id": 20, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 19, + "name": "InnerRecord", + "nameLocations": [ + "340:11:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 16, + "src": "340:11:0" + }, + "referencedDeclaration": 16, + "src": "340:11:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage_ptr", + "typeString": "struct Normal.InnerRecord" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 23, + "mutability": "mutable", + "name": "total", + "nameLocation": "374:5:0", + "nodeType": "VariableDeclaration", + "scope": 24, + "src": "367:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + }, + "typeName": { + "id": 22, + "name": "uint32", + "nodeType": "ElementaryTypeName", + "src": "367:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "visibility": "internal" + } + ], + "name": "DirectRecord", + "nameLocation": "294:12:0", + "nodeType": "StructDefinition", + "scope": 196, + "src": "287:99:0", + "visibility": "public" + }, + { + "canonicalName": "Normal.NormalStorage", + "id": 45, + "members": [ + { + "constant": false, + "id": 26, + "mutability": "mutable", + "name": "totalSupply", + "nameLocation": "431:11:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "423:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 25, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "423:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 28, + "mutability": "mutable", + "name": "marker", + "nameLocation": "459:6:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "452:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 27, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "452:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 31, + "mutability": "mutable", + "name": "record", + "nameLocation": "488:6:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "475:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage_ptr", + "typeString": "struct Normal.DirectRecord" + }, + "typeName": { + "id": 30, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 29, + "name": "DirectRecord", + "nameLocations": [ + "475:12:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 24, + "src": "475:12:0" + }, + "referencedDeclaration": 24, + "src": "475:12:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage_ptr", + "typeString": "struct Normal.DirectRecord" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 35, + "mutability": "mutable", + "name": "balances", + "nameLocation": "546:8:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "504:50:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "typeName": { + "id": 34, + "keyName": "account", + "keyNameLocation": "520:7:0", + "keyType": { + "id": 32, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "512:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "504:41:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "valueName": "value", + "valueNameLocation": "539:5:0", + "valueType": { + "id": 33, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "531:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 41, + "mutability": "mutable", + "name": "allowances", + "nameLocation": "634:10:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "564:80:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_mapping$_t_address_$_t_uint256_$_$", + "typeString": "mapping(address => mapping(address => uint256))" + }, + "typeName": { + "id": 40, + "keyName": "account", + "keyNameLocation": "580:7:0", + "keyType": { + "id": 36, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "572:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "564:69:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_mapping$_t_address_$_t_uint256_$_$", + "typeString": "mapping(address => mapping(address => uint256))" + }, + "valueName": "", + "valueNameLocation": "-1:-1:-1", + "valueType": { + "id": 39, + "keyName": "spender", + "keyNameLocation": "607:7:0", + "keyType": { + "id": 37, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "599:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "591:41:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + }, + "valueName": "value", + "valueNameLocation": "626:5:0", + "valueType": { + "id": 38, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "618:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 44, + "mutability": "mutable", + "name": "smallValues", + "nameLocation": "662:11:0", + "nodeType": "VariableDeclaration", + "scope": 45, + "src": "654:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint8_$dyn_storage_ptr", + "typeString": "uint8[]" + }, + "typeName": { + "baseType": { + "id": 42, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "654:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 43, + "nodeType": "ArrayTypeName", + "src": "654:7:0", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint8_$dyn_storage_ptr", + "typeString": "uint8[]" + } + }, + "visibility": "internal" + } + ], + "name": "NormalStorage", + "nameLocation": "399:13:0", + "nodeType": "StructDefinition", + "scope": 196, + "src": "392:288:0", + "visibility": "public" + }, + { + "canonicalName": "Normal.NormalSnapshot", + "id": 66, + "members": [ + { + "constant": false, + "id": 47, + "mutability": "mutable", + "name": "totalSupply", + "nameLocation": "726:11:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "718:19:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 46, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "718:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 49, + "mutability": "mutable", + "name": "marker", + "nameLocation": "754:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "747:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 48, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "747:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 51, + "mutability": "mutable", + "name": "headId", + "nameLocation": "777:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "770:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 50, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "770:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 53, + "mutability": "mutable", + "name": "tailId", + "nameLocation": "800:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "793:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 52, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "793:6:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 55, + "mutability": "mutable", + "name": "count", + "nameLocation": "823:5:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "816:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + }, + "typeName": { + "id": 54, + "name": "uint64", + "nodeType": "ElementaryTypeName", + "src": "816:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 57, + "mutability": "mutable", + "name": "count2", + "nameLocation": "844:6:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "838:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 56, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "838:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 59, + "mutability": "mutable", + "name": "total", + "nameLocation": "867:5:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "860:12:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + }, + "typeName": { + "id": 58, + "name": "uint32", + "nodeType": "ElementaryTypeName", + "src": "860:6:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 61, + "mutability": "mutable", + "name": "balance", + "nameLocation": "890:7:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "882:15:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 60, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "882:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 63, + "mutability": "mutable", + "name": "allowance", + "nameLocation": "915:9:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "907:17:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 62, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "907:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 65, + "mutability": "mutable", + "name": "smallValue", + "nameLocation": "940:10:0", + "nodeType": "VariableDeclaration", + "scope": 66, + "src": "934:16:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 64, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "934:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "visibility": "internal" + } + ], + "name": "NormalSnapshot", + "nameLocation": "693:14:0", + "nodeType": "StructDefinition", + "scope": 196, + "src": "686:271:0", + "visibility": "public" + }, + { + "body": { + "id": 77, + "nodeType": "Block", + "src": "1033:112:0", + "statements": [ + { + "assignments": [ + 73 + ], + "declarations": [ + { + "constant": false, + "id": 73, + "mutability": "mutable", + "name": "position", + "nameLocation": "1051:8:0", + "nodeType": "VariableDeclaration", + "scope": 77, + "src": "1043:16:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 72, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "1043:7:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "visibility": "internal" + } + ], + "id": 75, + "initialValue": { + "id": 74, + "name": "STORAGE_POSITION", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 6, + "src": "1062:16:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1043:35:0" + }, + { + "AST": { + "nativeSrc": "1097:42:0", + "nodeType": "YulBlock", + "src": "1097:42:0", + "statements": [ + { + "nativeSrc": "1111:18:0", + "nodeType": "YulAssignment", + "src": "1111:18:0", + "value": { + "name": "position", + "nativeSrc": "1121:8:0", + "nodeType": "YulIdentifier", + "src": "1121:8:0" + }, + "variableNames": [ + { + "name": "s.slot", + "nativeSrc": "1111:6:0", + "nodeType": "YulIdentifier", + "src": "1111:6:0" + } + ] + } + ] + }, + "evmVersion": "prague", + "externalReferences": [ + { + "declaration": 73, + "isOffset": false, + "isSlot": false, + "src": "1121:8:0", + "valueSize": 1 + }, + { + "declaration": 70, + "isOffset": false, + "isSlot": true, + "src": "1111:6:0", + "suffix": "slot", + "valueSize": 1 + } + ], + "id": 76, + "nodeType": "InlineAssembly", + "src": "1088:51:0" + } + ] + }, + "id": 78, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getStorage", + "nameLocation": "972:10:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 67, + "nodeType": "ParameterList", + "parameters": [], + "src": "982:2:0" + }, + "returnParameters": { + "id": 71, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 70, + "mutability": "mutable", + "name": "s", + "nameLocation": "1030:1:0", + "nodeType": "VariableDeclaration", + "scope": 78, + "src": "1008:23:0", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + }, + "typeName": { + "id": 69, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 68, + "name": "NormalStorage", + "nameLocations": [ + "1008:13:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 45, + "src": "1008:13:0" + }, + "referencedDeclaration": 45, + "src": "1008:13:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + } + }, + "visibility": "internal" + } + ], + "src": "1007:25:0" + }, + "scope": 196, + "src": "963:182:0", + "stateMutability": "pure", + "virtual": false, + "visibility": "internal" + }, + { + "body": { + "id": 182, + "nodeType": "Block", + "src": "1272:527:0", + "statements": [ + { + "assignments": [ + 92 + ], + "declarations": [ + { + "constant": false, + "id": 92, + "mutability": "mutable", + "name": "s", + "nameLocation": "1304:1:0", + "nodeType": "VariableDeclaration", + "scope": 182, + "src": "1282:23:0", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + }, + "typeName": { + "id": 91, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 90, + "name": "NormalStorage", + "nameLocations": [ + "1282:13:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 45, + "src": "1282:13:0" + }, + "referencedDeclaration": 45, + "src": "1282:13:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage" + } + }, + "visibility": "internal" + } + ], + "id": 95, + "initialValue": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 93, + "name": "getStorage", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 78, + "src": "1308:10:0", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$__$returns$_t_struct$_NormalStorage_$45_storage_ptr_$", + "typeString": "function () pure returns (struct Normal.NormalStorage storage pointer)" + } + }, + "id": 94, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1308:12:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1282:38:0" + }, + { + "expression": { + "id": 101, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 96, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1330:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 98, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1339:11:0", + "memberName": "totalSupply", + "nodeType": "MemberAccess", + "referencedDeclaration": 47, + "src": "1330:20:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "id": 99, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1353:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 100, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1355:11:0", + "memberName": "totalSupply", + "nodeType": "MemberAccess", + "referencedDeclaration": 26, + "src": "1353:13:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1330:36:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 102, + "nodeType": "ExpressionStatement", + "src": "1330:36:0" + }, + { + "expression": { + "id": 108, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 103, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1376:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 105, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1385:6:0", + "memberName": "marker", + "nodeType": "MemberAccess", + "referencedDeclaration": 49, + "src": "1376:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "id": 106, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1394:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 107, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1396:6:0", + "memberName": "marker", + "nodeType": "MemberAccess", + "referencedDeclaration": 28, + "src": "1394:8:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "src": "1376:26:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "id": 109, + "nodeType": "ExpressionStatement", + "src": "1376:26:0" + }, + { + "expression": { + "id": 116, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 110, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1412:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 112, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1421:6:0", + "memberName": "headId", + "nodeType": "MemberAccess", + "referencedDeclaration": 51, + "src": "1412:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "id": 113, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1430:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 114, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1432:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1430:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 115, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1439:6:0", + "memberName": "headId", + "nodeType": "MemberAccess", + "referencedDeclaration": 18, + "src": "1430:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "src": "1412:33:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "id": 117, + "nodeType": "ExpressionStatement", + "src": "1412:33:0" + }, + { + "expression": { + "id": 125, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 118, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1455:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 120, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1464:6:0", + "memberName": "tailId", + "nodeType": "MemberAccess", + "referencedDeclaration": 53, + "src": "1455:15:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "expression": { + "id": 121, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1473:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 122, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1475:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1473:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 123, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1482:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1473:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 124, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1488:6:0", + "memberName": "tailId", + "nodeType": "MemberAccess", + "referencedDeclaration": 11, + "src": "1473:21:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "src": "1455:39:0", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "id": 126, + "nodeType": "ExpressionStatement", + "src": "1455:39:0" + }, + { + "expression": { + "id": 134, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 127, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1504:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 129, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1513:5:0", + "memberName": "count", + "nodeType": "MemberAccess", + "referencedDeclaration": 55, + "src": "1504:14:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "expression": { + "id": 130, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1521:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 131, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1523:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1521:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 132, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1530:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1521:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 133, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1536:5:0", + "memberName": "count", + "nodeType": "MemberAccess", + "referencedDeclaration": 13, + "src": "1521:20:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "src": "1504:37:0", + "typeDescriptions": { + "typeIdentifier": "t_uint64", + "typeString": "uint64" + } + }, + "id": 135, + "nodeType": "ExpressionStatement", + "src": "1504:37:0" + }, + { + "expression": { + "id": 143, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 136, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1551:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 138, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1560:6:0", + "memberName": "count2", + "nodeType": "MemberAccess", + "referencedDeclaration": 57, + "src": "1551:15:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "expression": { + "id": 139, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1569:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 140, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1571:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1569:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 141, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1578:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1569:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 142, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1584:6:0", + "memberName": "count2", + "nodeType": "MemberAccess", + "referencedDeclaration": 15, + "src": "1569:21:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "src": "1551:39:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 144, + "nodeType": "ExpressionStatement", + "src": "1551:39:0" + }, + { + "expression": { + "id": 151, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 145, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1600:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 147, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1609:5:0", + "memberName": "total", + "nodeType": "MemberAccess", + "referencedDeclaration": 59, + "src": "1600:14:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "expression": { + "expression": { + "id": 148, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1617:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 149, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1619:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1617:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 150, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1626:5:0", + "memberName": "total", + "nodeType": "MemberAccess", + "referencedDeclaration": 23, + "src": "1617:14:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "src": "1600:31:0", + "typeDescriptions": { + "typeIdentifier": "t_uint32", + "typeString": "uint32" + } + }, + "id": 152, + "nodeType": "ExpressionStatement", + "src": "1600:31:0" + }, + { + "expression": { + "id": 160, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 153, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1641:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 155, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1650:7:0", + "memberName": "balance", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "1641:16:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "baseExpression": { + "expression": { + "id": 156, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1660:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 157, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1662:8:0", + "memberName": "balances", + "nodeType": "MemberAccess", + "referencedDeclaration": 35, + "src": "1660:10:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + } + }, + "id": 159, + "indexExpression": { + "id": 158, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 80, + "src": "1671:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1660:19:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1641:38:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 161, + "nodeType": "ExpressionStatement", + "src": "1641:38:0" + }, + { + "expression": { + "id": 171, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 162, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1689:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 164, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1698:9:0", + "memberName": "allowance", + "nodeType": "MemberAccess", + "referencedDeclaration": 63, + "src": "1689:18:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "baseExpression": { + "baseExpression": { + "expression": { + "id": 165, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1710:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 166, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1712:10:0", + "memberName": "allowances", + "nodeType": "MemberAccess", + "referencedDeclaration": 41, + "src": "1710:12:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_mapping$_t_address_$_t_uint256_$_$", + "typeString": "mapping(address => mapping(address => uint256))" + } + }, + "id": 168, + "indexExpression": { + "id": 167, + "name": "account", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 80, + "src": "1723:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1710:21:0", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_uint256_$", + "typeString": "mapping(address => uint256)" + } + }, + "id": 170, + "indexExpression": { + "id": 169, + "name": "spender", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 82, + "src": "1732:7:0", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1710:30:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1689:51:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 172, + "nodeType": "ExpressionStatement", + "src": "1689:51:0" + }, + { + "expression": { + "id": 180, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "id": 173, + "name": "snapshot", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 88, + "src": "1750:8:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot memory" + } + }, + "id": 175, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1759:10:0", + "memberName": "smallValue", + "nodeType": "MemberAccess", + "referencedDeclaration": 65, + "src": "1750:19:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "baseExpression": { + "expression": { + "id": 176, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 92, + "src": "1772:1:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 177, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1774:11:0", + "memberName": "smallValues", + "nodeType": "MemberAccess", + "referencedDeclaration": 44, + "src": "1772:13:0", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint8_$dyn_storage", + "typeString": "uint8[] storage ref" + } + }, + "id": 179, + "indexExpression": { + "id": 178, + "name": "index", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 84, + "src": "1786:5:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1772:20:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "src": "1750:42:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 181, + "nodeType": "ExpressionStatement", + "src": "1750:42:0" + } + ] + }, + "functionSelector": "4fad3e6d", + "id": 183, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "readAll", + "nameLocation": "1160:7:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 85, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 80, + "mutability": "mutable", + "name": "account", + "nameLocation": "1176:7:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1168:15:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 79, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1168:7:0", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 82, + "mutability": "mutable", + "name": "spender", + "nameLocation": "1193:7:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1185:15:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 81, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1185:7:0", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "visibility": "internal" + }, + { + "constant": false, + "id": 84, + "mutability": "mutable", + "name": "index", + "nameLocation": "1210:5:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1202:13:0", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 83, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1202:7:0", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "visibility": "internal" + } + ], + "src": "1167:49:0" + }, + "returnParameters": { + "id": 89, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 88, + "mutability": "mutable", + "name": "snapshot", + "nameLocation": "1262:8:0", + "nodeType": "VariableDeclaration", + "scope": 183, + "src": "1240:30:0", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_memory_ptr", + "typeString": "struct Normal.NormalSnapshot" + }, + "typeName": { + "id": 87, + "nodeType": "UserDefinedTypeName", + "pathNode": { + "id": 86, + "name": "NormalSnapshot", + "nameLocations": [ + "1240:14:0" + ], + "nodeType": "IdentifierPath", + "referencedDeclaration": 66, + "src": "1240:14:0" + }, + "referencedDeclaration": 66, + "src": "1240:14:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalSnapshot_$66_storage_ptr", + "typeString": "struct Normal.NormalSnapshot" + } + }, + "visibility": "internal" + } + ], + "src": "1239:32:0" + }, + "scope": 196, + "src": "1151:648:0", + "stateMutability": "view", + "virtual": false, + "visibility": "external" + }, + { + "body": { + "id": 194, + "nodeType": "Block", + "src": "1835:65:0", + "statements": [ + { + "expression": { + "id": 192, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "expression": { + "expression": { + "expression": { + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 186, + "name": "getStorage", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 78, + "src": "1845:10:0", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$__$returns$_t_struct$_NormalStorage_$45_storage_ptr_$", + "typeString": "function () pure returns (struct Normal.NormalStorage storage pointer)" + } + }, + "id": 187, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "nameLocations": [], + "names": [], + "nodeType": "FunctionCall", + "src": "1845:12:0", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_struct$_NormalStorage_$45_storage_ptr", + "typeString": "struct Normal.NormalStorage storage pointer" + } + }, + "id": 188, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1858:6:0", + "memberName": "record", + "nodeType": "MemberAccess", + "referencedDeclaration": 31, + "src": "1845:19:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_DirectRecord_$24_storage", + "typeString": "struct Normal.DirectRecord storage ref" + } + }, + "id": 189, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberLocation": "1865:5:0", + "memberName": "inner", + "nodeType": "MemberAccess", + "referencedDeclaration": 21, + "src": "1845:25:0", + "typeDescriptions": { + "typeIdentifier": "t_struct$_InnerRecord_$16_storage", + "typeString": "struct Normal.InnerRecord storage ref" + } + }, + "id": 190, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberLocation": "1871:6:0", + "memberName": "count2", + "nodeType": "MemberAccess", + "referencedDeclaration": 15, + "src": "1845:32:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "id": 191, + "name": "SAMPLE_COUNT2", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 9, + "src": "1880:13:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "src": "1845:48:0", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 193, + "nodeType": "ExpressionStatement", + "src": "1845:48:0" + } + ] + }, + "functionSelector": "ac5b16dc", + "id": 195, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "setCount2", + "nameLocation": "1814:9:0", + "nodeType": "FunctionDefinition", + "parameters": { + "id": 184, + "nodeType": "ParameterList", + "parameters": [], + "src": "1823:2:0" + }, + "returnParameters": { + "id": 185, + "nodeType": "ParameterList", + "parameters": [], + "src": "1835:0:0" + }, + "scope": 196, + "src": "1805:95:0", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + } + ], + "scope": 197, + "src": "59:1843:0", + "usedErrors": [], + "usedEvents": [] + } + ], + "src": "32:1871:0" +} diff --git a/cli/test/modules/validation/output.test.ts b/cli/test/modules/validation/output.test.ts new file mode 100644 index 00000000..aae20f8d --- /dev/null +++ b/cli/test/modules/validation/output.test.ts @@ -0,0 +1,220 @@ +import { describe, expect, it, vi } from "vitest"; +import { Context } from "../../../src/context/context"; +import { showReport } from "../../../src/modules/validation/output"; + +describe("validation output", () => { + it("reports selector and storage failures in the same run", async () => { + const ctx = Context.create(); + ctx.state.validationSelectorCollisions = { + success: false, + result: { checkedFacets: 2, collisions: [] }, + error: { + code: "SELECTOR_COLLISION_DETECTED", + message: "selector failure", + nativeError: null, + }, + }; + ctx.state.validationVirtualStorageLayout = { + success: false, + result: { records: [], warnings: [], collisions: [], unsupported: [] }, + error: { + code: "VIRTUAL_STORAGE_COLLISION_DETECTED", + message: "storage failure", + nativeError: null, + }, + }; + const output = vi.spyOn(console, "error").mockImplementation(() => undefined); + + try { + await showReport(ctx); + + const messages = output.mock.calls.flat().map(String); + expect(messages.some((message) => message.includes("selector failure"))).toBe(true); + expect(messages.some((message) => message.includes("storage failure"))).toBe(true); + } finally { + output.mockRestore(); + } + }); + + it("reports conflicting storage variables without exposing virtual type codes", async () => { + const ctx = Context.create(); + ctx.state.validationVirtualStorageLayout = { + success: false, + result: { + records: [], + warnings: [], + unsupported: [], + collisions: [{ + id: "0x5a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc9", + virtualPath: "erc20", + reason: "normal layout is not append-only compatible", + mismatches: [{ + position: 0, + left: { + contractName: "ERC20DataFacet", + structName: "ERC20Storage", + variableName: "balanceOf", + typeName: "mapping(address => uint256)", + storagePath: "ERC20Storage.balanceOf", + sourceName: "ERC20DataFacet.sol", + }, + right: { + contractName: "ERC20IdentifierCollisionError", + structName: "Data", + variableName: "value", + typeName: "uint256", + storagePath: "Data.value", + sourceName: "Collision.sol", + }, + }], + records: [ + storageRecord("ERC20DataFacet", "ERC20DataFacet.sol", ["0xf1", "0x03"]), + storageRecord("ERC20ApproveFacet", "ERC20ApproveFacet.sol", ["0xf1", "0x03"]), + storageRecord("ERC20IdentifierCollisionError", "Collision.sol", ["0x2f", "0xf1"]), + ], + }], + }, + error: { + code: "VIRTUAL_STORAGE_COLLISION_DETECTED", + message: "storage failure", + nativeError: null, + }, + }; + const output = vi.spyOn(console, "error").mockImplementation(() => undefined); + + try { + await showReport(ctx); + + const messages = output.mock.calls.flat().map(String); + expect(messages).toContain("\nerc20"); + expect(messages).toContain(" ERC20DataFacet: ERC20Storage.balanceOf"); + expect(messages).toContain(" Type: mapping(address => uint256)"); + expect(messages).toContain(" Storage path: ERC20Storage.balanceOf"); + expect(messages).toContain(" ERC20IdentifierCollisionError: Data.value"); + expect(messages).toContain(" Type: uint256"); + expect(messages.some((message) => message.includes("0xf1"))).toBe(false); + expect(messages.some((message) => message.includes("0x2f"))).toBe(false); + } finally { + output.mockRestore(); + } + }); + + it("reports unknown storage compatibility as incomplete instead of passed or collided", async () => { + const ctx = Context.create(); + const records = [ + storageRecord("KnownFacet", "KnownFacet.sol", ["0x2f", "0x03"]), + storageRecord("UnknownFacet", "UnknownFacet.sol", ["0x2f", "0xfe"]), + ]; + ctx.state.validationVirtualStorageLayout = { + success: false, + result: { + records, + warnings: [], + collisions: [], + unsupported: [{ + id: records[0].id, + virtualPath: "erc20", + reason: "layout contains an unknown storage type", + records, + variables: [{ + contractName: "UnknownFacet", + structName: "Data", + variableName: "value", + typeName: "unknown", + storagePath: "Data.value", + sourceName: "UnknownFacet.sol", + }], + }], + }, + error: { + code: "VIRTUAL_STORAGE_LAYOUT_UNSUPPORTED", + message: "Storage layout compatibility could not be proven.", + nativeError: null, + }, + }; + const warnings = vi.spyOn(console, "warn").mockImplementation(() => undefined); + const errors = vi.spyOn(console, "error").mockImplementation(() => undefined); + + try { + await showReport(ctx); + + const messages = warnings.mock.calls.flat().map(String); + expect(messages.some((message) => message.includes("Validation incomplete"))).toBe(true); + expect(messages.some( + (message) => message.includes("Storage layout compatibility could not be proven."), + )).toBe(true); + expect(messages.some((message) => message.includes("UnknownFacet: Data.value"))).toBe(true); + expect(messages.some((message) => message.includes("Storage path: Data.value"))).toBe(true); + expect(errors).not.toHaveBeenCalled(); + } finally { + warnings.mockRestore(); + errors.mockRestore(); + } + }); + + it("reports collisions and uncertain storage in the same run", async () => { + const ctx = Context.create(); + const records = [ + storageRecord("KnownFacet", "KnownFacet.sol", ["0x2f", "0x03"]), + storageRecord("UnknownFacet", "UnknownFacet.sol", ["0x2f", "0xfe"]), + ]; + ctx.state.validationVirtualStorageLayout = { + success: false, + result: { + records, + warnings: [], + collisions: [{ + id: records[0].id, + virtualPath: "erc20", + reason: "normal layout is not append-only compatible", + records, + mismatches: [], + }], + unsupported: [{ + id: records[0].id, + virtualPath: "erc20", + reason: "layout contains an unknown storage type", + records, + variables: [], + }], + }, + error: { + code: "VIRTUAL_STORAGE_COLLISION_DETECTED", + message: "Selected facets declare incompatible storage layouts.", + nativeError: null, + }, + }; + const warnings = vi.spyOn(console, "warn").mockImplementation(() => undefined); + const errors = vi.spyOn(console, "error").mockImplementation(() => undefined); + + try { + await showReport(ctx); + + expect(errors.mock.calls.flat().map(String).some( + (message) => message.includes("Validation failed"), + )).toBe(true); + expect(warnings.mock.calls.flat().map(String).some( + (message) => message.includes("Validation incomplete"), + )).toBe(true); + } finally { + warnings.mockRestore(); + errors.mockRestore(); + } + }); +}); + +function storageRecord(contractName: string, sourceName: string, layout: string[]) { + return { + id: "0x5a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc9", + virtualPath: "erc20", + kind: "normal" as const, + codeWidth: 1 as const, + layout, + serializedLayout: ["0x01", ...layout], + slots: [], + source: "slot-assignment" as const, + sourceName, + contractName, + structName: "Data", + }; +} diff --git a/cli/test/modules/validation/project.test.ts b/cli/test/modules/validation/project.test.ts new file mode 100644 index 00000000..6a45a77e --- /dev/null +++ b/cli/test/modules/validation/project.test.ts @@ -0,0 +1,205 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { describe, expect, it } from "vitest"; +import { Context } from "../../../src/context/context"; +import { loadValidationProject } from "../../../src/modules/validation/project"; + +describe("validation project loader", () => { + it("loads framework and facet contract names from the nearest compose.json", async () => { + const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compose-validation-project-")); + const nestedDirectory = path.join(projectRoot, "src", "facets"); + const ctx = Context.create(); + + try { + await fs.mkdir(nestedDirectory, { recursive: true }); + await fs.writeFile( + path.join(projectRoot, "compose.json"), + JSON.stringify({ + framework: "foundry", + diamonds: { + Example: { + contract: "src/Diamond.sol:Diamond", + facets: { + counter: { + source: "local", + contract: "src/facets/CounterFacet.sol:CounterFacet", + }, + }, + }, + }, + }), + "utf8", + ); + ctx.param.projectRoot = nestedDirectory; + + const project = await loadValidationProject(ctx); + + expect(project.diamonds).toEqual([ + { + name: "Example", + sourcePath: path.join(projectRoot, "src", "Diamond.sol"), + facets: [{ + contractName: "CounterFacet", + sourcePath: path.join(projectRoot, "src", "facets", "CounterFacet.sol"), + }], + }, + ]); + expect(project.diamondSourcePaths).toEqual([ + path.join(projectRoot, "src", "Diamond.sol"), + ]); + expect(project.facetSources).toEqual([ + { + contractName: "CounterFacet", + sourcePath: path.join(projectRoot, "src", "facets", "CounterFacet.sol"), + }, + ]); + expect(ctx.param.projectRoot).toBe(projectRoot); + expect(ctx.param.framework).toBe("foundry"); + expect(ctx.state.validationProject?.success).toBe(true); + } finally { + await fs.rm(projectRoot, { recursive: true, force: true }); + } + }); + + it("preserves facet ownership for each diamond", async () => { + const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compose-validation-scopes-")); + const ctx = Context.create(); + + try { + await fs.writeFile( + path.join(projectRoot, "compose.json"), + JSON.stringify({ + framework: "foundry", + diamonds: { + Alpha: { + contract: "src/Alpha.sol:Alpha", + facets: { + AlphaFacet: { + source: "local", + contract: "src/AlphaFacet.sol:AlphaFacet", + }, + }, + }, + Beta: { + contract: "src/Beta.sol:Beta", + facets: { + BetaFacet: { + source: "local", + contract: "src/BetaFacet.sol:BetaFacet", + }, + }, + }, + }, + }), + "utf8", + ); + ctx.param.projectRoot = projectRoot; + + const project = await loadValidationProject(ctx); + + expect(project.diamonds).toEqual([ + { + name: "Alpha", + sourcePath: path.join(projectRoot, "src", "Alpha.sol"), + facets: [{ + contractName: "AlphaFacet", + sourcePath: path.join(projectRoot, "src", "AlphaFacet.sol"), + }], + }, + { + name: "Beta", + sourcePath: path.join(projectRoot, "src", "Beta.sol"), + facets: [{ + contractName: "BetaFacet", + sourcePath: path.join(projectRoot, "src", "BetaFacet.sol"), + }], + }, + ]); + } finally { + await fs.rm(projectRoot, { recursive: true, force: true }); + } + }); + + it("preserves source identity for duplicate contract names", async () => { + const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compose-validation-identity-")); + const ctx = Context.create(); + + try { + await fs.writeFile( + path.join(projectRoot, "compose.json"), + JSON.stringify({ + framework: "foundry", + diamonds: { + Example: { + contract: "src/Diamond.sol:Diamond", + facets: { + fooA: { source: "local", contract: "src/a/Foo.sol:Foo" }, + fooB: { source: "local", contract: "src/b/Foo.sol:Foo" }, + }, + }, + }, + }), + "utf8", + ); + ctx.param.projectRoot = projectRoot; + + const project = await loadValidationProject(ctx); + const expectedFacets = [ + { contractName: "Foo", sourcePath: path.join(projectRoot, "src", "a", "Foo.sol") }, + { contractName: "Foo", sourcePath: path.join(projectRoot, "src", "b", "Foo.sol") }, + ]; + + expect(project.facetSources).toEqual(expectedFacets); + expect(project.diamonds[0].facets).toEqual(expectedFacets); + } finally { + await fs.rm(projectRoot, { recursive: true, force: true }); + } + }); + + it("resolves package facets from the installed Compose dependency", async () => { + const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compose-validation-package-")); + const packageFacetPath = path.join( + projectRoot, + "lib", + "Compose", + "src", + "diamond", + "PackageFacet.sol", + ); + const ctx = Context.create(); + + try { + await fs.mkdir(path.dirname(packageFacetPath), { recursive: true }); + await fs.writeFile(packageFacetPath, "contract PackageFacet {}", "utf8"); + await fs.writeFile( + path.join(projectRoot, "compose.json"), + JSON.stringify({ + framework: "foundry", + diamonds: { + Example: { + contract: "src/Diamond.sol:Diamond", + facets: { + PackageFacet: { + source: "package", + contract: "PackageFacet", + package: "@perfect-abstractions/compose", + }, + }, + }, + }, + }), + "utf8", + ); + ctx.param.projectRoot = projectRoot; + + const project = await loadValidationProject(ctx); + + expect(project.facetSources).toEqual([ + { contractName: "PackageFacet", sourcePath: packageFacetPath }, + ]); + } finally { + await fs.rm(projectRoot, { recursive: true, force: true }); + } + }); +}); diff --git a/cli/test/modules/validation/selectorScopes.test.ts b/cli/test/modules/validation/selectorScopes.test.ts new file mode 100644 index 00000000..8093bfc6 --- /dev/null +++ b/cli/test/modules/validation/selectorScopes.test.ts @@ -0,0 +1,85 @@ +import { describe, expect, it } from "vitest"; +import { HashingAdapter } from "../../../src/adapters/hashingAdapter"; +import { Context } from "../../../src/context/context"; +import { ValidationModule } from "../../../src/modules/validation/module"; +import { FacetScanResult } from "../../../src/modules/validation/types"; + +describe("selector collision scopes", () => { + it("allows the same selector in independent diamonds", async () => { + const ctx = Context.create(); + setFacetScan(ctx, [facet("AlphaFacet"), facet("BetaFacet")]); + + await ValidationModule.detectSelectorCollisions(ctx, { + hashing: HashingAdapter, + scopes: [ + { diamondName: "Alpha", facets: [reference("AlphaFacet")] }, + { diamondName: "Beta", facets: [reference("BetaFacet")] }, + ], + }); + + expect(ctx.state.validationSelectorCollisions?.success).toBe(true); + expect(ctx.state.validationSelectorCollisions?.result).toEqual({ + checkedFacets: 2, + collisions: [], + }); + }); + + it("reports the diamond containing a selector collision", async () => { + const ctx = Context.create(); + setFacetScan(ctx, [facet("AlphaFacet"), facet("BetaFacet")]); + + await ValidationModule.detectSelectorCollisions(ctx, { + hashing: HashingAdapter, + scopes: [ + { + diamondName: "SharedDiamond", + facets: [reference("AlphaFacet"), reference("BetaFacet")], + }, + ], + }); + + expect(ctx.state.validationSelectorCollisions?.success).toBe(false); + expect(ctx.state.validationSelectorCollisions?.result).toEqual({ + checkedFacets: 2, + collisions: [ + expect.objectContaining({ + diamondName: "SharedDiamond", + selector: HashingAdapter.keccak256("transfer(address,uint256)").slice(0, 10), + }), + ], + }); + }); +}); + +function facet(facetName: string): FacetScanResult { + return { + facetName, + path: `src/${facetName}.sol`, + functions: [{ + name: "transfer", + signature: "transfer(address,uint256)", + visibility: "external", + }], + exportedSelectors: ["transfer(address,uint256)"], + hasExportSelectorsFunction: true, + missingExports: [], + extraExports: [], + storageLayouts: [], + warnings: [], + }; +} + +function reference(contractName: string): { contractName: string; sourcePath: string } { + return { contractName, sourcePath: `src/${contractName}.sol` }; +} + +function setFacetScan( + ctx: ReturnType, + facets: FacetScanResult[], +): void { + ctx.state.facetScan = { + success: true, + result: { facets, facetCount: facets.length }, + error: null, + }; +} diff --git a/cli/test/modules/validation/virtualStorageLayout.test.ts b/cli/test/modules/validation/virtualStorageLayout.test.ts new file mode 100644 index 00000000..43b6161e --- /dev/null +++ b/cli/test/modules/validation/virtualStorageLayout.test.ts @@ -0,0 +1,675 @@ +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { describe, expect, it } from "vitest"; +import { SolidityAstSource } from "../../../src/adapters/interface/IFrameworkAdapter"; +import { + FacetReference, + VirtualStorageLayoutRecord, +} from "../../../src/modules/validation/types"; +import { + buildScopedVirtualStorageLayout, + buildVirtualStorageLayout, + deriveStorageRootId, + findUnsupportedVirtualStorageLayouts, + findVirtualStorageLayoutCollisions, + hashVirtualPath, +} from "../../../src/modules/validation/virtualStorageLayout"; + +function independentDiamondAstSource(): SolidityAstSource { + const contract = (id: number, name: string, typeName: string) => ({ + contractKind: "contract", + id, + linearizedBaseContracts: [id], + name, + nodeType: "ContractDefinition", + nodes: [{ + constant: false, + id: id + 1, + mutability: "mutable", + name: "value", + nodeType: "VariableDeclaration", + stateVariable: true, + typeName: { + id: id + 2, + name: typeName, + nodeType: "ElementaryTypeName", + }, + }], + }); + + return { + sourceName: "src/IndependentFacets.sol", + ast: { + id: 5000, + nodeType: "SourceUnit", + src: "0:0:0", + nodes: [ + contract(100, "AlphaFacet", "uint256"), + contract(200, "BetaFacet", "address"), + ], + }, + }; +} + +function duplicateContractAstSources(): SolidityAstSource[] { + const source = (sourceName: string, id: number, typeName: string): SolidityAstSource => ({ + sourceName, + ast: { + id: id + 1000, + nodeType: "SourceUnit", + src: "0:0:0", + nodes: [{ + contractKind: "contract", + id, + linearizedBaseContracts: [id], + name: "Foo", + nodeType: "ContractDefinition", + nodes: [{ + constant: false, + id: id + 1, + mutability: "mutable", + name: "value", + nodeType: "VariableDeclaration", + stateVariable: true, + typeName: { + id: id + 2, + name: typeName, + nodeType: "ElementaryTypeName", + }, + }], + }], + }, + }); + + return [ + source("src/a/Foo.sol", 6000, "uint256"), + source("src/b/Foo.sol", 7000, "address"), + ]; +} + +function nestedVirtualPathAstSource( + standard: "erc8042" | "erc7201" = "erc8042", +): SolidityAstSource { + const uintField = (id: number, name: string) => ({ + id, + name, + nodeType: "VariableDeclaration", + typeName: { id: id + 1000, name: "uint256", nodeType: "ElementaryTypeName" }, + }); + const deepStruct = { + id: 300, + members: [uintField(301, "value")], + name: "DeepStorage", + nodeType: "StructDefinition", + }; + const childStruct = { + id: 200, + members: [ + uintField(201, "a"), + uintField(202, "b"), + uintField(203, "c"), + { + id: 204, + name: "items", + nodeType: "VariableDeclaration", + typeName: { + baseType: { + id: 1204, + name: "DeepStorage", + nodeType: "UserDefinedTypeName", + referencedDeclaration: 300, + }, + id: 2204, + length: null, + nodeType: "ArrayTypeName", + }, + }, + ], + name: "ChildStorage", + nodeType: "StructDefinition", + }; + const rootStruct = { + documentation: { text: `@custom:storage-location ${standard}:erc20` }, + id: 100, + members: [ + uintField(101, "a"), + uintField(102, "b"), + uintField(103, "c"), + uintField(104, "d"), + uintField(105, "e"), + { + id: 106, + name: "children", + nodeType: "VariableDeclaration", + typeName: { + id: 1106, + keyType: { id: 2106, name: "uint256", nodeType: "ElementaryTypeName" }, + nodeType: "Mapping", + valueType: { + id: 3106, + name: "ChildStorage", + nodeType: "UserDefinedTypeName", + referencedDeclaration: 200, + }, + }, + }, + ], + name: "RootStorage", + nodeType: "StructDefinition", + }; + + return { + sourceName: "src/PathFacet.sol", + ast: { + id: 9000, + nodeType: "SourceUnit", + src: "0:0:0", + nodes: [{ + contractKind: "contract", + id: 1, + linearizedBaseContracts: [1], + name: "PathFacet", + nodeType: "ContractDefinition", + nodes: [rootStruct, childStruct, deepStruct], + }], + }, + }; +} + +function fixedArrayStructAstSource(): SolidityAstSource { + const elementaryField = (id: number, name: string, typeName: string) => ({ + id, + name, + nodeType: "VariableDeclaration", + typeName: { id: id + 1000, name: typeName, nodeType: "ElementaryTypeName" }, + }); + const elementStruct = { + id: 200, + members: [ + elementaryField(201, "small", "uint8"), + elementaryField(202, "wide", "uint96"), + ], + name: "ElementStorage", + nodeType: "StructDefinition", + }; + const tailStruct = { + id: 300, + members: [elementaryField(301, "value", "uint256")], + name: "TailStorage", + nodeType: "StructDefinition", + }; + const rootStruct = { + documentation: { text: "@custom:storage-location erc8042:fixed.struct" }, + id: 100, + members: [ + { + id: 101, + name: "items", + nodeType: "VariableDeclaration", + typeName: { + baseType: { + id: 1101, + name: "ElementStorage", + nodeType: "UserDefinedTypeName", + referencedDeclaration: 200, + }, + id: 2101, + length: { id: 3101, nodeType: "Literal", value: "5" }, + nodeType: "ArrayTypeName", + }, + }, + { + id: 102, + name: "tails", + nodeType: "VariableDeclaration", + typeName: { + id: 1102, + keyType: { id: 2102, name: "uint256", nodeType: "ElementaryTypeName" }, + nodeType: "Mapping", + valueType: { + id: 3102, + name: "TailStorage", + nodeType: "UserDefinedTypeName", + referencedDeclaration: 300, + }, + }, + }, + ], + name: "RootStorage", + nodeType: "StructDefinition", + }; + + return { + sourceName: "src/FixedArrayFacet.sol", + ast: { + id: 9000, + nodeType: "SourceUnit", + src: "0:0:0", + nodes: [{ + contractKind: "contract", + id: 1, + linearizedBaseContracts: [1], + name: "FixedArrayFacet", + nodeType: "ContractDefinition", + nodes: [rootStruct, elementStruct, tailStruct], + }], + }, + }; +} + +const normalAstPath = resolve( + __dirname, + "fixtures/normal/expected/hardhat.ast.json", +); + +function normalAstSource(): SolidityAstSource { + return { + sourceName: "project/contracts/Normal.sol", + ast: JSON.parse(readFileSync(normalAstPath, "utf8")), + }; +} + +function libraryReachabilityAstSource(): SolidityAstSource { + const annotatedStruct = (id: number, name: string, namespace: string) => ({ + documentation: { text: `@custom:storage-location erc8042:${namespace}` }, + id, + members: [{ + id: id + 100, + name: "value", + nodeType: "VariableDeclaration", + typeName: { id: id + 200, name: "uint256", nodeType: "ElementaryTypeName" }, + }], + name, + nodeType: "StructDefinition", + }); + + return { + sourceName: "src/ReachableFacet.sol", + ast: { + id: 1000, + nodeType: "SourceUnit", + src: "0:0:0", + nodes: [ + { + contractKind: "contract", + id: 1, + linearizedBaseContracts: [1], + name: "ReachableFacet", + nodeType: "ContractDefinition", + nodes: [{ + body: { + id: 3, + nodeType: "Block", + statements: [{ + expression: { + expression: { + id: 5, + memberName: "touchUsedStorage", + nodeType: "MemberAccess", + referencedDeclaration: 12, + }, + id: 4, + nodeType: "FunctionCall", + }, + id: 6, + nodeType: "ExpressionStatement", + }], + }, + id: 2, + kind: "function", + name: "run", + nodeType: "FunctionDefinition", + parameters: { id: 7, nodeType: "ParameterList", parameters: [] }, + visibility: "external", + }], + }, + { + contractKind: "library", + id: 10, + linearizedBaseContracts: [10], + name: "StorageLibrary", + nodeType: "ContractDefinition", + nodes: [ + annotatedStruct(20, "UsedStorage", "used.storage"), + annotatedStruct(21, "UnusedStorage", "unused.storage"), + { + body: { + id: 13, + nodeType: "Block", + statements: [{ + declarations: [{ + id: 14, + name: "storageValue", + nodeType: "VariableDeclaration", + typeName: { + id: 15, + name: "UsedStorage", + nodeType: "UserDefinedTypeName", + referencedDeclaration: 20, + }, + }], + id: 16, + nodeType: "VariableDeclarationStatement", + }], + }, + id: 12, + kind: "function", + name: "touchUsedStorage", + nodeType: "FunctionDefinition", + parameters: { id: 17, nodeType: "ParameterList", parameters: [] }, + visibility: "internal", + }, + ], + }, + ], + }, + }; +} + +function record(layout: string[]): VirtualStorageLayoutRecord { + return { + id: hashVirtualPath("shared.storage"), + virtualPath: "shared.storage", + kind: "normal", + codeWidth: 1, + layout, + serializedLayout: ["0x01", ...layout], + slots: [[256]], + source: "slot-assignment", + sourceName: "src/Facet.sol", + contractName: "Facet", + structName: "FacetStorage", + }; +} + +function facet(contractName: string, sourcePath: string): FacetReference { + return { contractName, sourcePath }; +} + +describe("virtual storage layout", () => { + it("derives root and nested IDs from canonical readable paths", () => { + expect(hashVirtualPath("erc20")).toBe( + "0x5a28e9363bb942b639270062aa6bb295f434bcdfc42c97267bf003f272060dc9", + ); + expect(hashVirtualPath("erc20.5")).toBe( + "0xd9017c3d0d4c93e47f2713117035682e1f0ea26c03fe341b3b184c078338d0d9", + ); + expect(hashVirtualPath("erc20.5.3")).toBe( + "0x15a679fb4dca6bd150612cf0d9a19d650ed3bb8b67f60e0808e5033d49d74c5a", + ); + }); + + it("derives ERC-8042 and ERC-7201 namespace roots with their canonical formulas", () => { + expect(deriveStorageRootId("example.main", "erc8042")).toBe( + hashVirtualPath("example.main"), + ); + expect(deriveStorageRootId("example.main", "erc7201")).toBe( + "0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500", + ); + expect(deriveStorageRootId( + "0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500", + "slot-assignment", + )).toBe("0x183a6125c38840424c4a85fa12bab2ab606c4b6d0e7cc73c0c06ba5300eab500"); + + const result = buildVirtualStorageLayout( + [nestedVirtualPathAstSource("erc7201")], + [facet("PathFacet", "src/PathFacet.sol")], + ); + expect(result.records[0].id).toBe(deriveStorageRootId("erc20", "erc7201")); + expect(result.records[0].source).toBe("erc7201"); + }); + + it("uses source path to scope storage for duplicate contract names", () => { + const sources = duplicateContractAstSources(); + const fooA = facet("Foo", "src/a/Foo.sol"); + const fooB = facet("Foo", "src/b/Foo.sol"); + + const selected = buildVirtualStorageLayout(sources, [fooB]); + expect(selected.records).toEqual([ + expect.objectContaining({ + contractName: "Foo", + layout: ["0x03"], + sourceName: "src/b/Foo.sol", + slots: [[160]], + }), + ]); + + expect(buildVirtualStorageLayout(sources, [fooA, fooB]).collisions).toEqual([ + expect.objectContaining({ + id: `0x${"0".repeat(64)}`, + virtualPath: "0x0", + mismatches: expect.arrayContaining([expect.objectContaining({ + left: expect.objectContaining({ variableName: "value" }), + right: expect.objectContaining({ variableName: "value" }), + })]), + }), + ]); + }); + + it("keeps full paths and container kinds for nested virtual records", () => { + const result = buildVirtualStorageLayout( + [nestedVirtualPathAstSource()], + [facet("PathFacet", "src/PathFacet.sol")], + ); + + expect(result.records.map((record) => ({ + id: record.id, + kind: record.kind, + path: record.virtualPath, + }))).toEqual([ + { + id: hashVirtualPath("erc20"), + kind: "normal", + path: "erc20", + }, + { + id: hashVirtualPath("erc20.5"), + kind: "normal", + path: "erc20.5", + }, + { + id: hashVirtualPath("erc20.5.3"), + kind: "immutable", + path: "erc20.5.3", + }, + ]); + }); + + it("preserves the physical span of packed structs in fixed arrays", () => { + const result = buildVirtualStorageLayout( + [fixedArrayStructAstSource()], + [facet("FixedArrayFacet", "src/FixedArrayFacet.sol")], + ); + + expect(result.warnings).toEqual([]); + expect(result.collisions).toEqual([]); + expect(result.records.map((record) => ({ + kind: record.kind, + path: record.virtualPath, + slots: record.slots, + }))).toEqual([ + { + kind: "normal", + path: "fixed.struct", + slots: [ + [8, 96], + [8, 96], + [8, 96], + [8, 96], + [8, 96], + [256], + ], + }, + { + kind: "immutable", + path: "fixed.struct.0", + slots: [[8, 96]], + }, + { + kind: "normal", + path: "fixed.struct.5", + slots: [[256]], + }, + ]); + }); + + it("isolates storage layouts between diamonds", () => { + const source = independentDiamondAstSource(); + + expect(buildVirtualStorageLayout( + [source], + [ + facet("AlphaFacet", "src/IndependentFacets.sol"), + facet("BetaFacet", "src/IndependentFacets.sol"), + ], + ).collisions).toHaveLength(1); + + const scoped = buildScopedVirtualStorageLayout([source], [ + { diamondName: "Alpha", facets: [facet("AlphaFacet", "src/IndependentFacets.sol")] }, + { diamondName: "Beta", facets: [facet("BetaFacet", "src/IndependentFacets.sol")] }, + ]); + + expect(scoped.collisions).toEqual([]); + expect(scoped.records.map((item) => item.diamondName)).toEqual(["Alpha", "Beta"]); + }); + + it("reports storage contradictions inside the same diamond", () => { + const result = buildScopedVirtualStorageLayout([independentDiamondAstSource()], [{ + diamondName: "SharedDiamond", + facets: [ + facet("AlphaFacet", "src/IndependentFacets.sol"), + facet("BetaFacet", "src/IndependentFacets.sol"), + ], + }]); + + expect(result.collisions).toEqual([ + expect.objectContaining({ + diamondName: "SharedDiamond", + id: `0x${"0".repeat(64)}`, + virtualPath: "0x0", + }), + ]); + }); + + it("builds the canonical layout and packing from compiler AST", () => { + const result = buildVirtualStorageLayout( + [normalAstSource()], + [facet("Normal", "project/contracts/Normal.sol")], + ); + + expect(result.warnings).toEqual([]); + expect(result.collisions).toEqual([]); + expect(result.records).toHaveLength(1); + expect(result.records[0]).toEqual({ + id: "0xb4df32537f6767405c9db7d67260e5375218aecdea91f4240ad14000623cbdff", + virtualPath: "evmole.normal", + kind: "normal", + codeWidth: 1, + layout: [ + "0x2f", "0x53", "0xf4", "0x53", "0xf4", "0x53", "0x17", "0x10", + "0xff", "0x13", "0xff", "0xf1", "0x03", "0x2f", "0xff", "0xf1", + "0x03", "0xf1", "0x03", "0x2f", "0xff", "0xf2", "0x10", "0xff", + ], + serializedLayout: [ + "0x01", "0x2f", "0x53", "0xf4", "0x53", "0xf4", "0x53", "0x17", + "0x10", "0xff", "0x13", "0xff", "0xf1", "0x03", "0x2f", "0xff", + "0xf1", "0x03", "0xf1", "0x03", "0x2f", "0xff", "0xf2", "0x10", "0xff", + ], + slots: [[256], [32], [32], [32, 64, 8], [32], [256], [256], [256]], + source: "slot-assignment", + sourceName: "project/contracts/Normal.sol", + contractName: "Normal", + structName: "NormalStorage", + }); + }); + + it("ignores compiled contracts outside the selected facet graph", () => { + const source: SolidityAstSource = { + sourceName: "src/OtherFacet.sol", + ast: { + id: 8000, + nodeType: "SourceUnit", + src: "0:0:0", + nodes: [{ + contractKind: "contract", + id: 8001, + linearizedBaseContracts: [8001], + name: "OtherFacet", + nodeType: "ContractDefinition", + nodes: [], + }], + }, + }; + const result = buildVirtualStorageLayout( + [normalAstSource(), source], + [facet("OtherFacet", "src/OtherFacet.sol")], + ); + + expect(result.records).toEqual([]); + expect(result.collisions).toEqual([]); + expect(result.warnings).toEqual([{ + sourceName: "src/OtherFacet.sol", + message: "OtherFacet: no storage pattern found; storage validation skipped for this facet.", + }]); + }); + + it("keeps only referenced roots from a reachable storage library", () => { + const result = buildVirtualStorageLayout( + [libraryReachabilityAstSource()], + [facet("ReachableFacet", "src/ReachableFacet.sol")], + ); + + expect(result.records.map((item) => ({ id: item.id, path: item.virtualPath }))).toEqual([{ + id: "0x5beaa2863186d437dda8f3099114cae898c8516639341d5786ade60d517a8a90", + path: "used.storage", + }]); + }); + + it("accepts append-only roots and rejects clear type contradictions", () => { + expect(findVirtualStorageLayoutCollisions([ + record(["0x2f"]), + record(["0x2f", "0x03"]), + ])).toEqual([]); + + expect(findVirtualStorageLayoutCollisions([ + record(["0x2f", "0xfe"]), + record(["0x10", "0x03"]), + ])).toEqual([ + expect.objectContaining({ + id: "0x1b0734a7bedafd59afc4f0cdc0bb15fd1c76495dc1393123b69bf74b08e29564", + virtualPath: "shared.storage", + reason: "normal layout is not append-only compatible", + }), + ]); + + expect(findVirtualStorageLayoutCollisions([ + record(["0x2f", "0xfe"]), + record(["0x2f", "0x03"]), + ])).toEqual([]); + + expect(findUnsupportedVirtualStorageLayouts([ + record(["0x2f", "0xfe"]), + record(["0x2f", "0x03"]), + ])).toEqual([ + expect.objectContaining({ + virtualPath: "shared.storage", + reason: "layout contains an unknown storage type", + }), + ]); + + expect(findUnsupportedVirtualStorageLayouts([ + record(["0x2f", "0x71"]), + record(["0x2f", "0x03"]), + ])).toEqual([]); + + expect(findVirtualStorageLayoutCollisions([ + record(["0x2f", "0x71"]), + record(["0x2f", "0x03"]), + ])).toHaveLength(1); + + expect(findVirtualStorageLayoutCollisions([ + record(["0x2f", "0x71"]), + record(["0x2f", "0x71"]), + ])).toEqual([]); + }); +}); diff --git a/cli/test/pipelines/initPipeline/initPipeline.test.ts b/cli/test/pipelines/initPipeline/initPipeline.test.ts new file mode 100644 index 00000000..495fdb40 --- /dev/null +++ b/cli/test/pipelines/initPipeline/initPipeline.test.ts @@ -0,0 +1,132 @@ +import { describe, expect, it, vi } from "vitest"; +import { + IFrameworkAdapter, + SolidityAstSource, +} from "../../../src/adapters/interface/IFrameworkAdapter"; +import { HashingAdapter } from "../../../src/adapters/hashingAdapter"; +import { Context } from "../../../src/context/context"; +import { ConfigModule } from "../../../src/modules/config/module"; +import { DeployGenerationModule } from "../../../src/modules/deployGeneration/module"; +import { DiamondGenerationModule } from "../../../src/modules/diamondGeneration/module"; +import { InitModule } from "../../../src/modules/init/module"; +import { PreflightModule } from "../../../src/modules/preflight/module"; +import { ProjectDirModule } from "../../../src/modules/projectDir/module"; +import { ScaffoldingModule } from "../../../src/modules/scaffolding/module"; +import { TestGenerationModule } from "../../../src/modules/testGeneration/module"; +import { ValidationModule } from "../../../src/modules/validation/module"; +import { InitPipeline } from "../../../src/pipelines/initPipeline"; +import { DependencyResolver } from "../../../src/resolver/dependencyResolver"; + +function contractAst(sourceName: string, contractName: string, id: number): SolidityAstSource { + return { + sourceName, + ast: { + id, + nodeType: "SourceUnit", + src: "0:0:0", + absolutePath: sourceName, + nodes: [ + { + id: id + 1, + nodeType: "ContractDefinition", + name: contractName, + contractKind: "contract", + linearizedBaseContracts: [id + 1], + nodes: [], + src: "0:0:0", + }, + ], + }, + }; +} + +describe("InitPipeline source validation", () => { + it("merges Compose package and project facet paths before compiling AST", async () => { + const ctx = Context.create(); + const packagePath = "@perfect-abstractions/compose/diamond/PackageFacet.sol"; + const resolvedPackagePath = "/tmp/compose-project/lib/Compose/src/diamond/PackageFacet.sol"; + const projectPath = "/tmp/compose-project/src/facets/ProjectFacet.sol"; + Object.assign(ctx.param, { + yes: true, + framework: "foundry", + projectRoot: "/tmp/compose-project", + projectName: "example", + installDeps: false, + base: "counter", + libraries: [], + extensions: [], + access: [], + accessExtensions: [], + }); + + const compileAst = vi.fn(async () => [ + contractAst(resolvedPackagePath, "PackageFacet", 1), + contractAst(projectPath, "ProjectFacet", 10), + ]); + const adapter = { + getContractSourceRoot: vi.fn(() => "/tmp/compose-project/src"), + getScriptRoot: vi.fn(() => "/tmp/compose-project/script"), + getTestRoot: vi.fn(() => "/tmp/compose-project/test"), + resolveSoliditySourcePath: vi.fn(async (_ctx, sourcePath: string) => + sourcePath === packagePath ? resolvedPackagePath : sourcePath), + compileAst, + initProject: vi.fn(async () => undefined), + writeConfig: vi.fn(async () => undefined), + } as unknown as IFrameworkAdapter; + + vi.spyOn(InitModule, "showComposeHeader").mockImplementation(() => undefined); + vi.spyOn(InitModule, "showSuccess").mockImplementation(() => undefined); + vi.spyOn(ConfigModule, "loadBasesCatalog").mockImplementation(async (parentCtx) => { + parentCtx.config.bases = {}; + return parentCtx; + }); + vi.spyOn(ConfigModule, "getDiamondCompilerVersion").mockReturnValue("0.8.30"); + vi.spyOn(InitModule, "runInitNonInteractive").mockImplementation(async (parentCtx) => parentCtx); + vi.spyOn(PreflightModule, "check").mockImplementation(async (parentCtx) => parentCtx); + vi.spyOn(ProjectDirModule, "resolve").mockImplementation(async (parentCtx) => parentCtx); + vi.spyOn(ProjectDirModule, "validate").mockImplementation(async (parentCtx) => parentCtx); + vi.spyOn(ScaffoldingModule, "copyFacets").mockResolvedValue([ + { + facetName: "package", + contractName: "PackageFacet", + targetPath: packagePath, + origin: "package", + }, + { + facetName: "project", + contractName: "ProjectFacet", + targetPath: projectPath, + origin: "local", + }, + ]); + vi.spyOn(ValidationModule, "showReport").mockImplementation(async (parentCtx) => parentCtx); + vi.spyOn(DiamondGenerationModule, "generateDiamondContract") + .mockImplementation(async (parentCtx) => parentCtx); + vi.spyOn(DeployGenerationModule, "generateDeployScript") + .mockImplementation(async (parentCtx) => parentCtx); + vi.spyOn(TestGenerationModule, "generateTestFile") + .mockImplementation(async (parentCtx) => parentCtx); + vi.spyOn(ScaffoldingModule, "buildComposeJson").mockImplementation((parentCtx) => parentCtx); + vi.spyOn(ScaffoldingModule, "validateLocalFacetFiles") + .mockImplementation(async (parentCtx) => parentCtx); + vi.spyOn(ScaffoldingModule, "writeComposeConfig") + .mockImplementation(async (parentCtx) => parentCtx); + vi.spyOn(DependencyResolver, "resolve").mockResolvedValue({ + foundry: adapter, + hashing: HashingAdapter, + }); + + try { + const result = await InitPipeline.execute(ctx); + + expect(result).toBe(ctx); + expect(compileAst).toHaveBeenCalledWith(ctx, [resolvedPackagePath, projectPath]); + expect(result.state.validationComposeFacetSources?.success).toBe(true); + expect(result.state.validationProjectFacetSources?.success).toBe(true); + expect(result.state.initValidation?.success).toBe(true); + expect(result.status.success).toBe(true); + } finally { + vi.restoreAllMocks(); + } + }); +}); diff --git a/cli/test/pipelines/validatePipeline/fixtures/CompatibleStorageFacet.sol b/cli/test/pipelines/validatePipeline/fixtures/CompatibleStorageFacet.sol new file mode 100644 index 00000000..14bcf080 --- /dev/null +++ b/cli/test/pipelines/validatePipeline/fixtures/CompatibleStorageFacet.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +contract CompatibleStorageFacet { + enum Status { None, Active, Paused } + type UserId is uint64; + + /** + * @custom:storage-location erc8042:compose.fixture.virtual-storage + */ + struct Storage { + bool flag; + Status status; + address owner; + uint8 smallUint; + uint16 mediumUint; + int24 signedValue; + bytes2 shortBytes; + UserId userId; + bytes dynamicBytes; + string text; + } + + bytes32 private constant STORAGE_POSITION = + keccak256("compose.fixture.virtual-storage"); + + function readCompatibleOwner() external view returns (address) { + return _storage().owner; + } + + function exportSelectors() external pure returns (bytes4[] memory selectors) { + selectors = new bytes4[](1); + selectors[0] = this.readCompatibleOwner.selector; + } + + function _storage() private pure returns (Storage storage s) { + bytes32 position = STORAGE_POSITION; + assembly { + s.slot := position + } + } +} diff --git a/cli/test/pipelines/validatePipeline/fixtures/FullStorageFacet.sol b/cli/test/pipelines/validatePipeline/fixtures/FullStorageFacet.sol new file mode 100644 index 00000000..6a709dd0 --- /dev/null +++ b/cli/test/pipelines/validatePipeline/fixtures/FullStorageFacet.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +contract FullStorageFacet { + enum Status { None, Active, Paused } + type UserId is uint64; + + struct InlineChild { + bytes4 tailFacetNodeId; + uint32 facetCount; + } + + struct InlineOuter { + bytes4 headFacetNodeId; + InlineChild child; + uint32 selectorCount; + } + + struct ContainerChild { + uint256 amount; + bool active; + address owner; + } + + /** + * @custom:storage-location erc8042:compose.fixture.virtual-storage + */ + struct Storage { + bool flag; + Status status; + address owner; + uint8 smallUint; + uint16 mediumUint; + int24 signedValue; + bytes2 shortBytes; + UserId userId; + + bytes dynamicBytes; + string text; + + function(uint256) external returns (uint256) externalFn; + + InlineOuter inlineStruct; + + mapping(address => uint256) balances; + mapping(uint256 => uint256[]) mapToDynamicArray; + uint256[] dynamicValues; + uint8[] packedDynamicValues; + + uint256[5] fixedFive; + uint256[300] fixedThreeHundred; + uint256[5][10] nestedFixed; + + mapping(address => ContainerChild) childByAddress; + ContainerChild[] childList; + ContainerChild[2] fixedChildren; + mapping(uint256 => ContainerChild[]) nestedChildren; + + mapping(bytes => uint256) bytesKeyed; + mapping(string => uint256) stringKeyed; + + function(uint256) internal returns (uint256) internalFn; + } + + bytes32 private constant STORAGE_POSITION = + keccak256("compose.fixture.virtual-storage"); + + function readFullFlag() external view returns (bool) { + return _storage().flag; + } + + function exportSelectors() external pure returns (bytes4[] memory selectors) { + selectors = new bytes4[](1); + selectors[0] = this.readFullFlag.selector; + } + + function _storage() private pure returns (Storage storage s) { + bytes32 position = STORAGE_POSITION; + assembly { + s.slot := position + } + } +} diff --git a/cli/test/pipelines/validatePipeline/fixtures/IncompatibleStorageFacet.sol b/cli/test/pipelines/validatePipeline/fixtures/IncompatibleStorageFacet.sol new file mode 100644 index 00000000..40af10b0 --- /dev/null +++ b/cli/test/pipelines/validatePipeline/fixtures/IncompatibleStorageFacet.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.30; + +contract IncompatibleStorageFacet { + enum Status { None, Active, Paused } + type UserId is uint64; + + struct InlineChild { + bytes4 tailFacetNodeId; + uint64 facetCount; + } + + struct InlineOuter { + bytes4 headFacetNodeId; + InlineChild child; + uint32 selectorCount; + } + + struct ContainerChild { + address amount; + bool active; + address owner; + } + + /** + * @custom:storage-location erc8042:compose.fixture.virtual-storage + */ + struct Storage { + bool flag; + Status status; + address owner; + uint8 smallUint; + uint16 mediumUint; + int24 signedValue; + bytes2 shortBytes; + UserId userId; + + bytes dynamicBytes; + string text; + + function(uint256) external returns (uint256) externalFn; + + InlineOuter inlineStruct; + + mapping(address => uint256) balances; + mapping(uint256 => address[]) mapToDynamicArray; + address[] dynamicValues; + uint16[] packedDynamicValues; + + address[5] fixedFive; + address[300] fixedThreeHundred; + address[5][10] nestedFixed; + + mapping(address => ContainerChild) childByAddress; + ContainerChild[] childList; + ContainerChild[2] fixedChildren; + mapping(uint256 => ContainerChild[]) nestedChildren; + + mapping(bytes => uint256) bytesKeyed; + mapping(string => uint256) stringKeyed; + + uint256 internalFn; + } + + bytes32 private constant STORAGE_POSITION = + keccak256("compose.fixture.virtual-storage"); + + function readIncompatibleFlag() external view returns (bool) { + return _storage().flag; + } + + function exportSelectors() external pure returns (bytes4[] memory selectors) { + selectors = new bytes4[](1); + selectors[0] = this.readIncompatibleFlag.selector; + } + + function _storage() private pure returns (Storage storage s) { + bytes32 position = STORAGE_POSITION; + assembly { + s.slot := position + } + } +} diff --git a/cli/test/pipelines/validatePipeline/harness.ts b/cli/test/pipelines/validatePipeline/harness.ts new file mode 100644 index 00000000..e7b9924e --- /dev/null +++ b/cli/test/pipelines/validatePipeline/harness.ts @@ -0,0 +1,60 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import { ComposeContext } from "../../../src/context/types"; +import { Context } from "../../../src/context/context"; + +export type ValidatePipelineHarness = { + ctx: ComposeContext; + projectRoot: string; + cleanup(): Promise; +}; + +const facets = [ + "FullStorageFacet", + "CompatibleStorageFacet", + "IncompatibleStorageFacet", +]; + +/** Creates a Foundry project containing compatible and incompatible storage facets. */ +export async function createValidatePipelineHarness(): Promise { + const projectRoot = await fs.mkdtemp(path.join(os.tmpdir(), "compose-validate-pipeline-")); + const sourceRoot = path.join(projectRoot, "src"); + const fixtureRoot = path.join(__dirname, "fixtures"); + const ctx = Context.create(); + + await fs.mkdir(sourceRoot, { recursive: true }); + await Promise.all(facets.map((facet) => fs.copyFile( + path.join(fixtureRoot, `${facet}.sol`), + path.join(sourceRoot, `${facet}.sol`), + ))); + await fs.writeFile( + path.join(projectRoot, "foundry.toml"), + '[profile.default]\nsrc = "src"\nout = "out"\nsolc = "0.8.30"\n', + "utf8", + ); + await fs.writeFile( + path.join(projectRoot, "compose.json"), + JSON.stringify({ + framework: "foundry", + diamonds: { + StorageDiamond: { + contract: "src/Diamond.sol:Diamond", + facets: Object.fromEntries(facets.map((facet) => [facet, { + source: "local", + contract: `src/${facet}.sol:${facet}`, + }])), + }, + }, + }, null, 2), + "utf8", + ); + + ctx.param.command = "validate"; + ctx.param.projectRoot = projectRoot; + return { + ctx, + projectRoot, + cleanup: () => fs.rm(projectRoot, { recursive: true, force: true }), + }; +} diff --git a/cli/test/pipelines/validatePipeline/validatePipeline.test.ts b/cli/test/pipelines/validatePipeline/validatePipeline.test.ts new file mode 100644 index 00000000..ad094c00 --- /dev/null +++ b/cli/test/pipelines/validatePipeline/validatePipeline.test.ts @@ -0,0 +1,102 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import { describe, expect, it, vi } from "vitest"; +import { ValidatePipeline } from "../../../src/pipelines/validatePipeline"; +import { findVirtualStorageLayoutCollisions } from "../../../src/modules/validation/virtualStorageLayout"; +import { ValidationModule } from "../../../src/modules/validation/module"; +import { createValidatePipelineHarness } from "./harness"; + +describe("validate pipeline", () => { + it("compiles Solidity and reports only the incompatible storage variables", async () => { + const harness = await createValidatePipelineHarness(); + const errors = vi.spyOn(console, "error").mockImplementation(() => undefined); + const warnings = vi.spyOn(console, "warn").mockImplementation(() => undefined); + + try { + const result = await ValidatePipeline.execute(harness.ctx); + const validation = ValidationModule.getVirtualStorageLayoutValidationState(result); + const collision = validation?.result?.collisions[0]; + const mismatchSummary = validation?.result?.collisions.flatMap((item) => + item.mismatches.map((mismatch) => [ + item.virtualPath, + `${mismatch.left.structName}.${mismatch.left.variableName}: ${mismatch.left.typeName}`, + `${mismatch.right.structName}.${mismatch.right.variableName}: ${mismatch.right.typeName}`, + ].join(" | ")) + ) ?? []; + const rootRecords = validation?.result?.records.filter( + (record) => record.virtualPath === "compose.fixture.virtual-storage", + ) ?? []; + const recordFor = (contractName: string) => rootRecords.find( + (record) => record.contractName === contractName, + )!; + + expect(result.state.validatePipeline?.success).toBe(false); + expect(validation?.success).toBe(false); + expect(collision?.diamondName).toBe("StorageDiamond"); + expect(collision?.virtualPath).toBe("compose.fixture.virtual-storage"); + expect(collision?.records.map((record) => record.contractName).sort()).toEqual([ + "CompatibleStorageFacet", + "FullStorageFacet", + "IncompatibleStorageFacet", + ]); + expect(mismatchSummary).toEqual([ + "compose.fixture.virtual-storage | InlineChild.facetCount: uint32 | InlineChild.facetCount: uint64", + "compose.fixture.virtual-storage | Storage.mapToDynamicArray: mapping(uint256 => uint256[]) | Storage.mapToDynamicArray: mapping(uint256 => address[])", + "compose.fixture.virtual-storage | Storage.dynamicValues: uint256[] | Storage.dynamicValues: address[]", + "compose.fixture.virtual-storage | Storage.packedDynamicValues: uint8[] | Storage.packedDynamicValues: uint16[]", + "compose.fixture.virtual-storage | Storage.fixedFive: uint256[5] | Storage.fixedFive: address[5]", + "compose.fixture.virtual-storage | Storage.fixedThreeHundred: uint256[300] | Storage.fixedThreeHundred: address[300]", + "compose.fixture.virtual-storage | Storage.nestedFixed: uint256[5][10] | Storage.nestedFixed: address[5][10]", + "compose.fixture.virtual-storage | Storage.internalFn: function (uint256) returns (uint256) | Storage.internalFn: uint256", + "compose.fixture.virtual-storage.367 | ContainerChild.amount: uint256 | ContainerChild.amount: address", + "compose.fixture.virtual-storage.368 | ContainerChild.amount: uint256 | ContainerChild.amount: address", + "compose.fixture.virtual-storage.369 | ContainerChild.amount: uint256 | ContainerChild.amount: address", + "compose.fixture.virtual-storage.373 | ContainerChild.amount: uint256 | ContainerChild.amount: address", + ]); + expect(findVirtualStorageLayoutCollisions([ + recordFor("FullStorageFacet"), + recordFor("CompatibleStorageFacet"), + ])).toEqual([]); + expect(findVirtualStorageLayoutCollisions([ + recordFor("FullStorageFacet"), + recordFor("IncompatibleStorageFacet"), + ])).toHaveLength(1); + await expect(fs.access(path.join( + harness.projectRoot, + "out", + "FullStorageFacet.sol", + "FullStorageFacet.json", + ))).resolves.toBeUndefined(); + + const output = errors.mock.calls.flat().map(String); + expect(output).toContain(" FullStorageFacet: InlineChild.facetCount"); + expect(output).toContain(" IncompatibleStorageFacet: InlineChild.facetCount"); + expect(output).toContain(" Storage path: Storage.inlineStruct.child.facetCount"); + expect(output).toContain(" FullStorageFacet: Storage.fixedThreeHundred"); + expect(output).toContain(" IncompatibleStorageFacet: Storage.fixedThreeHundred"); + expect(output).toContain(" Storage path: Storage.fixedThreeHundred"); + expect(output).toContain(" Storage path: Storage.childByAddress[key].amount"); + expect(output).toContain(" Storage path: Storage.childList[index].amount"); + expect(output).toContain(" Storage path: Storage.fixedChildren[index].amount"); + expect(output).toContain(" Storage path: Storage.nestedChildren[key][index].amount"); + expect(output.filter((message) => message === "")).toHaveLength(12); + expect(output.filter((message) => message === ` ${"─".repeat(48)}`)).toHaveLength(7); + expect(output.filter((message) => message.includes("ContainerChild.amount"))).toHaveLength(8); + expect(output.some((message) => message.includes("CompatibleStorageFacet:"))).toBe(false); + expect(output.some((message) => message.includes("0xf1"))).toBe(false); + expect(output.some((message) => message.includes("0x2f"))).toBe(false); + + const warningOutput = warnings.mock.calls.flat().map(String); + expect(warningOutput.some( + (message) => message.includes("FullStorageFacet: Storage.internalFn"), + )).toBe(true); + expect(warningOutput.some( + (message) => message.includes("internal function storage type uses compiler-specific representation"), + )).toBe(true); + } finally { + errors.mockRestore(); + warnings.mockRestore(); + await harness.cleanup(); + } + }, 30_000); +});