@blue-quickjs/abi-manifest 0.1.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/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/lib/abi-manifest.d.ts +64 -0
- package/dist/lib/abi-manifest.d.ts.map +1 -0
- package/dist/lib/abi-manifest.js +316 -0
- package/dist/lib/host-v1-manifest.d.ts +6 -0
- package/dist/lib/host-v1-manifest.d.ts.map +1 -0
- package/dist/lib/host-v1-manifest.js +99 -0
- package/package.json +37 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 @blue-labs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# abi-manifest
|
|
2
|
+
|
|
3
|
+
This library was generated with [Nx](https://nx.dev).
|
|
4
|
+
|
|
5
|
+
## Building
|
|
6
|
+
|
|
7
|
+
Run `nx build abi-manifest` to build the library.
|
|
8
|
+
|
|
9
|
+
## Running unit tests
|
|
10
|
+
|
|
11
|
+
Run `nx test abi-manifest` to execute the unit tests via [Vitest](https://vitest.dev/).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,2BAA2B,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export type AbiEffect = 'READ' | 'EMIT' | 'MUTATE';
|
|
2
|
+
export interface AbiSchemaString {
|
|
3
|
+
type: 'string';
|
|
4
|
+
}
|
|
5
|
+
export interface AbiSchemaDv {
|
|
6
|
+
type: 'dv';
|
|
7
|
+
}
|
|
8
|
+
export interface AbiSchemaNull {
|
|
9
|
+
type: 'null';
|
|
10
|
+
}
|
|
11
|
+
export type AbiSchema = AbiSchemaString | AbiSchemaDv | AbiSchemaNull;
|
|
12
|
+
export interface AbiGasParameters {
|
|
13
|
+
schedule_id: string;
|
|
14
|
+
base: number;
|
|
15
|
+
k_arg_bytes: number;
|
|
16
|
+
k_ret_bytes: number;
|
|
17
|
+
k_units: number;
|
|
18
|
+
}
|
|
19
|
+
export interface AbiLimits {
|
|
20
|
+
max_request_bytes: number;
|
|
21
|
+
max_response_bytes: number;
|
|
22
|
+
max_units: number;
|
|
23
|
+
arg_utf8_max?: number[];
|
|
24
|
+
}
|
|
25
|
+
export interface AbiErrorCode {
|
|
26
|
+
code: string;
|
|
27
|
+
tag: string;
|
|
28
|
+
}
|
|
29
|
+
export interface AbiFunction {
|
|
30
|
+
fn_id: number;
|
|
31
|
+
js_path: string[];
|
|
32
|
+
effect: AbiEffect;
|
|
33
|
+
arity: number;
|
|
34
|
+
arg_schema: AbiSchema[];
|
|
35
|
+
return_schema: AbiSchema;
|
|
36
|
+
gas: AbiGasParameters;
|
|
37
|
+
limits: AbiLimits;
|
|
38
|
+
error_codes: AbiErrorCode[];
|
|
39
|
+
}
|
|
40
|
+
export interface AbiManifest {
|
|
41
|
+
abi_id: string;
|
|
42
|
+
abi_version: number;
|
|
43
|
+
functions: AbiFunction[];
|
|
44
|
+
}
|
|
45
|
+
export type CanonicalAbiFunction = AbiFunction;
|
|
46
|
+
export type CanonicalAbiManifest = AbiManifest & {
|
|
47
|
+
functions: CanonicalAbiFunction[];
|
|
48
|
+
};
|
|
49
|
+
export type AbiManifestErrorCode = 'INVALID_TYPE' | 'MISSING_FIELD' | 'UNKNOWN_FIELD' | 'INVALID_VALUE' | 'OUT_OF_RANGE' | 'UNSORTED' | 'DUPLICATE' | 'PATH_CONFLICT';
|
|
50
|
+
export declare class AbiManifestError extends Error {
|
|
51
|
+
readonly code: AbiManifestErrorCode;
|
|
52
|
+
readonly path?: string | undefined;
|
|
53
|
+
constructor(code: AbiManifestErrorCode, message: string, path?: string | undefined);
|
|
54
|
+
}
|
|
55
|
+
export interface AbiManifestBytes {
|
|
56
|
+
bytes: Uint8Array;
|
|
57
|
+
hash: string;
|
|
58
|
+
manifest: CanonicalAbiManifest;
|
|
59
|
+
}
|
|
60
|
+
export declare function validateAbiManifest(manifest: AbiManifest): CanonicalAbiManifest;
|
|
61
|
+
export declare function encodeAbiManifest(manifest: AbiManifest): Uint8Array;
|
|
62
|
+
export declare function hashAbiManifest(manifest: AbiManifest): AbiManifestBytes;
|
|
63
|
+
export declare function hashAbiManifestBytes(bytes: ArrayBufferView | ArrayBuffer | Uint8Array): string;
|
|
64
|
+
//# sourceMappingURL=abi-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"abi-manifest.d.ts","sourceRoot":"","sources":["../../src/lib/abi-manifest.ts"],"names":[],"mappings":"AAYA,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEnD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,SAAS,GAAG,eAAe,GAAG,WAAW,GAAG,aAAa,CAAC;AAEtE,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,SAAS;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,SAAS,EAAE,CAAC;IACxB,aAAa,EAAE,SAAS,CAAC;IACzB,GAAG,EAAE,gBAAgB,CAAC;IACtB,MAAM,EAAE,SAAS,CAAC;IAClB,WAAW,EAAE,YAAY,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC;AAC/C,MAAM,MAAM,oBAAoB,GAAG,WAAW,GAAG;IAC/C,SAAS,EAAE,oBAAoB,EAAE,CAAC;CACnC,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAC5B,cAAc,GACd,eAAe,GACf,eAAe,GACf,eAAe,GACf,cAAc,GACd,UAAU,GACV,WAAW,GACX,eAAe,CAAC;AAEpB,qBAAa,gBAAiB,SAAQ,KAAK;aAEvB,IAAI,EAAE,oBAAoB;aAE1B,IAAI,CAAC,EAAE,MAAM;gBAFb,IAAI,EAAE,oBAAoB,EAC1C,OAAO,EAAE,MAAM,EACC,IAAI,CAAC,EAAE,MAAM,YAAA;CAKhC;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,UAAU,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,oBAAoB,CAAC;CAChC;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,WAAW,GACpB,oBAAoB,CAMtB;AAED,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,WAAW,GAAG,UAAU,CAGnE;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,WAAW,GAAG,gBAAgB,CAQvE;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,eAAe,GAAG,WAAW,GAAG,UAAU,GAChD,MAAM,CAQR"}
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { DV_LIMIT_DEFAULTS, encodeDv } from '@blue-quickjs/dv';
|
|
2
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
3
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
4
|
+
const UINT32_MAX = 0xffffffff;
|
|
5
|
+
const JS_PATH_SEGMENT = /^[A-Za-z0-9_-]+$/;
|
|
6
|
+
const FORBIDDEN_SEGMENTS = new Set(['__proto__', 'prototype', 'constructor']);
|
|
7
|
+
const RESERVED_ERROR_CODES = new Set([
|
|
8
|
+
'HOST_TRANSPORT',
|
|
9
|
+
'HOST_ENVELOPE_INVALID',
|
|
10
|
+
]);
|
|
11
|
+
export class AbiManifestError extends Error {
|
|
12
|
+
code;
|
|
13
|
+
path;
|
|
14
|
+
constructor(code, message, path) {
|
|
15
|
+
super(path ? `${message} (${path})` : message);
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.path = path;
|
|
18
|
+
this.name = 'AbiManifestError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function validateAbiManifest(manifest) {
|
|
22
|
+
const normalized = validateManifestRoot(manifest);
|
|
23
|
+
ensureUniqueFnIds(normalized.functions);
|
|
24
|
+
ensureFunctionsSorted(normalized.functions);
|
|
25
|
+
ensureNoJsPathConflicts(normalized.functions);
|
|
26
|
+
return normalized;
|
|
27
|
+
}
|
|
28
|
+
export function encodeAbiManifest(manifest) {
|
|
29
|
+
const canonical = validateAbiManifest(manifest);
|
|
30
|
+
return encodeDv(canonical);
|
|
31
|
+
}
|
|
32
|
+
export function hashAbiManifest(manifest) {
|
|
33
|
+
const canonical = validateAbiManifest(manifest);
|
|
34
|
+
const bytes = encodeDv(canonical);
|
|
35
|
+
return {
|
|
36
|
+
bytes,
|
|
37
|
+
hash: hashAbiManifestBytes(bytes),
|
|
38
|
+
manifest: canonical,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function hashAbiManifestBytes(bytes) {
|
|
42
|
+
const view = bytes instanceof Uint8Array
|
|
43
|
+
? bytes
|
|
44
|
+
: bytes instanceof ArrayBuffer
|
|
45
|
+
? new Uint8Array(bytes)
|
|
46
|
+
: new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
47
|
+
return bytesToHex(sha256(view));
|
|
48
|
+
}
|
|
49
|
+
function validateManifestRoot(manifest) {
|
|
50
|
+
const value = expectPlainObject(manifest, 'manifest');
|
|
51
|
+
enforceExactKeys(value, ['abi_id', 'abi_version', 'functions'], 'manifest');
|
|
52
|
+
const abiId = expectNonEmptyString(value.abi_id, 'manifest.abi_id');
|
|
53
|
+
const abiVersion = expectUint32(value.abi_version, 'manifest.abi_version', {
|
|
54
|
+
min: 1,
|
|
55
|
+
});
|
|
56
|
+
const functions = expectArray(value.functions, 'manifest.functions').map((fn, index) => validateFunction(fn, `manifest.functions[${index}]`));
|
|
57
|
+
return {
|
|
58
|
+
abi_id: abiId,
|
|
59
|
+
abi_version: abiVersion,
|
|
60
|
+
functions,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function validateFunction(value, path) {
|
|
64
|
+
const fn = expectPlainObject(value, path);
|
|
65
|
+
enforceExactKeys(fn, [
|
|
66
|
+
'fn_id',
|
|
67
|
+
'js_path',
|
|
68
|
+
'effect',
|
|
69
|
+
'arity',
|
|
70
|
+
'arg_schema',
|
|
71
|
+
'return_schema',
|
|
72
|
+
'gas',
|
|
73
|
+
'limits',
|
|
74
|
+
'error_codes',
|
|
75
|
+
], path);
|
|
76
|
+
const fnId = expectUint32(fn.fn_id, `${path}.fn_id`, { min: 1 });
|
|
77
|
+
const jsPath = validateJsPath(fn.js_path, `${path}.js_path`);
|
|
78
|
+
const effect = validateEffect(fn.effect, `${path}.effect`);
|
|
79
|
+
const arity = expectUint32(fn.arity, `${path}.arity`);
|
|
80
|
+
const argSchema = validateArgSchemas(expectArray(fn.arg_schema, `${path}.arg_schema`), arity, `${path}.arg_schema`);
|
|
81
|
+
const returnSchema = validateSchema(fn.return_schema, `${path}.return_schema`);
|
|
82
|
+
const gas = validateGas(fn.gas, `${path}.gas`);
|
|
83
|
+
const limits = validateLimits(fn.limits, argSchema, arity, `${path}.limits`);
|
|
84
|
+
const errorCodes = validateErrorCodes(expectArray(fn.error_codes, `${path}.error_codes`), `${path}.error_codes`);
|
|
85
|
+
ensureGasMaxChargeWithinBounds(gas, limits, `${path}.gas`);
|
|
86
|
+
return {
|
|
87
|
+
fn_id: fnId,
|
|
88
|
+
js_path: jsPath,
|
|
89
|
+
effect,
|
|
90
|
+
arity,
|
|
91
|
+
arg_schema: argSchema,
|
|
92
|
+
return_schema: returnSchema,
|
|
93
|
+
gas,
|
|
94
|
+
limits,
|
|
95
|
+
error_codes: errorCodes,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function validateJsPath(value, path) {
|
|
99
|
+
const segments = expectArray(value, path);
|
|
100
|
+
if (segments.length === 0) {
|
|
101
|
+
throw error('INVALID_VALUE', 'js_path must contain at least one segment', path);
|
|
102
|
+
}
|
|
103
|
+
return segments.map((segment, index) => {
|
|
104
|
+
const segmentPath = `${path}[${index}]`;
|
|
105
|
+
const str = expectNonEmptyString(segment, segmentPath);
|
|
106
|
+
if (!JS_PATH_SEGMENT.test(str)) {
|
|
107
|
+
throw error('INVALID_VALUE', `js_path segment must match ${JS_PATH_SEGMENT.source}: ${str}`, segmentPath);
|
|
108
|
+
}
|
|
109
|
+
if (FORBIDDEN_SEGMENTS.has(str)) {
|
|
110
|
+
throw error('INVALID_VALUE', `js_path segment is forbidden: ${str}`, segmentPath);
|
|
111
|
+
}
|
|
112
|
+
return str;
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
function validateEffect(value, path) {
|
|
116
|
+
if (value !== 'READ' && value !== 'EMIT' && value !== 'MUTATE') {
|
|
117
|
+
throw error('INVALID_VALUE', 'effect must be READ, EMIT, or MUTATE', path);
|
|
118
|
+
}
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
function validateArgSchemas(schemas, arity, path) {
|
|
122
|
+
if (schemas.length !== arity) {
|
|
123
|
+
throw error('INVALID_VALUE', `arg_schema length (${schemas.length}) must equal arity (${arity})`, path);
|
|
124
|
+
}
|
|
125
|
+
return schemas.map((schema, index) => validateSchema(schema, `${path}[${index}]`));
|
|
126
|
+
}
|
|
127
|
+
function validateSchema(value, path) {
|
|
128
|
+
const schema = expectPlainObject(value, path);
|
|
129
|
+
enforceExactKeys(schema, ['type'], path);
|
|
130
|
+
if (schema.type === 'string' ||
|
|
131
|
+
schema.type === 'dv' ||
|
|
132
|
+
schema.type === 'null') {
|
|
133
|
+
return { type: schema.type };
|
|
134
|
+
}
|
|
135
|
+
throw error('INVALID_VALUE', `unsupported schema type: ${String(schema.type)}`, path);
|
|
136
|
+
}
|
|
137
|
+
function validateGas(value, path) {
|
|
138
|
+
const gas = expectPlainObject(value, path);
|
|
139
|
+
enforceExactKeys(gas, ['schedule_id', 'base', 'k_arg_bytes', 'k_ret_bytes', 'k_units'], path);
|
|
140
|
+
return {
|
|
141
|
+
schedule_id: expectNonEmptyString(gas.schedule_id, `${path}.schedule_id`),
|
|
142
|
+
base: expectUint32(gas.base, `${path}.base`),
|
|
143
|
+
k_arg_bytes: expectUint32(gas.k_arg_bytes, `${path}.k_arg_bytes`),
|
|
144
|
+
k_ret_bytes: expectUint32(gas.k_ret_bytes, `${path}.k_ret_bytes`),
|
|
145
|
+
k_units: expectUint32(gas.k_units, `${path}.k_units`),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function validateLimits(value, argSchema, arity, path) {
|
|
149
|
+
const limits = expectPlainObject(value, path);
|
|
150
|
+
enforceExactKeys(limits, ['max_request_bytes', 'max_response_bytes', 'max_units', 'arg_utf8_max'], path, ['arg_utf8_max']);
|
|
151
|
+
const maxRequestBytes = expectUint32(limits.max_request_bytes, `${path}.max_request_bytes`, {
|
|
152
|
+
min: 1,
|
|
153
|
+
max: DV_LIMIT_DEFAULTS.maxEncodedBytes,
|
|
154
|
+
});
|
|
155
|
+
const maxResponseBytes = expectUint32(limits.max_response_bytes, `${path}.max_response_bytes`, {
|
|
156
|
+
min: 1,
|
|
157
|
+
max: DV_LIMIT_DEFAULTS.maxEncodedBytes,
|
|
158
|
+
});
|
|
159
|
+
const maxUnits = expectUint32(limits.max_units, `${path}.max_units`);
|
|
160
|
+
let argUtf8Max;
|
|
161
|
+
if (limits.arg_utf8_max !== undefined) {
|
|
162
|
+
const parsed = expectArray(limits.arg_utf8_max, `${path}.arg_utf8_max`);
|
|
163
|
+
if (parsed.length !== arity) {
|
|
164
|
+
throw error('INVALID_VALUE', `arg_utf8_max length (${parsed.length}) must equal arity (${arity})`, `${path}.arg_utf8_max`);
|
|
165
|
+
}
|
|
166
|
+
argUtf8Max = parsed.map((entry, index) => {
|
|
167
|
+
const limit = expectUint32(entry, `${path}.arg_utf8_max[${index}]`, {
|
|
168
|
+
min: 1,
|
|
169
|
+
max: DV_LIMIT_DEFAULTS.maxStringBytes,
|
|
170
|
+
});
|
|
171
|
+
if (argSchema[index].type !== 'string') {
|
|
172
|
+
throw error('INVALID_VALUE', 'arg_utf8_max may only be used for string arguments', `${path}.arg_utf8_max[${index}]`);
|
|
173
|
+
}
|
|
174
|
+
return limit;
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return {
|
|
178
|
+
max_request_bytes: maxRequestBytes,
|
|
179
|
+
max_response_bytes: maxResponseBytes,
|
|
180
|
+
max_units: maxUnits,
|
|
181
|
+
...(argUtf8Max ? { arg_utf8_max: argUtf8Max } : {}),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function validateErrorCodes(codes, path) {
|
|
185
|
+
const parsed = codes.map((entry, index) => validateErrorCode(entry, `${path}[${index}]`));
|
|
186
|
+
ensureErrorCodesSorted(parsed, path);
|
|
187
|
+
return parsed;
|
|
188
|
+
}
|
|
189
|
+
function validateErrorCode(value, path) {
|
|
190
|
+
const code = expectPlainObject(value, path);
|
|
191
|
+
enforceExactKeys(code, ['code', 'tag'], path);
|
|
192
|
+
const errorCode = expectNonEmptyString(code.code, `${path}.code`);
|
|
193
|
+
if (RESERVED_ERROR_CODES.has(errorCode)) {
|
|
194
|
+
throw error('INVALID_VALUE', `error_codes may not include reserved code "${errorCode}"`, `${path}.code`);
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
code: errorCode,
|
|
198
|
+
tag: expectNonEmptyString(code.tag, `${path}.tag`),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function ensureFunctionsSorted(functions) {
|
|
202
|
+
for (let i = 1; i < functions.length; i += 1) {
|
|
203
|
+
if (functions[i - 1].fn_id >= functions[i].fn_id) {
|
|
204
|
+
throw error('UNSORTED', 'functions must be sorted by ascending fn_id without duplicates', 'manifest.functions');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function ensureUniqueFnIds(functions) {
|
|
209
|
+
const seen = new Set();
|
|
210
|
+
for (const fn of functions) {
|
|
211
|
+
if (seen.has(fn.fn_id)) {
|
|
212
|
+
throw error('DUPLICATE', `duplicate fn_id ${fn.fn_id}`, 'manifest.functions');
|
|
213
|
+
}
|
|
214
|
+
seen.add(fn.fn_id);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
function ensureErrorCodesSorted(codes, path) {
|
|
218
|
+
for (let i = 1; i < codes.length; i += 1) {
|
|
219
|
+
const previous = codes[i - 1].code;
|
|
220
|
+
const current = codes[i].code;
|
|
221
|
+
if (previous > current) {
|
|
222
|
+
throw error('UNSORTED', 'error_codes must be sorted by code', path);
|
|
223
|
+
}
|
|
224
|
+
if (previous === current) {
|
|
225
|
+
throw error('DUPLICATE', `duplicate error code ${current}`, path);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function ensureNoJsPathConflicts(functions) {
|
|
230
|
+
for (let i = 0; i < functions.length; i += 1) {
|
|
231
|
+
for (let j = i + 1; j < functions.length; j += 1) {
|
|
232
|
+
if (isPathConflict(functions[i].js_path, functions[j].js_path)) {
|
|
233
|
+
throw error('PATH_CONFLICT', `js_path collision between "${functions[i].js_path.join('.')}" and "${functions[j].js_path.join('.')}"`, 'manifest.functions');
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function isPathConflict(a, b) {
|
|
239
|
+
const len = Math.min(a.length, b.length);
|
|
240
|
+
for (let i = 0; i < len; i += 1) {
|
|
241
|
+
if (a[i] !== b[i]) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
function expectPlainObject(value, path) {
|
|
248
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
249
|
+
throw error('INVALID_TYPE', 'expected object', path);
|
|
250
|
+
}
|
|
251
|
+
const proto = Object.getPrototypeOf(value);
|
|
252
|
+
if (proto !== Object.prototype && proto !== null) {
|
|
253
|
+
throw error('INVALID_TYPE', 'expected plain object', path);
|
|
254
|
+
}
|
|
255
|
+
return value;
|
|
256
|
+
}
|
|
257
|
+
function expectArray(value, path) {
|
|
258
|
+
if (!Array.isArray(value)) {
|
|
259
|
+
throw error('INVALID_TYPE', 'expected array', path);
|
|
260
|
+
}
|
|
261
|
+
return value;
|
|
262
|
+
}
|
|
263
|
+
function expectNonEmptyString(value, path) {
|
|
264
|
+
if (typeof value !== 'string') {
|
|
265
|
+
throw error('INVALID_TYPE', 'expected string', path);
|
|
266
|
+
}
|
|
267
|
+
if (value.length === 0) {
|
|
268
|
+
throw error('INVALID_VALUE', 'string must be non-empty', path);
|
|
269
|
+
}
|
|
270
|
+
return value;
|
|
271
|
+
}
|
|
272
|
+
function expectUint32(value, path, bounds) {
|
|
273
|
+
if (typeof value !== 'number' || !Number.isInteger(value)) {
|
|
274
|
+
throw error('INVALID_TYPE', 'expected integer', path);
|
|
275
|
+
}
|
|
276
|
+
if (Object.is(value, -0)) {
|
|
277
|
+
throw error('INVALID_VALUE', 'integer must not be -0', path);
|
|
278
|
+
}
|
|
279
|
+
const min = bounds?.min ?? 0;
|
|
280
|
+
const max = bounds?.max ?? UINT32_MAX;
|
|
281
|
+
if (value < min || value > max) {
|
|
282
|
+
throw error('OUT_OF_RANGE', `value ${value} must be in [${min}, ${max}]`, path);
|
|
283
|
+
}
|
|
284
|
+
return value;
|
|
285
|
+
}
|
|
286
|
+
function enforceExactKeys(value, allowed, path, optional) {
|
|
287
|
+
const allowedSet = new Set(allowed);
|
|
288
|
+
const optionalSet = new Set(optional ?? []);
|
|
289
|
+
for (const key of Object.keys(value)) {
|
|
290
|
+
if (!allowedSet.has(key)) {
|
|
291
|
+
throw error('UNKNOWN_FIELD', `unknown field "${key}"`, path);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
for (const key of allowed) {
|
|
295
|
+
if (optionalSet.has(key)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
if (!Object.prototype.hasOwnProperty.call(value, key)) {
|
|
299
|
+
throw error('MISSING_FIELD', `missing required field "${key}"`, path);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
function ensureGasMaxChargeWithinBounds(gas, limits, path) {
|
|
304
|
+
const UINT64_MAX = 0xffffffffffffffffn;
|
|
305
|
+
const base = BigInt(gas.base);
|
|
306
|
+
const argBytes = BigInt(gas.k_arg_bytes) * BigInt(limits.max_request_bytes);
|
|
307
|
+
const retBytes = BigInt(gas.k_ret_bytes) * BigInt(limits.max_response_bytes);
|
|
308
|
+
const hostUnits = BigInt(gas.k_units) * BigInt(limits.max_units);
|
|
309
|
+
const total = base + argBytes + retBytes + hostUnits;
|
|
310
|
+
if (total > UINT64_MAX) {
|
|
311
|
+
throw error('OUT_OF_RANGE', 'gas charges overflow uint64 bounds', path);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
function error(code, message, path) {
|
|
315
|
+
return new AbiManifestError(code, message, path);
|
|
316
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { AbiManifest } from './abi-manifest.js';
|
|
2
|
+
export declare const HOST_V1_MANIFEST: AbiManifest;
|
|
3
|
+
export declare const HOST_V1_BYTES_HEX = "a3666162695f696467486f73742e76316966756e6374696f6e7383a963676173a5646261736514676b5f756e697473016b6b5f6172675f6279746573016b6b5f7265745f6279746573016b7363686564756c655f69646b646f632d726561642d76316561726974790165666e5f696401666566666563746452454144666c696d697473a4696d61785f756e6974731903e86c6172675f757466385f6d617881190800716d61785f726571756573745f6279746573191000726d61785f726573706f6e73655f62797465731a00040000676a735f706174688268646f63756d656e74636765746a6172675f736368656d6181a1647479706566737472696e676b6572726f725f636f64657383a26374616771686f73742f696e76616c69645f7061746864636f64656c494e56414c49445f50415448a2637461676a686f73742f6c696d697464636f64656e4c494d49545f4558434545444544a2637461676e686f73742f6e6f745f666f756e6464636f6465694e4f545f464f554e446d72657475726e5f736368656d61a16474797065626476a963676173a5646261736514676b5f756e697473016b6b5f6172675f6279746573016b6b5f7265745f6279746573016b7363686564756c655f69646b646f632d726561642d76316561726974790165666e5f696402666566666563746452454144666c696d697473a4696d61785f756e6974731903e86c6172675f757466385f6d617881190800716d61785f726571756573745f6279746573191000726d61785f726573706f6e73655f62797465731a00040000676a735f706174688268646f63756d656e746c67657443616e6f6e6963616c6a6172675f736368656d6181a1647479706566737472696e676b6572726f725f636f64657383a26374616771686f73742f696e76616c69645f7061746864636f64656c494e56414c49445f50415448a2637461676a686f73742f6c696d697464636f64656e4c494d49545f4558434545444544a2637461676e686f73742f6e6f745f666f756e6464636f6465694e4f545f464f554e446d72657475726e5f736368656d61a16474797065626476a963676173a5646261736505676b5f756e697473016b6b5f6172675f6279746573016b6b5f7265745f6279746573006b7363686564756c655f696467656d69742d76316561726974790165666e5f6964036665666665637464454d4954666c696d697473a3696d61785f756e697473190400716d61785f726571756573745f6279746573198000726d61785f726573706f6e73655f62797465731840676a735f706174688164656d69746a6172675f736368656d6181a164747970656264766b6572726f725f636f64657381a2637461676a686f73742f6c696d697464636f64656e4c494d49545f45584345454445446d72657475726e5f736368656d61a16474797065646e756c6c6b6162695f76657273696f6e01";
|
|
4
|
+
export declare const HOST_V1_HASH = "e23b0b2ee169900bbde7aff78e6ce20fead1715c60f8a8e3106d9959450a3d34";
|
|
5
|
+
export declare const HOST_V1_BYTES: Uint8Array<ArrayBufferLike>;
|
|
6
|
+
//# sourceMappingURL=host-v1-manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-v1-manifest.d.ts","sourceRoot":"","sources":["../../src/lib/host-v1-manifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAKrD,eAAO,MAAM,gBAAgB,EAAE,WA8E9B,CAAC;AAEF,eAAO,MAAM,iBAAiB,qlEACsjE,CAAC;AAErlE,eAAO,MAAM,YAAY,qEAC2C,CAAC;AAErE,eAAO,MAAM,aAAa,6BAAqC,CAAC"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// These fixtures mirror the files in libs/test-harness/fixtures/abi-manifest.
|
|
2
|
+
// They are inlined to avoid any Node-only dependencies so the constants remain
|
|
3
|
+
// usable in browser environments.
|
|
4
|
+
export const HOST_V1_MANIFEST = {
|
|
5
|
+
abi_id: 'Host.v1',
|
|
6
|
+
abi_version: 1,
|
|
7
|
+
functions: [
|
|
8
|
+
{
|
|
9
|
+
fn_id: 1,
|
|
10
|
+
js_path: ['document', 'get'],
|
|
11
|
+
effect: 'READ',
|
|
12
|
+
arity: 1,
|
|
13
|
+
arg_schema: [{ type: 'string' }],
|
|
14
|
+
return_schema: { type: 'dv' },
|
|
15
|
+
gas: {
|
|
16
|
+
schedule_id: 'doc-read-v1',
|
|
17
|
+
base: 20,
|
|
18
|
+
k_arg_bytes: 1,
|
|
19
|
+
k_ret_bytes: 1,
|
|
20
|
+
k_units: 1,
|
|
21
|
+
},
|
|
22
|
+
limits: {
|
|
23
|
+
max_request_bytes: 4096,
|
|
24
|
+
max_response_bytes: 262144,
|
|
25
|
+
max_units: 1000,
|
|
26
|
+
arg_utf8_max: [2048],
|
|
27
|
+
},
|
|
28
|
+
error_codes: [
|
|
29
|
+
{ code: 'INVALID_PATH', tag: 'host/invalid_path' },
|
|
30
|
+
{ code: 'LIMIT_EXCEEDED', tag: 'host/limit' },
|
|
31
|
+
{ code: 'NOT_FOUND', tag: 'host/not_found' },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
fn_id: 2,
|
|
36
|
+
js_path: ['document', 'getCanonical'],
|
|
37
|
+
effect: 'READ',
|
|
38
|
+
arity: 1,
|
|
39
|
+
arg_schema: [{ type: 'string' }],
|
|
40
|
+
return_schema: { type: 'dv' },
|
|
41
|
+
gas: {
|
|
42
|
+
schedule_id: 'doc-read-v1',
|
|
43
|
+
base: 20,
|
|
44
|
+
k_arg_bytes: 1,
|
|
45
|
+
k_ret_bytes: 1,
|
|
46
|
+
k_units: 1,
|
|
47
|
+
},
|
|
48
|
+
limits: {
|
|
49
|
+
max_request_bytes: 4096,
|
|
50
|
+
max_response_bytes: 262144,
|
|
51
|
+
max_units: 1000,
|
|
52
|
+
arg_utf8_max: [2048],
|
|
53
|
+
},
|
|
54
|
+
error_codes: [
|
|
55
|
+
{ code: 'INVALID_PATH', tag: 'host/invalid_path' },
|
|
56
|
+
{ code: 'LIMIT_EXCEEDED', tag: 'host/limit' },
|
|
57
|
+
{ code: 'NOT_FOUND', tag: 'host/not_found' },
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
fn_id: 3,
|
|
62
|
+
js_path: ['emit'],
|
|
63
|
+
effect: 'EMIT',
|
|
64
|
+
arity: 1,
|
|
65
|
+
arg_schema: [{ type: 'dv' }],
|
|
66
|
+
return_schema: { type: 'null' },
|
|
67
|
+
gas: {
|
|
68
|
+
schedule_id: 'emit-v1',
|
|
69
|
+
base: 5,
|
|
70
|
+
k_arg_bytes: 1,
|
|
71
|
+
k_ret_bytes: 0,
|
|
72
|
+
k_units: 1,
|
|
73
|
+
},
|
|
74
|
+
limits: {
|
|
75
|
+
max_request_bytes: 32768,
|
|
76
|
+
max_response_bytes: 64,
|
|
77
|
+
max_units: 1024,
|
|
78
|
+
},
|
|
79
|
+
error_codes: [{ code: 'LIMIT_EXCEEDED', tag: 'host/limit' }],
|
|
80
|
+
},
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
export const HOST_V1_BYTES_HEX = 'a3666162695f696467486f73742e76316966756e6374696f6e7383a963676173a5646261736514676b5f756e697473016b6b5f6172675f6279746573016b6b5f7265745f6279746573016b7363686564756c655f69646b646f632d726561642d76316561726974790165666e5f696401666566666563746452454144666c696d697473a4696d61785f756e6974731903e86c6172675f757466385f6d617881190800716d61785f726571756573745f6279746573191000726d61785f726573706f6e73655f62797465731a00040000676a735f706174688268646f63756d656e74636765746a6172675f736368656d6181a1647479706566737472696e676b6572726f725f636f64657383a26374616771686f73742f696e76616c69645f7061746864636f64656c494e56414c49445f50415448a2637461676a686f73742f6c696d697464636f64656e4c494d49545f4558434545444544a2637461676e686f73742f6e6f745f666f756e6464636f6465694e4f545f464f554e446d72657475726e5f736368656d61a16474797065626476a963676173a5646261736514676b5f756e697473016b6b5f6172675f6279746573016b6b5f7265745f6279746573016b7363686564756c655f69646b646f632d726561642d76316561726974790165666e5f696402666566666563746452454144666c696d697473a4696d61785f756e6974731903e86c6172675f757466385f6d617881190800716d61785f726571756573745f6279746573191000726d61785f726573706f6e73655f62797465731a00040000676a735f706174688268646f63756d656e746c67657443616e6f6e6963616c6a6172675f736368656d6181a1647479706566737472696e676b6572726f725f636f64657383a26374616771686f73742f696e76616c69645f7061746864636f64656c494e56414c49445f50415448a2637461676a686f73742f6c696d697464636f64656e4c494d49545f4558434545444544a2637461676e686f73742f6e6f745f666f756e6464636f6465694e4f545f464f554e446d72657475726e5f736368656d61a16474797065626476a963676173a5646261736505676b5f756e697473016b6b5f6172675f6279746573016b6b5f7265745f6279746573006b7363686564756c655f696467656d69742d76316561726974790165666e5f6964036665666665637464454d4954666c696d697473a3696d61785f756e697473190400716d61785f726571756573745f6279746573198000726d61785f726573706f6e73655f62797465731840676a735f706174688164656d69746a6172675f736368656d6181a164747970656264766b6572726f725f636f64657381a2637461676a686f73742f6c696d697464636f64656e4c494d49545f45584345454445446d72657475726e5f736368656d61a16474797065646e756c6c6b6162695f76657273696f6e01';
|
|
84
|
+
export const HOST_V1_HASH = 'e23b0b2ee169900bbde7aff78e6ce20fead1715c60f8a8e3106d9959450a3d34';
|
|
85
|
+
export const HOST_V1_BYTES = hexToUint8Array(HOST_V1_BYTES_HEX);
|
|
86
|
+
function hexToUint8Array(hex) {
|
|
87
|
+
if (hex.length % 2 !== 0) {
|
|
88
|
+
throw new Error('hex string must have an even length');
|
|
89
|
+
}
|
|
90
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
91
|
+
for (let i = 0; i < bytes.length; i += 1) {
|
|
92
|
+
const byte = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
93
|
+
if (Number.isNaN(byte)) {
|
|
94
|
+
throw new Error(`invalid hex at position ${i * 2}`);
|
|
95
|
+
}
|
|
96
|
+
bytes[i] = byte;
|
|
97
|
+
}
|
|
98
|
+
return bytes;
|
|
99
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@blue-quickjs/abi-manifest",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
"./package.json": "./package.json",
|
|
10
|
+
".": {
|
|
11
|
+
"@blue-quickjs/source": "./src/index.ts",
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"!**/*.tsbuildinfo"
|
|
20
|
+
],
|
|
21
|
+
"nx": {
|
|
22
|
+
"name": "abi-manifest"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@noble/hashes": "^1.5.0",
|
|
26
|
+
"tslib": "^2.3.0",
|
|
27
|
+
"@blue-quickjs/dv": "0.1.0"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"publishConfig": {
|
|
31
|
+
"access": "public"
|
|
32
|
+
},
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/bluecontract/blue-quickjs.git"
|
|
36
|
+
}
|
|
37
|
+
}
|