forge-pack 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +134 -0
- package/dist/{pack-A6vKuf56.js → codegen--WByOfZu.mjs} +68 -70
- package/dist/{index-ECFAYGrl.d.ts → index.d.mts} +15 -14
- package/dist/index.mjs +3 -0
- package/package.json +24 -12
- package/dist/cli-Dc4X1WbF.d.ts +0 -1
- package/dist/cli.js +0 -83
- package/dist/index.d.ts +0 -68
- package/dist/index.js +0 -3
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# forge-pack
|
|
2
|
+
|
|
3
|
+
Generate self-contained Solidity deployer libraries from [Forge](https://book.getfoundry.sh/) build artifacts.
|
|
4
|
+
|
|
5
|
+
`forge-pack` reads your compiled contract JSON, resolves library dependencies, and outputs a single `.sol` file containing a deployer library with `deploy()`, `deploy2()` (CREATE2), and `initcode()` functions — ready to use in scripts or tests.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
The package does not require installation, you can directly use `npx`/`pnpx` to run it:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx forge-pack@latest <contract-name>
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## CLI Usage
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
forge-pack <ContractName> [options]
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Options
|
|
22
|
+
|
|
23
|
+
| Flag | Description | Default |
|
|
24
|
+
| ------------------ | ------------------------------------------ | ------------- |
|
|
25
|
+
| `--out <dir>` | Forge output directory | `./out` |
|
|
26
|
+
| `--output <dir>` | Where to write the deployer `.sol` file | `./deployers` |
|
|
27
|
+
| `--build` | Run `forge build` before reading artifacts | `false` |
|
|
28
|
+
| `--pragma <range>` | Solidity pragma for generated file | `>=0.8.0` |
|
|
29
|
+
| `-h, --help` | Show help message | — |
|
|
30
|
+
|
|
31
|
+
### Example
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Generate a deployer for MyToken
|
|
35
|
+
forge-pack MyToken
|
|
36
|
+
|
|
37
|
+
# Build first, then generate with a specific pragma
|
|
38
|
+
forge-pack MyToken --build --pragma "^0.8.20"
|
|
39
|
+
|
|
40
|
+
# Use a custom output directory
|
|
41
|
+
forge-pack MyToken --output src/deployers
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
This produces a file like `deployers/MyTokenDeployer.sol`:
|
|
45
|
+
|
|
46
|
+
```solidity
|
|
47
|
+
// SPDX-License-Identifier: UNLICENSED
|
|
48
|
+
pragma solidity >=0.8.0;
|
|
49
|
+
|
|
50
|
+
library MyTokenDeployer {
|
|
51
|
+
function deploy(string memory name, string memory symbol) internal returns (address deployed) { ... }
|
|
52
|
+
function deploy2(bytes32 salt, string memory name, string memory symbol) internal returns (address deployed) { ... }
|
|
53
|
+
function initcode(string memory name, string memory symbol) internal pure returns (bytes memory) { ... }
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Constructor parameters are automatically extracted from the ABI. Struct types used in constructor arguments get their definitions included in the generated file.
|
|
58
|
+
|
|
59
|
+
## Library Dependencies
|
|
60
|
+
|
|
61
|
+
If your contract links against external libraries, `forge-pack` resolves them recursively in topological order and generates inline deployment helpers. The deployer handles deploying libraries before the main contract, so the output remains self-contained.
|
|
62
|
+
|
|
63
|
+
## Programmatic API
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { findArtifact, parseArtifact, generateDeployer, resolveLibraries } from "forge-pack";
|
|
67
|
+
|
|
68
|
+
const artifactPath = findArtifact("MyToken", "./out");
|
|
69
|
+
const parsed = parseArtifact(artifactPath, "MyToken");
|
|
70
|
+
|
|
71
|
+
const libraries = resolveLibraries(parsed.linkReferences, "./out");
|
|
72
|
+
const solidity = generateDeployer(parsed, { pragma: ">=0.8.0", libraries });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Exports
|
|
76
|
+
|
|
77
|
+
**Functions:**
|
|
78
|
+
|
|
79
|
+
- `findArtifact(contractName, outDir, options?)` — Locate a contract artifact in the Forge output directory
|
|
80
|
+
- `parseArtifact(artifactPath, contractName)` — Parse a Forge artifact JSON into a structured object
|
|
81
|
+
- `resolveLibraries(linkReferences, outDir)` — Recursively resolve library dependencies in deploy order
|
|
82
|
+
- `generateDeployer(parsed, options?)` — Generate Solidity deployer library source code
|
|
83
|
+
|
|
84
|
+
**Types:** `ParsedArtifact`, `LinkReference`, `LinkReferences`, `AbiParam`, `AbiEntry`, `FindArtifactOptions`, `ResolvedLibrary`, `GenerateDeployerOptions`
|
|
85
|
+
|
|
86
|
+
## License
|
|
87
|
+
|
|
88
|
+
MIT
|
package/dist/cli.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { i as parseArtifact, n as resolveLibraries, r as findArtifact, t as generateDeployer } from "./codegen--WByOfZu.mjs";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
|
|
7
|
+
//#region src/deploy-helper.ts
|
|
8
|
+
const DEPLOY_HELPER_SOL = `// SPDX-License-Identifier: MIT
|
|
9
|
+
pragma solidity ^0.8.10;
|
|
10
|
+
|
|
11
|
+
library DeployHelper {
|
|
12
|
+
function deploy(bytes memory initcode, bytes32 salt) internal returns (address contractAddress) {
|
|
13
|
+
assembly ("memory-safe") {
|
|
14
|
+
contractAddress := create2(callvalue(), add(initcode, 32), mload(initcode), salt)
|
|
15
|
+
if iszero(contractAddress) {
|
|
16
|
+
let ptr := mload(0x40)
|
|
17
|
+
let errorSize := returndatasize()
|
|
18
|
+
returndatacopy(ptr, 0, errorSize)
|
|
19
|
+
revert(ptr, errorSize)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function deploy(bytes memory initcode) internal returns (address contractAddress) {
|
|
25
|
+
contractAddress = deploy(initcode, bytes32(0));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// @dev Deploys a library using CREATE2 with zero salt.
|
|
29
|
+
/// If the library is already deployed (duplicate across contracts), returns the existing address.
|
|
30
|
+
function deployLibrary(bytes memory initcode) internal returns (address contractAddress) {
|
|
31
|
+
bytes32 salt = bytes32(0);
|
|
32
|
+
contractAddress = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(initcode))))));
|
|
33
|
+
if (contractAddress.code.length > 0) return contractAddress;
|
|
34
|
+
contractAddress = deploy(initcode, salt);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
`;
|
|
38
|
+
|
|
39
|
+
//#endregion
|
|
40
|
+
//#region src/cli.ts
|
|
41
|
+
const pkgPath = new URL("../package.json", import.meta.url);
|
|
42
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
43
|
+
const { values, positionals } = parseArgs({
|
|
44
|
+
allowPositionals: true,
|
|
45
|
+
options: {
|
|
46
|
+
out: {
|
|
47
|
+
type: "string",
|
|
48
|
+
default: "./out"
|
|
49
|
+
},
|
|
50
|
+
output: {
|
|
51
|
+
type: "string",
|
|
52
|
+
default: "./deployers"
|
|
53
|
+
},
|
|
54
|
+
build: {
|
|
55
|
+
type: "boolean",
|
|
56
|
+
default: false
|
|
57
|
+
},
|
|
58
|
+
pragma: {
|
|
59
|
+
type: "string",
|
|
60
|
+
default: ">=0.8.0"
|
|
61
|
+
},
|
|
62
|
+
version: {
|
|
63
|
+
type: "boolean",
|
|
64
|
+
short: "v",
|
|
65
|
+
default: false
|
|
66
|
+
},
|
|
67
|
+
help: {
|
|
68
|
+
type: "boolean",
|
|
69
|
+
short: "h",
|
|
70
|
+
default: false
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
if (values.version) {
|
|
75
|
+
console.log(pkg.version);
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
if (values.help || positionals.length === 0) {
|
|
79
|
+
console.log(`Usage: forge-pack <ContractName...> [options]
|
|
80
|
+
|
|
81
|
+
Options:
|
|
82
|
+
--out <dir> Forge output directory (default: ./out)
|
|
83
|
+
--output <dir> Where to write the deployer .sol files (default: ./deployers)
|
|
84
|
+
--build Run \`forge build\` before reading artifacts
|
|
85
|
+
--pragma <range> Solidity pragma for generated files (default: >=0.8.0)
|
|
86
|
+
-v, --version Show version number
|
|
87
|
+
-h, --help Show this help message`);
|
|
88
|
+
process.exit(values.help ? 0 : 1);
|
|
89
|
+
}
|
|
90
|
+
const outDir = resolve(values.out);
|
|
91
|
+
const outputDir = resolve(values.output);
|
|
92
|
+
const pragma = values.pragma;
|
|
93
|
+
if (values.build) {
|
|
94
|
+
console.log("Running forge build...");
|
|
95
|
+
execSync("forge build", { stdio: "inherit" });
|
|
96
|
+
}
|
|
97
|
+
const utilsDir = join(outputDir, "utils");
|
|
98
|
+
const deployHelperPath = join(utilsDir, "DeployHelper.sol");
|
|
99
|
+
mkdirSync(utilsDir, { recursive: true });
|
|
100
|
+
if (!existsSync(deployHelperPath)) {
|
|
101
|
+
writeFileSync(deployHelperPath, DEPLOY_HELPER_SOL);
|
|
102
|
+
console.log(`Generated ${deployHelperPath}`);
|
|
103
|
+
}
|
|
104
|
+
let hasError = false;
|
|
105
|
+
for (const contractName of positionals) try {
|
|
106
|
+
const parsed = parseArtifact(findArtifact(contractName, outDir), contractName);
|
|
107
|
+
if (!parsed.bytecode) {
|
|
108
|
+
console.error(`Error: No bytecode found for "${contractName}". Is it an abstract contract or interface?`);
|
|
109
|
+
hasError = true;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
const hasLinks = Object.keys(parsed.linkReferences).length > 0;
|
|
113
|
+
let libraries;
|
|
114
|
+
if (hasLinks) {
|
|
115
|
+
libraries = resolveLibraries(parsed.linkReferences, outDir);
|
|
116
|
+
const libNames = libraries.map((l) => l.lib);
|
|
117
|
+
console.log(`[${contractName}] Resolved ${libNames.length} library dep(s): ${libNames.join(", ")}`);
|
|
118
|
+
}
|
|
119
|
+
const solidity = generateDeployer(parsed, {
|
|
120
|
+
pragma,
|
|
121
|
+
libraries
|
|
122
|
+
});
|
|
123
|
+
mkdirSync(outputDir, { recursive: true });
|
|
124
|
+
const outPath = join(outputDir, `${contractName}Deployer.sol`);
|
|
125
|
+
writeFileSync(outPath, solidity);
|
|
126
|
+
console.log(`Generated ${outPath}`);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error(`Error [${contractName}]: ${err.message}`);
|
|
129
|
+
hasError = true;
|
|
130
|
+
}
|
|
131
|
+
if (hasError) process.exit(1);
|
|
132
|
+
|
|
133
|
+
//#endregion
|
|
134
|
+
export { };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
-
//#region src/
|
|
5
|
-
function findArtifact(contractName, outDir
|
|
4
|
+
//#region src/artifact.ts
|
|
5
|
+
function findArtifact(contractName, outDir) {
|
|
6
6
|
const candidates = [];
|
|
7
7
|
const entries = readdirSync(outDir);
|
|
8
8
|
for (const entry of entries) {
|
|
@@ -15,11 +15,6 @@ function findArtifact(contractName, outDir, options) {
|
|
|
15
15
|
} catch {}
|
|
16
16
|
}
|
|
17
17
|
if (candidates.length === 0) throw new Error(`No artifact found for "${contractName}" in ${outDir}. Run \`forge build\` first.`);
|
|
18
|
-
if (options?.solcVersion && candidates.length > 1) {
|
|
19
|
-
for (const c of candidates) if ((JSON.parse(readFileSync(c, "utf-8"))?.metadata?.compiler?.version)?.startsWith(options.solcVersion)) return c;
|
|
20
|
-
throw new Error(`No artifact for "${contractName}" compiled with solc ${options.solcVersion}.`);
|
|
21
|
-
}
|
|
22
|
-
if (candidates.length > 1) throw new Error(`Multiple artifacts found for "${contractName}". Use --solc to disambiguate:\n` + candidates.map((c) => ` ${c}`).join("\n"));
|
|
23
18
|
return candidates[0];
|
|
24
19
|
}
|
|
25
20
|
function parseArtifact(artifactPath, contractName) {
|
|
@@ -34,6 +29,7 @@ function parseArtifact(artifactPath, contractName) {
|
|
|
34
29
|
const optimizerRuns = settings?.optimizer?.runs;
|
|
35
30
|
const viaIR = settings?.viaIR;
|
|
36
31
|
const evmVersion = settings?.evmVersion;
|
|
32
|
+
const bytecodeHash = metadata?.settings?.metadata?.bytecodeHash ?? settings?.metadata?.bytecodeHash;
|
|
37
33
|
let sourcePath;
|
|
38
34
|
if (metadata?.settings?.compilationTarget) {
|
|
39
35
|
const targets = metadata.settings.compilationTarget;
|
|
@@ -48,13 +44,13 @@ function parseArtifact(artifactPath, contractName) {
|
|
|
48
44
|
solcVersion,
|
|
49
45
|
optimizerRuns,
|
|
50
46
|
viaIR,
|
|
51
|
-
evmVersion
|
|
47
|
+
evmVersion,
|
|
48
|
+
bytecodeHash
|
|
52
49
|
};
|
|
53
50
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
*/
|
|
51
|
+
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/resolve.ts
|
|
58
54
|
function collectLibIds(linkRefs) {
|
|
59
55
|
const seen = /* @__PURE__ */ new Set();
|
|
60
56
|
const result = [];
|
|
@@ -76,6 +72,7 @@ function makeParamName(libName) {
|
|
|
76
72
|
/**
|
|
77
73
|
* Recursively resolve all library dependencies for a contract's linkReferences.
|
|
78
74
|
* Returns libraries in topological order (deploy-order: leaves first).
|
|
75
|
+
* Disambiguates duplicate paramName values with numeric suffixes.
|
|
79
76
|
*/
|
|
80
77
|
function resolveLibraries(linkRefs, outDir) {
|
|
81
78
|
const resolved = /* @__PURE__ */ new Map();
|
|
@@ -105,8 +102,31 @@ function resolveLibraries(linkRefs, outDir) {
|
|
|
105
102
|
}
|
|
106
103
|
const topLevel = collectLibIds(linkRefs);
|
|
107
104
|
for (const { file, lib } of topLevel) resolve(file, lib);
|
|
108
|
-
|
|
105
|
+
const libs = Array.from(resolved.values());
|
|
106
|
+
const nameCount = /* @__PURE__ */ new Map();
|
|
107
|
+
for (const entry of libs) {
|
|
108
|
+
const count = nameCount.get(entry.paramName) ?? 0;
|
|
109
|
+
nameCount.set(entry.paramName, count + 1);
|
|
110
|
+
}
|
|
111
|
+
const collisions = /* @__PURE__ */ new Set();
|
|
112
|
+
for (const [name, count] of nameCount) if (count > 1) collisions.add(name);
|
|
113
|
+
if (collisions.size > 0) {
|
|
114
|
+
const nameIndex = /* @__PURE__ */ new Map();
|
|
115
|
+
for (const entry of libs) {
|
|
116
|
+
if (!collisions.has(entry.paramName)) continue;
|
|
117
|
+
const idx = (nameIndex.get(entry.paramName) ?? 0) + 1;
|
|
118
|
+
nameIndex.set(entry.paramName, idx);
|
|
119
|
+
if (idx > 1) entry.paramName = `${entry.paramName}${idx}`;
|
|
120
|
+
}
|
|
121
|
+
const keyToName = /* @__PURE__ */ new Map();
|
|
122
|
+
for (const entry of libs) keyToName.set(`${entry.file}:${entry.lib}`, entry.paramName);
|
|
123
|
+
for (const entry of libs) entry.deps = collectLibIds(entry.artifact.linkReferences).map((d) => keyToName.get(`${d.file}:${d.lib}`));
|
|
124
|
+
}
|
|
125
|
+
return libs;
|
|
109
126
|
}
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/codegen.ts
|
|
110
130
|
function extractStructName(internalType) {
|
|
111
131
|
let name = internalType.replace(/^struct\s+/, "");
|
|
112
132
|
name = name.replace(/(\[\d*\])+$/, "");
|
|
@@ -216,13 +236,31 @@ function renderInitcodeBody(bytecode, segments, hasLinks) {
|
|
|
216
236
|
if (!hasLinks) return ` return hex"${bytecode}";`;
|
|
217
237
|
return ` return abi.encodePacked(${segments.map((seg) => seg.type === "hex" ? `hex"${seg.value}"` : seg.value).join(", ")});`;
|
|
218
238
|
}
|
|
239
|
+
function renderDeployBody(opts) {
|
|
240
|
+
const { inlineLibs, resolvedLibs, ctorParams, initcodeCallArgs } = opts;
|
|
241
|
+
const lines = [];
|
|
242
|
+
if (inlineLibs) for (const rlib of resolvedLibs) {
|
|
243
|
+
const { libParams: libLibParams } = buildBytecodeSegments(rlib.artifact.bytecode, rlib.artifact.linkReferences);
|
|
244
|
+
const callArgs = libLibParams.length > 0 ? libLibParams.map((lp) => lp.name).join(", ") : "";
|
|
245
|
+
lines.push(` address ${rlib.paramName} = DeployHelper.deployLibrary(_${rlib.paramName}Initcode(${callArgs}));`);
|
|
246
|
+
}
|
|
247
|
+
if (ctorParams.length > 0) {
|
|
248
|
+
const encodeArgs = ctorParams.map((p, i) => p.name || `arg${i}`).join(", ");
|
|
249
|
+
lines.push(` bytes memory args = abi.encode(${encodeArgs});`);
|
|
250
|
+
lines.push(` bytes memory initcode_ = abi.encodePacked(initcode(${initcodeCallArgs}), args);`);
|
|
251
|
+
} else lines.push(` bytes memory initcode_ = initcode(${initcodeCallArgs});`);
|
|
252
|
+
lines.push(` deployed = DeployHelper.deploy(initcode_, salt);`);
|
|
253
|
+
return lines.join("\n");
|
|
254
|
+
}
|
|
219
255
|
function generateDeployer(parsed, pragmaOrOpts) {
|
|
220
256
|
const opts = typeof pragmaOrOpts === "string" ? { pragma: pragmaOrOpts } : pragmaOrOpts ?? {};
|
|
221
257
|
const pragma = opts.pragma ?? ">=0.8.0";
|
|
222
258
|
const resolvedLibs = opts.libraries ?? [];
|
|
223
259
|
const { contractName, abi, bytecode, linkReferences } = parsed;
|
|
224
260
|
const libName = `${contractName}Deployer`;
|
|
225
|
-
const
|
|
261
|
+
const ctorEntry = abi.find((e) => e.type === "constructor");
|
|
262
|
+
const ctorParams = ctorEntry?.inputs ?? [];
|
|
263
|
+
const isPayable = ctorEntry?.stateMutability === "payable";
|
|
226
264
|
const structDefs = collectStructDefs(ctorParams);
|
|
227
265
|
const structNames = new Set(structDefs.map((s) => s.name));
|
|
228
266
|
const { segments: mainSegments, libParams: mainLibParams } = buildBytecodeSegments(bytecode, linkReferences);
|
|
@@ -232,8 +270,9 @@ function generateDeployer(parsed, pragmaOrOpts) {
|
|
|
232
270
|
if (parsed.sourcePath) metaLines.push(`@notice Source Contract: ${parsed.sourcePath}`);
|
|
233
271
|
if (parsed.solcVersion) metaLines.push(`- solc: ${parsed.solcVersion}`);
|
|
234
272
|
if (parsed.optimizerRuns !== void 0) metaLines.push(`- optimizer_runs: ${parsed.optimizerRuns}`);
|
|
235
|
-
|
|
273
|
+
metaLines.push(`- viaIR: ${parsed.viaIR ?? false}`);
|
|
236
274
|
if (parsed.evmVersion) metaLines.push(`- evm_version: ${parsed.evmVersion}`);
|
|
275
|
+
metaLines.push(`- bytecodeHash: ${parsed.bytecodeHash ?? "ipfs"}`);
|
|
237
276
|
const metaBlock = metaLines.length > 0 ? [
|
|
238
277
|
` /**`,
|
|
239
278
|
` * @dev autogenerated by forge-pack`,
|
|
@@ -258,74 +297,33 @@ function generateDeployer(parsed, pragmaOrOpts) {
|
|
|
258
297
|
const deployParams = [];
|
|
259
298
|
for (let i = 0; i < ctorParams.length; i++) deployParams.push(formatParamWithStructs(ctorParams[i], i, structNames));
|
|
260
299
|
if (hasLinks && !inlineLibs) for (const lp of mainLibParams) deployParams.push(`address ${lp.name}`);
|
|
261
|
-
const deployParamStr = deployParams.join(", ")
|
|
262
|
-
const deployBodyLines = [];
|
|
263
|
-
if (inlineLibs) for (const rlib of resolvedLibs) {
|
|
264
|
-
const { libParams: libLibParams } = buildBytecodeSegments(rlib.artifact.bytecode, rlib.artifact.linkReferences);
|
|
265
|
-
const callArgs = libLibParams.length > 0 ? libLibParams.map((lp) => lp.name).join(", ") : "";
|
|
266
|
-
deployBodyLines.push(` address ${rlib.paramName} = _create(_${rlib.paramName}Initcode(${callArgs}));`);
|
|
267
|
-
}
|
|
300
|
+
const deployParamStr = deployParams.length > 0 ? `${deployParams.join(", ")}, bytes32 salt` : `bytes32 salt`;
|
|
268
301
|
const initcodeCallArgs = hasLinks ? mainLibParams.map((lp) => lp.name).join(", ") : "";
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
const deployBody = deployBodyLines.join("\n");
|
|
279
|
-
const deploy2BodyLines = [];
|
|
280
|
-
if (inlineLibs) for (const rlib of resolvedLibs) {
|
|
281
|
-
const { libParams: libLibParams } = buildBytecodeSegments(rlib.artifact.bytecode, rlib.artifact.linkReferences);
|
|
282
|
-
const callArgs = libLibParams.length > 0 ? libLibParams.map((lp) => lp.name).join(", ") : "";
|
|
283
|
-
deploy2BodyLines.push(` address ${rlib.paramName} = _create(_${rlib.paramName}Initcode(${callArgs}));`);
|
|
284
|
-
}
|
|
285
|
-
if (ctorParams.length > 0) {
|
|
286
|
-
const encodeArgs = ctorParams.map((p, i) => p.name || `arg${i}`).join(", ");
|
|
287
|
-
deploy2BodyLines.push(` bytes memory args = abi.encode(${encodeArgs});`);
|
|
288
|
-
deploy2BodyLines.push(` bytes memory initcode_ = abi.encodePacked(initcode(${initcodeCallArgs}), args);`);
|
|
289
|
-
deploy2BodyLines.push(` deployed = _create2(initcode_, salt);`);
|
|
290
|
-
} else {
|
|
291
|
-
deploy2BodyLines.push(` bytes memory initcode_ = initcode(${initcodeCallArgs});`);
|
|
292
|
-
deploy2BodyLines.push(` deployed = _create2(initcode_, salt);`);
|
|
293
|
-
}
|
|
294
|
-
const deploy2Body = deploy2BodyLines.join("\n");
|
|
295
|
-
const deploy2Params = deployParams.length > 0 ? `${deployParamStr}, bytes32 salt` : `bytes32 salt`;
|
|
302
|
+
const payableModifier = isPayable ? " payable" : "";
|
|
303
|
+
const deployBody = renderDeployBody({
|
|
304
|
+
inlineLibs,
|
|
305
|
+
resolvedLibs,
|
|
306
|
+
ctorParams,
|
|
307
|
+
initcodeCallArgs,
|
|
308
|
+
isPayable
|
|
309
|
+
});
|
|
296
310
|
return `// SPDX-License-Identifier: MIT
|
|
297
311
|
pragma solidity ${pragma};
|
|
298
312
|
|
|
313
|
+
import {DeployHelper} from "./utils/DeployHelper.sol";
|
|
314
|
+
|
|
299
315
|
library ${libName} {
|
|
300
316
|
${metaBlock}${structBlock ? `\n${structBlock}\n` : ""}
|
|
301
|
-
function deploy(${deployParamStr}) internal returns (address deployed) {
|
|
317
|
+
function deploy(${deployParamStr}) internal${payableModifier} returns (address deployed) {
|
|
302
318
|
${deployBody}
|
|
303
319
|
}
|
|
304
320
|
|
|
305
|
-
function deploy2(${deploy2Params}) internal returns (address deployed) {
|
|
306
|
-
${deploy2Body}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
321
|
function initcode(${initcodeParams}) internal pure returns (bytes memory) {
|
|
310
322
|
${initcodeBody}
|
|
311
323
|
}
|
|
312
|
-
${libInitcodeFns.length > 0 ? "\n" + libInitcodeFns.join("\n\n") + "\n" : ""}
|
|
313
|
-
function _create(bytes memory initcode_) private returns (address deployed) {
|
|
314
|
-
assembly {
|
|
315
|
-
deployed := create(0, add(initcode_, 0x20), mload(initcode_))
|
|
316
|
-
if iszero(deployed) { revert(0, returndatasize()) }
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function _create2(bytes memory initcode_, bytes32 salt) private returns (address deployed) {
|
|
321
|
-
assembly {
|
|
322
|
-
deployed := create2(0, add(initcode_, 0x20), mload(initcode_), salt)
|
|
323
|
-
if iszero(deployed) { revert(0, returndatasize()) }
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
}
|
|
324
|
+
${libInitcodeFns.length > 0 ? "\n" + libInitcodeFns.join("\n\n") + "\n" : ""}}
|
|
327
325
|
`;
|
|
328
326
|
}
|
|
329
327
|
|
|
330
328
|
//#endregion
|
|
331
|
-
export {
|
|
329
|
+
export { parseArtifact as i, resolveLibraries as n, findArtifact as r, generateDeployer as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
//#region src/
|
|
1
|
+
//#region src/types.d.ts
|
|
2
2
|
interface LinkReference {
|
|
3
3
|
start: number;
|
|
4
4
|
length: number;
|
|
@@ -32,14 +32,8 @@ interface ParsedArtifact {
|
|
|
32
32
|
optimizerRuns?: number;
|
|
33
33
|
viaIR?: boolean;
|
|
34
34
|
evmVersion?: string;
|
|
35
|
+
bytecodeHash?: string;
|
|
35
36
|
}
|
|
36
|
-
interface FindArtifactOptions {
|
|
37
|
-
solcVersion?: string;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* A library dependency resolved from linkReferences, with its own parsed
|
|
41
|
-
* artifact and the param name used in generated code.
|
|
42
|
-
*/
|
|
43
37
|
interface ResolvedLibrary {
|
|
44
38
|
/** Parameter-style name, e.g. "mathLib" */
|
|
45
39
|
paramName: string;
|
|
@@ -52,17 +46,24 @@ interface ResolvedLibrary {
|
|
|
52
46
|
/** Param names of libraries this library depends on (its own linkReferences) */
|
|
53
47
|
deps: string[];
|
|
54
48
|
}
|
|
55
|
-
|
|
49
|
+
interface GenerateDeployerOptions {
|
|
50
|
+
pragma?: string;
|
|
51
|
+
libraries?: ResolvedLibrary[];
|
|
52
|
+
}
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/artifact.d.ts
|
|
55
|
+
declare function findArtifact(contractName: string, outDir: string): string;
|
|
56
56
|
declare function parseArtifact(artifactPath: string, contractName: string): ParsedArtifact;
|
|
57
|
+
//#endregion
|
|
58
|
+
//#region src/resolve.d.ts
|
|
57
59
|
/**
|
|
58
60
|
* Recursively resolve all library dependencies for a contract's linkReferences.
|
|
59
61
|
* Returns libraries in topological order (deploy-order: leaves first).
|
|
62
|
+
* Disambiguates duplicate paramName values with numeric suffixes.
|
|
60
63
|
*/
|
|
61
64
|
declare function resolveLibraries(linkRefs: LinkReferences, outDir: string): ResolvedLibrary[];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
libraries?: ResolvedLibrary[];
|
|
65
|
-
}
|
|
65
|
+
//#endregion
|
|
66
|
+
//#region src/codegen.d.ts
|
|
66
67
|
declare function generateDeployer(parsed: ParsedArtifact, pragmaOrOpts?: string | GenerateDeployerOptions): string;
|
|
67
68
|
//#endregion
|
|
68
|
-
export { type AbiEntry, type AbiParam, type
|
|
69
|
+
export { type AbiEntry, type AbiParam, type GenerateDeployerOptions, type LinkReference, type LinkReferences, type ParsedArtifact, type ResolvedLibrary, findArtifact, generateDeployer, parseArtifact, resolveLibraries };
|
package/dist/index.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "forge-pack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Generate self-contained Solidity deployer files from Forge build artifacts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"forge-pack": "./dist/cli.
|
|
7
|
+
"forge-pack": "./dist/cli.mjs"
|
|
8
8
|
},
|
|
9
|
-
"main": "./dist/index.
|
|
10
|
-
"types": "./dist/index.d.
|
|
9
|
+
"main": "./dist/index.mjs",
|
|
10
|
+
"types": "./dist/index.d.mts",
|
|
11
11
|
"exports": {
|
|
12
12
|
".": {
|
|
13
|
-
"import": "./dist/index.
|
|
14
|
-
"types": "./dist/index.d.
|
|
13
|
+
"import": "./dist/index.mjs",
|
|
14
|
+
"types": "./dist/index.d.mts"
|
|
15
15
|
}
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
@@ -24,18 +24,30 @@
|
|
|
24
24
|
"deployer",
|
|
25
25
|
"bytecode"
|
|
26
26
|
],
|
|
27
|
-
"
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/akshatmittal/forge-pack.git"
|
|
30
|
+
},
|
|
31
|
+
"author": "Akshat Mittal <hi@akshatmittal.com>",
|
|
28
32
|
"license": "MIT",
|
|
29
33
|
"devDependencies": {
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"@
|
|
34
|
+
"@changesets/changelog-github": "^0.5.2",
|
|
35
|
+
"@changesets/cli": "^2.29.8",
|
|
36
|
+
"@changesets/config": "^3.1.2",
|
|
37
|
+
"@types/node": "^24.10.13",
|
|
38
|
+
"prettier": "^3.8.1",
|
|
39
|
+
"tsdown": "^0.20.3",
|
|
40
|
+
"typescript": "^5.9.3"
|
|
33
41
|
},
|
|
34
42
|
"publishConfig": {
|
|
35
43
|
"access": "public"
|
|
36
44
|
},
|
|
37
45
|
"scripts": {
|
|
38
|
-
"build": "tsdown
|
|
39
|
-
"dev": "tsdown --watch"
|
|
46
|
+
"build": "tsdown",
|
|
47
|
+
"dev": "tsdown --watch",
|
|
48
|
+
"changeset": "changeset",
|
|
49
|
+
"version": "changeset version",
|
|
50
|
+
"release": "changeset publish",
|
|
51
|
+
"format": "prettier --write \"**/*.{ts,tsx,md,json}\""
|
|
40
52
|
}
|
|
41
53
|
}
|
package/dist/cli-Dc4X1WbF.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/cli.js
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { i as resolveLibraries, n as generateDeployer, r as parseArtifact, t as findArtifact } from "./pack-A6vKuf56.js";
|
|
3
|
-
import { parseArgs } from "node:util";
|
|
4
|
-
import { execSync } from "node:child_process";
|
|
5
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { join, resolve } from "node:path";
|
|
7
|
-
|
|
8
|
-
//#region src/cli.ts
|
|
9
|
-
const { values, positionals } = parseArgs({
|
|
10
|
-
allowPositionals: true,
|
|
11
|
-
options: {
|
|
12
|
-
out: {
|
|
13
|
-
type: "string",
|
|
14
|
-
default: "./out"
|
|
15
|
-
},
|
|
16
|
-
output: {
|
|
17
|
-
type: "string",
|
|
18
|
-
default: "./deployers"
|
|
19
|
-
},
|
|
20
|
-
build: {
|
|
21
|
-
type: "boolean",
|
|
22
|
-
default: false
|
|
23
|
-
},
|
|
24
|
-
solc: { type: "string" },
|
|
25
|
-
pragma: {
|
|
26
|
-
type: "string",
|
|
27
|
-
default: ">=0.8.0"
|
|
28
|
-
},
|
|
29
|
-
help: {
|
|
30
|
-
type: "boolean",
|
|
31
|
-
short: "h",
|
|
32
|
-
default: false
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
if (values.help || positionals.length === 0) {
|
|
37
|
-
console.log(`Usage: forge-pack <ContractName> [options]
|
|
38
|
-
|
|
39
|
-
Options:
|
|
40
|
-
--out <dir> Forge output directory (default: ./out)
|
|
41
|
-
--output <dir> Where to write the deployer .sol file (default: ./deployers)
|
|
42
|
-
--build Run \`forge build\` before reading artifacts
|
|
43
|
-
--solc <version> Filter artifact by solc version (when multiple exist)
|
|
44
|
-
--pragma <range> Solidity pragma for generated file (default: >=0.8.0)
|
|
45
|
-
-h, --help Show this help message`);
|
|
46
|
-
process.exit(values.help ? 0 : 1);
|
|
47
|
-
}
|
|
48
|
-
const contractName = positionals[0];
|
|
49
|
-
const outDir = resolve(values.out);
|
|
50
|
-
const outputDir = resolve(values.output);
|
|
51
|
-
const pragma = values.pragma;
|
|
52
|
-
if (values.build) {
|
|
53
|
-
console.log("Running forge build...");
|
|
54
|
-
execSync("forge build", { stdio: "inherit" });
|
|
55
|
-
}
|
|
56
|
-
try {
|
|
57
|
-
const parsed = parseArtifact(findArtifact(contractName, outDir, { solcVersion: values.solc }), contractName);
|
|
58
|
-
if (!parsed.bytecode) {
|
|
59
|
-
console.error(`Error: No bytecode found for "${contractName}". Is it an abstract contract or interface?`);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
const hasLinks = Object.keys(parsed.linkReferences).length > 0;
|
|
63
|
-
let libraries;
|
|
64
|
-
if (hasLinks) {
|
|
65
|
-
libraries = resolveLibraries(parsed.linkReferences, outDir);
|
|
66
|
-
const libNames = libraries.map((l) => l.lib);
|
|
67
|
-
console.log(`Resolved ${libNames.length} library dep(s): ${libNames.join(", ")}`);
|
|
68
|
-
}
|
|
69
|
-
const solidity = generateDeployer(parsed, {
|
|
70
|
-
pragma,
|
|
71
|
-
libraries
|
|
72
|
-
});
|
|
73
|
-
mkdirSync(outputDir, { recursive: true });
|
|
74
|
-
const outPath = join(outputDir, `${contractName}Deployer.sol`);
|
|
75
|
-
writeFileSync(outPath, solidity);
|
|
76
|
-
console.log(`Generated ${outPath}`);
|
|
77
|
-
} catch (err) {
|
|
78
|
-
console.error(`Error: ${err.message}`);
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
//#endregion
|
|
83
|
-
export { };
|
package/dist/index.d.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
//#region src/pack.d.ts
|
|
2
|
-
interface LinkReference {
|
|
3
|
-
start: number;
|
|
4
|
-
length: number;
|
|
5
|
-
}
|
|
6
|
-
interface LinkReferences {
|
|
7
|
-
[file: string]: {
|
|
8
|
-
[lib: string]: LinkReference[];
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
interface AbiParam {
|
|
12
|
-
name: string;
|
|
13
|
-
type: string;
|
|
14
|
-
internalType?: string;
|
|
15
|
-
components?: AbiParam[];
|
|
16
|
-
indexed?: boolean;
|
|
17
|
-
}
|
|
18
|
-
interface AbiEntry {
|
|
19
|
-
type: string;
|
|
20
|
-
name?: string;
|
|
21
|
-
inputs?: AbiParam[];
|
|
22
|
-
outputs?: AbiParam[];
|
|
23
|
-
stateMutability?: string;
|
|
24
|
-
}
|
|
25
|
-
interface ParsedArtifact {
|
|
26
|
-
contractName: string;
|
|
27
|
-
abi: AbiEntry[];
|
|
28
|
-
bytecode: string;
|
|
29
|
-
linkReferences: LinkReferences;
|
|
30
|
-
sourcePath?: string;
|
|
31
|
-
solcVersion?: string;
|
|
32
|
-
optimizerRuns?: number;
|
|
33
|
-
viaIR?: boolean;
|
|
34
|
-
evmVersion?: string;
|
|
35
|
-
}
|
|
36
|
-
interface FindArtifactOptions {
|
|
37
|
-
solcVersion?: string;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* A library dependency resolved from linkReferences, with its own parsed
|
|
41
|
-
* artifact and the param name used in generated code.
|
|
42
|
-
*/
|
|
43
|
-
interface ResolvedLibrary {
|
|
44
|
-
/** Parameter-style name, e.g. "mathLib" */
|
|
45
|
-
paramName: string;
|
|
46
|
-
/** Source file declaring the library, e.g. "src/libs/MathLib.sol" */
|
|
47
|
-
file: string;
|
|
48
|
-
/** Library contract name, e.g. "MathLib" */
|
|
49
|
-
lib: string;
|
|
50
|
-
/** Parsed artifact for this library */
|
|
51
|
-
artifact: ParsedArtifact;
|
|
52
|
-
/** Param names of libraries this library depends on (its own linkReferences) */
|
|
53
|
-
deps: string[];
|
|
54
|
-
}
|
|
55
|
-
declare function findArtifact(contractName: string, outDir: string, options?: FindArtifactOptions): string;
|
|
56
|
-
declare function parseArtifact(artifactPath: string, contractName: string): ParsedArtifact;
|
|
57
|
-
/**
|
|
58
|
-
* Recursively resolve all library dependencies for a contract's linkReferences.
|
|
59
|
-
* Returns libraries in topological order (deploy-order: leaves first).
|
|
60
|
-
*/
|
|
61
|
-
declare function resolveLibraries(linkRefs: LinkReferences, outDir: string): ResolvedLibrary[];
|
|
62
|
-
interface GenerateDeployerOptions {
|
|
63
|
-
pragma?: string;
|
|
64
|
-
libraries?: ResolvedLibrary[];
|
|
65
|
-
}
|
|
66
|
-
declare function generateDeployer(parsed: ParsedArtifact, pragmaOrOpts?: string | GenerateDeployerOptions): string;
|
|
67
|
-
//#endregion
|
|
68
|
-
export { type AbiEntry, type AbiParam, type FindArtifactOptions, type GenerateDeployerOptions, type LinkReference, type LinkReferences, type ParsedArtifact, type ResolvedLibrary, findArtifact, generateDeployer, parseArtifact, resolveLibraries };
|
package/dist/index.js
DELETED