recon-generate 0.0.21 → 0.0.23
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/dist/dictionary.d.ts +25 -0
- package/dist/dictionary.js +246 -0
- package/dist/index.js +22 -0
- package/dist/templateManager.js +2 -1
- package/dist/unpack.d.ts +3 -0
- package/dist/unpack.js +276 -0
- package/package.json +1 -1
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface ConstructorParam {
|
|
2
|
+
name: string;
|
|
3
|
+
type: string;
|
|
4
|
+
}
|
|
5
|
+
interface ContractInfo {
|
|
6
|
+
constructor: ConstructorParam[];
|
|
7
|
+
state_variables: {
|
|
8
|
+
[variableName: string]: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
interface FileContracts {
|
|
12
|
+
[contractName: string]: ContractInfo;
|
|
13
|
+
}
|
|
14
|
+
interface DictionaryOutput {
|
|
15
|
+
[filePath: string]: FileContracts;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Run the dictionary command
|
|
19
|
+
*/
|
|
20
|
+
export declare const runDictionary: (buildInfoPath: string, options?: {
|
|
21
|
+
outputPath?: string;
|
|
22
|
+
json?: boolean;
|
|
23
|
+
srcPrefix?: string;
|
|
24
|
+
}) => Promise<DictionaryOutput>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.runDictionary = void 0;
|
|
37
|
+
const fs = __importStar(require("fs/promises"));
|
|
38
|
+
const solc_typed_ast_1 = require("solc-typed-ast");
|
|
39
|
+
const utils_1 = require("./utils");
|
|
40
|
+
/**
|
|
41
|
+
* Load build-info from a JSON file
|
|
42
|
+
*/
|
|
43
|
+
const loadBuildInfo = async (buildInfoPath) => {
|
|
44
|
+
var _a;
|
|
45
|
+
const fileContent = await fs.readFile(buildInfoPath, 'utf-8');
|
|
46
|
+
const buildInfo = JSON.parse(fileContent);
|
|
47
|
+
const buildOutput = (_a = buildInfo.output) !== null && _a !== void 0 ? _a : buildInfo;
|
|
48
|
+
if (!buildOutput) {
|
|
49
|
+
throw new Error(`Build-info file ${buildInfoPath} is missing output data.`);
|
|
50
|
+
}
|
|
51
|
+
const filteredAstData = { ...buildOutput };
|
|
52
|
+
if (filteredAstData.sources) {
|
|
53
|
+
const validSources = {};
|
|
54
|
+
for (const [key, content] of Object.entries(filteredAstData.sources)) {
|
|
55
|
+
const ast = content.ast || content.legacyAST || content.AST;
|
|
56
|
+
if (ast && (ast.nodeType === 'SourceUnit' || ast.name === 'SourceUnit')) {
|
|
57
|
+
validSources[key] = content;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
filteredAstData.sources = validSources;
|
|
61
|
+
}
|
|
62
|
+
const reader = new solc_typed_ast_1.ASTReader();
|
|
63
|
+
const sourceUnits = reader.read(filteredAstData);
|
|
64
|
+
return { sourceUnits };
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Check if a type string represents an address, contract, or interface type
|
|
68
|
+
*/
|
|
69
|
+
const isRelevantType = (typeString) => {
|
|
70
|
+
return (typeString === 'address' ||
|
|
71
|
+
typeString === 'address payable' ||
|
|
72
|
+
typeString.startsWith('contract ') ||
|
|
73
|
+
typeString.startsWith('interface '));
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* Extract the type name from a typeString
|
|
77
|
+
* e.g., "contract IHub" -> "IHub", "address" -> "address"
|
|
78
|
+
*/
|
|
79
|
+
const extractTypeName = (typeString) => {
|
|
80
|
+
if (typeString.startsWith('contract ')) {
|
|
81
|
+
return typeString.slice('contract '.length);
|
|
82
|
+
}
|
|
83
|
+
if (typeString.startsWith('interface ')) {
|
|
84
|
+
return typeString.slice('interface '.length);
|
|
85
|
+
}
|
|
86
|
+
return typeString;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Get state variable map by ID for a contract (including inherited)
|
|
90
|
+
*/
|
|
91
|
+
const getStateVarMap = (contract) => {
|
|
92
|
+
const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
|
|
93
|
+
const varMap = new Map();
|
|
94
|
+
for (const stateVar of stateVars) {
|
|
95
|
+
varMap.set(stateVar.id, stateVar);
|
|
96
|
+
}
|
|
97
|
+
return varMap;
|
|
98
|
+
};
|
|
99
|
+
/**
|
|
100
|
+
* Check if an expression references a state variable and return the variable name
|
|
101
|
+
*/
|
|
102
|
+
const getReferencedStateVar = (node, stateVarIds) => {
|
|
103
|
+
const ref = node.vReferencedDeclaration;
|
|
104
|
+
if (ref instanceof solc_typed_ast_1.VariableDeclaration && stateVarIds.has(ref.id)) {
|
|
105
|
+
return ref.name;
|
|
106
|
+
}
|
|
107
|
+
return null;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Extract the casted type name from a type conversion call
|
|
111
|
+
* e.g., IERC20(token) -> "IERC20"
|
|
112
|
+
*/
|
|
113
|
+
const getCastedTypeName = (call) => {
|
|
114
|
+
if (call.kind !== solc_typed_ast_1.FunctionCallKind.TypeConversion) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const expr = call.vExpression;
|
|
118
|
+
if (expr instanceof solc_typed_ast_1.Identifier) {
|
|
119
|
+
const ref = expr.vReferencedDeclaration;
|
|
120
|
+
if (ref instanceof solc_typed_ast_1.ContractDefinition) {
|
|
121
|
+
return ref.name;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Collect state variables that are address, contract, or interface types
|
|
128
|
+
*/
|
|
129
|
+
const collectRelevantStateVars = (stateVars) => {
|
|
130
|
+
const vars = new Map();
|
|
131
|
+
for (const stateVar of stateVars) {
|
|
132
|
+
const typeString = stateVar.typeString;
|
|
133
|
+
if (typeString && isRelevantType(typeString)) {
|
|
134
|
+
const typeName = extractTypeName(typeString);
|
|
135
|
+
vars.set(stateVar.name, typeName);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return vars;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Get constructor parameters for a contract
|
|
142
|
+
*/
|
|
143
|
+
const getConstructorParams = (contract) => {
|
|
144
|
+
const functions = (0, utils_1.getDefinitions)(contract, 'vFunctions', true);
|
|
145
|
+
const constructor = functions.find(fn => fn.kind === solc_typed_ast_1.FunctionKind.Constructor);
|
|
146
|
+
if (!constructor) {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
return constructor.vParameters.vParameters.map(param => ({
|
|
150
|
+
name: param.name,
|
|
151
|
+
type: extractTypeName(param.typeString),
|
|
152
|
+
}));
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Find all state variable casts in a contract
|
|
156
|
+
* e.g., IFoo(stateVar) or Foo(stateVar)
|
|
157
|
+
*/
|
|
158
|
+
const findStateVarCasts = (contract, stateVarMap) => {
|
|
159
|
+
const casts = new Map();
|
|
160
|
+
for (const call of contract.getChildrenByType(solc_typed_ast_1.FunctionCall)) {
|
|
161
|
+
const castedType = getCastedTypeName(call);
|
|
162
|
+
if (!castedType)
|
|
163
|
+
continue;
|
|
164
|
+
for (const arg of call.vArguments) {
|
|
165
|
+
let stateVarName = null;
|
|
166
|
+
if (arg instanceof solc_typed_ast_1.Identifier) {
|
|
167
|
+
stateVarName = getReferencedStateVar(arg, stateVarMap);
|
|
168
|
+
}
|
|
169
|
+
else if (arg instanceof solc_typed_ast_1.MemberAccess) {
|
|
170
|
+
stateVarName = getReferencedStateVar(arg, stateVarMap);
|
|
171
|
+
}
|
|
172
|
+
if (stateVarName) {
|
|
173
|
+
if (!casts.has(stateVarName)) {
|
|
174
|
+
casts.set(stateVarName, castedType);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
return casts;
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* Run the dictionary command
|
|
183
|
+
*/
|
|
184
|
+
const runDictionary = async (buildInfoPath, options = {}) => {
|
|
185
|
+
var _a;
|
|
186
|
+
const log = options.json ? () => { } : console.log.bind(console);
|
|
187
|
+
const srcPrefix = (_a = options.srcPrefix) !== null && _a !== void 0 ? _a : 'src/';
|
|
188
|
+
log(`[recon-generate] Loading build-info from ${buildInfoPath}`);
|
|
189
|
+
const { sourceUnits } = await loadBuildInfo(buildInfoPath);
|
|
190
|
+
if (!sourceUnits || sourceUnits.length === 0) {
|
|
191
|
+
throw new Error('No source units were produced from the build-info; cannot generate dictionary.');
|
|
192
|
+
}
|
|
193
|
+
log(`[recon-generate] Found ${sourceUnits.length} source units`);
|
|
194
|
+
const output = {};
|
|
195
|
+
for (const unit of sourceUnits) {
|
|
196
|
+
const filePath = unit.absolutePath;
|
|
197
|
+
if (!filePath.startsWith(srcPrefix)) {
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
for (const contract of unit.getChildrenByType(solc_typed_ast_1.ContractDefinition)) {
|
|
201
|
+
// Skip interfaces, libraries, and abstract contracts
|
|
202
|
+
if (contract.kind !== solc_typed_ast_1.ContractKind.Contract) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (contract.abstract) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
// Get all state variables including inherited ones
|
|
209
|
+
const stateVars = (0, utils_1.getDefinitions)(contract, 'vStateVariables', true);
|
|
210
|
+
const stateVarMap = getStateVarMap(contract);
|
|
211
|
+
// 1. Get constructor parameters
|
|
212
|
+
const constructorParams = getConstructorParams(contract);
|
|
213
|
+
// 2. Collect state variables that are address/contract/interface types
|
|
214
|
+
const declaredVars = collectRelevantStateVars(stateVars);
|
|
215
|
+
// 3. Find all casts of state variables to contract/interface types
|
|
216
|
+
const casts = findStateVarCasts(contract, stateVarMap);
|
|
217
|
+
// Merge: casts override declared types
|
|
218
|
+
const mergedVars = {};
|
|
219
|
+
for (const [varName, typeName] of declaredVars) {
|
|
220
|
+
mergedVars[varName] = typeName;
|
|
221
|
+
}
|
|
222
|
+
for (const [varName, castedType] of casts) {
|
|
223
|
+
mergedVars[varName] = castedType;
|
|
224
|
+
}
|
|
225
|
+
// Include all implemented contracts
|
|
226
|
+
if (!output[filePath]) {
|
|
227
|
+
output[filePath] = {};
|
|
228
|
+
}
|
|
229
|
+
output[filePath][contract.name] = {
|
|
230
|
+
constructor: constructorParams,
|
|
231
|
+
state_variables: mergedVars,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
const jsonOutput = JSON.stringify(output, null, 2);
|
|
236
|
+
if (options.json) {
|
|
237
|
+
console.log(jsonOutput);
|
|
238
|
+
}
|
|
239
|
+
if (options.outputPath || !options.json) {
|
|
240
|
+
const dictPath = options.outputPath || 'recon-dictionary.json';
|
|
241
|
+
await fs.writeFile(dictPath, jsonOutput);
|
|
242
|
+
log(`[recon-generate] Wrote dictionary file to ${dictPath}`);
|
|
243
|
+
}
|
|
244
|
+
return output;
|
|
245
|
+
};
|
|
246
|
+
exports.runDictionary = runDictionary;
|
package/dist/index.js
CHANGED
|
@@ -46,6 +46,7 @@ const utils_1 = require("./utils");
|
|
|
46
46
|
const link_1 = require("./link");
|
|
47
47
|
const link2_1 = require("./link2");
|
|
48
48
|
const sourcemap_1 = require("./sourcemap");
|
|
49
|
+
const dictionary_1 = require("./dictionary");
|
|
49
50
|
function parseFilter(input) {
|
|
50
51
|
if (!input)
|
|
51
52
|
return undefined;
|
|
@@ -242,6 +243,27 @@ async function main() {
|
|
|
242
243
|
verbose: !!opts.verbose,
|
|
243
244
|
});
|
|
244
245
|
});
|
|
246
|
+
program
|
|
247
|
+
.command('dictionary <buildInfo>')
|
|
248
|
+
.description('Extract storage variables with contract/interface types from build-info')
|
|
249
|
+
.option('-o, --output <path>', 'Custom output path for the dictionary JSON file')
|
|
250
|
+
.option('--json', 'Output JSON to terminal (also saves to file if -o is specified)')
|
|
251
|
+
.option('--src-prefix <prefix>', 'Source directory prefix to filter (default: "src/")', 'src/')
|
|
252
|
+
.action(async function (buildInfoPath) {
|
|
253
|
+
// @ts-ignore - Commander types are complex
|
|
254
|
+
const opts = this.opts();
|
|
255
|
+
const resolvedBuildInfo = path.isAbsolute(buildInfoPath)
|
|
256
|
+
? buildInfoPath
|
|
257
|
+
: path.resolve(process.cwd(), buildInfoPath);
|
|
258
|
+
const outputPath = opts.output
|
|
259
|
+
? (path.isAbsolute(opts.output) ? opts.output : path.resolve(process.cwd(), opts.output))
|
|
260
|
+
: undefined;
|
|
261
|
+
await (0, dictionary_1.runDictionary)(resolvedBuildInfo, {
|
|
262
|
+
outputPath,
|
|
263
|
+
json: !!opts.json,
|
|
264
|
+
srcPrefix: opts.srcPrefix,
|
|
265
|
+
});
|
|
266
|
+
});
|
|
245
267
|
program
|
|
246
268
|
.command('info <contractName>')
|
|
247
269
|
.description('Generate contract info JSON (payable, constants, function relations, etc.)')
|
package/dist/templateManager.js
CHANGED
|
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.TemplateManager = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
38
|
const fs = __importStar(require("fs/promises"));
|
|
39
|
+
const case_1 = require("case");
|
|
39
40
|
const templates = __importStar(require("./templates"));
|
|
40
41
|
const types_1 = require("./types");
|
|
41
42
|
class TemplateManager {
|
|
@@ -209,7 +210,7 @@ class TemplateManager {
|
|
|
209
210
|
[path.join(suiteTargetsDir, 'ManagersTargets.sol')]: templates.managersTargetsTemplate({}),
|
|
210
211
|
};
|
|
211
212
|
for (const [contractName, functions] of Object.entries(separatedByContract)) {
|
|
212
|
-
const targetPath = path.join(suiteTargetsDir, `${contractName}Targets.sol`);
|
|
213
|
+
const targetPath = path.join(suiteTargetsDir, `${(0, case_1.pascal)(contractName)}Targets.sol`);
|
|
213
214
|
files[targetPath] = templates.targetsTemplate({
|
|
214
215
|
contractName,
|
|
215
216
|
path: functions.length > 0 ? functions[0].contractPath : '',
|
package/dist/unpack.d.ts
ADDED
package/dist/unpack.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.unpackBuildInfo = unpackBuildInfo;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
/**
|
|
40
|
+
* Load a single build-info JSON file
|
|
41
|
+
*/
|
|
42
|
+
async function loadBuildInfo(filePath) {
|
|
43
|
+
const content = await fs.promises.readFile(filePath, 'utf8');
|
|
44
|
+
return JSON.parse(content);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Load all build-info files from a path (file or directory)
|
|
48
|
+
*/
|
|
49
|
+
async function loadAllBuildInfos(inputPath) {
|
|
50
|
+
const stat = await fs.promises.stat(inputPath);
|
|
51
|
+
if (stat.isFile()) {
|
|
52
|
+
const buildInfo = await loadBuildInfo(inputPath);
|
|
53
|
+
return [buildInfo];
|
|
54
|
+
}
|
|
55
|
+
else if (stat.isDirectory()) {
|
|
56
|
+
const entries = await fs.promises.readdir(inputPath);
|
|
57
|
+
const jsonFiles = entries.filter(f => f.endsWith('.json'));
|
|
58
|
+
if (jsonFiles.length === 0) {
|
|
59
|
+
throw new Error(`No JSON files found in directory: ${inputPath}`);
|
|
60
|
+
}
|
|
61
|
+
console.log(`Found ${jsonFiles.length} build-info files in directory`);
|
|
62
|
+
const buildInfos = [];
|
|
63
|
+
for (const file of jsonFiles) {
|
|
64
|
+
const filePath = path.join(inputPath, file);
|
|
65
|
+
console.log(` Loading: ${file}`);
|
|
66
|
+
const buildInfo = await loadBuildInfo(filePath);
|
|
67
|
+
buildInfos.push(buildInfo);
|
|
68
|
+
}
|
|
69
|
+
return buildInfos;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
throw new Error(`Invalid input path: ${inputPath}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Merge multiple build-info files into a single structure
|
|
77
|
+
*/
|
|
78
|
+
function mergeBuildInfos(buildInfos) {
|
|
79
|
+
var _a, _b, _c;
|
|
80
|
+
const merged = {
|
|
81
|
+
sources: {},
|
|
82
|
+
remappings: new Set(),
|
|
83
|
+
includePaths: new Set(),
|
|
84
|
+
basePath: undefined,
|
|
85
|
+
solcVersions: new Set(),
|
|
86
|
+
optimizer: undefined,
|
|
87
|
+
viaIR: false,
|
|
88
|
+
};
|
|
89
|
+
for (const buildInfo of buildInfos) {
|
|
90
|
+
merged.solcVersions.add(buildInfo.solcVersion);
|
|
91
|
+
for (const [sourcePath, sourceData] of Object.entries(buildInfo.input.sources)) {
|
|
92
|
+
if (!merged.sources[sourcePath]) {
|
|
93
|
+
merged.sources[sourcePath] = sourceData;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if ((_a = buildInfo.input.settings) === null || _a === void 0 ? void 0 : _a.remappings) {
|
|
97
|
+
for (const remap of buildInfo.input.settings.remappings) {
|
|
98
|
+
merged.remappings.add(remap);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (buildInfo.input.includePaths) {
|
|
102
|
+
for (const includePath of buildInfo.input.includePaths) {
|
|
103
|
+
merged.includePaths.add(includePath);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
if (!merged.basePath && buildInfo.input.basePath) {
|
|
107
|
+
merged.basePath = buildInfo.input.basePath;
|
|
108
|
+
}
|
|
109
|
+
if (!merged.optimizer && ((_b = buildInfo.input.settings) === null || _b === void 0 ? void 0 : _b.optimizer)) {
|
|
110
|
+
merged.optimizer = buildInfo.input.settings.optimizer;
|
|
111
|
+
}
|
|
112
|
+
if ((_c = buildInfo.input.settings) === null || _c === void 0 ? void 0 : _c.viaIR) {
|
|
113
|
+
merged.viaIR = true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return merged;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Normalize a source path: contracts/ -> src/
|
|
120
|
+
*/
|
|
121
|
+
function normalizePath(sourcePath) {
|
|
122
|
+
if (sourcePath.startsWith('contracts/')) {
|
|
123
|
+
return 'src/' + sourcePath.slice('contracts/'.length);
|
|
124
|
+
}
|
|
125
|
+
return sourcePath;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Rewrite imports in source content:
|
|
129
|
+
* - contracts/ -> src/
|
|
130
|
+
* - Handle includePath-based src/ imports
|
|
131
|
+
*/
|
|
132
|
+
function rewriteImports(sourcePath, content, includePaths, basePath) {
|
|
133
|
+
let result = content;
|
|
134
|
+
// Rewrite contracts/ imports to src/
|
|
135
|
+
result = result.replace(/(import\s+(?:{[^}]+}\s+from\s+)?["'])contracts\//g, '$1src/');
|
|
136
|
+
// Handle includePath-based src/ imports (for libraries with self-referential imports)
|
|
137
|
+
if (includePaths.size > 0) {
|
|
138
|
+
let relativeIncludePath;
|
|
139
|
+
for (const includePath of includePaths) {
|
|
140
|
+
let relative;
|
|
141
|
+
if (basePath && includePath.startsWith(basePath)) {
|
|
142
|
+
relative = includePath.slice(basePath.length).replace(/^\//, '');
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
// Normalize the path for comparison
|
|
148
|
+
const normalizedSourcePath = normalizePath(sourcePath);
|
|
149
|
+
const normalizedRelative = normalizePath(relative);
|
|
150
|
+
if (normalizedRelative && normalizedSourcePath.startsWith(`${normalizedRelative}/`)) {
|
|
151
|
+
relativeIncludePath = normalizedRelative;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
if (relativeIncludePath) {
|
|
156
|
+
const importRegex = /import\s+(?:{[^}]+}\s+from\s+)?["']src\/([^"']+)["']/g;
|
|
157
|
+
result = result.replace(importRegex, (match, importPath) => {
|
|
158
|
+
const currentDir = path.dirname(normalizePath(sourcePath));
|
|
159
|
+
const targetPath = `${relativeIncludePath}/src/${importPath}`;
|
|
160
|
+
let relativeTo = path.relative(currentDir, path.dirname(targetPath));
|
|
161
|
+
if (!relativeTo) {
|
|
162
|
+
relativeTo = '.';
|
|
163
|
+
}
|
|
164
|
+
else if (!relativeTo.startsWith('.')) {
|
|
165
|
+
relativeTo = './' + relativeTo;
|
|
166
|
+
}
|
|
167
|
+
let newImport = path.posix.join(relativeTo, path.basename(targetPath));
|
|
168
|
+
if (!newImport.startsWith('.')) {
|
|
169
|
+
newImport = './' + newImport;
|
|
170
|
+
}
|
|
171
|
+
return match.replace(`"src/${importPath}"`, `"${newImport}"`).replace(`'src/${importPath}'`, `'${newImport}'`);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Detect remappings needed based on source paths (for Hardhat-style @scope/ imports)
|
|
179
|
+
*/
|
|
180
|
+
function detectRemappings(sources) {
|
|
181
|
+
const remappings = new Set();
|
|
182
|
+
const scopedDirs = new Set();
|
|
183
|
+
// Find all @scope/ style directories
|
|
184
|
+
for (const sourcePath of Object.keys(sources)) {
|
|
185
|
+
if (sourcePath.startsWith('@')) {
|
|
186
|
+
const parts = sourcePath.split('/');
|
|
187
|
+
if (parts.length >= 2) {
|
|
188
|
+
const scopeDir = parts[0] + '/';
|
|
189
|
+
scopedDirs.add(scopeDir);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
// Create remappings for each scoped directory
|
|
194
|
+
for (const scopeDir of scopedDirs) {
|
|
195
|
+
remappings.add(`${scopeDir}=${scopeDir}`);
|
|
196
|
+
}
|
|
197
|
+
return remappings;
|
|
198
|
+
}
|
|
199
|
+
async function unpackBuildInfo(inputPath, outputDir, _options = {}) {
|
|
200
|
+
var _a;
|
|
201
|
+
console.log(`Reading build-info from: ${inputPath}`);
|
|
202
|
+
const buildInfos = await loadAllBuildInfos(inputPath);
|
|
203
|
+
const merged = mergeBuildInfos(buildInfos);
|
|
204
|
+
const solcVersionsArray = Array.from(merged.solcVersions);
|
|
205
|
+
console.log(`Solc Version(s): ${solcVersionsArray.join(', ')}`);
|
|
206
|
+
const sourceCount = Object.keys(merged.sources).length;
|
|
207
|
+
console.log(`Found ${sourceCount} unique source files`);
|
|
208
|
+
if (merged.includePaths.size > 0) {
|
|
209
|
+
console.log(`Found ${merged.includePaths.size} include paths`);
|
|
210
|
+
}
|
|
211
|
+
await fs.promises.mkdir(outputDir, { recursive: true });
|
|
212
|
+
// Write each source file
|
|
213
|
+
let written = 0;
|
|
214
|
+
for (const [sourcePath, sourceData] of Object.entries(merged.sources)) {
|
|
215
|
+
// Normalize path: contracts/ -> src/
|
|
216
|
+
const normalizedPath = normalizePath(sourcePath);
|
|
217
|
+
const fullPath = path.join(outputDir, normalizedPath);
|
|
218
|
+
const dir = path.dirname(fullPath);
|
|
219
|
+
await fs.promises.mkdir(dir, { recursive: true });
|
|
220
|
+
// Rewrite imports
|
|
221
|
+
const content = rewriteImports(sourcePath, sourceData.content, merged.includePaths, merged.basePath);
|
|
222
|
+
await fs.promises.writeFile(fullPath, content);
|
|
223
|
+
written++;
|
|
224
|
+
if (written % 50 === 0) {
|
|
225
|
+
console.log(` Written ${written}/${sourceCount} files...`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
console.log(`Written ${written} source files to ${outputDir}`);
|
|
229
|
+
// Detect if this is a Hardhat-style project (has @scope/ directories)
|
|
230
|
+
const hasNodeModulesStyle = Array.from(Object.keys(merged.sources)).some(p => p.startsWith('@'));
|
|
231
|
+
const libsDir = hasNodeModulesStyle ? '.' : 'lib';
|
|
232
|
+
// Generate foundry.toml - no solc version specified, let auto_detect handle it
|
|
233
|
+
let foundryToml = `[profile.default]\nsrc = "src"\nout = "out"\nlibs = ["${libsDir}"]\nauto_detect_solc = true\n`;
|
|
234
|
+
if ((_a = merged.optimizer) === null || _a === void 0 ? void 0 : _a.enabled) {
|
|
235
|
+
foundryToml += `optimizer = true\noptimizer_runs = ${merged.optimizer.runs}\n`;
|
|
236
|
+
}
|
|
237
|
+
if (merged.viaIR) {
|
|
238
|
+
foundryToml += `via_ir = true\n`;
|
|
239
|
+
}
|
|
240
|
+
// Collect remappings: from build-info + auto-detected @scope/ dirs
|
|
241
|
+
const allRemappings = new Set();
|
|
242
|
+
for (const remap of merged.remappings) {
|
|
243
|
+
// Also normalize contracts/ -> src/ in remappings
|
|
244
|
+
allRemappings.add(remap.replace(/contracts\//g, 'src/'));
|
|
245
|
+
}
|
|
246
|
+
const detectedRemappings = detectRemappings(merged.sources);
|
|
247
|
+
for (const remap of detectedRemappings) {
|
|
248
|
+
allRemappings.add(remap);
|
|
249
|
+
}
|
|
250
|
+
if (allRemappings.size > 0) {
|
|
251
|
+
foundryToml += `\nremappings = [\n`;
|
|
252
|
+
for (const remap of allRemappings) {
|
|
253
|
+
foundryToml += ` "${remap}",\n`;
|
|
254
|
+
}
|
|
255
|
+
foundryToml += `]\n`;
|
|
256
|
+
}
|
|
257
|
+
const foundryTomlPath = path.join(outputDir, 'foundry.toml');
|
|
258
|
+
await fs.promises.writeFile(foundryTomlPath, foundryToml);
|
|
259
|
+
console.log(`Generated ${foundryTomlPath}`);
|
|
260
|
+
console.log('\nDone! To build the project:');
|
|
261
|
+
console.log(` cd ${outputDir}`);
|
|
262
|
+
console.log(' forge build');
|
|
263
|
+
}
|
|
264
|
+
// CLI entry point
|
|
265
|
+
if (require.main === module) {
|
|
266
|
+
const args = process.argv.slice(2);
|
|
267
|
+
if (args.length < 2) {
|
|
268
|
+
console.error('Usage: npx ts-node src/unpack.ts <build-info.json|build-info-dir> <output-dir>');
|
|
269
|
+
process.exit(1);
|
|
270
|
+
}
|
|
271
|
+
const [inputPath, outputDir] = args;
|
|
272
|
+
unpackBuildInfo(inputPath, outputDir).catch(err => {
|
|
273
|
+
console.error('Failed:', err.message);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
});
|
|
276
|
+
}
|