agoric 0.21.2-dev-3dacdc3.0.3dacdc3 → 0.21.2-dev-2db363c.0.2db363c

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agoric",
3
- "version": "0.21.2-dev-3dacdc3.0.3dacdc3",
3
+ "version": "0.21.2-dev-2db363c.0.2db363c",
4
4
  "description": "Manage the Agoric Javascript smart contract platform",
5
5
  "type": "module",
6
6
  "main": "src/main.js",
@@ -11,7 +11,8 @@
11
11
  "exports": {
12
12
  "./src/entrypoint.js": "./src/entrypoint.js",
13
13
  "./src/helpers.js": "./src/helpers.js",
14
- "./src/lib/index.js": "./src/lib/index.js"
14
+ "./src/lib/index.js": "./src/lib/index.js",
15
+ "./src/proposals.js": "./src/proposals.js"
15
16
  },
16
17
  "files": [
17
18
  "src",
@@ -29,28 +30,28 @@
29
30
  "lint:eslint": "yarn run -T eslint ."
30
31
  },
31
32
  "devDependencies": {
32
- "@agoric/cosmic-swingset": "0.41.4-dev-3dacdc3.0.3dacdc3",
33
- "@agoric/deploy-script-support": "0.10.4-dev-3dacdc3.0.3dacdc3",
33
+ "@agoric/cosmic-swingset": "0.41.4-dev-2db363c.0.2db363c",
34
+ "@agoric/deploy-script-support": "0.10.4-dev-2db363c.0.2db363c",
34
35
  "ava": "^6.4.1",
35
36
  "c8": "^10.1.2"
36
37
  },
37
38
  "dependencies": {
38
- "@agoric/access-token": "0.4.22-dev-3dacdc3.0.3dacdc3",
39
- "@agoric/cache": "0.3.3-dev-3dacdc3.0.3dacdc3",
40
- "@agoric/casting": "0.4.3-dev-3dacdc3.0.3dacdc3",
41
- "@agoric/client-utils": "0.1.1-dev-3dacdc3.0.3dacdc3",
42
- "@agoric/cosmic-proto": "0.4.1-dev-3dacdc3.0.3dacdc3",
43
- "@agoric/ertp": "0.16.3-dev-3dacdc3.0.3dacdc3",
44
- "@agoric/governance": "0.10.4-dev-3dacdc3.0.3dacdc3",
45
- "@agoric/inter-protocol": "0.16.2-dev-3dacdc3.0.3dacdc3",
46
- "@agoric/internal": "0.3.3-dev-3dacdc3.0.3dacdc3",
47
- "@agoric/network": "0.1.1-dev-3dacdc3.0.3dacdc3",
48
- "@agoric/smart-wallet": "0.5.4-dev-3dacdc3.0.3dacdc3",
49
- "@agoric/store": "0.9.3-dev-3dacdc3.0.3dacdc3",
50
- "@agoric/swingset-vat": "0.32.3-dev-3dacdc3.0.3dacdc3",
51
- "@agoric/vats": "0.15.2-dev-3dacdc3.0.3dacdc3",
52
- "@agoric/zoe": "0.26.3-dev-3dacdc3.0.3dacdc3",
53
- "@agoric/zone": "0.2.3-dev-3dacdc3.0.3dacdc3",
39
+ "@agoric/access-token": "0.4.22-dev-2db363c.0.2db363c",
40
+ "@agoric/cache": "0.3.3-dev-2db363c.0.2db363c",
41
+ "@agoric/casting": "0.4.3-dev-2db363c.0.2db363c",
42
+ "@agoric/client-utils": "0.1.1-dev-2db363c.0.2db363c",
43
+ "@agoric/cosmic-proto": "0.4.1-dev-2db363c.0.2db363c",
44
+ "@agoric/ertp": "0.16.3-dev-2db363c.0.2db363c",
45
+ "@agoric/governance": "0.10.4-dev-2db363c.0.2db363c",
46
+ "@agoric/inter-protocol": "0.16.2-dev-2db363c.0.2db363c",
47
+ "@agoric/internal": "0.3.3-dev-2db363c.0.2db363c",
48
+ "@agoric/network": "0.1.1-dev-2db363c.0.2db363c",
49
+ "@agoric/smart-wallet": "0.5.4-dev-2db363c.0.2db363c",
50
+ "@agoric/store": "0.9.3-dev-2db363c.0.2db363c",
51
+ "@agoric/swingset-vat": "0.32.3-dev-2db363c.0.2db363c",
52
+ "@agoric/vats": "0.15.2-dev-2db363c.0.2db363c",
53
+ "@agoric/zoe": "0.26.3-dev-2db363c.0.2db363c",
54
+ "@agoric/zone": "0.2.3-dev-2db363c.0.2db363c",
54
55
  "@cosmjs/crypto": "^0.36.0",
55
56
  "@cosmjs/encoding": "^0.36.0",
56
57
  "@cosmjs/math": "^0.36.0",
@@ -101,5 +102,5 @@
101
102
  "engines": {
102
103
  "node": "^20.9 || ^22.11"
103
104
  },
104
- "gitHead": "3dacdc33b578ee52258e5771192bf8e57beb603b"
105
+ "gitHead": "2db363cfdc60b99cc644823e99bced301cf748b5"
105
106
  }
