Skip to content

Commit

Permalink
Merge pull request #16 from gnosisguild/fix/regex-match-contracts-and…
Browse files Browse the repository at this point in the history
…-libraries

Fix library reference substitution and regular expression matching for both contracts and libraries
  • Loading branch information
samepant authored Sep 9, 2024
2 parents 437900c + df8a837 commit 1b99ccd
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 12 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>",
"license": "LGPL-3.0+",
Expand Down
80 changes: 73 additions & 7 deletions src/artifact/internal/getBuildArtifact.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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")
Expand All @@ -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<string, any>} linkReferences - References to libraries, as returned by the compiler.
* @param {Record<string, string>} 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, Record<string, MastercopyArtifact>>
): 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.
*
Expand Down Expand Up @@ -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;
}
Expand Down
14 changes: 11 additions & 3 deletions src/artifact/writeMastercopyFromBuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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,
Expand All @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,13 @@ export type BuildArtifact = {
compilerInput: any;
bytecode: string;
abi: any;
linkReferences: Record<
string,
Record<string, { length: number; start: number }[]>
>;
};

export type MastercopyArtifact = BuildArtifact & {
export type MastercopyArtifact = Omit<BuildArtifact, "linkReferences"> & {
contractVersion: string;
factory: string;
constructorArgs: {
Expand Down

0 comments on commit 1b99ccd

Please sign in to comment.