@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.
Files changed (128) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  2. package/dist/analysis/call-collector.cjs +233 -0
  3. package/dist/analysis/call-collector.mjs +232 -0
  4. package/dist/analysis/dependency-analyzer.cjs +68 -0
  5. package/dist/analysis/dependency-analyzer.mjs +68 -0
  6. package/dist/analysis/round-planner.cjs +65 -0
  7. package/dist/analysis/round-planner.mjs +65 -0
  8. package/dist/analysis/types.d.cts +115 -0
  9. package/dist/analysis/types.d.mts +115 -0
  10. package/dist/debug.cjs +17 -0
  11. package/dist/debug.mjs +15 -0
  12. package/dist/environment/context.cjs +11 -0
  13. package/dist/environment/context.mjs +11 -0
  14. package/dist/environment/contract-caller.cjs +81 -0
  15. package/dist/environment/contract-caller.mjs +77 -0
  16. package/dist/environment/environment.cjs +254 -0
  17. package/dist/environment/environment.d.cts +84 -0
  18. package/dist/environment/environment.d.mts +84 -0
  19. package/dist/environment/environment.mjs +252 -0
  20. package/dist/environment/error-wrapper.cjs +29 -0
  21. package/dist/environment/error-wrapper.mjs +27 -0
  22. package/dist/environment/index.cjs +1 -0
  23. package/dist/environment/index.d.mts +2 -0
  24. package/dist/environment/index.mjs +2 -0
  25. package/dist/environment/replay-cache.cjs +48 -0
  26. package/dist/environment/replay-cache.mjs +47 -0
  27. package/dist/environment/types.d.cts +60 -0
  28. package/dist/environment/types.d.mts +60 -0
  29. package/dist/errors/errors.cjs +68 -0
  30. package/dist/errors/errors.d.cts +64 -0
  31. package/dist/errors/errors.d.mts +64 -0
  32. package/dist/errors/errors.mjs +63 -0
  33. package/dist/errors/index.cjs +3 -0
  34. package/dist/errors/index.d.mts +1 -0
  35. package/dist/errors/index.mjs +2 -0
  36. package/dist/execution/multi-round-executor.cjs +45 -0
  37. package/dist/execution/multi-round-executor.mjs +44 -0
  38. package/dist/execution/multicall-batcher.cjs +51 -0
  39. package/dist/execution/multicall-batcher.mjs +50 -0
  40. package/dist/execution/multicall.cjs +39 -0
  41. package/dist/execution/multicall.mjs +38 -0
  42. package/dist/execution/result-cache.cjs +63 -0
  43. package/dist/execution/result-cache.mjs +63 -0
  44. package/dist/execution/round-executor.cjs +81 -0
  45. package/dist/execution/round-executor.mjs +80 -0
  46. package/dist/execution/types.d.cts +58 -0
  47. package/dist/execution/types.d.mts +58 -0
  48. package/dist/factory.cjs +6 -0
  49. package/dist/factory.d.cts +7 -0
  50. package/dist/factory.d.mts +6 -0
  51. package/dist/factory.mjs +6 -0
  52. package/dist/index.cjs +18 -0
  53. package/dist/index.d.cts +7 -0
  54. package/dist/index.d.mts +7 -0
  55. package/dist/index.mjs +6 -0
  56. package/package.json +26 -19
  57. package/dist/analysis/call-collector.d.ts +0 -20
  58. package/dist/analysis/call-collector.d.ts.map +0 -1
  59. package/dist/analysis/call-collector.js +0 -272
  60. package/dist/analysis/dependency-analyzer.d.ts +0 -14
  61. package/dist/analysis/dependency-analyzer.d.ts.map +0 -1
  62. package/dist/analysis/dependency-analyzer.js +0 -76
  63. package/dist/analysis/index.d.ts +0 -2
  64. package/dist/analysis/index.d.ts.map +0 -1
  65. package/dist/analysis/index.js +0 -1
  66. package/dist/analysis/round-planner.d.ts +0 -32
  67. package/dist/analysis/round-planner.d.ts.map +0 -1
  68. package/dist/analysis/round-planner.js +0 -69
  69. package/dist/analysis/types.d.ts +0 -113
  70. package/dist/analysis/types.d.ts.map +0 -1
  71. package/dist/analysis/types.js +0 -1
  72. package/dist/debug.d.ts +0 -13
  73. package/dist/debug.d.ts.map +0 -1
  74. package/dist/debug.js +0 -12
  75. package/dist/environment/context.d.ts +0 -3
  76. package/dist/environment/context.d.ts.map +0 -1
  77. package/dist/environment/context.js +0 -8
  78. package/dist/environment/contract-caller.d.ts +0 -25
  79. package/dist/environment/contract-caller.d.ts.map +0 -1
  80. package/dist/environment/contract-caller.js +0 -85
  81. package/dist/environment/environment.d.ts +0 -81
  82. package/dist/environment/environment.d.ts.map +0 -1
  83. package/dist/environment/environment.js +0 -279
  84. package/dist/environment/error-wrapper.d.ts +0 -11
  85. package/dist/environment/error-wrapper.d.ts.map +0 -1
  86. package/dist/environment/error-wrapper.js +0 -33
  87. package/dist/environment/index.d.ts +0 -3
  88. package/dist/environment/index.d.ts.map +0 -1
  89. package/dist/environment/index.js +0 -2
  90. package/dist/environment/replay-cache.d.ts +0 -23
  91. package/dist/environment/replay-cache.d.ts.map +0 -1
  92. package/dist/environment/replay-cache.js +0 -51
  93. package/dist/environment/types.d.ts +0 -57
  94. package/dist/environment/types.d.ts.map +0 -1
  95. package/dist/environment/types.js +0 -1
  96. package/dist/errors/errors.d.ts +0 -63
  97. package/dist/errors/errors.d.ts.map +0 -1
  98. package/dist/errors/errors.js +0 -63
  99. package/dist/errors/index.d.ts +0 -2
  100. package/dist/errors/index.d.ts.map +0 -1
  101. package/dist/errors/index.js +0 -1
  102. package/dist/execution/index.d.ts +0 -2
  103. package/dist/execution/index.d.ts.map +0 -1
  104. package/dist/execution/index.js +0 -1
  105. package/dist/execution/multi-round-executor.d.ts +0 -17
  106. package/dist/execution/multi-round-executor.d.ts.map +0 -1
  107. package/dist/execution/multi-round-executor.js +0 -47
  108. package/dist/execution/multicall-batcher.d.ts +0 -14
  109. package/dist/execution/multicall-batcher.d.ts.map +0 -1
  110. package/dist/execution/multicall-batcher.js +0 -53
  111. package/dist/execution/multicall.d.ts +0 -42
  112. package/dist/execution/multicall.d.ts.map +0 -1
  113. package/dist/execution/multicall.js +0 -29
  114. package/dist/execution/result-cache.d.ts +0 -47
  115. package/dist/execution/result-cache.d.ts.map +0 -1
  116. package/dist/execution/result-cache.js +0 -65
  117. package/dist/execution/round-executor.d.ts +0 -18
  118. package/dist/execution/round-executor.d.ts.map +0 -1
  119. package/dist/execution/round-executor.js +0 -95
  120. package/dist/execution/types.d.ts +0 -55
  121. package/dist/execution/types.d.ts.map +0 -1
  122. package/dist/execution/types.js +0 -1
  123. package/dist/factory.d.ts +0 -3
  124. package/dist/factory.d.ts.map +0 -1
  125. package/dist/factory.js +0 -2
  126. package/dist/index.d.ts +0 -10
  127. package/dist/index.d.ts.map +0 -1
  128. package/dist/index.js +0 -7
