@seljs/runtime 1.0.0 → 1.0.1-beta.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/_virtual/_rolldown/runtime.cjs +23 -0
- package/dist/analysis/call-collector.cjs +233 -0
- package/dist/analysis/call-collector.mjs +232 -0
- package/dist/analysis/dependency-analyzer.cjs +68 -0
- package/dist/analysis/dependency-analyzer.mjs +68 -0
- package/dist/analysis/round-planner.cjs +65 -0
- package/dist/analysis/round-planner.mjs +65 -0
- package/dist/analysis/types.d.cts +115 -0
- package/dist/analysis/types.d.mts +115 -0
- package/dist/debug.cjs +17 -0
- package/dist/debug.mjs +15 -0
- package/dist/environment/context.cjs +11 -0
- package/dist/environment/context.mjs +11 -0
- package/dist/environment/contract-caller.cjs +81 -0
- package/dist/environment/contract-caller.mjs +77 -0
- package/dist/environment/environment.cjs +254 -0
- package/dist/environment/environment.d.cts +84 -0
- package/dist/environment/environment.d.mts +84 -0
- package/dist/environment/environment.mjs +252 -0
- package/dist/environment/error-wrapper.cjs +29 -0
- package/dist/environment/error-wrapper.mjs +27 -0
- package/dist/environment/index.cjs +1 -0
- package/dist/environment/index.d.mts +2 -0
- package/dist/environment/index.mjs +2 -0
- package/dist/environment/replay-cache.cjs +48 -0
- package/dist/environment/replay-cache.mjs +47 -0
- package/dist/environment/types.d.cts +60 -0
- package/dist/environment/types.d.mts +60 -0
- package/dist/errors/errors.cjs +68 -0
- package/dist/errors/errors.d.cts +64 -0
- package/dist/errors/errors.d.mts +64 -0
- package/dist/errors/errors.mjs +63 -0
- package/dist/errors/index.cjs +3 -0
- package/dist/errors/index.d.mts +1 -0
- package/dist/errors/index.mjs +2 -0
- package/dist/execution/multi-round-executor.cjs +45 -0
- package/dist/execution/multi-round-executor.mjs +44 -0
- package/dist/execution/multicall-batcher.cjs +51 -0
- package/dist/execution/multicall-batcher.mjs +50 -0
- package/dist/execution/multicall.cjs +39 -0
- package/dist/execution/multicall.mjs +38 -0
- package/dist/execution/result-cache.cjs +63 -0
- package/dist/execution/result-cache.mjs +63 -0
- package/dist/execution/round-executor.cjs +81 -0
- package/dist/execution/round-executor.mjs +80 -0
- package/dist/execution/types.d.cts +58 -0
- package/dist/execution/types.d.mts +58 -0
- package/dist/factory.cjs +6 -0
- package/dist/factory.d.cts +7 -0
- package/dist/factory.d.mts +6 -0
- package/dist/factory.mjs +6 -0
- package/dist/index.cjs +18 -0
- package/dist/index.d.cts +7 -0
- package/dist/index.d.mts +7 -0
- package/dist/index.mjs +6 -0
- package/package.json +26 -19
- package/dist/analysis/call-collector.d.ts +0 -20
- package/dist/analysis/call-collector.d.ts.map +0 -1
- package/dist/analysis/call-collector.js +0 -272
- package/dist/analysis/dependency-analyzer.d.ts +0 -14
- package/dist/analysis/dependency-analyzer.d.ts.map +0 -1
- package/dist/analysis/dependency-analyzer.js +0 -76
- package/dist/analysis/index.d.ts +0 -2
- package/dist/analysis/index.d.ts.map +0 -1
- package/dist/analysis/index.js +0 -1
- package/dist/analysis/round-planner.d.ts +0 -32
- package/dist/analysis/round-planner.d.ts.map +0 -1
- package/dist/analysis/round-planner.js +0 -69
- package/dist/analysis/types.d.ts +0 -113
- package/dist/analysis/types.d.ts.map +0 -1
- package/dist/analysis/types.js +0 -1
- package/dist/debug.d.ts +0 -13
- package/dist/debug.d.ts.map +0 -1
- package/dist/debug.js +0 -12
- package/dist/environment/context.d.ts +0 -3
- package/dist/environment/context.d.ts.map +0 -1
- package/dist/environment/context.js +0 -8
- package/dist/environment/contract-caller.d.ts +0 -25
- package/dist/environment/contract-caller.d.ts.map +0 -1
- package/dist/environment/contract-caller.js +0 -85
- package/dist/environment/environment.d.ts +0 -81
- package/dist/environment/environment.d.ts.map +0 -1
- package/dist/environment/environment.js +0 -279
- package/dist/environment/error-wrapper.d.ts +0 -11
- package/dist/environment/error-wrapper.d.ts.map +0 -1
- package/dist/environment/error-wrapper.js +0 -33
- package/dist/environment/index.d.ts +0 -3
- package/dist/environment/index.d.ts.map +0 -1
- package/dist/environment/index.js +0 -2
- package/dist/environment/replay-cache.d.ts +0 -23
- package/dist/environment/replay-cache.d.ts.map +0 -1
- package/dist/environment/replay-cache.js +0 -51
- package/dist/environment/types.d.ts +0 -57
- package/dist/environment/types.d.ts.map +0 -1
- package/dist/environment/types.js +0 -1
- package/dist/errors/errors.d.ts +0 -63
- package/dist/errors/errors.d.ts.map +0 -1
- package/dist/errors/errors.js +0 -63
- package/dist/errors/index.d.ts +0 -2
- package/dist/errors/index.d.ts.map +0 -1
- package/dist/errors/index.js +0 -1
- package/dist/execution/index.d.ts +0 -2
- package/dist/execution/index.d.ts.map +0 -1
- package/dist/execution/index.js +0 -1
- package/dist/execution/multi-round-executor.d.ts +0 -17
- package/dist/execution/multi-round-executor.d.ts.map +0 -1
- package/dist/execution/multi-round-executor.js +0 -47
- package/dist/execution/multicall-batcher.d.ts +0 -14
- package/dist/execution/multicall-batcher.d.ts.map +0 -1
- package/dist/execution/multicall-batcher.js +0 -53
- package/dist/execution/multicall.d.ts +0 -42
- package/dist/execution/multicall.d.ts.map +0 -1
- package/dist/execution/multicall.js +0 -29
- package/dist/execution/result-cache.d.ts +0 -47
- package/dist/execution/result-cache.d.ts.map +0 -1
- package/dist/execution/result-cache.js +0 -65
- package/dist/execution/round-executor.d.ts +0 -18
- package/dist/execution/round-executor.d.ts.map +0 -1
- package/dist/execution/round-executor.js +0 -95
- package/dist/execution/types.d.ts +0 -55
- package/dist/execution/types.d.ts.map +0 -1
- package/dist/execution/types.js +0 -1
- package/dist/factory.d.ts +0 -3
- package/dist/factory.d.ts.map +0 -1
- package/dist/factory.js +0 -2
- package/dist/index.d.ts +0 -10
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -7
package/package.json
CHANGED
|
@@ -1,30 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seljs/runtime",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1-beta.9",
|
|
4
|
+
"repository": {
|
|
5
|
+
"url": "https://github.com/abinnovision/seljs"
|
|
6
|
+
},
|
|
4
7
|
"license": "Apache-2.0",
|
|
5
8
|
"author": {
|
|
6
9
|
"name": "abi group GmbH",
|
|
7
10
|
"email": "info@abigroup.io",
|
|
8
11
|
"url": "https://abigroup.io/"
|
|
9
12
|
},
|
|
10
|
-
"repository": {
|
|
11
|
-
"url": "https://github.com/abinnovision/seljs"
|
|
12
|
-
},
|
|
13
13
|
"type": "module",
|
|
14
14
|
"exports": {
|
|
15
15
|
".": {
|
|
16
|
-
"import":
|
|
17
|
-
|
|
16
|
+
"import": {
|
|
17
|
+
"types": "./dist/index.d.mts",
|
|
18
|
+
"default": "./dist/index.mjs"
|
|
19
|
+
},
|
|
20
|
+
"require": {
|
|
21
|
+
"types": "./dist/index.d.cts",
|
|
22
|
+
"default": "./dist/index.cjs"
|
|
23
|
+
}
|
|
18
24
|
}
|
|
19
25
|
},
|
|
20
|
-
"main": "./dist/index.
|
|
21
|
-
"types": "./dist/index.d.
|
|
26
|
+
"main": "./dist/index.cjs",
|
|
27
|
+
"types": "./dist/index.d.cts",
|
|
22
28
|
"files": [
|
|
23
29
|
"dist",
|
|
24
30
|
"LICENSE.md"
|
|
25
31
|
],
|
|
26
32
|
"scripts": {
|
|
27
|
-
"build": "
|
|
33
|
+
"build": "tsdown",
|
|
28
34
|
"format:check": "prettier --check '{{src,test}/**/*,*}.{{t,j}s{,x},json{,5},md,y{,a}ml}'",
|
|
29
35
|
"format:fix": "prettier --write '{{src,test}/**/*,*}.{{t,j}s{,x},json{,5},md,y{,a}ml}'",
|
|
30
36
|
"lint:check": "eslint '{{src,test}/**/*,*}.{t,j}s{,x}'",
|
|
@@ -34,16 +40,6 @@
|
|
|
34
40
|
"test-unit:watch": "vitest watch",
|
|
35
41
|
"typecheck": "tsc --noEmit"
|
|
36
42
|
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"@marcbachmann/cel-js": "^7.5.2",
|
|
39
|
-
"@seljs/checker": "1.0.0",
|
|
40
|
-
"@seljs/common": "1.0.0",
|
|
41
|
-
"@seljs/env": "1.0.0",
|
|
42
|
-
"@seljs/schema": "1.0.0",
|
|
43
|
-
"@seljs/types": "1.0.0",
|
|
44
|
-
"debug": "^4.4.3",
|
|
45
|
-
"viem": "^2.47.2"
|
|
46
|
-
},
|
|
47
43
|
"lint-staged": {
|
|
48
44
|
"{{src,test}/**/*,*}.{{t,j}s{,x},json{,5},md,y{,a}ml}": [
|
|
49
45
|
"prettier --write"
|
|
@@ -52,6 +48,16 @@
|
|
|
52
48
|
"eslint --fix"
|
|
53
49
|
]
|
|
54
50
|
},
|
|
51
|
+
"dependencies": {
|
|
52
|
+
"@marcbachmann/cel-js": "^7.5.3",
|
|
53
|
+
"@seljs/checker": "1.0.1-beta.9",
|
|
54
|
+
"@seljs/common": "1.0.1-beta.9",
|
|
55
|
+
"@seljs/env": "1.0.1-beta.9",
|
|
56
|
+
"@seljs/schema": "1.0.0",
|
|
57
|
+
"@seljs/types": "1.0.1-beta.9",
|
|
58
|
+
"debug": "^4.4.3",
|
|
59
|
+
"viem": "^2.47.2"
|
|
60
|
+
},
|
|
55
61
|
"devDependencies": {
|
|
56
62
|
"@abinnovision/eslint-config-base": "^3.2.0",
|
|
57
63
|
"@abinnovision/prettier-config": "^2.1.5",
|
|
@@ -61,6 +67,7 @@
|
|
|
61
67
|
"concurrently": "^9.2.1",
|
|
62
68
|
"eslint": "^9.39.4",
|
|
63
69
|
"prettier": "^3.8.1",
|
|
70
|
+
"tsdown": "^0.21.3",
|
|
64
71
|
"typescript": "^5.9.3",
|
|
65
72
|
"vitest": "^4.0.18",
|
|
66
73
|
"zod": "^4.3.6"
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import type { CollectedCall } from "./types.js";
|
|
2
|
-
import type { ASTNode } from "@marcbachmann/cel-js";
|
|
3
|
-
/** Minimal interface required by collectCalls for contract lookup. */
|
|
4
|
-
export interface ContractLookup {
|
|
5
|
-
get: (name: string) => unknown;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Collects contract calls from a CEL AST by traversing the tree.
|
|
9
|
-
*
|
|
10
|
-
* Walks the AST looking for `rcall` nodes whose receiver matches a registered
|
|
11
|
-
* contract. For each matched call, classifies arguments as literals, variable
|
|
12
|
-
* references, or results of other contract calls (creating dependency edges).
|
|
13
|
-
* Skips `cel.bind()` calls while still collecting any nested contract calls.
|
|
14
|
-
*
|
|
15
|
-
* @param ast The root AST node from a parsed CEL expression
|
|
16
|
-
* @param registry Contract lookup to validate receiver names against
|
|
17
|
-
* @returns Array of collected calls with dependency information
|
|
18
|
-
*/
|
|
19
|
-
export declare const collectCalls: (ast: ASTNode, registry: ContractLookup) => CollectedCall[];
|
|
20
|
-
//# sourceMappingURL=call-collector.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"call-collector.d.ts","sourceRoot":"","sources":["../../src/analysis/call-collector.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAgB,aAAa,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CAC/B;AAwHD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,YAAY,GACxB,KAAK,OAAO,EACZ,UAAU,cAAc,KACtB,aAAa,EA2Nf,CAAC"}
|
|
@@ -1,272 +0,0 @@
|
|
|
1
|
-
import { COMPREHENSION_MACROS, SCALAR_WRAPPER_FUNCTIONS } from "@seljs/checker";
|
|
2
|
-
import { isAstNode } from "@seljs/common";
|
|
3
|
-
import { createLogger } from "../debug.js";
|
|
4
|
-
const debug = createLogger("analysis:collect");
|
|
5
|
-
/**
|
|
6
|
-
* Generates a deterministic call identifier from contract name, method, and arguments.
|
|
7
|
-
*
|
|
8
|
-
* Format: "contract:method:arg1,arg2,..."
|
|
9
|
-
* Used for deduplication and dependency tracking between calls.
|
|
10
|
-
*/
|
|
11
|
-
const generateCallId = (contract, method, args) => {
|
|
12
|
-
const argKey = args
|
|
13
|
-
.map((arg) => {
|
|
14
|
-
if (arg.variableName !== undefined) {
|
|
15
|
-
return arg.variableName;
|
|
16
|
-
}
|
|
17
|
-
if (arg.value !== undefined) {
|
|
18
|
-
const v = arg.value;
|
|
19
|
-
return typeof v === "string" ||
|
|
20
|
-
typeof v === "number" ||
|
|
21
|
-
typeof v === "boolean" ||
|
|
22
|
-
typeof v === "bigint"
|
|
23
|
-
? String(v)
|
|
24
|
-
: "?";
|
|
25
|
-
}
|
|
26
|
-
if (arg.dependsOnCallId !== undefined) {
|
|
27
|
-
return arg.dependsOnCallId;
|
|
28
|
-
}
|
|
29
|
-
return "?";
|
|
30
|
-
})
|
|
31
|
-
.join(",");
|
|
32
|
-
return `${contract}:${method}:${argKey}`;
|
|
33
|
-
};
|
|
34
|
-
/**
|
|
35
|
-
* Attempts to extract a scalar argument from a Solidity cast/wrapper call
|
|
36
|
-
* (e.g. `uint256(42)`, `solAddress("0x...")`, `int256(tokenId)`).
|
|
37
|
-
*
|
|
38
|
-
* Handles both literal values and variable references.
|
|
39
|
-
* Returns undefined if the node is not a recognized scalar wrapper.
|
|
40
|
-
*/
|
|
41
|
-
const collectScalarArgument = (argNode) => {
|
|
42
|
-
if (!isAstNode(argNode) || argNode.op !== "call") {
|
|
43
|
-
return undefined;
|
|
44
|
-
}
|
|
45
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
|
46
|
-
if (!Array.isArray(argNode.args) || argNode.args.length !== 2) {
|
|
47
|
-
return undefined;
|
|
48
|
-
}
|
|
49
|
-
const fnName = argNode.args[0];
|
|
50
|
-
const fnArgs = argNode.args[1];
|
|
51
|
-
if (typeof fnName !== "string" ||
|
|
52
|
-
!Array.isArray(fnArgs) ||
|
|
53
|
-
fnArgs.length !== 1 ||
|
|
54
|
-
!SCALAR_WRAPPER_FUNCTIONS.has(fnName)) {
|
|
55
|
-
return undefined;
|
|
56
|
-
}
|
|
57
|
-
const inner = fnArgs[0];
|
|
58
|
-
if (!isAstNode(inner)) {
|
|
59
|
-
return undefined;
|
|
60
|
-
}
|
|
61
|
-
if (inner.op === "value") {
|
|
62
|
-
return { type: "literal", value: inner.args };
|
|
63
|
-
}
|
|
64
|
-
if (inner.op === "id" && typeof inner.args === "string") {
|
|
65
|
-
return { type: "variable", variableName: inner.args };
|
|
66
|
-
}
|
|
67
|
-
return undefined;
|
|
68
|
-
};
|
|
69
|
-
/**
|
|
70
|
-
* Extracts the method name, receiver identifier, and argument nodes from an rcall AST node.
|
|
71
|
-
*
|
|
72
|
-
* An rcall node has the structure: `["methodName", receiverNode, [argNodes...]]`
|
|
73
|
-
*/
|
|
74
|
-
const getRCallArgs = (node) => {
|
|
75
|
-
if (!Array.isArray(node.args) || node.args.length < 3) {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
const methodNode = node.args[0];
|
|
79
|
-
const receiverNode = node.args[1];
|
|
80
|
-
const argNodes = node.args[2];
|
|
81
|
-
if (typeof methodNode !== "string" || !Array.isArray(argNodes)) {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
let receiverName;
|
|
85
|
-
if (isAstNode(receiverNode) &&
|
|
86
|
-
receiverNode.op === "id" &&
|
|
87
|
-
typeof receiverNode.args === "string") {
|
|
88
|
-
receiverName = receiverNode.args;
|
|
89
|
-
}
|
|
90
|
-
return { method: methodNode, receiverName, args: argNodes };
|
|
91
|
-
};
|
|
92
|
-
/**
|
|
93
|
-
* Collects contract calls from a CEL AST by traversing the tree.
|
|
94
|
-
*
|
|
95
|
-
* Walks the AST looking for `rcall` nodes whose receiver matches a registered
|
|
96
|
-
* contract. For each matched call, classifies arguments as literals, variable
|
|
97
|
-
* references, or results of other contract calls (creating dependency edges).
|
|
98
|
-
* Skips `cel.bind()` calls while still collecting any nested contract calls.
|
|
99
|
-
*
|
|
100
|
-
* @param ast The root AST node from a parsed CEL expression
|
|
101
|
-
* @param registry Contract lookup to validate receiver names against
|
|
102
|
-
* @returns Array of collected calls with dependency information
|
|
103
|
-
*/
|
|
104
|
-
export const collectCalls = (ast, registry) => {
|
|
105
|
-
const calls = [];
|
|
106
|
-
/** Variable names that are scoped (comprehension iteration vars, cel.bind vars) — NOT user context */
|
|
107
|
-
const scopedVars = new Set();
|
|
108
|
-
/** rcall AST nodes skipped because they have unresolvable args */
|
|
109
|
-
const deferredNodes = new WeakSet();
|
|
110
|
-
const traverse = (node) => {
|
|
111
|
-
if (Array.isArray(node)) {
|
|
112
|
-
for (const item of node) {
|
|
113
|
-
traverse(item);
|
|
114
|
-
}
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
117
|
-
if (!isAstNode(node)) {
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
if (node.op === "rcall") {
|
|
121
|
-
collectRCall(node);
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
traverse(node.args);
|
|
125
|
-
};
|
|
126
|
-
/**
|
|
127
|
-
* Handles cel.bind() — registers the bind variable name for the body scope,
|
|
128
|
-
* traverses the initializer and body, then removes the scoped var.
|
|
129
|
-
*/
|
|
130
|
-
const handleCelBind = (args) => {
|
|
131
|
-
if (args.length >= 3) {
|
|
132
|
-
const nameNode = args[0];
|
|
133
|
-
if (isAstNode(nameNode) &&
|
|
134
|
-
nameNode.op === "id" &&
|
|
135
|
-
typeof nameNode.args === "string") {
|
|
136
|
-
traverse(args[1]);
|
|
137
|
-
scopedVars.add(nameNode.args);
|
|
138
|
-
traverse(args[2]);
|
|
139
|
-
scopedVars.delete(nameNode.args);
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
for (const argNode of args) {
|
|
144
|
-
traverse(argNode);
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
/**
|
|
148
|
-
* Handles rcall nodes whose receiver is NOT a registered contract.
|
|
149
|
-
* Traverses the receiver node and args, registering comprehension
|
|
150
|
-
* iteration variables for their body scope.
|
|
151
|
-
*/
|
|
152
|
-
const handleNonContractRCall = (node, method, args) => {
|
|
153
|
-
// Always traverse the receiver to collect contract calls within it
|
|
154
|
-
if (Array.isArray(node.args) && node.args.length >= 2) {
|
|
155
|
-
traverse(node.args[1]);
|
|
156
|
-
}
|
|
157
|
-
// Comprehension macros: register iteration variable for body scope
|
|
158
|
-
if (COMPREHENSION_MACROS.has(method) && args.length >= 2) {
|
|
159
|
-
const iterVarNode = args[0];
|
|
160
|
-
if (isAstNode(iterVarNode) &&
|
|
161
|
-
iterVarNode.op === "id" &&
|
|
162
|
-
typeof iterVarNode.args === "string") {
|
|
163
|
-
scopedVars.add(iterVarNode.args);
|
|
164
|
-
for (let i = 1; i < args.length; i++) {
|
|
165
|
-
traverse(args[i]);
|
|
166
|
-
}
|
|
167
|
-
scopedVars.delete(iterVarNode.args);
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
for (const argNode of args) {
|
|
172
|
-
traverse(argNode);
|
|
173
|
-
}
|
|
174
|
-
};
|
|
175
|
-
/**
|
|
176
|
-
* Classifies a single argument node, returning the resolved CallArgument
|
|
177
|
-
* or `undefined` if unresolvable. When `undefined` is returned, the caller
|
|
178
|
-
* should mark the parent call as deferred.
|
|
179
|
-
*/
|
|
180
|
-
const classifyArg = (argNode) => {
|
|
181
|
-
const scalarArg = collectScalarArgument(argNode);
|
|
182
|
-
if (scalarArg) {
|
|
183
|
-
return { arg: scalarArg, traversed: false };
|
|
184
|
-
}
|
|
185
|
-
if (isAstNode(argNode) && argNode.op === "value") {
|
|
186
|
-
return {
|
|
187
|
-
arg: { type: "literal", value: argNode.args },
|
|
188
|
-
traversed: false,
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
if (isAstNode(argNode) &&
|
|
192
|
-
argNode.op === "id" &&
|
|
193
|
-
typeof argNode.args === "string") {
|
|
194
|
-
if (scopedVars.has(argNode.args)) {
|
|
195
|
-
return { arg: undefined, traversed: false };
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
198
|
-
arg: { type: "variable", variableName: argNode.args },
|
|
199
|
-
traversed: false,
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
if (isAstNode(argNode) && argNode.op === "rcall") {
|
|
203
|
-
const innerCall = collectRCall(argNode);
|
|
204
|
-
if (innerCall) {
|
|
205
|
-
return {
|
|
206
|
-
arg: { type: "call_result", dependsOnCallId: innerCall.id },
|
|
207
|
-
traversed: true,
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
// Inner rcall was not collected — check if it was deferred
|
|
211
|
-
if (deferredNodes.has(argNode)) {
|
|
212
|
-
// inner calls already collected by recursive collectRCall
|
|
213
|
-
return { arg: undefined, traversed: true };
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return { arg: undefined, traversed: false };
|
|
217
|
-
};
|
|
218
|
-
/**
|
|
219
|
-
* Processes an rcall AST node, collecting it as a contract call if the
|
|
220
|
-
* receiver is a registered contract. Recursively processes nested rcalls
|
|
221
|
-
* in arguments to build dependency chains.
|
|
222
|
-
*/
|
|
223
|
-
const collectRCall = (node) => {
|
|
224
|
-
const callArgs = getRCallArgs(node);
|
|
225
|
-
if (!callArgs) {
|
|
226
|
-
traverse(node.args);
|
|
227
|
-
return undefined;
|
|
228
|
-
}
|
|
229
|
-
const { method, receiverName, args } = callArgs;
|
|
230
|
-
if (receiverName === "cel" && method === "bind") {
|
|
231
|
-
handleCelBind(args);
|
|
232
|
-
return undefined;
|
|
233
|
-
}
|
|
234
|
-
if (!receiverName || !registry.get(receiverName)) {
|
|
235
|
-
handleNonContractRCall(node, method, args);
|
|
236
|
-
return undefined;
|
|
237
|
-
}
|
|
238
|
-
const collectedArgs = [];
|
|
239
|
-
let hasUnresolvableArg = false;
|
|
240
|
-
for (const argNode of args) {
|
|
241
|
-
const { arg, traversed } = classifyArg(argNode);
|
|
242
|
-
if (arg) {
|
|
243
|
-
collectedArgs.push(arg);
|
|
244
|
-
}
|
|
245
|
-
else {
|
|
246
|
-
hasUnresolvableArg = true;
|
|
247
|
-
if (!traversed) {
|
|
248
|
-
traverse(argNode);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
if (hasUnresolvableArg) {
|
|
253
|
-
deferredNodes.add(node);
|
|
254
|
-
debug("deferred %s.%s (unresolvable args)", receiverName, method);
|
|
255
|
-
return undefined;
|
|
256
|
-
}
|
|
257
|
-
const call = {
|
|
258
|
-
id: generateCallId(receiverName, method, collectedArgs),
|
|
259
|
-
contract: receiverName,
|
|
260
|
-
method,
|
|
261
|
-
args: collectedArgs,
|
|
262
|
-
astNode: node,
|
|
263
|
-
};
|
|
264
|
-
calls.push(call);
|
|
265
|
-
debug("found %s.%s (id=%s)", receiverName, method, call.id);
|
|
266
|
-
return call;
|
|
267
|
-
};
|
|
268
|
-
// Start traversal from the root of the AST
|
|
269
|
-
traverse(ast);
|
|
270
|
-
debug("collected %d total calls", calls.length);
|
|
271
|
-
return calls;
|
|
272
|
-
};
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { CollectedCall, DependencyGraph } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Builds a dependency graph from collected contract calls.
|
|
4
|
-
*
|
|
5
|
-
* Creates a directed graph where nodes represent contract calls and edges
|
|
6
|
-
* represent data dependencies (when one call's argument is the result of
|
|
7
|
-
* another call). Validates that no circular dependencies exist.
|
|
8
|
-
*
|
|
9
|
-
* @param calls - Array of collected contract calls from AST traversal
|
|
10
|
-
* @returns A dependency graph with nodes and directed edges
|
|
11
|
-
* @throws {@link CircularDependencyError} If a circular dependency is detected between calls
|
|
12
|
-
*/
|
|
13
|
-
export declare const analyzeDependencies: (calls: CollectedCall[]) => DependencyGraph;
|
|
14
|
-
//# sourceMappingURL=dependency-analyzer.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"dependency-analyzer.d.ts","sourceRoot":"","sources":["../../src/analysis/dependency-analyzer.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAa,MAAM,YAAY,CAAC;AAiD5E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAC/B,OAAO,aAAa,EAAE,KACpB,eAwCF,CAAC"}
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import { createLogger } from "../debug.js";
|
|
2
|
-
import { CircularDependencyError } from "../errors/index.js";
|
|
3
|
-
const debug = createLogger("analysis:dependency");
|
|
4
|
-
/**
|
|
5
|
-
* Detects circular dependencies in the graph using DFS with a recursion stack.
|
|
6
|
-
*
|
|
7
|
-
* @throws {@link CircularDependencyError} If a cycle is found, including the call IDs forming the cycle
|
|
8
|
-
*/
|
|
9
|
-
const detectCycles = (nodes, edges) => {
|
|
10
|
-
const visited = new Set();
|
|
11
|
-
const inStack = new Set();
|
|
12
|
-
const dfs = (nodeId, path) => {
|
|
13
|
-
if (inStack.has(nodeId)) {
|
|
14
|
-
const cycleStart = path.indexOf(nodeId);
|
|
15
|
-
const cycle = path.slice(cycleStart);
|
|
16
|
-
throw new CircularDependencyError(`Circular dependency detected: ${cycle.join(" -> ")} -> ${nodeId}`, { callIds: cycle });
|
|
17
|
-
}
|
|
18
|
-
if (visited.has(nodeId)) {
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
visited.add(nodeId);
|
|
22
|
-
inStack.add(nodeId);
|
|
23
|
-
path.push(nodeId);
|
|
24
|
-
for (const depId of edges.get(nodeId) ?? []) {
|
|
25
|
-
dfs(depId, path);
|
|
26
|
-
}
|
|
27
|
-
path.pop();
|
|
28
|
-
inStack.delete(nodeId);
|
|
29
|
-
};
|
|
30
|
-
for (const nodeId of nodes.keys()) {
|
|
31
|
-
if (!visited.has(nodeId)) {
|
|
32
|
-
dfs(nodeId, []);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
/**
|
|
37
|
-
* Builds a dependency graph from collected contract calls.
|
|
38
|
-
*
|
|
39
|
-
* Creates a directed graph where nodes represent contract calls and edges
|
|
40
|
-
* represent data dependencies (when one call's argument is the result of
|
|
41
|
-
* another call). Validates that no circular dependencies exist.
|
|
42
|
-
*
|
|
43
|
-
* @param calls - Array of collected contract calls from AST traversal
|
|
44
|
-
* @returns A dependency graph with nodes and directed edges
|
|
45
|
-
* @throws {@link CircularDependencyError} If a circular dependency is detected between calls
|
|
46
|
-
*/
|
|
47
|
-
export const analyzeDependencies = (calls) => {
|
|
48
|
-
const nodes = new Map();
|
|
49
|
-
const edges = new Map();
|
|
50
|
-
// First pass: create a node and empty edge set for each call
|
|
51
|
-
for (const call of calls) {
|
|
52
|
-
nodes.set(call.id, { call, dependsOn: [], dependedOnBy: [] });
|
|
53
|
-
edges.set(call.id, new Set());
|
|
54
|
-
}
|
|
55
|
-
// Second pass: wire up dependency edges from call_result arguments
|
|
56
|
-
for (const call of calls) {
|
|
57
|
-
for (const arg of call.args) {
|
|
58
|
-
if (arg.type === "call_result" && arg.dependsOnCallId) {
|
|
59
|
-
const depId = arg.dependsOnCallId;
|
|
60
|
-
edges.get(call.id)?.add(depId);
|
|
61
|
-
const callNode = nodes.get(call.id);
|
|
62
|
-
if (callNode && !callNode.dependsOn.includes(depId)) {
|
|
63
|
-
callNode.dependsOn.push(depId);
|
|
64
|
-
}
|
|
65
|
-
const depNode = nodes.get(depId);
|
|
66
|
-
if (depNode && !depNode.dependedOnBy.includes(call.id)) {
|
|
67
|
-
depNode.dependedOnBy.push(call.id);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
// Run cycle detection to ensure there are no circular dependencies in the graph.
|
|
73
|
-
detectCycles(nodes, edges);
|
|
74
|
-
debug("graph: %d nodes, %d edges", nodes.size, [...edges.values()].reduce((sum, deps) => sum + deps.size, 0));
|
|
75
|
-
return { nodes, edges };
|
|
76
|
-
};
|
package/dist/analysis/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/analysis/index.ts"],"names":[],"mappings":"AAAA,mBAAmB,YAAY,CAAC"}
|
package/dist/analysis/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { DependencyGraph, ExecutionPlan } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Defines limits for the round planner to prevent excessive execution.
|
|
4
|
-
*/
|
|
5
|
-
export interface RoundPlannerLimits {
|
|
6
|
-
/**
|
|
7
|
-
* Upper limit on the number of execution rounds.
|
|
8
|
-
*
|
|
9
|
-
* @default 10
|
|
10
|
-
*/
|
|
11
|
-
maxRounds?: number;
|
|
12
|
-
/**
|
|
13
|
-
* Upper limit on the total number of calls in the graph.
|
|
14
|
-
*
|
|
15
|
-
* @default 100
|
|
16
|
-
*/
|
|
17
|
-
maxCalls?: number;
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Plans execution rounds from a dependency graph using topological sorting.
|
|
21
|
-
*
|
|
22
|
-
* Groups independent calls into parallel rounds, ensuring all dependencies
|
|
23
|
-
* of a call are executed in previous rounds. Enforces limits on the number
|
|
24
|
-
* of rounds and total calls to prevent excessive execution.
|
|
25
|
-
*
|
|
26
|
-
* @param graph Dependency graph representing calls and their relationships.
|
|
27
|
-
* @param limits Optional limits for maximum rounds and total calls.
|
|
28
|
-
* @returns Execution plan containing planned rounds, total calls, and maximum depth.
|
|
29
|
-
* @throws ExecutionLimitError If total calls exceed maxCalls or rounds exceed maxRounds.
|
|
30
|
-
*/
|
|
31
|
-
export declare const planRounds: (graph: DependencyGraph, limits?: RoundPlannerLimits) => ExecutionPlan;
|
|
32
|
-
//# sourceMappingURL=round-planner.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"round-planner.d.ts","sourceRoot":"","sources":["../../src/analysis/round-planner.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACX,eAAe,EACf,aAAa,EAEb,MAAM,YAAY,CAAC;AAIpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IAEnB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,UAAU,GACtB,OAAO,eAAe,EACtB,SAAQ,kBAAuB,KAC7B,aA6EF,CAAC"}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { createLogger } from "../debug.js";
|
|
2
|
-
import { ExecutionLimitError } from "../errors/index.js";
|
|
3
|
-
const debug = createLogger("analysis:plan");
|
|
4
|
-
/**
|
|
5
|
-
* Plans execution rounds from a dependency graph using topological sorting.
|
|
6
|
-
*
|
|
7
|
-
* Groups independent calls into parallel rounds, ensuring all dependencies
|
|
8
|
-
* of a call are executed in previous rounds. Enforces limits on the number
|
|
9
|
-
* of rounds and total calls to prevent excessive execution.
|
|
10
|
-
*
|
|
11
|
-
* @param graph Dependency graph representing calls and their relationships.
|
|
12
|
-
* @param limits Optional limits for maximum rounds and total calls.
|
|
13
|
-
* @returns Execution plan containing planned rounds, total calls, and maximum depth.
|
|
14
|
-
* @throws ExecutionLimitError If total calls exceed maxCalls or rounds exceed maxRounds.
|
|
15
|
-
*/
|
|
16
|
-
export const planRounds = (graph, limits = {}) => {
|
|
17
|
-
const maxRounds = limits.maxRounds ?? 10;
|
|
18
|
-
const maxCalls = limits.maxCalls ?? 100;
|
|
19
|
-
const totalCalls = graph.nodes.size;
|
|
20
|
-
if (totalCalls > maxCalls) {
|
|
21
|
-
throw new ExecutionLimitError(`Execution limit exceeded: ${String(totalCalls)} calls exceeds maxCalls (${String(maxCalls)})`, { limitType: "maxCalls", limit: maxCalls, actual: totalCalls });
|
|
22
|
-
}
|
|
23
|
-
if (totalCalls === 0) {
|
|
24
|
-
return { rounds: [], totalCalls: 0, maxDepth: 0 };
|
|
25
|
-
}
|
|
26
|
-
const inDegree = new Map();
|
|
27
|
-
for (const nodeId of graph.nodes.keys()) {
|
|
28
|
-
inDegree.set(nodeId, graph.edges.get(nodeId)?.size ?? 0);
|
|
29
|
-
}
|
|
30
|
-
const rounds = [];
|
|
31
|
-
const processed = new Set();
|
|
32
|
-
while (processed.size < totalCalls) {
|
|
33
|
-
const readyNodes = [];
|
|
34
|
-
for (const [nodeId, degree] of inDegree) {
|
|
35
|
-
if (degree === 0 && !processed.has(nodeId)) {
|
|
36
|
-
readyNodes.push(nodeId);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (readyNodes.length === 0) {
|
|
40
|
-
break;
|
|
41
|
-
}
|
|
42
|
-
if (rounds.length >= maxRounds) {
|
|
43
|
-
throw new ExecutionLimitError(`Execution limit exceeded: requires more than maxRounds (${String(maxRounds)}) rounds`, {
|
|
44
|
-
limitType: "maxRounds",
|
|
45
|
-
limit: maxRounds,
|
|
46
|
-
actual: rounds.length + 1,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
const round = {
|
|
50
|
-
roundNumber: rounds.length,
|
|
51
|
-
calls: readyNodes
|
|
52
|
-
.map((id) => graph.nodes.get(id)?.call)
|
|
53
|
-
.filter((c) => c !== undefined),
|
|
54
|
-
};
|
|
55
|
-
rounds.push(round);
|
|
56
|
-
for (const nodeId of readyNodes) {
|
|
57
|
-
processed.add(nodeId);
|
|
58
|
-
const node = graph.nodes.get(nodeId);
|
|
59
|
-
if (!node) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
for (const dependentId of node.dependedOnBy) {
|
|
63
|
-
inDegree.set(dependentId, (inDegree.get(dependentId) ?? 0) - 1);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
debug("planned %d rounds for %d calls (max depth=%d)", rounds.length, totalCalls, rounds.length);
|
|
68
|
-
return { rounds, totalCalls, maxDepth: rounds.length };
|
|
69
|
-
};
|