Skip to content

Commit

Permalink
feat: add tests to resolveLinkedBytecode and linkBytecode
Browse files Browse the repository at this point in the history
  • Loading branch information
schaable committed Oct 14, 2024
1 parent 35746a6 commit 5c78b30
Showing 1 changed file with 327 additions and 0 deletions.
327 changes: 327 additions & 0 deletions v-next/hardhat-utils/test/bytecode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,327 @@
import type { PrefixedHexString } from "../src/hex.js";

import assert from "node:assert/strict";
import { describe, it } from "node:test";

import { resolveLinkedBytecode, linkBytecode } from "../src/bytecode.js";
import { getUnprefixedHexString } from "../src/hex.js";

interface LibraryAddresses {
[contractName: string]: PrefixedHexString;
}

describe("bytecode", () => {
describe("resolveLinkedBytecode", () => {
describe("validation", () => {
it("should throw InvalidLibraryAddressError if a library address is invalid", () => {
const artifact = {
bytecode:
"0xbytecodebytecodebytecode__$placeholderplaceholderplaceholderp$__bytecode",
linkReferences: {
"contracts/Library.sol": {
Library: [{ start: 12, length: 20 }],
},
},
};

const libraries: LibraryAddresses = {
Library: "0xnotanaddress",
};

assert.throws(() => resolveLinkedBytecode(artifact, libraries), {
name: "InvalidLibraryAddressError",
message: `The following libraries have invalid addresses:
\t* "Library": "0xnotanaddress"
Please provide valid Ethereum addresses for these libraries.`,
});
});

it("should throw AmbiguousLibraryNameError if a library name matches multiple needed libraries", () => {
const artifact = {
bytecode:
"0xbytecodebytecodebytecode__$placeholderplaceholderplaceholderp$__bytecode__$placeholderplaceholderplaceholderp$__",
linkReferences: {
"contracts/Foo.sol": {
Library: [{ start: 12, length: 20 }],
},
"contracts/Bar.sol": {
Library: [{ start: 36, length: 20 }],
},
},
};

const libraries: LibraryAddresses = {
Library: "0x1234567890123456789012345678901234567890",
};

assert.throws(() => resolveLinkedBytecode(artifact, libraries), {
name: "AmbiguousLibraryNameError",
message: `The following libraries may resolve to multiple libraries:
\t* "Library":
\t\t* "contracts/Foo.sol:Library"
\t\t* "contracts/Bar.sol:Library"
Please provide the fully qualified name for these libraries.`,
});
});

it("should throw UnnecessaryLibraryError if an unnecessary library is provided", () => {
const artifact = {
bytecode: "0xbytecodebytecodebytecode",
linkReferences: {},
};

const libraries: LibraryAddresses = {
Library: "0x1234567890123456789012345678901234567890",
};

assert.throws(() => resolveLinkedBytecode(artifact, libraries), {
name: "UnnecessaryLibraryError",
message: `The following libraries are not referenced by the contract:
\t* "Library"
Please provide only the libraries that are actually needed.`,
});
});

it("should throw OverlappingLibrariesError if a library is provided more than once", () => {
const artifact = {
bytecode:
"0xbytecodebytecodebytecode__$placeholderplaceholderplaceholderp$__bytecode",
linkReferences: {
"contracts/Library.sol": {
Library: [{ start: 12, length: 20 }],
},
},
};

const libraries: LibraryAddresses = {
"contracts/Library.sol:Library":
"0x1234567890123456789012345678901234567890",
Library: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
};

assert.throws(() => resolveLinkedBytecode(artifact, libraries), {
name: "OverlappingLibrariesError",
message: `The following libraries are provided more than once:
\t* "contracts/Library.sol:Library"
Please ensure that each library is provided only once, either by its name or its fully qualified name.`,
});
});

it("should throw MissingLibrariesError if a needed library is missing", () => {
const artifact = {
bytecode:
"0xbytecodebytecodebytecode__$placeholderplaceholderplaceholderp$__bytecode",
linkReferences: {
"contracts/Library.sol": {
Library: [{ start: 12, length: 20 }],
},
},
};

assert.throws(() => resolveLinkedBytecode(artifact, {}), {
name: "MissingLibrariesError",
message: `The following libraries are missing:
\t* "contracts/Library.sol:Library"
Please provide all the required libraries.`,
});
});
});

describe("linking", () => {
it("should link a contract with a library", () => {
const artifact = {
bytecode:
"0xbytecodebytecodebytecode__$placeholderplaceholderplaceholderp$__bytecode",
linkReferences: {
"contracts/Library.sol": {
// The start position of the reference doesn't take into account
// the "0x" prefix of the bytecode.
Library: [{ start: 12, length: 20 }],
},
},
};
const library1Fqn = "contracts/Library.sol:Library";
const libraries: LibraryAddresses = {
[library1Fqn]: "0x1234567890123456789012345678901234567890",
};
const expectedBytecode = `0xbytecodebytecodebytecode${getUnprefixedHexString(libraries[library1Fqn])}bytecode`;

assert.equal(
resolveLinkedBytecode(artifact, libraries),
expectedBytecode,
);
});

it("should link a contract with multiple libraries", () => {
const artifact = {
bytecode:
"0xbytecodebytecodebytecode__$placeholderplaceholderplaceholderp$__bytecode__$placeholderplaceholderplaceholderp$__bytecodebytecodebytecode__$placeholderplaceholderplaceholderp$____$placeholderplaceholderplaceholderp$__",
linkReferences: {
"contracts/Foo.sol": {
Library1: [
{ start: 12, length: 20 },
{ start: 36, length: 20 },
],
Library2: [{ start: 88, length: 20 }],
},
"contracts/Bar.sol": {
Library3: [{ start: 68, length: 20 }],
},
},
};
const library1Fqn = "contracts/Foo.sol:Library1";
const library2Fqn = "contracts/Foo.sol:Library2";
const library3Fqn = "contracts/Bar.sol:Library3";
const libraries: LibraryAddresses = {
[library1Fqn]: "0x1234567890123456789012345678901234567890",
[library2Fqn]: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
[library3Fqn]: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
};

const expectedBytecode = `0xbytecodebytecodebytecode${getUnprefixedHexString(libraries[library1Fqn])}bytecode${getUnprefixedHexString(libraries[library1Fqn])}bytecodebytecodebytecode${getUnprefixedHexString(libraries[library3Fqn])}${getUnprefixedHexString(libraries[library2Fqn])}`;

assert.equal(
resolveLinkedBytecode(artifact, libraries),
expectedBytecode,
);
});

it("should allow library names without source names if they are unique", () => {
const artifact = {
bytecode:
"0xbytecodebytecodebytecode__$placeholderplaceholderplaceholderp$__bytecode__$placeholderplaceholderplaceholderp$__bytecodebytecodebytecode__$placeholderplaceholderplaceholderp$____$placeholderplaceholderplaceholderp$__",
linkReferences: {
"contracts/Foo.sol": {
Library1: [
{ start: 12, length: 20 },
{ start: 36, length: 20 },
],
Library2: [{ start: 88, length: 20 }],
},
"contracts/Bar.sol": {
Library3: [{ start: 68, length: 20 }],
},
},
};
const libraries: LibraryAddresses = {
Library1: "0x1234567890123456789012345678901234567890",
Library2: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
Library3: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
};

const expectedBytecode = `0xbytecodebytecodebytecode${getUnprefixedHexString(libraries.Library1)}bytecode${getUnprefixedHexString(libraries.Library1)}bytecodebytecodebytecode${getUnprefixedHexString(libraries.Library3)}${getUnprefixedHexString(libraries.Library2)}`;

assert.equal(
resolveLinkedBytecode(artifact, libraries),
expectedBytecode,
);
});
});
});

describe("linkBytecode", () => {
it("should link a contract with a library", () => {
const artifact = {
bytecode:
"0xbytecodebytecodebytecode__$placeholderplaceholderplaceholderp$__bytecode",
linkReferences: {
"contracts/Library.sol": {
// The start position of the reference doesn't take into account
// the "0x" prefix of the bytecode.
Library: [{ start: 12, length: 20 }],
},
},
};
const libraries = [
{
sourceName: "contracts/Library.sol",
libraryName: "Library",
libraryFqn: "contracts/Library.sol:Library",
address: "0x1234567890123456789012345678901234567890",
},
];
const expectedBytecode = `0xbytecodebytecodebytecode${getUnprefixedHexString(libraries[0].address)}bytecode`;

assert.equal(linkBytecode(artifact, libraries), expectedBytecode);
});

it("should link a contract with multiple libraries", () => {
const artifact = {
bytecode:
"0xbytecodebytecodebytecode__$placeholderplaceholderplaceholderp$__bytecode__$placeholderplaceholderplaceholderp$__bytecodebytecodebytecode__$placeholderplaceholderplaceholderp$____$placeholderplaceholderplaceholderp$__",
linkReferences: {
"contracts/Foo.sol": {
Library1: [
{ start: 12, length: 20 },
{ start: 36, length: 20 },
],
Library2: [{ start: 88, length: 20 }],
},
"contracts/Bar.sol": {
Library3: [{ start: 68, length: 20 }],
},
},
};
const libraries = [
{
sourceName: "contracts/Foo.sol",
libraryName: "Library1",
libraryFqn: "contracts/Foo.sol:Library1",
address: "0x1234567890123456789012345678901234567890",
},
{
sourceName: "contracts/Foo.sol",
libraryName: "Library2",
libraryFqn: "contracts/Foo.sol:Library2",
address: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
},
{
sourceName: "contracts/Bar.sol",
libraryName: "Library3",
libraryFqn: "contracts/Bar.sol:Library3",
address: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
},
];
const [library1, library2, library3] = libraries;

const expectedBytecode = `0xbytecodebytecodebytecode${getUnprefixedHexString(library1.address)}bytecode${getUnprefixedHexString(library1.address)}bytecodebytecodebytecode${getUnprefixedHexString(library3.address)}${getUnprefixedHexString(library2.address)}`;

assert.equal(linkBytecode(artifact, libraries), expectedBytecode);
});

it("should return the same bytecode if no libraries are provided", () => {
const artifact = {
bytecode:
"0xbytecodebytecodebytecode__$placeholderplaceholderplaceholderp$__bytecode",
linkReferences: {
"contracts/Library.sol": {
Library: [{ start: 12, length: 20 }],
},
},
};

assert.equal(linkBytecode(artifact, []), artifact.bytecode);
});

it("should return the same bytecode if there are no libraries to link", () => {
const artifact = {
bytecode: "0xbytecodebytecodebytecode",
linkReferences: {},
};
const libraries = [
{
sourceName: "contracts/Library.sol",
libraryName: "Library",
libraryFqn: "contracts/Library.sol:Library",
address: "0x1234567890123456789012345678901234567890",
},
];
assert.equal(linkBytecode(artifact, libraries), artifact.bytecode);
});
});
});

0 comments on commit 5c78b30

Please sign in to comment.