@@ -0,0 +1,23 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+ //#endregion
23
+ exports.__toESM = __toESM;
@@ -0,0 +1,233 @@
1
+ require("../_virtual/_rolldown/runtime.cjs");
2
+ const require_debug = require("../debug.cjs");
3
+ let _seljs_checker = require("@seljs/checker");
4
+ let _seljs_common = require("@seljs/common");
5
+ //#region src/analysis/call-collector.ts
6
+ const debug = require_debug.createLogger("analysis:collect");
7
+ /**
8
+ * Generates a deterministic call identifier from contract name, method, and arguments.
9
+ *
10
+ * Format: "contract:method:arg1,arg2,..."
11
+ * Used for deduplication and dependency tracking between calls.
12
+ */
13
+ const generateCallId = (contract, method, args) => {
14
+ return `${contract}:${method}:${args.map((arg) => {
15
+ if (arg.variableName !== void 0) return arg.variableName;
16
+ if (arg.value !== void 0) {
17
+ const v = arg.value;
18
+ return typeof v === "string" || typeof v === "number" || typeof v === "boolean" || typeof v === "bigint" ? String(v) : "?";
19
+ }
20
+ if (arg.dependsOnCallId !== void 0) return arg.dependsOnCallId;
21
+ return "?";
22
+ }).join(",")}`;
23
+ };
24
+ /**
25
+ * Attempts to extract a scalar argument from a Solidity cast/wrapper call
26
+ * (e.g. `uint256(42)`, `solAddress("0x...")`, `int256(tokenId)`).
27
+ *
28
+ * Handles both literal values and variable references.
29
+ * Returns undefined if the node is not a recognized scalar wrapper.
30
+ */
31
+ const collectScalarArgument = (argNode) => {
32
+ if (!(0, _seljs_common.isAstNode)(argNode) || argNode.op !== "call") return;
33
+ if (!Array.isArray(argNode.args) || argNode.args.length !== 2) return;
34
+ const fnName = argNode.args[0];
35
+ const fnArgs = argNode.args[1];
36
+ if (typeof fnName !== "string" || !Array.isArray(fnArgs) || fnArgs.length !== 1 || !_seljs_checker.SCALAR_WRAPPER_FUNCTIONS.has(fnName)) return;
37
+ const inner = fnArgs[0];
38
+ if (!(0, _seljs_common.isAstNode)(inner)) return;
39
+ if (inner.op === "value") return {
40
+ type: "literal",
41
+ value: inner.args
42
+ };
43
+ if (inner.op === "id" && typeof inner.args === "string") return {
44
+ type: "variable",
45
+ variableName: inner.args
46
+ };
47
+ };
48
+ /**
49
+ * Extracts the method name, receiver identifier, and argument nodes from an rcall AST node.
50
+ *
51
+ * An rcall node has the structure: `["methodName", receiverNode, [argNodes...]]`
52
+ */
53
+ const getRCallArgs = (node) => {
54
+ if (!Array.isArray(node.args) || node.args.length < 3) return;
55
+ const methodNode = node.args[0];
56
+ const receiverNode = node.args[1];
57
+ const argNodes = node.args[2];
58
+ if (typeof methodNode !== "string" || !Array.isArray(argNodes)) return;
59
+ let receiverName;
60
+ if ((0, _seljs_common.isAstNode)(receiverNode) && receiverNode.op === "id" && typeof receiverNode.args === "string") receiverName = receiverNode.args;
61
+ return {
62
+ method: methodNode,
63
+ receiverName,
64
+ args: argNodes
65
+ };
66
+ };
67
+ /**
68
+ * Collects contract calls from a CEL AST by traversing the tree.
69
+ *
70
+ * Walks the AST looking for `rcall` nodes whose receiver matches a registered
71
+ * contract. For each matched call, classifies arguments as literals, variable
72
+ * references, or results of other contract calls (creating dependency edges).
73
+ * Skips `cel.bind()` calls while still collecting any nested contract calls.
74
+ *
75
+ * @param ast The root AST node from a parsed CEL expression
76
+ * @param registry Contract lookup to validate receiver names against
77
+ * @returns Array of collected calls with dependency information
78
+ */
79
+ const collectCalls = (ast, registry) => {
80
+ const calls = [];
81
+ /** Variable names that are scoped (comprehension iteration vars, cel.bind vars) — NOT user context */
82
+ const scopedVars = /* @__PURE__ */ new Set();
83
+ /** rcall AST nodes skipped because they have unresolvable args */
84
+ const deferredNodes = /* @__PURE__ */ new WeakSet();
85
+ const traverse = (node) => {
86
+ if (Array.isArray(node)) {
87
+ for (const item of node) traverse(item);
88
+ return;
89
+ }
90
+ if (!(0, _seljs_common.isAstNode)(node)) return;
91
+ if (node.op === "rcall") {
92
+ collectRCall(node);
93
+ return;
94
+ }
95
+ traverse(node.args);
96
+ };
97
+ /**
98
+ * Handles cel.bind() — registers the bind variable name for the body scope,
99
+ * traverses the initializer and body, then removes the scoped var.
100
+ */
101
+ const handleCelBind = (args) => {
102
+ if (args.length >= 3) {
103
+ const nameNode = args[0];
104
+ if ((0, _seljs_common.isAstNode)(nameNode) && nameNode.op === "id" && typeof nameNode.args === "string") {
105
+ traverse(args[1]);
106
+ scopedVars.add(nameNode.args);
107
+ traverse(args[2]);
108
+ scopedVars.delete(nameNode.args);
109
+ return;
110
+ }
111
+ }
112
+ for (const argNode of args) traverse(argNode);
113
+ };
114
+ /**
115
+ * Handles rcall nodes whose receiver is NOT a registered contract.
116
+ * Traverses the receiver node and args, registering comprehension
117
+ * iteration variables for their body scope.
118
+ */
119
+ const handleNonContractRCall = (node, method, args) => {
120
+ if (Array.isArray(node.args) && node.args.length >= 2) traverse(node.args[1]);
121
+ if (_seljs_checker.COMPREHENSION_MACROS.has(method) && args.length >= 2) {
122
+ const iterVarNode = args[0];
123
+ if ((0, _seljs_common.isAstNode)(iterVarNode) && iterVarNode.op === "id" && typeof iterVarNode.args === "string") {
124
+ scopedVars.add(iterVarNode.args);
125
+ for (let i = 1; i < args.length; i++) traverse(args[i]);
126
+ scopedVars.delete(iterVarNode.args);
127
+ return;
128
+ }
129
+ }
130
+ for (const argNode of args) traverse(argNode);
131
+ };
132
+ /**
133
+ * Classifies a single argument node, returning the resolved CallArgument
134
+ * or `undefined` if unresolvable. When `undefined` is returned, the caller
135
+ * should mark the parent call as deferred.
136
+ */
137
+ const classifyArg = (argNode) => {
138
+ const scalarArg = collectScalarArgument(argNode);
139
+ if (scalarArg) return {
140
+ arg: scalarArg,
141
+ traversed: false
142
+ };
143
+ if ((0, _seljs_common.isAstNode)(argNode) && argNode.op === "value") return {
144
+ arg: {
145
+ type: "literal",
146
+ value: argNode.args
147
+ },
148
+ traversed: false
149
+ };
150
+ if ((0, _seljs_common.isAstNode)(argNode) && argNode.op === "id" && typeof argNode.args === "string") {
151
+ if (scopedVars.has(argNode.args)) return {
152
+ arg: void 0,
153
+ traversed: false
154
+ };
155
+ return {
156
+ arg: {
157
+ type: "variable",
158
+ variableName: argNode.args
159
+ },
160
+ traversed: false
161
+ };
162
+ }
163
+ if ((0, _seljs_common.isAstNode)(argNode) && argNode.op === "rcall") {
164
+ const innerCall = collectRCall(argNode);
165
+ if (innerCall) return {
166
+ arg: {
167
+ type: "call_result",
168
+ dependsOnCallId: innerCall.id
169
+ },
170
+ traversed: true
171
+ };
172
+ if (deferredNodes.has(argNode)) return {
173
+ arg: void 0,
174
+ traversed: true
175
+ };
176
+ }
177
+ return {
178
+ arg: void 0,
179
+ traversed: false
180
+ };
181
+ };
182
+ /**
183
+ * Processes an rcall AST node, collecting it as a contract call if the
184
+ * receiver is a registered contract. Recursively processes nested rcalls
185
+ * in arguments to build dependency chains.
186
+ */
187
+ const collectRCall = (node) => {
188
+ const callArgs = getRCallArgs(node);
189
+ if (!callArgs) {
190
+ traverse(node.args);
191
+ return;
192
+ }
193
+ const { method, receiverName, args } = callArgs;
194
+ if (receiverName === "cel" && method === "bind") {
195
+ handleCelBind(args);
196
+ return;
197
+ }
198
+ if (!receiverName || !registry.get(receiverName)) {
199
+ handleNonContractRCall(node, method, args);
200
+ return;
201
+ }
202
+ const collectedArgs = [];
203
+ let hasUnresolvableArg = false;
204
+ for (const argNode of args) {
205
+ const { arg, traversed } = classifyArg(argNode);
206
+ if (arg) collectedArgs.push(arg);
207
+ else {
208
+ hasUnresolvableArg = true;
209
+ if (!traversed) traverse(argNode);
210
+ }
211
+ }
212
+ if (hasUnresolvableArg) {
213
+ deferredNodes.add(node);
214
+ debug("deferred %s.%s (unresolvable args)", receiverName, method);
215
+ return;
216
+ }
217
+ const call = {
218
+ id: generateCallId(receiverName, method, collectedArgs),
219
+ contract: receiverName,
220
+ method,
221
+ args: collectedArgs,
222
+ astNode: node
223
+ };
224
+ calls.push(call);
225
+ debug("found %s.%s (id=%s)", receiverName, method, call.id);
226
+ return call;
227
+ };
228
+ traverse(ast);
229
+ debug("collected %d total calls", calls.length);
230
+ return calls;
231
+ };
232
+ //#endregion
233
+ exports.collectCalls = collectCalls;
@@ -0,0 +1,232 @@
1
+ import { createLogger } from "../debug.mjs";
2
+ import { COMPREHENSION_MACROS, SCALAR_WRAPPER_FUNCTIONS } from "@seljs/checker";
3
+ import { isAstNode } from "@seljs/common";
4
+ //#region src/analysis/call-collector.ts
5
+ const debug = createLogger("analysis:collect");
6
+ /**
7
+ * Generates a deterministic call identifier from contract name, method, and arguments.
8
+ *
9
+ * Format: "contract:method:arg1,arg2,..."
10
+ * Used for deduplication and dependency tracking between calls.
11
+ */
12
+ const generateCallId = (contract, method, args) => {
13
+ return `${contract}:${method}:${args.map((arg) => {
14
+ if (arg.variableName !== void 0) return arg.variableName;
15
+ if (arg.value !== void 0) {
16
+ const v = arg.value;
17
+ return typeof v === "string" || typeof v === "number" || typeof v === "boolean" || typeof v === "bigint" ? String(v) : "?";
18
+ }
19
+ if (arg.dependsOnCallId !== void 0) return arg.dependsOnCallId;
20
+ return "?";
21
+ }).join(",")}`;
22
+ };
23
+ /**
24
+ * Attempts to extract a scalar argument from a Solidity cast/wrapper call
25
+ * (e.g. `uint256(42)`, `solAddress("0x...")`, `int256(tokenId)`).
26
+ *
27
+ * Handles both literal values and variable references.
28
+ * Returns undefined if the node is not a recognized scalar wrapper.
29
+ */
30
+ const collectScalarArgument = (argNode) => {
31
+ if (!isAstNode(argNode) || argNode.op !== "call") return;
32
+ if (!Array.isArray(argNode.args) || argNode.args.length !== 2) return;
33
+ const fnName = argNode.args[0];
34
+ const fnArgs = argNode.args[1];
35
+ if (typeof fnName !== "string" || !Array.isArray(fnArgs) || fnArgs.length !== 1 || !SCALAR_WRAPPER_FUNCTIONS.has(fnName)) return;
36
+ const inner = fnArgs[0];
37
+ if (!isAstNode(inner)) return;
38
+ if (inner.op === "value") return {
39
+ type: "literal",
40
+ value: inner.args
41
+ };
42
+ if (inner.op === "id" && typeof inner.args === "string") return {
43
+ type: "variable",
44
+ variableName: inner.args
45
+ };
46
+ };
47
+ /**
48
+ * Extracts the method name, receiver identifier, and argument nodes from an rcall AST node.
49
+ *
50
+ * An rcall node has the structure: `["methodName", receiverNode, [argNodes...]]`
51
+ */
52
+ const getRCallArgs = (node) => {
53
+ if (!Array.isArray(node.args) || node.args.length < 3) return;
54
+ const methodNode = node.args[0];
55
+ const receiverNode = node.args[1];
56
+ const argNodes = node.args[2];
57
+ if (typeof methodNode !== "string" || !Array.isArray(argNodes)) return;
58
+ let receiverName;
59
+ if (isAstNode(receiverNode) && receiverNode.op === "id" && typeof receiverNode.args === "string") receiverName = receiverNode.args;
60
+ return {
61
+ method: methodNode,
62
+ receiverName,
63
+ args: argNodes
64
+ };
65
+ };
66
+ /**
67
+ * Collects contract calls from a CEL AST by traversing the tree.
68
+ *
69
+ * Walks the AST looking for `rcall` nodes whose receiver matches a registered
70
+ * contract. For each matched call, classifies arguments as literals, variable
71
+ * references, or results of other contract calls (creating dependency edges).
72
+ * Skips `cel.bind()` calls while still collecting any nested contract calls.
73
+ *
74
+ * @param ast The root AST node from a parsed CEL expression
75
+ * @param registry Contract lookup to validate receiver names against
76
+ * @returns Array of collected calls with dependency information
77
+ */
78
+ const collectCalls = (ast, registry) => {
79
+ const calls = [];
80
+ /** Variable names that are scoped (comprehension iteration vars, cel.bind vars) — NOT user context */
81
+ const scopedVars = /* @__PURE__ */ new Set();
82
+ /** rcall AST nodes skipped because they have unresolvable args */
83
+ const deferredNodes = /* @__PURE__ */ new WeakSet();
84
+ const traverse = (node) => {
85
+ if (Array.isArray(node)) {
86
+ for (const item of node) traverse(item);
87
+ return;
88
+ }
89
+ if (!isAstNode(node)) return;
90
+ if (node.op === "rcall") {
91
+ collectRCall(node);
92
+ return;
93
+ }
94
+ traverse(node.args);
95
+ };
96
+ /**
97
+ * Handles cel.bind() — registers the bind variable name for the body scope,
98
+ * traverses the initializer and body, then removes the scoped var.
99
+ */
100
+ const handleCelBind = (args) => {
101
+ if (args.length >= 3) {
102
+ const nameNode = args[0];
103
+ if (isAstNode(nameNode) && nameNode.op === "id" && typeof nameNode.args === "string") {
104
+ traverse(args[1]);
105
+ scopedVars.add(nameNode.args);
106
+ traverse(args[2]);
107
+ scopedVars.delete(nameNode.args);
108
+ return;
109
+ }
110
+ }
111
+ for (const argNode of args) traverse(argNode);
112
+ };
113
+ /**
114
+ * Handles rcall nodes whose receiver is NOT a registered contract.
115
+ * Traverses the receiver node and args, registering comprehension
116
+ * iteration variables for their body scope.
117
+ */
118
+ const handleNonContractRCall = (node, method, args) => {
119
+ if (Array.isArray(node.args) && node.args.length >= 2) traverse(node.args[1]);
120
+ if (COMPREHENSION_MACROS.has(method) && args.length >= 2) {
121
+ const iterVarNode = args[0];
122
+ if (isAstNode(iterVarNode) && iterVarNode.op === "id" && typeof iterVarNode.args === "string") {
123
+ scopedVars.add(iterVarNode.args);
124
+ for (let i = 1; i < args.length; i++) traverse(args[i]);
125
+ scopedVars.delete(iterVarNode.args);
126
+ return;
127
+ }
128
+ }
129
+ for (const argNode of args) traverse(argNode);
130
+ };
131
+ /**
132
+ * Classifies a single argument node, returning the resolved CallArgument
133
+ * or `undefined` if unresolvable. When `undefined` is returned, the caller
134
+ * should mark the parent call as deferred.
135
+ */
136
+ const classifyArg = (argNode) => {
137
+ const scalarArg = collectScalarArgument(argNode);
138
+ if (scalarArg) return {
139
+ arg: scalarArg,
140
+ traversed: false
141
+ };
142
+ if (isAstNode(argNode) && argNode.op === "value") return {
143
+ arg: {
144
+ type: "literal",
145
+ value: argNode.args
146
+ },
147
+ traversed: false
148
+ };
149
+ if (isAstNode(argNode) && argNode.op === "id" && typeof argNode.args === "string") {
150
+ if (scopedVars.has(argNode.args)) return {
151
+ arg: void 0,
152
+ traversed: false
153
+ };
154
+ return {
155
+ arg: {
156
+ type: "variable",
157
+ variableName: argNode.args
158
+ },
159
+ traversed: false
160
+ };
161
+ }
162
+ if (isAstNode(argNode) && argNode.op === "rcall") {
163
+ const innerCall = collectRCall(argNode);
164
+ if (innerCall) return {
165
+ arg: {
166
+ type: "call_result",
167
+ dependsOnCallId: innerCall.id
168
+ },
169
+ traversed: true
170
+ };
171
+ if (deferredNodes.has(argNode)) return {
172
+ arg: void 0,
173
+ traversed: true
174
+ };
175
+ }
176
+ return {
177
+ arg: void 0,
178
+ traversed: false
179
+ };
180
+ };
181
+ /**
182
+ * Processes an rcall AST node, collecting it as a contract call if the
183
+ * receiver is a registered contract. Recursively processes nested rcalls
184
+ * in arguments to build dependency chains.
185
+ */
186
+ const collectRCall = (node) => {
187
+ const callArgs = getRCallArgs(node);
188
+ if (!callArgs) {
189
+ traverse(node.args);
190
+ return;
191
+ }
192
+ const { method, receiverName, args } = callArgs;
193
+ if (receiverName === "cel" && method === "bind") {
194
+ handleCelBind(args);
195
+ return;
196
+ }
197
+ if (!receiverName || !registry.get(receiverName)) {
198
+ handleNonContractRCall(node, method, args);
199
+ return;
200
+ }
201
+ const collectedArgs = [];
202
+ let hasUnresolvableArg = false;
203
+ for (const argNode of args) {
204
+ const { arg, traversed } = classifyArg(argNode);
205
+ if (arg) collectedArgs.push(arg);
206
+ else {
207
+ hasUnresolvableArg = true;
208
+ if (!traversed) traverse(argNode);
209
+ }
210
+ }
211
+ if (hasUnresolvableArg) {
212
+ deferredNodes.add(node);
213
+ debug("deferred %s.%s (unresolvable args)", receiverName, method);
214
+ return;
215
+ }
216
+ const call = {
217
+ id: generateCallId(receiverName, method, collectedArgs),
218
+ contract: receiverName,
219
+ method,
220
+ args: collectedArgs,
221
+ astNode: node
222
+ };
223
+ calls.push(call);
224
+ debug("found %s.%s (id=%s)", receiverName, method, call.id);
225
+ return call;
226
+ };
227
+ traverse(ast);
228
+ debug("collected %d total calls", calls.length);
229
+ return calls;
230
+ };
231
+ //#endregion
232
+ export { collectCalls };
@@ -0,0 +1,68 @@
1
+ const require_debug = require("../debug.cjs");
2
+ const require_errors = require("../errors/errors.cjs");
3
+ require("../errors/index.cjs");
4
+ //#region src/analysis/dependency-analyzer.ts
5
+ const debug = require_debug.createLogger("analysis:dependency");
6
+ /**
7
+ * Detects circular dependencies in the graph using DFS with a recursion stack.
8
+ *
9
+ * @throws {@link CircularDependencyError} If a cycle is found, including the call IDs forming the cycle
10
+ */
11
+ const detectCycles = (nodes, edges) => {
12
+ const visited = /* @__PURE__ */ new Set();
13
+ const inStack = /* @__PURE__ */ new Set();
14
+ const dfs = (nodeId, path) => {
15
+ if (inStack.has(nodeId)) {
16
+ const cycleStart = path.indexOf(nodeId);
17
+ const cycle = path.slice(cycleStart);
18
+ throw new require_errors.CircularDependencyError(`Circular dependency detected: ${cycle.join(" -> ")} -> ${nodeId}`, { callIds: cycle });
19
+ }
20
+ if (visited.has(nodeId)) return;
21
+ visited.add(nodeId);
22
+ inStack.add(nodeId);
23
+ path.push(nodeId);
24
+ for (const depId of edges.get(nodeId) ?? []) dfs(depId, path);
25
+ path.pop();
26
+ inStack.delete(nodeId);
27
+ };
28
+ for (const nodeId of nodes.keys()) if (!visited.has(nodeId)) dfs(nodeId, []);
29
+ };
30
+ /**
31
+ * Builds a dependency graph from collected contract calls.
32
+ *
33
+ * Creates a directed graph where nodes represent contract calls and edges
34
+ * represent data dependencies (when one call's argument is the result of
35
+ * another call). Validates that no circular dependencies exist.
36
+ *
37
+ * @param calls - Array of collected contract calls from AST traversal
38
+ * @returns A dependency graph with nodes and directed edges
39
+ * @throws {@link CircularDependencyError} If a circular dependency is detected between calls
40
+ */
41
+ const analyzeDependencies = (calls) => {
42
+ const nodes = /* @__PURE__ */ new Map();
43
+ const edges = /* @__PURE__ */ new Map();
44
+ for (const call of calls) {
45
+ nodes.set(call.id, {
46
+ call,
47
+ dependsOn: [],
48
+ dependedOnBy: []
49
+ });
50
+ edges.set(call.id, /* @__PURE__ */ new Set());
51
+ }
52
+ for (const call of calls) for (const arg of call.args) if (arg.type === "call_result" && arg.dependsOnCallId) {
53
+ const depId = arg.dependsOnCallId;
54
+ edges.get(call.id)?.add(depId);
55
+ const callNode = nodes.get(call.id);
56
+ if (callNode && !callNode.dependsOn.includes(depId)) callNode.dependsOn.push(depId);
57
+ const depNode = nodes.get(depId);
58
+ if (depNode && !depNode.dependedOnBy.includes(call.id)) depNode.dependedOnBy.push(call.id);
59
+ }
60
+ detectCycles(nodes, edges);
61
+ debug("graph: %d nodes, %d edges", nodes.size, [...edges.values()].reduce((sum, deps) => sum + deps.size, 0));
62
+ return {
63
+ nodes,
64
+ edges
65
+ };
66
+ };
67
+ //#endregion
68
+ exports.analyzeDependencies = analyzeDependencies;
@@ -0,0 +1,68 @@
1
+ import { createLogger } from "../debug.mjs";
2
+ import { CircularDependencyError } from "../errors/errors.mjs";
3
+ import "../errors/index.mjs";
4
+ //#region src/analysis/dependency-analyzer.ts
5
+ const debug = createLogger("analysis:dependency");
6
+ /**
7
+ * Detects circular dependencies in the graph using DFS with a recursion stack.
8
+ *
9
+ * @throws {@link CircularDependencyError} If a cycle is found, including the call IDs forming the cycle
10
+ */
11
+ const detectCycles = (nodes, edges) => {
12
+ const visited = /* @__PURE__ */ new Set();
13
+ const inStack = /* @__PURE__ */ new Set();
14
+ const dfs = (nodeId, path) => {
15
+ if (inStack.has(nodeId)) {
16
+ const cycleStart = path.indexOf(nodeId);
17
+ const cycle = path.slice(cycleStart);
18
+ throw new CircularDependencyError(`Circular dependency detected: ${cycle.join(" -> ")} -> ${nodeId}`, { callIds: cycle });
19
+ }
20
+ if (visited.has(nodeId)) return;
21
+ visited.add(nodeId);
22
+ inStack.add(nodeId);
23
+ path.push(nodeId);
24
+ for (const depId of edges.get(nodeId) ?? []) dfs(depId, path);
25
+ path.pop();
26
+ inStack.delete(nodeId);
27
+ };
28
+ for (const nodeId of nodes.keys()) if (!visited.has(nodeId)) dfs(nodeId, []);
29
+ };
30
+ /**
31
+ * Builds a dependency graph from collected contract calls.
32
+ *
33
+ * Creates a directed graph where nodes represent contract calls and edges
34
+ * represent data dependencies (when one call's argument is the result of
35
+ * another call). Validates that no circular dependencies exist.
36
+ *
37
+ * @param calls - Array of collected contract calls from AST traversal
38
+ * @returns A dependency graph with nodes and directed edges
39
+ * @throws {@link CircularDependencyError} If a circular dependency is detected between calls
40
+ */
41
+ const analyzeDependencies = (calls) => {
42
+ const nodes = /* @__PURE__ */ new Map();
43
+ const edges = /* @__PURE__ */ new Map();
44
+ for (const call of calls) {
45
+ nodes.set(call.id, {
46
+ call,
47
+ dependsOn: [],
48
+ dependedOnBy: []
49
+ });
50
+ edges.set(call.id, /* @__PURE__ */ new Set());
51
+ }
52
+ for (const call of calls) for (const arg of call.args) if (arg.type === "call_result" && arg.dependsOnCallId) {
53
+ const depId = arg.dependsOnCallId;
54
+ edges.get(call.id)?.add(depId);
55
+ const callNode = nodes.get(call.id);
56
+ if (callNode && !callNode.dependsOn.includes(depId)) callNode.dependsOn.push(depId);
57
+ const depNode = nodes.get(depId);
58
+ if (depNode && !depNode.dependedOnBy.includes(call.id)) depNode.dependedOnBy.push(call.id);
59
+ }
60
+ detectCycles(nodes, edges);
61
+ debug("graph: %d nodes, %d edges", nodes.size, [...edges.values()].reduce((sum, deps) => sum + deps.size, 0));
62
+ return {
63
+ nodes,
64
+ edges
65
+ };
66
+ };
67
+ //#endregion
68
+ export { analyzeDependencies };
@@ -0,0 +1,65 @@
1
+ const require_debug = require("../debug.cjs");
2
+ const require_errors = require("../errors/errors.cjs");
3
+ require("../errors/index.cjs");
4
+ //#region src/analysis/round-planner.ts
5
+ const debug = require_debug.createLogger("analysis:plan");
6
+ /**
7
+ * Plans execution rounds from a dependency graph using topological sorting.
8
+ *
9
+ * Groups independent calls into parallel rounds, ensuring all dependencies
10
+ * of a call are executed in previous rounds. Enforces limits on the number
11
+ * of rounds and total calls to prevent excessive execution.
12
+ *
13
+ * @param graph Dependency graph representing calls and their relationships.
14
+ * @param limits Optional limits for maximum rounds and total calls.
15
+ * @returns Execution plan containing planned rounds, total calls, and maximum depth.
16
+ * @throws ExecutionLimitError If total calls exceed maxCalls or rounds exceed maxRounds.
17
+ */
18
+ const planRounds = (graph, limits = {}) => {
19
+ const maxRounds = limits.maxRounds ?? 10;
20
+ const maxCalls = limits.maxCalls ?? 100;
21
+ const totalCalls = graph.nodes.size;
22
+ if (totalCalls > maxCalls) throw new require_errors.ExecutionLimitError(`Execution limit exceeded: ${String(totalCalls)} calls exceeds maxCalls (${String(maxCalls)})`, {
23
+ limitType: "maxCalls",
24
+ limit: maxCalls,
25
+ actual: totalCalls
26
+ });
27
+ if (totalCalls === 0) return {
28
+ rounds: [],
29
+ totalCalls: 0,
30
+ maxDepth: 0
31
+ };
32
+ const inDegree = /* @__PURE__ */ new Map();
33
+ for (const nodeId of graph.nodes.keys()) inDegree.set(nodeId, graph.edges.get(nodeId)?.size ?? 0);
34
+ const rounds = [];
35
+ const processed = /* @__PURE__ */ new Set();
36
+ while (processed.size < totalCalls) {
37
+ const readyNodes = [];
38
+ for (const [nodeId, degree] of inDegree) if (degree === 0 && !processed.has(nodeId)) readyNodes.push(nodeId);
39
+ if (readyNodes.length === 0) break;
40
+ if (rounds.length >= maxRounds) throw new require_errors.ExecutionLimitError(`Execution limit exceeded: requires more than maxRounds (${String(maxRounds)}) rounds`, {
41
+ limitType: "maxRounds",
42
+ limit: maxRounds,
43
+ actual: rounds.length + 1
44
+ });
45
+ const round = {
46
+ roundNumber: rounds.length,
47
+ calls: readyNodes.map((id) => graph.nodes.get(id)?.call).filter((c) => c !== void 0)
48
+ };
49
+ rounds.push(round);
50
+ for (const nodeId of readyNodes) {
51
+ processed.add(nodeId);
52
+ const node = graph.nodes.get(nodeId);
53
+ if (!node) continue;
54
+ for (const dependentId of node.dependedOnBy) inDegree.set(dependentId, (inDegree.get(dependentId) ?? 0) - 1);
55
+ }
56
+ }
57
+ debug("planned %d rounds for %d calls (max depth=%d)", rounds.length, totalCalls, rounds.length);
58
+ return {
59
+ rounds,
60
+ totalCalls,
61
+ maxDepth: rounds.length
62
+ };
63
+ };
64
+ //#endregion
65
+ exports.planRounds = planRounds;