@@ -96,7 +96,9 @@ export const statPlans = async path => {
96
96
  const plan = JSON.parse(fs.readFileSync(join(path, planfile), 'utf8'));
97
97
  console.log('\n**\nPLAN', plan.name);
98
98
  for (const bundle of plan.bundles) {
99
- await statBundle(bundle.fileName);
99
+ if (bundle.fileName) {
100
+ await statBundle(bundle.fileName);
101
+ }
100
102
  }
101
103
  }
102
104
  };
@@ -0,0 +1,411 @@
1
+ /* eslint-env node */
2
+ // @ts-check
3
+
4
+ import childProcessAmbient from 'node:child_process';
5
+ import fsRaw from 'node:fs';
6
+ import os from 'node:os';
7
+ import path from 'node:path';
8
+ import { createRequire } from 'node:module';
9
+
10
+ import makeScratchPad from '@agoric/internal/src/scratch.js';
11
+
12
+ import { makeScriptLoader } from './scripts.js';
13
+
14
+ /** @import {promises as FsPromises} from 'node:fs' */
15
+ /** @import {EndoZipBase64Bundle} from '@agoric/swingset-vat' */
16
+ /** @import {CoreEvalSDKType} from '@agoric/cosmic-proto/swingset/swingset.js' */
17
+ /** @import {CoreEvalMaterialRecord} from '@agoric/deploy-script-support/src/writeCoreEvalParts.js' */
18
+
19
+ const consoleThis = console;
20
+ const require = createRequire(import.meta.url);
21
+
22
+ /**
23
+ * @typedef {'prefer-in-process' | 'in-process-only' | 'shell-only'} ProposalBuildMode
24
+ */
25
+
26
+ /**
27
+ * @typedef {{
28
+ * evals: CoreEvalSDKType[];
29
+ * bundles: EndoZipBase64Bundle[];
30
+ * dependencies: string[];
31
+ * resolvedBuilderPath: string;
32
+ * modeUsed: 'in-process' | 'shell';
33
+ * }} ProposalBuildResult
34
+ */
35
+
36
+ /**
37
+ * @param {string} moduleSpecifier
38
+ * @param {string[]} paths
39
+ */
40
+ const resolveModuleSpecifier = (moduleSpecifier, paths) => {
41
+ try {
42
+ // Use Node's CJS resolver semantics for parity with agoric script loading.
43
+ // This can differ from strict ESM resolution behavior.
44
+ return require.resolve(moduleSpecifier, { paths });
45
+ } catch (_err) {
46
+ if (path.isAbsolute(moduleSpecifier)) {
47
+ return path.normalize(moduleSpecifier);
48
+ }
49
+ return path.resolve(paths[0] || process.cwd(), moduleSpecifier);
50
+ }
51
+ };
52
+
53
+ /**
54
+ * @param {FsPromises} fs
55
+ * @param {string} filePath
56
+ */
57
+ const readJSONFile = async (fs, filePath) => {
58
+ await null;
59
+ const data = await fs.readFile(filePath, 'utf8');
60
+ return harden(JSON.parse(data));
61
+ };
62
+
63
+ /**
64
+ * @param {string} agoricRunOutput
65
+ */
66
+ const parseProposalParts = agoricRunOutput => {
67
+ const evals = [
68
+ ...agoricRunOutput.matchAll(
69
+ /swingset-core-eval (?<permit>\S+) (?<script>\S+)/g,
70
+ ),
71
+ ].map(m => {
72
+ if (!m.groups) {
73
+ throw Error(`Invalid proposal output ${m[0]}`);
74
+ }
75
+ const { permit, script } = m.groups;
76
+ return { permit, script };
77
+ });
78
+
79
+ if (!evals.length) {
80
+ throw Error(
81
+ `No swingset-core-eval found in proposal output: ${agoricRunOutput}`,
82
+ );
83
+ }
84
+
85
+ const bundles = [
86
+ ...agoricRunOutput.matchAll(/swingset install-bundle @([^\n]+)/g),
87
+ ].map(([, bundle]) => bundle);
88
+ if (!bundles.length) {
89
+ throw Error(`No bundles found in proposal output: ${agoricRunOutput}`);
90
+ }
91
+
92
+ return { evals, bundles };
93
+ };
94
+
95
+ /**
96
+ * Resolve a file path relative to a working directory.
97
+ *
98
+ * @param {string | number} filePath
99
+ * @param {string} cwd
100
+ */
101
+ const resolveFromCwd = (filePath, cwd) => path.resolve(cwd, String(filePath));
102
+
103
+ /**
104
+ * @param {FsPromises} fs
105
+ * @param {string} cwd
106
+ * @param {string | number} filePath
107
+ */
108
+ const readTextFromCwd = (fs, cwd, filePath) =>
109
+ fs.readFile(resolveFromCwd(filePath, cwd), 'utf8');
110
+
111
+ /**
112
+ * @param {FsPromises} fs
113
+ * @param {string} cwd
114
+ * @param {string | number} filePath
115
+ */
116
+ const readJSONFromCwd = (fs, cwd, filePath) =>
117
+ readJSONFile(fs, resolveFromCwd(filePath, cwd));
118
+
119
+ /**
120
+ * Materialize in-memory proposal output from writeCoreEval callback records.
121
+ *
122
+ * @param {CoreEvalMaterialRecord[]} records
123
+ * @param {string} resolvedBuilderPath
124
+ * @param {string} cwd
125
+ */
126
+ const materializeFromRecords = (records, resolvedBuilderPath, cwd) => {
127
+ if (!records.length) {
128
+ return undefined;
129
+ }
130
+ const dependencySet = new Set([resolvedBuilderPath]);
131
+ /** @type {CoreEvalSDKType[]} */
132
+ const evals = [];
133
+ /** @type {EndoZipBase64Bundle[]} */
134
+ const bundles = [];
135
+
136
+ for (const record of records) {
137
+ evals.push(record.eval);
138
+ for (const bundleInfo of record.bundles) {
139
+ bundles.push(bundleInfo.bundle);
140
+ const resolvedEntrypoint = resolveModuleSpecifier(bundleInfo.entrypoint, [
141
+ path.dirname(resolvedBuilderPath),
142
+ cwd,
143
+ ]);
144
+ dependencySet.add(resolvedEntrypoint);
145
+ }
146
+ }
147
+
148
+ return harden({
149
+ evals,
150
+ bundles,
151
+ dependencies: [...dependencySet].sort(),
152
+ });
153
+ };
154
+
155
+ /**
156
+ * @param {FsPromises} fs
157
+ * @param {string} outputDir
158
+ * @param {string} resolvedBuilderPath
159
+ */
160
+ const readProposalMaterialsFromPlans = async (
161
+ fs,
162
+ outputDir,
163
+ resolvedBuilderPath,
164
+ ) => {
165
+ const files = await fs.readdir(outputDir);
166
+ const planFiles = files.filter(f => f.endsWith('-plan.json')).sort();
167
+
168
+ // TODO: Replace this with metadata capture during writeCoreEval execution.
169
+ if (!planFiles.length) {
170
+ throw Error(`No core-eval plan files found in ${outputDir}`);
171
+ }
172
+
173
+ const dependencySet = new Set([resolvedBuilderPath]);
174
+
175
+ /** @type {CoreEvalSDKType[]} */
176
+ const evals = [];
177
+ /** @type {EndoZipBase64Bundle[]} */
178
+ const bundles = [];
179
+
180
+ for (const planFile of planFiles) {
181
+ /**
182
+ * @type {{
183
+ * permit: string;
184
+ * script: string;
185
+ * bundles: Array<{ entrypoint: string; fileName: string }>;
186
+ * }}
187
+ */
188
+ const plan = await readJSONFile(fs, path.join(outputDir, planFile));
189
+
190
+ const [jsonPermits, jsCode] = await Promise.all([
191
+ readTextFromCwd(fs, outputDir, plan.permit),
192
+ readTextFromCwd(fs, outputDir, plan.script),
193
+ ]);
194
+ evals.push({ json_permits: jsonPermits, js_code: jsCode });
195
+
196
+ for (const bundleInfo of plan.bundles) {
197
+ bundles.push(await readJSONFromCwd(fs, outputDir, bundleInfo.fileName));
198
+
199
+ const resolvedEntrypoint = resolveModuleSpecifier(bundleInfo.entrypoint, [
200
+ path.dirname(resolvedBuilderPath),
201
+ outputDir,
202
+ ]);
203
+ dependencySet.add(resolvedEntrypoint);
204
+ }
205
+ }
206
+
207
+ return harden({
208
+ evals,
209
+ bundles,
210
+ dependencies: [...dependencySet].sort(),
211
+ });
212
+ };
213
+
214
+ /**
215
+ * @param {{ fs: FsPromises; cwd: string; }} param0
216
+ */
217
+ const makeScopedWriteFile = ({ fs, cwd }) => {
218
+ /** @type {typeof fs.writeFile} */
219
+ return async (filePath, data, options) => {
220
+ const abs = resolveFromCwd(String(filePath), cwd);
221
+ await fs.mkdir(path.dirname(abs), { recursive: true });
222
+ return fs.writeFile(abs, data, options);
223
+ };
224
+ };
225
+
226
+ /**
227
+ * @param {{
228
+ * fs: FsPromises;
229
+ * cwd: string;
230
+ * resolvedBuilderPath: string;
231
+ * args: string[];
232
+ * now: () => number;
233
+ * cacheDir: string;
234
+ * console: Console;
235
+ * }} param0
236
+ */
237
+ const runInProcess = async ({
238
+ fs,
239
+ cwd,
240
+ resolvedBuilderPath,
241
+ args,
242
+ now,
243
+ cacheDir,
244
+ console,
245
+ }) => {
246
+ /** @type {CoreEvalMaterialRecord[]} */
247
+ const coreEvalRecords = [];
248
+ const endowments = {
249
+ cacheDir,
250
+ now,
251
+ onWriteCoreEval: record => {
252
+ coreEvalRecords.push(record);
253
+ },
254
+ scriptArgs: args,
255
+ writeFile: makeScopedWriteFile({ fs, cwd }),
256
+ };
257
+
258
+ const runScript = makeScriptLoader(
259
+ [resolvedBuilderPath],
260
+ {
261
+ progname: 'agoric',
262
+ rawArgs: ['run', resolvedBuilderPath, ...args],
263
+ endowments,
264
+ },
265
+ { fs, console },
266
+ );
267
+
268
+ const homeP = Promise.resolve({ scratch: Promise.resolve(makeScratchPad()) });
269
+ await runScript({ home: homeP });
270
+
271
+ const materialized = materializeFromRecords(
272
+ coreEvalRecords,
273
+ resolvedBuilderPath,
274
+ cwd,
275
+ );
276
+ if (materialized) {
277
+ return materialized;
278
+ }
279
+
280
+ return readProposalMaterialsFromPlans(fs, cwd, resolvedBuilderPath);
281
+ };
282
+
283
+ /**
284
+ * @param {{
285
+ * fs: FsPromises;
286
+ * cwd: string;
287
+ * resolvedBuilderPath: string;
288
+ * args: string[];
289
+ * childProcess: Pick<typeof import('node:child_process'), 'execFileSync'>;
290
+ * }} param0
291
+ */
292
+ const runWithShell = async ({
293
+ fs,
294
+ cwd,
295
+ resolvedBuilderPath,
296
+ args,
297
+ childProcess,
298
+ }) => {
299
+ const agoricEntrypoint = require.resolve('agoric/src/entrypoint.js');
300
+ const agoricRunOutput = childProcess.execFileSync(
301
+ agoricEntrypoint,
302
+ ['run', resolvedBuilderPath, ...args],
303
+ { cwd },
304
+ );
305
+
306
+ const built = parseProposalParts(agoricRunOutput.toString());
307
+
308
+ const evals = await Promise.all(
309
+ built.evals.map(async ({ permit, script }) => {
310
+ const [permits, code] = await Promise.all(
311
+ [permit, script].map(filePath => readTextFromCwd(fs, cwd, filePath)),
312
+ );
313
+ return { json_permits: permits, js_code: code };
314
+ }),
315
+ );
316
+
317
+ const bundles = await Promise.all(
318
+ built.bundles.map(async filePath => readJSONFromCwd(fs, cwd, filePath)),
319
+ );
320
+
321
+ return harden({
322
+ evals,
323
+ bundles,
324
+ dependencies: [resolvedBuilderPath],
325
+ });
326
+ };
327
+
328
+ /**
329
+ * @param {{
330
+ * builderPath: string;
331
+ * args?: string[];
332
+ * cwd?: string;
333
+ * mode?: ProposalBuildMode;
334
+ * cacheDir?: string;
335
+ * fs?: FsPromises;
336
+ * now?: () => number;
337
+ * console?: Console;
338
+ * childProcess?: Pick<typeof import('node:child_process'), 'execFileSync'>;
339
+ * }} opts
340
+ * @returns {Promise<ProposalBuildResult>}
341
+ */
342
+ export const buildCoreEvalProposal = async ({
343
+ builderPath,
344
+ args = [],
345
+ cwd = process.cwd(),
346
+ mode = 'prefer-in-process',
347
+ cacheDir = path.join(os.homedir(), '.agoric', 'cache'),
348
+ fs = fsRaw.promises,
349
+ now = Date.now,
350
+ console = consoleThis,
351
+ childProcess = childProcessAmbient,
352
+ }) => {
353
+ if (!builderPath) {
354
+ throw Error('builderPath is required');
355
+ }
356
+ await null;
357
+
358
+ const resolvedBuilderPath = resolveModuleSpecifier(builderPath, [cwd]);
359
+
360
+ const inProcess = async () =>
361
+ runInProcess({
362
+ fs,
363
+ cwd,
364
+ resolvedBuilderPath,
365
+ args,
366
+ now,
367
+ cacheDir,
368
+ console,
369
+ }).then(result => ({
370
+ ...result,
371
+ modeUsed: /** @type {const} */ ('in-process'),
372
+ }));
373
+
374
+ const shell = async () =>
375
+ runWithShell({
376
+ fs,
377
+ cwd,
378
+ resolvedBuilderPath,
379
+ args,
380
+ childProcess,
381
+ }).then(result => ({
382
+ ...result,
383
+ modeUsed: /** @type {const} */ ('shell'),
384
+ }));
385
+
386
+ switch (mode) {
387
+ case 'in-process-only': {
388
+ const result = await inProcess();
389
+ return harden({ ...result, resolvedBuilderPath });
390
+ }
391
+ case 'shell-only': {
392
+ const result = await shell();
393
+ return harden({ ...result, resolvedBuilderPath });
394
+ }
395
+ case 'prefer-in-process': {
396
+ try {
397
+ const result = await inProcess();
398
+ return harden({ ...result, resolvedBuilderPath });
399
+ } catch (err) {
400
+ console.warn(
401
+ `in-process proposal build failed, falling back to shell for ${resolvedBuilderPath}`,
402
+ err,
403
+ );
404
+ const result = await shell();
405
+ return harden({ ...result, resolvedBuilderPath });
406
+ }
407
+ }
408
+ default:
409
+ throw Error(`Invalid proposal build mode: ${mode}`);
410
+ }
411
+ };