@everyprotocol/every-cli 0.1.8 → 0.1.9
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/cmdgen.js +1 -0
- package/dist/cmds/matter.js +8 -301
- package/dist/cmds/relation.js +144 -6
- package/dist/enums.js +41 -0
- package/dist/matter.js +479 -0
- package/dist/utils.js +10 -0
- package/package.json +1 -1
package/dist/cmdgen.js
CHANGED
|
@@ -25,6 +25,7 @@ const getWriteAction = (config, funcName, abiFunc, abi) => async function writeA
|
|
|
25
25
|
const address = config.getContract(conf, this.args, abiFunc);
|
|
26
26
|
const account = walletClient.account;
|
|
27
27
|
const simulation = { address, abi, functionName: funcName, args, account };
|
|
28
|
+
console.log({ address, functionName: funcName, args, account });
|
|
28
29
|
await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
|
|
29
30
|
};
|
|
30
31
|
export const getCommandGen = (config) => function genCmd(cmdName) {
|
package/dist/cmds/matter.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import "@polkadot/api-augment/substrate";
|
|
3
3
|
import * as fs from "fs";
|
|
4
|
-
import
|
|
4
|
+
import path from "path";
|
|
5
5
|
import columify from "columnify";
|
|
6
|
-
import { loadBinary, loadJson } from "../utils.js";
|
|
6
|
+
import { j11String, loadBinary, loadJson } from "../utils.js";
|
|
7
7
|
import { submitTransaction } from "../substrate.js";
|
|
8
8
|
import { network } from "../commander-patch.js";
|
|
9
9
|
import { Logger } from "../logger.js";
|
|
10
|
-
import
|
|
11
|
-
import csv from "csv-parser";
|
|
12
|
-
import { fromHex, pad, sha256 } from "viem";
|
|
13
|
-
import { memoize } from "lodash-es";
|
|
10
|
+
import { compileEnumCsv, compilePermCsv, computeHash, parseFileSpec, specToInput, } from "../matter.js";
|
|
14
11
|
const matterRegisterCmd = new Command("register")
|
|
15
12
|
.description("Register matters")
|
|
16
13
|
.argument("<files...>", "Paths of matter blob files")
|
|
@@ -24,7 +21,7 @@ const matterRegisterCmd = new Command("register")
|
|
|
24
21
|
const inputs = [];
|
|
25
22
|
for (const file of files) {
|
|
26
23
|
const spec = parseFileSpec(file);
|
|
27
|
-
const mi = await
|
|
24
|
+
const mi = await specToInput(spec);
|
|
28
25
|
mi.forEach((input) => {
|
|
29
26
|
if (!deduper.has(input.path)) {
|
|
30
27
|
inputs.push(input);
|
|
@@ -52,7 +49,7 @@ const matterRegisterCmd = new Command("register")
|
|
|
52
49
|
console.log(`Transaction confirmed: ${txn.txHash} ${filePath}`);
|
|
53
50
|
console.log(`Confirmed in: block ${header.number}, hash ${header.hash}`);
|
|
54
51
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
-
const events = r.events.map((e) => [e.event.method,
|
|
52
|
+
const events = r.events.map((e) => [e.event.method, j11String(e.event.data.toJSON())]);
|
|
56
53
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
54
|
const receipt = r.events.map((e) => ({ event: e.event.method, data: e.event.data.toJSON() }));
|
|
58
55
|
console.log(columify(events, { showHeaders: false }));
|
|
@@ -97,7 +94,7 @@ const matterHashCmd = new Command("hash")
|
|
|
97
94
|
const outputs = [];
|
|
98
95
|
for (const file of files) {
|
|
99
96
|
const spec = parseFileSpec(file);
|
|
100
|
-
const inputs = await
|
|
97
|
+
const inputs = await specToInput(spec);
|
|
101
98
|
inputs.forEach((input) => {
|
|
102
99
|
if (!seen.has(input.path)) {
|
|
103
100
|
seen.add(input.path);
|
|
@@ -135,311 +132,21 @@ export const matterCmd = new Command("matter")
|
|
|
135
132
|
.addCommand(matterCompileCmd)
|
|
136
133
|
.addCommand(matterHashCmd)
|
|
137
134
|
.addCommand(matterPickCmd);
|
|
138
|
-
function parseFileSpec(file) {
|
|
139
|
-
if (file.startsWith("@")) {
|
|
140
|
-
const p = file.slice(1).trim();
|
|
141
|
-
if (!p)
|
|
142
|
-
throw new Error("Invalid manifest file spec (empty path)");
|
|
143
|
-
return { path: p, type: "manifest" };
|
|
144
|
-
}
|
|
145
|
-
const parts = file.split(":");
|
|
146
|
-
if (parts.length < 1 || parts.length > 3) {
|
|
147
|
-
throw new Error(`Invalid matter spec: ${file}`);
|
|
148
|
-
}
|
|
149
|
-
const pathPart = parts[0];
|
|
150
|
-
let form;
|
|
151
|
-
let mime;
|
|
152
|
-
parts.slice(1).forEach((part) => {
|
|
153
|
-
if (part.startsWith("form=")) {
|
|
154
|
-
form = Number(part.slice(5));
|
|
155
|
-
}
|
|
156
|
-
else if (part.startsWith("mime=")) {
|
|
157
|
-
mime = part.slice(5);
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
throw new Error(`Invalid extra info: ${part}`);
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
const type = pathPart.endsWith(".enum.csv") || pathPart.endsWith(".perm.csv") ? "raw" : "blob";
|
|
164
|
-
return { path: pathPart, type, form, mime };
|
|
165
|
-
}
|
|
166
|
-
async function toRegisterInput(spec) {
|
|
167
|
-
if (spec.type == "blob") {
|
|
168
|
-
if (spec.form && spec.mime)
|
|
169
|
-
return [{ path: spec.path, form: spec.form, mime: spec.mime }];
|
|
170
|
-
const g = inferMatterMeta(spec.path);
|
|
171
|
-
return [{ path: spec.path, form: spec.form ?? g.form, mime: spec.mime ?? g.mime }];
|
|
172
|
-
}
|
|
173
|
-
else if (spec.type == "manifest") {
|
|
174
|
-
return loadJson(spec.path).map((item) => {
|
|
175
|
-
const { path, form, mime } = item;
|
|
176
|
-
return { path, form, mime };
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
else if (spec.type == "raw") {
|
|
180
|
-
if (spec.path.endsWith(".enum.csv")) {
|
|
181
|
-
const { blob, deps } = await compileEnumMatter(spec.path);
|
|
182
|
-
const path = spec.path.slice(0, -9) + ".enum.bin";
|
|
183
|
-
const { form, mime } = inferMatterMeta(path);
|
|
184
|
-
return [...deps, { blob, form, mime, path }];
|
|
185
|
-
}
|
|
186
|
-
else if (spec.path.endsWith(".perm.csv")) {
|
|
187
|
-
const { blob, deps } = await compileEnumMatter(spec.path);
|
|
188
|
-
const path = spec.path.slice(0, -9) + ".perm.bin";
|
|
189
|
-
const { form, mime } = inferMatterMeta(path);
|
|
190
|
-
return [...deps, { blob, form, mime, path }];
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
throw new Error("unknown raw matter file extension");
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
else {
|
|
197
|
-
throw new Error("unknown matter file type");
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
135
|
function getOutFiles(file, outDir) {
|
|
201
136
|
const dir = outDir ? path.resolve(outDir) : path.dirname(path.resolve(file));
|
|
202
137
|
if (file.endsWith(".enum.csv")) {
|
|
203
138
|
const baseName = path.basename(file).slice(0, -9);
|
|
204
139
|
const binFile = path.join(dir, `${baseName}.enum.bin`);
|
|
205
140
|
const depsFile = path.join(dir, `${baseName}.enum.deps`);
|
|
206
|
-
return { binFile, depsFile, outDir: dir, compile:
|
|
141
|
+
return { binFile, depsFile, outDir: dir, compile: compileEnumCsv };
|
|
207
142
|
}
|
|
208
143
|
else if (file.endsWith(".perm.csv")) {
|
|
209
144
|
const baseName = path.basename(file).slice(0, -9);
|
|
210
145
|
const binFile = path.join(dir, `${baseName}.perm.bin`);
|
|
211
146
|
const depsFile = path.join(dir, `${baseName}.perm.deps`);
|
|
212
|
-
return { binFile, depsFile, outDir: dir, compile:
|
|
147
|
+
return { binFile, depsFile, outDir: dir, compile: compilePermCsv };
|
|
213
148
|
}
|
|
214
149
|
else {
|
|
215
150
|
throw new Error("Unsupported raw matter file extension");
|
|
216
151
|
}
|
|
217
152
|
}
|
|
218
|
-
async function compileEnumMatter(file) {
|
|
219
|
-
const computeHashMemo = memoize(computeHashFile);
|
|
220
|
-
const deps = new Map();
|
|
221
|
-
let rows = 0;
|
|
222
|
-
let colTypes;
|
|
223
|
-
const mapHeaders = ({ header }) => {
|
|
224
|
-
const h = header.trim().toUpperCase();
|
|
225
|
-
if (h in CELL_FORMS) {
|
|
226
|
-
return h;
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
throw new Error(`Invalid column element type: ${h}`);
|
|
230
|
-
}
|
|
231
|
-
};
|
|
232
|
-
function resolvePath(v) {
|
|
233
|
-
if (path.isAbsolute(v))
|
|
234
|
-
return v;
|
|
235
|
-
const dir = path.dirname(file);
|
|
236
|
-
if (path.isAbsolute(file)) {
|
|
237
|
-
return path.resolve(dir, v);
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
return path.normalize(path.join(dir, v));
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
const mapValues = ({ header, value }) => {
|
|
244
|
-
const v = value.trim();
|
|
245
|
-
if (v.startsWith("0x")) {
|
|
246
|
-
return fromHex(pad(v, { size: 32, dir: "left" }), "bytes");
|
|
247
|
-
}
|
|
248
|
-
else if (header == "IMAGE" || header == "JSON") {
|
|
249
|
-
const { hash, mime, form, path } = computeHashMemo(resolvePath(v));
|
|
250
|
-
if (!deps.has(path)) {
|
|
251
|
-
deps.set(path, { hash, mime, form, path });
|
|
252
|
-
}
|
|
253
|
-
return fromHex(hash, "bytes");
|
|
254
|
-
}
|
|
255
|
-
else {
|
|
256
|
-
throw new Error("expect hex strings for cols other than IMAGE, JSON");
|
|
257
|
-
}
|
|
258
|
-
};
|
|
259
|
-
const aux = parseAuxData(file, { mapValues, mapHeaders });
|
|
260
|
-
const parser = csv({ skipComments: true, strict: true, mapHeaders, mapValues });
|
|
261
|
-
const blob = await new Promise((resolve, reject) => {
|
|
262
|
-
const content = [];
|
|
263
|
-
fs.createReadStream(file)
|
|
264
|
-
.pipe(parser)
|
|
265
|
-
.on("headers", (headers) => {
|
|
266
|
-
colTypes = headers;
|
|
267
|
-
})
|
|
268
|
-
.on("data", (data) => {
|
|
269
|
-
Object.values(data).forEach((value) => content.push(value));
|
|
270
|
-
rows += 1;
|
|
271
|
-
})
|
|
272
|
-
.on("end", () => {
|
|
273
|
-
const auxTypes = Object.keys(aux ?? {});
|
|
274
|
-
const auxValues = Object.values(aux ?? {});
|
|
275
|
-
const enumHeader = buildEnumHeader(auxTypes ?? [], colTypes ?? [], rows);
|
|
276
|
-
resolve(Buffer.concat([enumHeader, ...auxValues, ...content]));
|
|
277
|
-
})
|
|
278
|
-
.on("error", (e /* eslint-disable-line */) => {
|
|
279
|
-
reject(e);
|
|
280
|
-
});
|
|
281
|
-
});
|
|
282
|
-
return { blob, deps: Array.from(deps.values()) };
|
|
283
|
-
}
|
|
284
|
-
async function compilePermMatter(file) {
|
|
285
|
-
void file;
|
|
286
|
-
throw new Error("unimplemented");
|
|
287
|
-
}
|
|
288
|
-
function computeHash(form, mime, blob) {
|
|
289
|
-
if (form < 0 || form > 255) {
|
|
290
|
-
throw new Error("form must be uint8 (0..255)");
|
|
291
|
-
}
|
|
292
|
-
const mimeUTF8 = new TextEncoder().encode(mime);
|
|
293
|
-
if (mimeUTF8.length <= 0 || mimeUTF8.length > 31) {
|
|
294
|
-
throw new Error("form must be uint8 (0..255)");
|
|
295
|
-
}
|
|
296
|
-
const msg = new Uint8Array(32 + blob.length);
|
|
297
|
-
msg[0] = form & 0xff;
|
|
298
|
-
msg.set(pad(mimeUTF8, { size: 32, dir: "right" }), 1);
|
|
299
|
-
msg.set(blob, 32);
|
|
300
|
-
// SHA256(form:1 || mime:31 || blob:var)
|
|
301
|
-
return sha256(msg);
|
|
302
|
-
}
|
|
303
|
-
function computeHashFile(file) {
|
|
304
|
-
const spec = parseFileSpec(file);
|
|
305
|
-
if (spec.type != "blob") {
|
|
306
|
-
throw new Error("not blob type");
|
|
307
|
-
}
|
|
308
|
-
let form, mime;
|
|
309
|
-
if (spec.form && spec.mime) {
|
|
310
|
-
form = spec.form;
|
|
311
|
-
mime = spec.mime;
|
|
312
|
-
}
|
|
313
|
-
else {
|
|
314
|
-
const g = inferMatterMeta(spec.path);
|
|
315
|
-
form = spec.form ?? g.form;
|
|
316
|
-
mime = spec.mime ?? g.mime;
|
|
317
|
-
}
|
|
318
|
-
const hash = computeHash(form, mime, loadBinary(spec.path));
|
|
319
|
-
return { path: spec.path, form, mime, hash };
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Build a 32-byte EnumMatter header.
|
|
323
|
-
* Layout:
|
|
324
|
-
* [0..3] = "ENUM"
|
|
325
|
-
* [4] = ver, aux (u8.hi4, u8.lo4)
|
|
326
|
-
* [5] = cols (u8)
|
|
327
|
-
* [6..7] = rows (u16 LE)
|
|
328
|
-
* [8..15] = aux_types [u8; 8]
|
|
329
|
-
* [16..31] = col_types [u8; 16]
|
|
330
|
-
*/
|
|
331
|
-
function buildEnumHeader(auxTypes, colTypes, rows = 0) {
|
|
332
|
-
const colTypesLen = colTypes.length;
|
|
333
|
-
const auxTypesLen = auxTypes.length;
|
|
334
|
-
if (colTypesLen + auxTypesLen == 0) {
|
|
335
|
-
throw new Error("Empty header list");
|
|
336
|
-
}
|
|
337
|
-
if (auxTypesLen > 8) {
|
|
338
|
-
throw new Error(`Too many aux types (max 8): ${auxTypesLen}`);
|
|
339
|
-
}
|
|
340
|
-
if (colTypesLen > 16) {
|
|
341
|
-
throw new Error(`Too many column types (max 16): ${colTypesLen}`);
|
|
342
|
-
}
|
|
343
|
-
if (rows < 0 || rows > 0xffff) {
|
|
344
|
-
throw new Error(`Rows out of range: ${rows}`);
|
|
345
|
-
}
|
|
346
|
-
const buf = new Uint8Array(32);
|
|
347
|
-
buf.set([0x45, 0x4e, 0x55, 0x4d], 0); // magic "ENUM"
|
|
348
|
-
buf[4] = (0x01 << 4) | (auxTypesLen & 0x0f); // version(hi4), aux(lo4)
|
|
349
|
-
buf[5] = colTypesLen; // cols
|
|
350
|
-
buf[6] = rows & 0xff; // rows.lo
|
|
351
|
-
buf[7] = (rows >>> 8) & 0xff; // rows.hi
|
|
352
|
-
auxTypes.forEach((t, i) => (buf[8 + i] = formByName(t))); // aux types
|
|
353
|
-
colTypes.forEach((t, i) => (buf[16 + i] = formByName(t))); // col types
|
|
354
|
-
return buf;
|
|
355
|
-
}
|
|
356
|
-
const MATTER_FORMS = {
|
|
357
|
-
// Simple
|
|
358
|
-
JSON: 0x01,
|
|
359
|
-
IMAGE: 0x02,
|
|
360
|
-
// Code
|
|
361
|
-
WASM: 0xc0,
|
|
362
|
-
// Data
|
|
363
|
-
ENUM: 0xd0,
|
|
364
|
-
PERM: 0xd1,
|
|
365
|
-
// Info
|
|
366
|
-
INFO: 0xff,
|
|
367
|
-
};
|
|
368
|
-
const CELL_FORMS = {
|
|
369
|
-
JSON: 0x01,
|
|
370
|
-
IMAGE: 0x02,
|
|
371
|
-
INFO: 0xff,
|
|
372
|
-
};
|
|
373
|
-
function formByName(name) {
|
|
374
|
-
const value = MATTER_FORMS[name];
|
|
375
|
-
if (value === undefined) {
|
|
376
|
-
throw new Error(`Invalid form name: "${name}"`);
|
|
377
|
-
}
|
|
378
|
-
return value;
|
|
379
|
-
}
|
|
380
|
-
function inferMatterMeta(filePath) {
|
|
381
|
-
const ext = path.extname(filePath);
|
|
382
|
-
switch (ext) {
|
|
383
|
-
case ".wasm":
|
|
384
|
-
return { mime: "application/wasm", form: MATTER_FORMS.WASM };
|
|
385
|
-
case ".json":
|
|
386
|
-
return { mime: "application/json", form: MATTER_FORMS.JSON };
|
|
387
|
-
case ".jpg":
|
|
388
|
-
return { mime: "image/jpeg", form: MATTER_FORMS.IMAGE };
|
|
389
|
-
case ".jpeg":
|
|
390
|
-
return { mime: "image/jpeg", form: MATTER_FORMS.IMAGE };
|
|
391
|
-
case ".png":
|
|
392
|
-
return { mime: "image/png", form: MATTER_FORMS.IMAGE };
|
|
393
|
-
case ".bin":
|
|
394
|
-
if (filePath.endsWith(".enum.bin")) {
|
|
395
|
-
return { mime: "application/vnd.every.enum", form: MATTER_FORMS.ENUM };
|
|
396
|
-
}
|
|
397
|
-
else if (filePath.endsWith(".perm.bin")) {
|
|
398
|
-
return { mime: "application/vnd.every.perm", form: MATTER_FORMS.PERM };
|
|
399
|
-
}
|
|
400
|
-
else {
|
|
401
|
-
throw new Error("unknown matter file extension");
|
|
402
|
-
}
|
|
403
|
-
default:
|
|
404
|
-
throw new Error("unknown matter file extension");
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
function parseAuxData(filePath, options) {
|
|
408
|
-
const lines = readPrologue(filePath)
|
|
409
|
-
.filter((l) => /^\s*#\s*@aux\s+/i.test(l))
|
|
410
|
-
.map((l) => l.replace(/^\s*#\s*@aux\s+/i, "").trim());
|
|
411
|
-
const mapHeaders = options.mapHeaders ?? (({ header }) => header);
|
|
412
|
-
const mapValues = options.mapValues ?? (({ value }) => value);
|
|
413
|
-
if (lines.length == 0) {
|
|
414
|
-
return undefined;
|
|
415
|
-
}
|
|
416
|
-
else if (lines.length == 2) {
|
|
417
|
-
const headers = lines[0].split(",").map((header, index) => mapHeaders({ header, index }));
|
|
418
|
-
const parts = lines[1].split(",");
|
|
419
|
-
if (parts.length != headers.length) {
|
|
420
|
-
throw new Error("aux header and values mismatch");
|
|
421
|
-
}
|
|
422
|
-
const values = parts.map((value, index) => mapValues({ header: headers[index], index, value }));
|
|
423
|
-
return Object.fromEntries(headers.map((h, i) => [h, values[i]]));
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
throw new Error("invalid aux data");
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
function readPrologue(filePath, maxBytes = 2048) {
|
|
430
|
-
const fd = fs.openSync(filePath, "r");
|
|
431
|
-
const buf = Buffer.alloc(maxBytes);
|
|
432
|
-
const bytesRead = fs.readSync(fd, buf, 0, maxBytes, 0);
|
|
433
|
-
fs.closeSync(fd);
|
|
434
|
-
const lines = buf.subarray(0, bytesRead).toString("utf8").split(/\r?\n/);
|
|
435
|
-
const prologue = [];
|
|
436
|
-
for (const line of lines) {
|
|
437
|
-
if (line.trim().startsWith("#")) {
|
|
438
|
-
prologue.push(line);
|
|
439
|
-
}
|
|
440
|
-
else if (line.trim().length > 0) {
|
|
441
|
-
break; // stop at the first non-comment, non-empty line
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
return prologue;
|
|
445
|
-
}
|
package/dist/cmds/relation.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
|
-
import { Command } from "commander";
|
|
1
|
+
import { Argument, Command, Option } from "commander";
|
|
2
|
+
import { pad, padHex } from "viem";
|
|
3
|
+
import fs from "node:fs";
|
|
2
4
|
import { abi } from "../abi.js";
|
|
3
5
|
import { getCommandGen, makeFuncName } from "../cmdgen.js";
|
|
4
|
-
|
|
6
|
+
import { coerceValue, j11Parse, stringify } from "../utils.js";
|
|
7
|
+
import { outputOptions, writeOptions } from "../commander-patch.js";
|
|
8
|
+
import { submitSimulation } from "../ethereum.js";
|
|
9
|
+
import { Logger } from "../logger.js";
|
|
10
|
+
import { FromOpts } from "../from-opts.js";
|
|
11
|
+
import { parseEnum, RelationOwnerShift, RelationTerminator } from "../enums.js";
|
|
12
|
+
const genRelationCmd = getCommandGen({
|
|
5
13
|
getFuncName: (cmdName) => makeFuncName(cmdName, `relation`),
|
|
6
14
|
getAbiFuncs: (funcName) => abi.funcs.omniRegistry.filter((i) => i.name == funcName),
|
|
7
15
|
// eslint-disable-next-line
|
|
@@ -10,7 +18,137 @@ const cmdGenConfig = {
|
|
|
10
18
|
getContract: (conf, args, abiFunc) => conf.contracts.OmniRegistry,
|
|
11
19
|
// eslint-disable-next-line
|
|
12
20
|
getFuncArgs: (args, abiFunc) => args,
|
|
13
|
-
};
|
|
14
|
-
const
|
|
15
|
-
const
|
|
16
|
-
|
|
21
|
+
});
|
|
22
|
+
const registerCmd = genRegisterCmd();
|
|
23
|
+
const updateCmd = genUpdateCmd();
|
|
24
|
+
const otherCmds = "upgrade,touch,transfer,owner,descriptor,snapshot,rule,admit".split(",").map(genRelationCmd);
|
|
25
|
+
export const relationCmd = new Command("relation")
|
|
26
|
+
.description("manage relations")
|
|
27
|
+
.addCommand(registerCmd)
|
|
28
|
+
.addCommand(updateCmd)
|
|
29
|
+
.addCommands(otherCmds);
|
|
30
|
+
function genRegisterCmd() {
|
|
31
|
+
const funcName = "relationRegister";
|
|
32
|
+
const abiFuncs = abi.funcs.omniRegistry.filter((i) => i.name == funcName);
|
|
33
|
+
const abiNonFuncs = abi.nonFuncs.omniRegistry;
|
|
34
|
+
const data = new Argument("<data>", "Matter hash of the relation data");
|
|
35
|
+
const rule = new Argument("<rule>", "Relation rule");
|
|
36
|
+
const adjs = new Argument("<adjs...>", "Relation adjacencies");
|
|
37
|
+
const options = [...writeOptions, ...outputOptions];
|
|
38
|
+
const args = [data, rule, adjs];
|
|
39
|
+
function getFuncArgs(cmd) {
|
|
40
|
+
const code = pad("0x", { size: 20 });
|
|
41
|
+
const data = cmd.processedArgs[0];
|
|
42
|
+
const rule = stringify(resolveRule(cmd.processedArgs[1]));
|
|
43
|
+
const adjs = stringify(resolveAdjs(cmd.processedArgs[2]));
|
|
44
|
+
const args = [code, data, rule, adjs].map((arg, i) => coerceValue(arg, abiFuncs[0].inputs[i]));
|
|
45
|
+
return args;
|
|
46
|
+
}
|
|
47
|
+
async function getCmdAction() {
|
|
48
|
+
const opts = this.opts();
|
|
49
|
+
const args = getFuncArgs(this);
|
|
50
|
+
const { publicClient, walletClient, conf } = await FromOpts.toWriteEthereum(opts);
|
|
51
|
+
const address = conf.contracts.OmniRegistry;
|
|
52
|
+
const account = walletClient.account;
|
|
53
|
+
const simulation = {
|
|
54
|
+
address,
|
|
55
|
+
abi: [...abiFuncs, ...abiNonFuncs],
|
|
56
|
+
functionName: funcName,
|
|
57
|
+
args,
|
|
58
|
+
account,
|
|
59
|
+
};
|
|
60
|
+
await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
|
|
61
|
+
}
|
|
62
|
+
return new Command("register")
|
|
63
|
+
.description("Register a new relation")
|
|
64
|
+
.addOptions(options)
|
|
65
|
+
.addArguments(args)
|
|
66
|
+
.action(getCmdAction);
|
|
67
|
+
}
|
|
68
|
+
function genUpdateCmd() {
|
|
69
|
+
const funcName = "relationUpdate";
|
|
70
|
+
const abiFuncs = abi.funcs.omniRegistry.filter((i) => i.name == funcName);
|
|
71
|
+
// console.log(abiFuncs);
|
|
72
|
+
const abiNonFuncs = abi.nonFuncs.omniRegistry;
|
|
73
|
+
const rel = new Argument("<rel>", "Relation ID");
|
|
74
|
+
const data = new Argument("<data>", "Matter hash of the relation data");
|
|
75
|
+
const adjs = new Option("--adjs <adjacency...>", "Relation adjacencies");
|
|
76
|
+
const options = [adjs, ...writeOptions, ...outputOptions];
|
|
77
|
+
const args = [rel, data];
|
|
78
|
+
function getFuncArgs(cmd) {
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
80
|
+
const opts = cmd.opts();
|
|
81
|
+
let args = [cmd.processedArgs[0], cmd.processedArgs[1]];
|
|
82
|
+
let sig = "relationUpdate(uint64,bytes32)";
|
|
83
|
+
if (opts.adjs) {
|
|
84
|
+
sig = "relationUpdate(uint64,bytes32,(uint16,uint48)[])";
|
|
85
|
+
const adjs = stringify(resolveAdjs(opts.adjs));
|
|
86
|
+
args.push(adjs);
|
|
87
|
+
}
|
|
88
|
+
const abiFunc = abiFuncs.find((i) => i._metadata.signature == sig);
|
|
89
|
+
if (!abiFunc) {
|
|
90
|
+
throw new Error(`signature ${sig} not found in abi`);
|
|
91
|
+
}
|
|
92
|
+
args = args.map((arg, i) => coerceValue(arg, abiFunc.inputs[i]));
|
|
93
|
+
return { args, sig, abiFunc };
|
|
94
|
+
}
|
|
95
|
+
async function getCmdAction() {
|
|
96
|
+
const opts = this.opts();
|
|
97
|
+
const { args } = getFuncArgs(this);
|
|
98
|
+
const { publicClient, walletClient, conf } = await FromOpts.toWriteEthereum(opts);
|
|
99
|
+
const address = conf.contracts.OmniRegistry;
|
|
100
|
+
const account = walletClient.account;
|
|
101
|
+
const simulation = {
|
|
102
|
+
address,
|
|
103
|
+
abi: [...abiFuncs, ...abiNonFuncs],
|
|
104
|
+
functionName: funcName,
|
|
105
|
+
args,
|
|
106
|
+
account,
|
|
107
|
+
};
|
|
108
|
+
await submitSimulation(simulation, publicClient, walletClient, new Logger(opts));
|
|
109
|
+
}
|
|
110
|
+
return new Command("update")
|
|
111
|
+
.description("Update an existing relation")
|
|
112
|
+
.addOptions(options)
|
|
113
|
+
.addArguments(args)
|
|
114
|
+
.action(getCmdAction);
|
|
115
|
+
}
|
|
116
|
+
const U48_MAX = (1n << 48n) - 1n;
|
|
117
|
+
const DEG_MAX = 0x7fff;
|
|
118
|
+
const AT_LEAST_ONE_FLAG = 0x8000;
|
|
119
|
+
function resolveAdjs(args) {
|
|
120
|
+
const kind = (s) => {
|
|
121
|
+
if (/^other$/i.test(s))
|
|
122
|
+
return 0n;
|
|
123
|
+
if (/^total$/i.test(s))
|
|
124
|
+
return U48_MAX;
|
|
125
|
+
const n = BigInt(s);
|
|
126
|
+
if (n < 0n || n > U48_MAX)
|
|
127
|
+
throw new Error("kind out of range");
|
|
128
|
+
return n;
|
|
129
|
+
};
|
|
130
|
+
const degs = (s) => {
|
|
131
|
+
const plus = s.endsWith("+"), t = plus ? s.slice(0, -1) : s, n = Number(t);
|
|
132
|
+
if (!/^\d+$/.test(t) || n < 0 || n > DEG_MAX)
|
|
133
|
+
throw new Error("degs out of range");
|
|
134
|
+
return plus ? n | AT_LEAST_ONE_FLAG : n;
|
|
135
|
+
};
|
|
136
|
+
return args.map((s) => {
|
|
137
|
+
const [k, d] = s.split(":");
|
|
138
|
+
if (!k || !d)
|
|
139
|
+
throw new Error(`bad "${s}"`);
|
|
140
|
+
return { kind: kind(k.trim()), degs: degs(d.trim()) };
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
function resolveRule(arg) {
|
|
144
|
+
const json = arg.startsWith("@") ? fs.readFileSync(arg.slice(1), "utf8") : arg;
|
|
145
|
+
const input = j11Parse(json);
|
|
146
|
+
return {
|
|
147
|
+
version: input.version ?? 1,
|
|
148
|
+
relateShift: parseEnum(input.relateShift, RelationOwnerShift, "relateShift"),
|
|
149
|
+
terminator: parseEnum(input.terminator, RelationTerminator, "terminator"),
|
|
150
|
+
unrelateShift: parseEnum(input.unrelateShift, RelationOwnerShift, "unrelateShift"),
|
|
151
|
+
unrelateDelay: input.unrelateDelay ?? 0,
|
|
152
|
+
extra: padHex(input.extra ?? "0x", { size: 20 }),
|
|
153
|
+
};
|
|
154
|
+
}
|
package/dist/enums.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export var RelationTerminator;
|
|
2
|
+
(function (RelationTerminator) {
|
|
3
|
+
RelationTerminator[RelationTerminator["TailOwner"] = 0] = "TailOwner";
|
|
4
|
+
RelationTerminator[RelationTerminator["HeadOwner"] = 1] = "HeadOwner";
|
|
5
|
+
RelationTerminator[RelationTerminator["Either"] = 2] = "Either";
|
|
6
|
+
RelationTerminator[RelationTerminator["Neither"] = 3] = "Neither";
|
|
7
|
+
RelationTerminator[RelationTerminator["Anyone"] = 4] = "Anyone";
|
|
8
|
+
RelationTerminator[RelationTerminator["Nobody"] = 5] = "Nobody";
|
|
9
|
+
})(RelationTerminator || (RelationTerminator = {}));
|
|
10
|
+
export var RelationOwnerShift;
|
|
11
|
+
(function (RelationOwnerShift) {
|
|
12
|
+
RelationOwnerShift[RelationOwnerShift["Retain"] = 0] = "Retain";
|
|
13
|
+
RelationOwnerShift[RelationOwnerShift["TransferToTailOwner"] = 1] = "TransferToTailOwner";
|
|
14
|
+
RelationOwnerShift[RelationOwnerShift["TransferToHeadOwner"] = 2] = "TransferToHeadOwner";
|
|
15
|
+
RelationOwnerShift[RelationOwnerShift["TransferToCaller"] = 3] = "TransferToCaller";
|
|
16
|
+
RelationOwnerShift[RelationOwnerShift["TransferToPreset"] = 4] = "TransferToPreset";
|
|
17
|
+
RelationOwnerShift[RelationOwnerShift["TransferToBurned"] = 5] = "TransferToBurned";
|
|
18
|
+
RelationOwnerShift[RelationOwnerShift["TransferToResolved"] = 6] = "TransferToResolved";
|
|
19
|
+
RelationOwnerShift[RelationOwnerShift["TransferToIntended"] = 7] = "TransferToIntended";
|
|
20
|
+
RelationOwnerShift[RelationOwnerShift["HoldForTailOwner"] = 8] = "HoldForTailOwner";
|
|
21
|
+
RelationOwnerShift[RelationOwnerShift["HoldForHeadOwner"] = 9] = "HoldForHeadOwner";
|
|
22
|
+
RelationOwnerShift[RelationOwnerShift["HoldForCaller"] = 10] = "HoldForCaller";
|
|
23
|
+
RelationOwnerShift[RelationOwnerShift["HoldForPreset"] = 11] = "HoldForPreset";
|
|
24
|
+
RelationOwnerShift[RelationOwnerShift["HoldForBurned"] = 12] = "HoldForBurned";
|
|
25
|
+
RelationOwnerShift[RelationOwnerShift["HoldForResolved"] = 13] = "HoldForResolved";
|
|
26
|
+
RelationOwnerShift[RelationOwnerShift["HoldPending"] = 14] = "HoldPending";
|
|
27
|
+
})(RelationOwnerShift || (RelationOwnerShift = {}));
|
|
28
|
+
export function parseEnum(v, E, name) {
|
|
29
|
+
if (typeof v === "number") {
|
|
30
|
+
if (v in E)
|
|
31
|
+
return v;
|
|
32
|
+
}
|
|
33
|
+
else if (typeof v === "string") {
|
|
34
|
+
// accept exact key or case-insensitive key
|
|
35
|
+
const k = Object.keys(E).find((key) => key.toLowerCase() === v.toLowerCase());
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
if (k && typeof E[k] === "number")
|
|
38
|
+
return E[k];
|
|
39
|
+
}
|
|
40
|
+
throw new Error(`${name} must be a valid enum value or key`);
|
|
41
|
+
}
|
package/dist/matter.js
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import { loadBinary, loadJson } from "./utils.js";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import csv from "csv-parser";
|
|
5
|
+
import { fromHex, pad, sha256 } from "viem";
|
|
6
|
+
import { memoize } from "lodash-es";
|
|
7
|
+
const MATTER_FORMS = {
|
|
8
|
+
// Simple
|
|
9
|
+
JSON: 0x01,
|
|
10
|
+
IMAGE: 0x02,
|
|
11
|
+
// Code
|
|
12
|
+
WASM: 0xc0,
|
|
13
|
+
// Data
|
|
14
|
+
ENUM: 0xd0,
|
|
15
|
+
PERM: 0xd1,
|
|
16
|
+
// Info
|
|
17
|
+
INFO: 0xff,
|
|
18
|
+
};
|
|
19
|
+
const CELL_TYPES = {
|
|
20
|
+
JSON: 0x01,
|
|
21
|
+
IMAGE: 0x02,
|
|
22
|
+
INFO: 0xff,
|
|
23
|
+
};
|
|
24
|
+
export function formValueByName(name) {
|
|
25
|
+
const value = MATTER_FORMS[name];
|
|
26
|
+
if (value === undefined) {
|
|
27
|
+
throw new Error(`Invalid form name: "${name}"`);
|
|
28
|
+
}
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
export function metaFromExt(file) {
|
|
32
|
+
const ext = path.extname(file);
|
|
33
|
+
switch (ext) {
|
|
34
|
+
case ".wasm":
|
|
35
|
+
return { mime: "application/wasm", form: MATTER_FORMS.WASM };
|
|
36
|
+
case ".json":
|
|
37
|
+
return { mime: "application/json", form: MATTER_FORMS.JSON };
|
|
38
|
+
case ".jpg":
|
|
39
|
+
return { mime: "image/jpeg", form: MATTER_FORMS.IMAGE };
|
|
40
|
+
case ".jpeg":
|
|
41
|
+
return { mime: "image/jpeg", form: MATTER_FORMS.IMAGE };
|
|
42
|
+
case ".png":
|
|
43
|
+
return { mime: "image/png", form: MATTER_FORMS.IMAGE };
|
|
44
|
+
case ".bin":
|
|
45
|
+
if (file.endsWith(".enum.bin")) {
|
|
46
|
+
return { mime: "application/vnd.every.enum", form: MATTER_FORMS.ENUM };
|
|
47
|
+
}
|
|
48
|
+
else if (file.endsWith(".perm.bin")) {
|
|
49
|
+
return { mime: "application/vnd.every.perm", form: MATTER_FORMS.PERM };
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
throw new Error("unknown matter file extension");
|
|
53
|
+
}
|
|
54
|
+
default:
|
|
55
|
+
throw new Error("unknown matter file extension");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export function computeHash(form, mime, blob) {
|
|
59
|
+
if (form < 0 || form > 255) {
|
|
60
|
+
throw new Error("form must be uint8 (0..255)");
|
|
61
|
+
}
|
|
62
|
+
const mimeUTF8 = new TextEncoder().encode(mime);
|
|
63
|
+
if (mimeUTF8.length <= 0 || mimeUTF8.length > 31) {
|
|
64
|
+
throw new Error("form must be uint8 (0..255)");
|
|
65
|
+
}
|
|
66
|
+
const msg = new Uint8Array(32 + blob.length);
|
|
67
|
+
msg[0] = form & 0xff;
|
|
68
|
+
msg.set(pad(mimeUTF8, { size: 32, dir: "right" }), 1);
|
|
69
|
+
msg.set(blob, 32);
|
|
70
|
+
// SHA256(form:1 || mime:31 || blob:var)
|
|
71
|
+
return sha256(msg);
|
|
72
|
+
}
|
|
73
|
+
export function computeHashFromFile(file) {
|
|
74
|
+
const spec = parseFileSpec(file);
|
|
75
|
+
if (spec.type != "blob") {
|
|
76
|
+
throw new Error("not blob type");
|
|
77
|
+
}
|
|
78
|
+
let form, mime;
|
|
79
|
+
if (spec.form && spec.mime) {
|
|
80
|
+
form = spec.form;
|
|
81
|
+
mime = spec.mime;
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
const g = metaFromExt(spec.path);
|
|
85
|
+
form = spec.form ?? g.form;
|
|
86
|
+
mime = spec.mime ?? g.mime;
|
|
87
|
+
}
|
|
88
|
+
const hash = computeHash(form, mime, loadBinary(spec.path));
|
|
89
|
+
return { path: spec.path, form, mime, hash };
|
|
90
|
+
}
|
|
91
|
+
export function parseFileSpec(file) {
|
|
92
|
+
if (file.startsWith("@")) {
|
|
93
|
+
const p = file.slice(1).trim();
|
|
94
|
+
if (!p)
|
|
95
|
+
throw new Error("Invalid manifest file spec (empty path)");
|
|
96
|
+
return { path: p, type: "manifest" };
|
|
97
|
+
}
|
|
98
|
+
const parts = file.split(":");
|
|
99
|
+
if (parts.length < 1 || parts.length > 3) {
|
|
100
|
+
throw new Error(`Invalid matter spec: ${file}`);
|
|
101
|
+
}
|
|
102
|
+
const pathPart = parts[0];
|
|
103
|
+
let form;
|
|
104
|
+
let mime;
|
|
105
|
+
parts.slice(1).forEach((part) => {
|
|
106
|
+
if (part.startsWith("form=")) {
|
|
107
|
+
form = Number(part.slice(5));
|
|
108
|
+
}
|
|
109
|
+
else if (part.startsWith("mime=")) {
|
|
110
|
+
mime = part.slice(5);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
throw new Error(`Invalid extra info: ${part}`);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
const type = pathPart.endsWith(".enum.csv") || pathPart.endsWith(".perm.csv") ? "raw" : "blob";
|
|
117
|
+
return { path: pathPart, type, form, mime };
|
|
118
|
+
}
|
|
119
|
+
export async function specToInput(spec) {
|
|
120
|
+
if (spec.type == "blob") {
|
|
121
|
+
if (spec.form && spec.mime)
|
|
122
|
+
return [{ path: spec.path, form: spec.form, mime: spec.mime }];
|
|
123
|
+
const meta = metaFromExt(spec.path);
|
|
124
|
+
return [{ path: spec.path, form: spec.form ?? meta.form, mime: spec.mime ?? meta.mime }];
|
|
125
|
+
}
|
|
126
|
+
else if (spec.type == "manifest") {
|
|
127
|
+
return loadJson(spec.path).map((item) => {
|
|
128
|
+
const { path, form, mime } = item;
|
|
129
|
+
return { path, form, mime };
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
else if (spec.type == "raw") {
|
|
133
|
+
if (spec.path.endsWith(".enum.csv")) {
|
|
134
|
+
const { blob, deps } = await compileEnumCsv(spec.path);
|
|
135
|
+
const path = spec.path.slice(0, -9) + ".enum.bin";
|
|
136
|
+
const { form, mime } = metaFromExt(path);
|
|
137
|
+
return [...deps, { blob, form, mime, path }];
|
|
138
|
+
}
|
|
139
|
+
else if (spec.path.endsWith(".perm.csv")) {
|
|
140
|
+
const { blob, deps } = await compilePermCsv(spec.path);
|
|
141
|
+
const path = spec.path.slice(0, -9) + ".perm.bin";
|
|
142
|
+
const { form, mime } = metaFromExt(path);
|
|
143
|
+
return [...deps, { blob, form, mime, path }];
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
throw new Error("unknown raw matter file extension");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
throw new Error("unknown matter file type");
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export async function compileEnumCsv(csvFile) {
|
|
154
|
+
const deps = new Map();
|
|
155
|
+
const hashCellFileMemo = HashCellFileMemo(csvFile, deps);
|
|
156
|
+
const mapHeaders = ({ header }) => parseCsvHeader(header);
|
|
157
|
+
const mapValues = ({ header, value }) => {
|
|
158
|
+
const c = parseCsvCell(value, header);
|
|
159
|
+
if (c.type == "hex") {
|
|
160
|
+
const bin = fromHex(pad(c.hex, { size: 32, dir: "left" }), "bytes");
|
|
161
|
+
return bin;
|
|
162
|
+
}
|
|
163
|
+
else if (c.type == "file") {
|
|
164
|
+
const input = hashCellFileMemo(c.file);
|
|
165
|
+
const bin = fromHex(input.hash, "bytes");
|
|
166
|
+
return bin;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
const aux = parseCsvAux(csvFile, hashCellFileMemo);
|
|
170
|
+
const rows = [];
|
|
171
|
+
const headers = [];
|
|
172
|
+
const blob = await new Promise((resolve, reject) => {
|
|
173
|
+
fs.createReadStream(csvFile)
|
|
174
|
+
.pipe(csv({ skipComments: true, strict: true, mapHeaders, mapValues }))
|
|
175
|
+
.on("error", (e) => reject(e))
|
|
176
|
+
.on("headers", (hs) => headers.push(...hs))
|
|
177
|
+
.on("data", (data) => rows.push(Object.values(data)))
|
|
178
|
+
.on("end", () => {
|
|
179
|
+
const auxTypes = Object.keys(aux ?? {});
|
|
180
|
+
const auxData = Object.values(aux ?? {});
|
|
181
|
+
const header = buildEnumBinHeader(auxTypes ?? [], headers, rows.length);
|
|
182
|
+
resolve(Buffer.concat([header, ...auxData, ...rows.flat()]));
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
return { blob, deps: Array.from(deps.values()) };
|
|
186
|
+
}
|
|
187
|
+
export async function compilePermCsv(csvFile) {
|
|
188
|
+
const deps = new Map();
|
|
189
|
+
const hashCellFileMemo = HashCellFileMemo(csvFile, deps);
|
|
190
|
+
const headers = [];
|
|
191
|
+
const columns = [];
|
|
192
|
+
const mapHeaders = (header) => {
|
|
193
|
+
const h = parsePermCsvHeader(header);
|
|
194
|
+
headers.push(h);
|
|
195
|
+
return h.header;
|
|
196
|
+
};
|
|
197
|
+
const mapValues = ({ header, value, index }) => {
|
|
198
|
+
const v = parsePermCsvCell(value, header);
|
|
199
|
+
const bin = v.type == "hex"
|
|
200
|
+
? fromHex(pad(v.hex, { size: 32, dir: "left" }), "bytes")
|
|
201
|
+
: v.type == "file"
|
|
202
|
+
? fromHex(hashCellFileMemo(v.file).hash, "bytes")
|
|
203
|
+
: null;
|
|
204
|
+
if (bin)
|
|
205
|
+
for (let i = 0; i < v.count; i++)
|
|
206
|
+
columns[index].push(bin);
|
|
207
|
+
return v.cell;
|
|
208
|
+
};
|
|
209
|
+
const aux = parseCsvAux(csvFile, hashCellFileMemo);
|
|
210
|
+
const blob = await new Promise((resolve, reject) => {
|
|
211
|
+
fs.createReadStream(csvFile)
|
|
212
|
+
.pipe(csv({ skipComments: true, strict: false, mapHeaders, mapValues }))
|
|
213
|
+
.on("error", (e) => reject(e))
|
|
214
|
+
.on("headers", (hs) => hs.forEach(() => columns.push([])))
|
|
215
|
+
.on("data", (data) => void data) // event must be subscribed, so that "end" works
|
|
216
|
+
.on("end", () => {
|
|
217
|
+
validatePermColumns(headers, columns);
|
|
218
|
+
const auxTypes = Object.keys(aux ?? {});
|
|
219
|
+
const auxData = Object.values(aux ?? {});
|
|
220
|
+
const header = buildPermBinHeader(auxTypes, headers, columns);
|
|
221
|
+
resolve(Buffer.concat([header, ...auxData, ...columns.flat()]));
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
return { blob, deps: Array.from(deps.values()) };
|
|
225
|
+
}
|
|
226
|
+
function validatePermColumns(headers, columns) {
|
|
227
|
+
const n = headers.length;
|
|
228
|
+
if (n !== columns.length)
|
|
229
|
+
throw new Error("headers/columns length mismatch");
|
|
230
|
+
const sizes = columns.map((c) => c.length);
|
|
231
|
+
const permIdxs = headers.map((h, i) => (h.perm ? i : -1)).filter((i) => i >= 0);
|
|
232
|
+
const prod = permIdxs.reduce((acc, cur) => acc * sizes[cur], 1);
|
|
233
|
+
if (prod === 0)
|
|
234
|
+
throw new Error("column height cannot be zero for permutation columns");
|
|
235
|
+
const ensureAllEqual = (idxs, expected) => {
|
|
236
|
+
if (idxs.length === 0)
|
|
237
|
+
return;
|
|
238
|
+
const ref = expected ?? sizes[idxs[0]];
|
|
239
|
+
for (const i of idxs) {
|
|
240
|
+
if (sizes[i] !== ref) {
|
|
241
|
+
throw new Error(`column heights mismatch: column ${i} has ${sizes[i]} but expected ${ref}`);
|
|
242
|
+
}
|
|
243
|
+
if (sizes[i] === 0) {
|
|
244
|
+
throw new Error(`column height cannot be zero (column ${i})`);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
const nonPermIdxs = headers.map((h, i) => (!h.perm ? i : -1)).filter((i) => i >= 0);
|
|
249
|
+
ensureAllEqual(nonPermIdxs, permIdxs.length === 0 ? undefined : prod);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Build an EnumMatter header.
|
|
253
|
+
* Layout:
|
|
254
|
+
* [0..3] = "ENUM"
|
|
255
|
+
* [4] = ver, aux (u8.hi4, u8.lo4)
|
|
256
|
+
* [5] = cols (u8)
|
|
257
|
+
* [6..7] = rows (u16 LE)
|
|
258
|
+
* [8..15] = aux_types [u8; 8]
|
|
259
|
+
* [16..31] = col_types [u8; 16]
|
|
260
|
+
*/
|
|
261
|
+
export function buildEnumBinHeader(auxTypes, colTypes, rows = 0) {
|
|
262
|
+
const colTypesLen = colTypes.length;
|
|
263
|
+
const auxTypesLen = auxTypes.length;
|
|
264
|
+
if (colTypesLen + auxTypesLen == 0) {
|
|
265
|
+
throw new Error("Empty header list");
|
|
266
|
+
}
|
|
267
|
+
if (auxTypesLen > 8) {
|
|
268
|
+
throw new Error(`Too many aux types (max 8): ${auxTypesLen}`);
|
|
269
|
+
}
|
|
270
|
+
if (colTypesLen > 16) {
|
|
271
|
+
throw new Error(`Too many column types (max 16): ${colTypesLen}`);
|
|
272
|
+
}
|
|
273
|
+
if (rows < 0 || rows > 0xffff) {
|
|
274
|
+
throw new Error(`Rows out of range: ${rows}`);
|
|
275
|
+
}
|
|
276
|
+
const buf = new Uint8Array(32);
|
|
277
|
+
buf.set([0x45, 0x4e, 0x55, 0x4d], 0); // magic "ENUM"
|
|
278
|
+
buf[4] = (0x01 << 4) | (auxTypesLen & 0x0f); // version(hi4), aux(lo4)
|
|
279
|
+
buf[5] = colTypesLen; // cols
|
|
280
|
+
buf[6] = rows & 0xff; // rows.lo
|
|
281
|
+
buf[7] = (rows >>> 8) & 0xff; // rows.hi
|
|
282
|
+
auxTypes.forEach((t, i) => (buf[8 + i] = formValueByName(t))); // aux types
|
|
283
|
+
colTypes.forEach((t, i) => (buf[16 + i] = formValueByName(t))); // col types
|
|
284
|
+
return buf;
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Build a PermMatter header.
|
|
288
|
+
* Layout:
|
|
289
|
+
* [0..3] = "PERM"
|
|
290
|
+
* [4] = ver, aux (u8.hi4, u8.lo4)
|
|
291
|
+
* [5] = cols (u8)
|
|
292
|
+
* [6..7] = enum_cols (16 bits)
|
|
293
|
+
* [8..15] = aux_types [u8; 8]
|
|
294
|
+
* [16..31] = col_types [u8; 16]
|
|
295
|
+
* [32..64] = col_heights [u16; 16]
|
|
296
|
+
*/
|
|
297
|
+
export function buildPermBinHeader(auxTypes, headers, columns) {
|
|
298
|
+
if (auxTypes.length > 8) {
|
|
299
|
+
throw new Error(`Too many aux types (max 8): ${auxTypes.length}`);
|
|
300
|
+
}
|
|
301
|
+
if (headers.length > 16) {
|
|
302
|
+
throw new Error(`Too many column types (max 16): ${headers.length}`);
|
|
303
|
+
}
|
|
304
|
+
if (columns.length != headers.length) {
|
|
305
|
+
throw new Error(`Headers and columns mismatch`);
|
|
306
|
+
}
|
|
307
|
+
const buf = new Uint8Array(columns.length > 0 ? 64 : 32);
|
|
308
|
+
buf.set([0x50, 0x45, 0x52, 0x4d], 0); // magic "ENUM"
|
|
309
|
+
buf[4] = (0x01 << 4) | (auxTypes.length & 0x0f); // version(hi4), aux(lo4)
|
|
310
|
+
buf[5] = headers.length; // cols
|
|
311
|
+
const bits = headers.reduce((prev, current, i) => {
|
|
312
|
+
return prev | (!current.perm ? 1 << (15 - i) : 0);
|
|
313
|
+
}, 0);
|
|
314
|
+
buf[6] = bits & 0xff; // bits.lo
|
|
315
|
+
buf[7] = (bits >>> 8) & 0xff; // bits.hi
|
|
316
|
+
auxTypes.forEach((t, i) => (buf[8 + i] = formValueByName(t))); // aux types
|
|
317
|
+
headers.forEach((h, i) => (buf[16 + i] = formValueByName(h.header))); // col types
|
|
318
|
+
columns.forEach((c, i) => {
|
|
319
|
+
const loByte = c.length & 0xff;
|
|
320
|
+
const hiByte = (c.length >>> 8) & 0xff;
|
|
321
|
+
buf[32 + i * 2] = loByte;
|
|
322
|
+
buf[32 + i * 2 + 1] = hiByte;
|
|
323
|
+
});
|
|
324
|
+
return buf;
|
|
325
|
+
}
|
|
326
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
327
|
+
function parseCsvAux(filePath, hashCellFileMemo) {
|
|
328
|
+
const lines = readPrologue(filePath)
|
|
329
|
+
.filter((l) => /^\s*#\s*@aux\s+/i.test(l))
|
|
330
|
+
.map((l) => l.replace(/^\s*#\s*@aux\s+/i, "").trim());
|
|
331
|
+
const mapHeaders = ({ header }) => parseCsvHeader(header);
|
|
332
|
+
const mapValues = ({ header, value }) => {
|
|
333
|
+
const c = parseCsvCell(value, header);
|
|
334
|
+
if (c.type == "hex") {
|
|
335
|
+
const bin = fromHex(pad(c.hex, { size: 32, dir: "left" }), "bytes");
|
|
336
|
+
return bin;
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
const input = hashCellFileMemo(c.file);
|
|
340
|
+
const bin = fromHex(input.hash, "bytes");
|
|
341
|
+
return bin;
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
if (lines.length == 0) {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
else if (lines.length == 2) {
|
|
348
|
+
const headers = lines[0].split(",").map((header, index) => mapHeaders({ header, index }));
|
|
349
|
+
const parts = lines[1].split(",");
|
|
350
|
+
if (parts.length != headers.length) {
|
|
351
|
+
throw new Error("aux header and values mismatch");
|
|
352
|
+
}
|
|
353
|
+
const values = parts.map((value, index) => mapValues({ header: headers[index], index, value }));
|
|
354
|
+
return Object.fromEntries(headers.map((h, i) => [h, values[i]]));
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
throw new Error("invalid aux data");
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function readPrologue(filePath, maxBytes = 2048) {
|
|
361
|
+
const fd = fs.openSync(filePath, "r");
|
|
362
|
+
const buf = Buffer.alloc(maxBytes);
|
|
363
|
+
const bytesRead = fs.readSync(fd, buf, 0, maxBytes, 0);
|
|
364
|
+
fs.closeSync(fd);
|
|
365
|
+
const lines = buf.subarray(0, bytesRead).toString("utf8").split(/\r?\n/);
|
|
366
|
+
const prologue = [];
|
|
367
|
+
for (const line of lines) {
|
|
368
|
+
if (line.trim().startsWith("#")) {
|
|
369
|
+
prologue.push(line);
|
|
370
|
+
}
|
|
371
|
+
else if (line.trim().length > 0) {
|
|
372
|
+
break; // stop at the first non-comment, non-empty line
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return prologue;
|
|
376
|
+
}
|
|
377
|
+
function parseCsvHeader(header) {
|
|
378
|
+
const h = header.trim().toUpperCase();
|
|
379
|
+
if (h in CELL_TYPES) {
|
|
380
|
+
return h;
|
|
381
|
+
}
|
|
382
|
+
else {
|
|
383
|
+
throw new Error(`Invalid element type: ${h}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
function parseCsvCell(input, header) {
|
|
387
|
+
const cell = input.trim();
|
|
388
|
+
if (cell.length == 0)
|
|
389
|
+
throw new Error("empty cell not permitted");
|
|
390
|
+
// (0xHEX | FILE) [* NUMBER] with optional spaces around *
|
|
391
|
+
const re = /^(?:(0x[0-9A-Fa-f]+)|([^\s*]+))$/;
|
|
392
|
+
const m = cell.match(re);
|
|
393
|
+
if (!m)
|
|
394
|
+
throw new Error("invalid cell");
|
|
395
|
+
const [, hex, file] = m;
|
|
396
|
+
if (hex) {
|
|
397
|
+
if ((hex.length - 2) % 2 !== 0 || hex.length > 66)
|
|
398
|
+
throw new Error("invalid hex cell");
|
|
399
|
+
return { cell, type: "hex", hex: hex, file: undefined };
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
if (header == "INFO") {
|
|
403
|
+
throw new Error("file not permitted for INFO cell");
|
|
404
|
+
}
|
|
405
|
+
return { cell, type: "file", hex: undefined, file };
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function parsePermCsvHeader({ header, index }) {
|
|
409
|
+
let h = header.trim().toUpperCase();
|
|
410
|
+
let perm = true;
|
|
411
|
+
if (h.endsWith("!")) {
|
|
412
|
+
h = h.slice(0, -1);
|
|
413
|
+
perm = false;
|
|
414
|
+
}
|
|
415
|
+
if (h in CELL_TYPES) {
|
|
416
|
+
return { header: h, perm, index };
|
|
417
|
+
}
|
|
418
|
+
else {
|
|
419
|
+
throw new Error(`Invalid column type: ${h}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
export function parsePermCsvCell(input, header) {
|
|
423
|
+
const cell = input.trim();
|
|
424
|
+
if (cell.length == 0)
|
|
425
|
+
return { cell, type: "empty", count: 0 };
|
|
426
|
+
// (0xHEX | FILE) [* NUMBER] with optional spaces around *
|
|
427
|
+
const re = /^(?:(0x[0-9A-Fa-f]+)|([^\s*]+))\s*(?:\*\s*(\d+))?$/;
|
|
428
|
+
const m = cell.match(re);
|
|
429
|
+
if (!m)
|
|
430
|
+
throw new Error("invalid cell");
|
|
431
|
+
const [, hex, file, num] = m;
|
|
432
|
+
if (hex) {
|
|
433
|
+
if ((hex.length - 2) % 2 !== 0 || hex.length > 66)
|
|
434
|
+
throw new Error("invalid hex cell");
|
|
435
|
+
return {
|
|
436
|
+
cell,
|
|
437
|
+
type: "hex",
|
|
438
|
+
hex: hex,
|
|
439
|
+
file: undefined,
|
|
440
|
+
count: num ? Number(num) : 1,
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
if (header == "INFO") {
|
|
445
|
+
throw new Error("file cell not permitted for INFO column");
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
cell,
|
|
449
|
+
type: "file",
|
|
450
|
+
hex: undefined,
|
|
451
|
+
file,
|
|
452
|
+
count: num ? Number(num) : 1,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
export function resolveCellPath(csvFile) {
|
|
457
|
+
return function (cellFile) {
|
|
458
|
+
if (path.isAbsolute(cellFile))
|
|
459
|
+
return cellFile;
|
|
460
|
+
const dir = path.dirname(csvFile);
|
|
461
|
+
if (path.isAbsolute(csvFile)) {
|
|
462
|
+
return path.resolve(dir, cellFile);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
return path.normalize(path.join(dir, cellFile));
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
function HashCellFileMemo(csvFile, deps) {
|
|
470
|
+
const resolvePath = resolveCellPath(csvFile);
|
|
471
|
+
function hashCellFile(p) {
|
|
472
|
+
const input = computeHashFromFile(resolvePath(p));
|
|
473
|
+
if (!deps.has(input.path)) {
|
|
474
|
+
deps.set(input.path, input);
|
|
475
|
+
}
|
|
476
|
+
return input;
|
|
477
|
+
}
|
|
478
|
+
return memoize(hashCellFile);
|
|
479
|
+
}
|
package/dist/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
+
import * as JSON11 from "json11";
|
|
4
5
|
import { isHex, hexToU8a } from "@polkadot/util";
|
|
5
6
|
import { base64Decode } from "@polkadot/util-crypto/base64";
|
|
6
7
|
import { decodePair } from "@polkadot/keyring/pair/decode";
|
|
@@ -190,3 +191,12 @@ export function toRelationId(v) {
|
|
|
190
191
|
}
|
|
191
192
|
return n;
|
|
192
193
|
}
|
|
194
|
+
export function stringify(v, options) {
|
|
195
|
+
return JSON11.stringify(v, options ?? { quoteNames: true, quote: '"', withBigInt: false });
|
|
196
|
+
}
|
|
197
|
+
export function j11String(v, options) {
|
|
198
|
+
return JSON11.stringify(v, options);
|
|
199
|
+
}
|
|
200
|
+
export function j11Parse(str) {
|
|
201
|
+
return JSON11.parse(str);
|
|
202
|
+
}
|