Skip to content

Commit

Permalink
Merge pull request #18 from gnosisguild/fix/zodiac-core-library-refer…
Browse files Browse the repository at this point in the history
…ences-verification

Fix missing library references in Zodiac Core mastercopy JSON
  • Loading branch information
samepant authored Sep 13, 2024
2 parents 1b99ccd + 7bb75fe commit d375c9f
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 74 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.3",
"version": "2.0.4",
"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
62 changes: 0 additions & 62 deletions src/artifact/internal/getBuildArtifact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,68 +40,6 @@ export default function getBuildArtifact(
};
}

/**
* 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
128 changes: 128 additions & 0 deletions src/artifact/internal/linkBuildArtifact.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import assert from "assert";
import { isAddress } from "ethers";

import { BuildArtifact, MastercopyArtifact } from "../../types";

/**
* Resolves library links in a build artifact
*
*/
export default function linkBuildArtifact({
artifact,
contractVersion,
minimalCompilerInput,
mastercopies,
}: {
artifact: BuildArtifact;
contractVersion: string;
minimalCompilerInput?: string;
mastercopies: Record<string, Record<string, MastercopyArtifact>>;
}): BuildArtifact {
const bytecode = linkBytecode(artifact, contractVersion, mastercopies);
const compilerInput = linkCompilerInput(
artifact,
contractVersion,
minimalCompilerInput || artifact.compilerInput,
mastercopies
);

return {
...artifact,
bytecode,
compilerInput,
};
}

/**
* 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.
*/
function linkBytecode(
artifact: BuildArtifact,
contractVersion: string,
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;
}

function linkCompilerInput(
artifact: BuildArtifact,
contractVersion: string,
compilerInput: any,
mastercopies: Record<string, Record<string, MastercopyArtifact>>
): any {
const result = { ...compilerInput };
for (const libraryPath of Object.keys(artifact.linkReferences)) {
for (const libraryName of Object.keys(
artifact.linkReferences[libraryPath]
)) {
const libraryAddress =
mastercopies[libraryName]?.[contractVersion]?.address;
if (!libraryAddress) {
continue;
}

assert(isAddress(libraryAddress));

result.settings = {
...result.settings,
libraries: {
...result.settings.libraries,
[libraryPath]: { [libraryName]: libraryAddress },
},
};
}
}

return result;
}
24 changes: 13 additions & 11 deletions src/artifact/writeMastercopyFromBuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,8 @@ import {
defaultBuildDir,
defaultMastercopyArtifactsFile,
} from "./internal/paths";
import getBuildArtifact, {
resolveLinksInBytecode,
} from "./internal/getBuildArtifact";
import getBuildArtifact from "./internal/getBuildArtifact";
import linkBuildArtifact from "./internal/linkBuildArtifact";

import { MastercopyArtifact } from "../types";

Expand Down Expand Up @@ -49,7 +48,7 @@ export default function writeMastercopyFromBuild({
compilerInput?: any;
buildDirPath?: string;
mastercopyArtifactsFile?: string;
}) {
}): MastercopyArtifact {
const buildArtifact = getBuildArtifact(contractName, buildDirPath);

const mastercopies = existsSync(mastercopyArtifactsFile)
Expand All @@ -60,11 +59,12 @@ export default function writeMastercopyFromBuild({
console.warn(`Warning: overriding artifact for ${contractVersion}`);
}

const bytecode = resolveLinksInBytecode(
const artifact = linkBuildArtifact({
artifact: buildArtifact,
contractVersion,
buildArtifact,
mastercopies
);
minimalCompilerInput,
mastercopies,
});

const mastercopyArtifact: MastercopyArtifact = {
contractName,
Expand All @@ -74,15 +74,15 @@ export default function writeMastercopyFromBuild({
factory,
address: predictSingletonAddress({
factory,
bytecode,
bytecode: artifact.bytecode,
constructorArgs,
salt,
}),
bytecode,
bytecode: artifact.bytecode,
constructorArgs,
salt,
abi: buildArtifact.abi,
compilerInput: minimalCompilerInput || buildArtifact.compilerInput,
compilerInput: artifact.compilerInput,
};

const nextMastercopies = {
Expand Down Expand Up @@ -111,4 +111,6 @@ export default function writeMastercopyFromBuild({
JSON.stringify(sortedMastercopies, null, 2),
"utf8"
);

return mastercopyArtifact;
}
2 changes: 2 additions & 0 deletions src/artifact/writeMastercopyFromExplorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,6 @@ export default async function writeMastercopyFromExplorer({
JSON.stringify(sortedMastercopies, null, 2),
"utf8"
);

return mastercopyArtifact;
}

0 comments on commit d375c9f

Please sign in to comment.