diff --git a/package.json b/package.json index fa0baac..e7fc8da 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@gnosis-guild/zodiac-core", - "version": "2.0.2", + "version": "2.0.3", "description": "Zodiac is a composable design philosophy and collection of standards for building DAO ecosystem tooling.", "author": "Auryn Macmillan ", "license": "LGPL-3.0+", diff --git a/src/artifact/internal/getBuildArtifact.ts b/src/artifact/internal/getBuildArtifact.ts index 5d96bda..2781c65 100644 --- a/src/artifact/internal/getBuildArtifact.ts +++ b/src/artifact/internal/getBuildArtifact.ts @@ -1,7 +1,9 @@ +import assert from "assert"; import path from "path"; +import { isAddress } from "ethers"; import { readdirSync, readFileSync, statSync } from "fs"; -import { BuildArtifact } from "../../types"; +import { BuildArtifact, MastercopyArtifact } from "../../types"; /** * Retrieves the build artifact for a specified contract. @@ -20,9 +22,8 @@ export default function getBuildArtifact( buildDirPath ); - const { sourceName, contractName, bytecode, abi } = JSON.parse( - readFileSync(artifactPath, "utf8") - ); + const { sourceName, contractName, bytecode, abi, linkReferences } = + JSON.parse(readFileSync(artifactPath, "utf8")); const { solcLongVersion, input } = JSON.parse( readFileSync(buildInfoPath, "utf8") @@ -33,11 +34,74 @@ export default function getBuildArtifact( sourceName, compilerVersion: `v${solcLongVersion}`, compilerInput: input, - abi, bytecode, + abi, + linkReferences, }; } +/** + * Replaces library references in the bytecode with actual deployed addresses. + * + * This function scans the bytecode and replaces placeholder references + * to libraries with their actual on-chain addresses. It ensures that + * the library addresses are valid and properly formatted. + * + * @param {string} bytecode - The bytecode that may contain library references. + * @param {Record} linkReferences - References to libraries, as returned by the compiler. + * @param {Record} libraryAddresses - A map of library names to their deployed addresses. + * @returns {string} - The updated bytecode with library references replaced by actual addresses. + * + * @throws {Error} - Throws if a library address is missing or incorrectly formatted. + */ +export function resolveLinksInBytecode( + contractVersion: string, + artifact: BuildArtifact, + mastercopies: Record> +): string { + let bytecode = artifact.bytecode; + + for (const libraryPath of Object.keys(artifact.linkReferences)) { + for (const libraryName of Object.keys( + artifact.linkReferences[libraryPath] + )) { + console.log(`libraryPath ${libraryPath} libraryName ${libraryName}`); + + if ( + !mastercopies[libraryName] || + !mastercopies[libraryName][contractVersion] + ) { + throw new Error( + `Could not link ${libraryName} for ${artifact.contractName}` + ); + } + + let { address: libraryAddress } = + mastercopies[libraryName][contractVersion]; + + assert(isAddress(libraryAddress)); + + for (const { length, start: offset } of artifact.linkReferences[ + libraryPath + ][libraryName]) { + assert(length == 20); + + // the offset is in bytes, and does not account for the trailing 0x + const left = 2 + offset * 2; + const right = left + length * 2; + + bytecode = `${bytecode.slice(0, left)}${libraryAddress.slice(2).toLowerCase()}${bytecode.slice(right)}`; + + console.log( + `Replaced library reference at ${offset} with address ${libraryAddress}` + ); + } + } + } + + return bytecode; +} + /** * Resolves the paths to the artifact and build info files for a specified contract. * @@ -94,8 +158,10 @@ export function sourcePathFromSourceCode( compilerInput.sources )) { const sourceCode = (sourceCodeEntry as any).content; - const contractPattern = new RegExp(`contract\\s+${contractName}\\s+`, "g"); - + const contractPattern = new RegExp( + `(contract|library)\\s+${contractName}\\s+`, + "g" + ); if (contractPattern.test(sourceCode)) { return sourceName; } diff --git a/src/artifact/writeMastercopyFromBuild.ts b/src/artifact/writeMastercopyFromBuild.ts index ecc73da..114c7c2 100644 --- a/src/artifact/writeMastercopyFromBuild.ts +++ b/src/artifact/writeMastercopyFromBuild.ts @@ -8,7 +8,9 @@ import { defaultBuildDir, defaultMastercopyArtifactsFile, } from "./internal/paths"; -import getBuildArtifact from "./internal/getBuildArtifact"; +import getBuildArtifact, { + resolveLinksInBytecode, +} from "./internal/getBuildArtifact"; import { MastercopyArtifact } from "../types"; @@ -58,6 +60,12 @@ export default function writeMastercopyFromBuild({ console.warn(`Warning: overriding artifact for ${contractVersion}`); } + const bytecode = resolveLinksInBytecode( + contractVersion, + buildArtifact, + mastercopies + ); + const mastercopyArtifact: MastercopyArtifact = { contractName, sourceName: buildArtifact.sourceName, @@ -66,11 +74,11 @@ export default function writeMastercopyFromBuild({ factory, address: predictSingletonAddress({ factory, - bytecode: buildArtifact.bytecode, + bytecode, constructorArgs, salt, }), - bytecode: buildArtifact.bytecode, + bytecode, constructorArgs, salt, abi: buildArtifact.abi, diff --git a/src/types.ts b/src/types.ts index aaa23db..553577f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -20,9 +20,13 @@ export type BuildArtifact = { compilerInput: any; bytecode: string; abi: any; + linkReferences: Record< + string, + Record + >; }; -export type MastercopyArtifact = BuildArtifact & { +export type MastercopyArtifact = Omit & { contractVersion: string; factory: string; constructorArgs: {