@proto-kit/module 0.1.1-develop.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.
Files changed (72) hide show
  1. package/LICENSE.md +201 -0
  2. package/README.md +114 -0
  3. package/dist/factories/MethodIdFactory.d.ts +10 -0
  4. package/dist/factories/MethodIdFactory.d.ts.map +1 -0
  5. package/dist/factories/MethodIdFactory.js +11 -0
  6. package/dist/factories/MethodIdFactory.js.map +1 -0
  7. package/dist/index.d.ts +11 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +11 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/method/MethodParameterEncoder.d.ts +26 -0
  12. package/dist/method/MethodParameterEncoder.d.ts.map +1 -0
  13. package/dist/method/MethodParameterEncoder.js +169 -0
  14. package/dist/method/MethodParameterEncoder.js.map +1 -0
  15. package/dist/method/runtimeMethod.d.ts +33 -0
  16. package/dist/method/runtimeMethod.d.ts.map +1 -0
  17. package/dist/method/runtimeMethod.js +169 -0
  18. package/dist/method/runtimeMethod.js.map +1 -0
  19. package/dist/module/decorator.d.ts +8 -0
  20. package/dist/module/decorator.d.ts.map +1 -0
  21. package/dist/module/decorator.js +16 -0
  22. package/dist/module/decorator.js.map +1 -0
  23. package/dist/runtime/MethodIdResolver.d.ts +20 -0
  24. package/dist/runtime/MethodIdResolver.d.ts.map +1 -0
  25. package/dist/runtime/MethodIdResolver.js +91 -0
  26. package/dist/runtime/MethodIdResolver.js.map +1 -0
  27. package/dist/runtime/Runtime.d.ts +72 -0
  28. package/dist/runtime/Runtime.d.ts.map +1 -0
  29. package/dist/runtime/Runtime.js +231 -0
  30. package/dist/runtime/Runtime.js.map +1 -0
  31. package/dist/runtime/RuntimeEnvironment.d.ts +10 -0
  32. package/dist/runtime/RuntimeEnvironment.d.ts.map +1 -0
  33. package/dist/runtime/RuntimeEnvironment.js +2 -0
  34. package/dist/runtime/RuntimeEnvironment.js.map +1 -0
  35. package/dist/runtime/RuntimeModule.d.ts +37 -0
  36. package/dist/runtime/RuntimeModule.d.ts.map +1 -0
  37. package/dist/runtime/RuntimeModule.js +80 -0
  38. package/dist/runtime/RuntimeModule.js.map +1 -0
  39. package/dist/state/InMemoryStateService.d.ts +15 -0
  40. package/dist/state/InMemoryStateService.d.ts.map +1 -0
  41. package/dist/state/InMemoryStateService.js +24 -0
  42. package/dist/state/InMemoryStateService.js.map +1 -0
  43. package/dist/state/decorator.d.ts +7 -0
  44. package/dist/state/decorator.d.ts.map +1 -0
  45. package/dist/state/decorator.js +40 -0
  46. package/dist/state/decorator.js.map +1 -0
  47. package/jest.config.cjs +12 -0
  48. package/package.json +35 -0
  49. package/src/factories/MethodIdFactory.ts +13 -0
  50. package/src/index.ts +10 -0
  51. package/src/method/MethodParameterEncoder.ts +260 -0
  52. package/src/method/runtimeMethod.ts +308 -0
  53. package/src/module/decorator.ts +21 -0
  54. package/src/runtime/MethodIdResolver.ts +108 -0
  55. package/src/runtime/Runtime.ts +395 -0
  56. package/src/runtime/RuntimeEnvironment.ts +16 -0
  57. package/src/runtime/RuntimeModule.ts +112 -0
  58. package/src/state/InMemoryStateService.ts +25 -0
  59. package/src/state/decorator.ts +61 -0
  60. package/test/Runtime.test.ts +70 -0
  61. package/test/TestingRuntime.ts +45 -0
  62. package/test/method/MethodParameterEncoder.test.ts +121 -0
  63. package/test/method/runtimeMethod-fail.test.ts +50 -0
  64. package/test/method/runtimeMethod.test.ts +46 -0
  65. package/test/modules/Admin.ts +19 -0
  66. package/test/modules/Balances.test.ts +337 -0
  67. package/test/modules/Balances.ts +54 -0
  68. package/test/modules/MethodIdResolver.test.ts +73 -0
  69. package/test/modules/State.test.ts +81 -0
  70. package/test/runtimeMethod.test.ts +215 -0
  71. package/test/tsconfig.json +7 -0
  72. package/tsconfig.json +8 -0
@@ -0,0 +1,308 @@
1
+ import { Bool, Field, FlexibleProvablePure, Poseidon } from "o1js";
2
+ import { container } from "tsyringe";
3
+ import {
4
+ StateTransition,
5
+ ProvableStateTransition,
6
+ MethodPublicOutput,
7
+ RuntimeMethodExecutionContext,
8
+ StateTransitionReductionList,
9
+ DefaultProvableHashList,
10
+ } from "@proto-kit/protocol";
11
+ import {
12
+ DecoratedMethod,
13
+ toProver,
14
+ ZkProgrammable,
15
+ ArgumentTypes,
16
+ } from "@proto-kit/common";
17
+
18
+ import type { RuntimeModule } from "../runtime/RuntimeModule.js";
19
+
20
+ import {
21
+ MethodParameterEncoder,
22
+ checkArgsProvable,
23
+ } from "./MethodParameterEncoder";
24
+
25
+ const errors = {
26
+ runtimeNotProvided: (name: string) =>
27
+ new Error(`Runtime was not provided for module: ${name}`),
28
+
29
+ methodInputsNotProvided: () =>
30
+ new Error(
31
+ "Method execution inputs not provided, provide them via context.inputs"
32
+ ),
33
+
34
+ runtimeNameNotSet: () => new Error("Runtime name was not set"),
35
+
36
+ fieldNotConstant: (name: string) =>
37
+ new Error(
38
+ `In-circuit field ${name} not a constant, this is likely a framework bug`
39
+ ),
40
+ };
41
+
42
+ export function toStateTransitionsHash(
43
+ stateTransitions: StateTransition<any>[]
44
+ ) {
45
+ const stateTransitionsHashList = new StateTransitionReductionList(
46
+ ProvableStateTransition
47
+ );
48
+
49
+ return stateTransitions
50
+ .map((stateTransition) => stateTransition.toProvable())
51
+ .reduce(
52
+ (allStateTransitionsHashList, stateTransition) =>
53
+ allStateTransitionsHashList.push(stateTransition),
54
+ stateTransitionsHashList
55
+ )
56
+ .toField();
57
+ }
58
+
59
+ export function toEventsHash(
60
+ events: {
61
+ eventType: FlexibleProvablePure<any>;
62
+ event: any;
63
+ eventName: string;
64
+ condition: Bool;
65
+ }[]
66
+ ) {
67
+ return events.reduce((acc, event) => {
68
+ const hashList = new DefaultProvableHashList(event.eventType, acc);
69
+ hashList.pushIf(event.event, event.condition);
70
+ return hashList.commitment;
71
+ }, Field(0));
72
+ }
73
+
74
+ export type WrappedMethod = (...args: ArgumentTypes) => MethodPublicOutput;
75
+ export type AsyncWrappedMethod = (
76
+ ...args: ArgumentTypes
77
+ ) => Promise<MethodPublicOutput>;
78
+
79
+ export function toWrappedMethod(
80
+ this: RuntimeModule<unknown>,
81
+ methodName: string,
82
+ moduleMethod: (...args: ArgumentTypes) => Promise<any>,
83
+ options: {
84
+ invocationType: RuntimeMethodInvocationType;
85
+ }
86
+ ): AsyncWrappedMethod {
87
+ const executionContext = container.resolve<RuntimeMethodExecutionContext>(
88
+ RuntimeMethodExecutionContext
89
+ );
90
+
91
+ const wrappedMethod: AsyncWrappedMethod = async (
92
+ ...args
93
+ ): Promise<MethodPublicOutput> => {
94
+ await Reflect.apply(moduleMethod, this, args);
95
+ const {
96
+ result: { stateTransitions, status, events },
97
+ } = executionContext.current();
98
+
99
+ const stateTransitionsHash = toStateTransitionsHash(stateTransitions);
100
+ const eventsHash = toEventsHash(events);
101
+
102
+ const { name, runtime } = this;
103
+
104
+ if (name === undefined) {
105
+ throw errors.runtimeNameNotSet();
106
+ }
107
+ if (runtime === undefined) {
108
+ throw errors.runtimeNotProvided(name);
109
+ }
110
+
111
+ const { transaction, networkState } = executionContext.witnessInput();
112
+ const { methodIdResolver } = runtime;
113
+
114
+ // Assert that the given transaction has the correct methodId
115
+ const thisMethodId = Field(methodIdResolver.getMethodId(name, methodName));
116
+ if (!thisMethodId.isConstant()) {
117
+ throw errors.fieldNotConstant("methodId");
118
+ }
119
+
120
+ transaction.methodId.assertEquals(
121
+ thisMethodId,
122
+ "Runtimemethod called with wrong methodId on the transaction object"
123
+ );
124
+
125
+ /**
126
+ * Use the type info obtained previously to convert
127
+ * the args passed to fields
128
+ */
129
+ const { fields } = MethodParameterEncoder.fromMethod(
130
+ this,
131
+ methodName
132
+ ).encode(args);
133
+
134
+ // Assert that the argsHash that has been signed matches the given arguments
135
+ const argsHash = Poseidon.hash(fields);
136
+
137
+ transaction.argsHash.assertEquals(
138
+ argsHash,
139
+ "argsHash and therefore arguments of transaction and runtime call does not match"
140
+ );
141
+
142
+ const isMessage = Bool(options.invocationType === "INCOMING_MESSAGE");
143
+ transaction.assertTransactionType(Bool(isMessage));
144
+
145
+ const transactionHash = transaction.hash();
146
+ const networkStateHash = networkState.hash();
147
+
148
+ return new MethodPublicOutput({
149
+ stateTransitionsHash,
150
+ status,
151
+ transactionHash,
152
+ networkStateHash,
153
+ isMessage,
154
+ eventsHash,
155
+ });
156
+ };
157
+
158
+ Object.defineProperty(wrappedMethod, "name", {
159
+ value: `wrapped_${methodName}`,
160
+ writable: false,
161
+ });
162
+
163
+ return wrappedMethod;
164
+ }
165
+
166
+ export function combineMethodName(
167
+ runtimeModuleName: string,
168
+ methodName: string
169
+ ) {
170
+ return `${runtimeModuleName}.${methodName}`;
171
+ }
172
+
173
+ export const runtimeMethodMetadataKey = "yab-method";
174
+ export const runtimeMethodNamesMetadataKey = "proto-kit-runtime-methods";
175
+ export const runtimeMethodTypeMetadataKey = "proto-kit-runtime-method-type";
176
+
177
+ /**
178
+ * Checks the metadata of the provided runtime module and its method,
179
+ * to see if it has been decorated with @runtimeMethod()
180
+ *
181
+ * @param target - Runtime module to check
182
+ * @param propertyKey - Name of the method to check in the prior runtime module
183
+ * @returns - If the provided method name is a runtime method or not
184
+ */
185
+ export function isRuntimeMethod(
186
+ target: RuntimeModule<unknown>,
187
+ propertyKey: string
188
+ ) {
189
+ return Boolean(
190
+ Reflect.getMetadata(runtimeMethodMetadataKey, target, propertyKey)
191
+ );
192
+ }
193
+
194
+ export type RuntimeMethodInvocationType = "SIGNATURE" | "INCOMING_MESSAGE";
195
+
196
+ function runtimeMethodInternal(options: {
197
+ invocationType: RuntimeMethodInvocationType;
198
+ }) {
199
+ return (
200
+ target: RuntimeModule<unknown>,
201
+ methodName: string,
202
+ descriptor: TypedPropertyDescriptor<(...args: any[]) => Promise<any>>
203
+ ) => {
204
+ checkArgsProvable(target, methodName);
205
+ const executionContext = container.resolve<RuntimeMethodExecutionContext>(
206
+ RuntimeMethodExecutionContext
207
+ );
208
+
209
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
210
+ let data: string[] | undefined = Reflect.getMetadata(
211
+ runtimeMethodNamesMetadataKey,
212
+ target
213
+ );
214
+ if (data !== undefined) {
215
+ data.push(methodName);
216
+ } else {
217
+ data = [methodName];
218
+ }
219
+ Reflect.defineMetadata(runtimeMethodNamesMetadataKey, data, target);
220
+
221
+ Reflect.defineMetadata(runtimeMethodMetadataKey, true, target, methodName);
222
+
223
+ Reflect.defineMetadata(
224
+ runtimeMethodTypeMetadataKey,
225
+ options.invocationType,
226
+ target,
227
+ methodName
228
+ );
229
+
230
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
231
+ const simulatedMethod = descriptor.value as DecoratedMethod;
232
+
233
+ descriptor.value = async function value(
234
+ this: RuntimeModule<unknown>,
235
+ ...args: ArgumentTypes
236
+ ) {
237
+ const constructorName = this.name!;
238
+
239
+ /**
240
+ * If its a top level method call, wrap it into a wrapped method,
241
+ * since it'll be turned into a real/mock prover in provableMethod().
242
+ *
243
+ * Otherwise provableMethod() will just call the originalMethod provided
244
+ * if method is not called at the top level.
245
+ */
246
+ const simulatedWrappedMethod = Reflect.apply(toWrappedMethod, this, [
247
+ methodName,
248
+ simulatedMethod,
249
+ options,
250
+ ]);
251
+
252
+ /**
253
+ * Before the prover runs, make sure it is operating on the correct
254
+ * RuntimeMethodExecutionContext state, meaning it enters and exits
255
+ * the context properly.
256
+ */
257
+
258
+ async function prover(this: ZkProgrammable<any, any>) {
259
+ executionContext.beforeMethod(constructorName, methodName, args);
260
+ const innerProver = toProver(
261
+ combineMethodName(constructorName, methodName),
262
+ simulatedWrappedMethod,
263
+ false,
264
+ ...args
265
+ ).bind(this);
266
+ let result: Awaited<ReturnType<typeof innerProver>>;
267
+ try {
268
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
269
+ result = await Reflect.apply(innerProver, this, args);
270
+ } finally {
271
+ executionContext.afterMethod();
272
+ }
273
+
274
+ return result;
275
+ }
276
+
277
+ executionContext.beforeMethod(constructorName, methodName, args);
278
+
279
+ if (executionContext.isTopLevel) {
280
+ if (!this.runtime) {
281
+ throw errors.runtimeNotProvided(constructorName);
282
+ }
283
+ executionContext.setProver(prover.bind(this.runtime.zkProgrammable));
284
+ }
285
+
286
+ let result: unknown;
287
+ try {
288
+ result = await Reflect.apply(simulatedMethod, this, args);
289
+ } finally {
290
+ executionContext.afterMethod();
291
+ }
292
+
293
+ return result;
294
+ };
295
+ };
296
+ }
297
+
298
+ export function runtimeMessage() {
299
+ return runtimeMethodInternal({
300
+ invocationType: "INCOMING_MESSAGE",
301
+ });
302
+ }
303
+
304
+ export function runtimeMethod() {
305
+ return runtimeMethodInternal({
306
+ invocationType: "SIGNATURE",
307
+ });
308
+ }
@@ -0,0 +1,21 @@
1
+ import { injectable } from "tsyringe";
2
+ import { StaticConfigurableModule, TypedClass } from "@proto-kit/common";
3
+
4
+ import { RuntimeModule } from "../runtime/RuntimeModule.js";
5
+
6
+ /**
7
+ * Marks the decorated class as a runtime module, while also
8
+ * making it injectable with our dependency injection solution.
9
+ */
10
+ export function runtimeModule() {
11
+ return (
12
+ /**
13
+ * Check if the target class extends RuntimeModule, while
14
+ * also providing static config presets
15
+ */
16
+ target: StaticConfigurableModule<unknown> &
17
+ TypedClass<RuntimeModule<unknown>>
18
+ ) => {
19
+ injectable()(target);
20
+ };
21
+ }
@@ -0,0 +1,108 @@
1
+ import { filterNonUndefined } from "@proto-kit/common";
2
+ import { stringToField, RuntimeMethodIdMapping } from "@proto-kit/protocol";
3
+ import { Poseidon } from "o1js";
4
+ import { inject, injectable } from "tsyringe";
5
+
6
+ import {
7
+ RuntimeMethodInvocationType,
8
+ runtimeMethodTypeMetadataKey,
9
+ } from "../method/runtimeMethod";
10
+
11
+ import type { Runtime, RuntimeModulesRecord } from "./Runtime";
12
+
13
+ /**
14
+ * Please see `getMethodId` to learn more about
15
+ * methodId encoding
16
+ */
17
+ @injectable()
18
+ export class MethodIdResolver {
19
+ private readonly dictionary: {
20
+ [key: string]: { moduleName: string; methodName: string };
21
+ } = {};
22
+
23
+ public constructor(
24
+ @inject("Runtime") private readonly runtime: Runtime<RuntimeModulesRecord>
25
+ ) {
26
+ this.dictionary = runtime.runtimeModuleNames.reduce<
27
+ Record<string, { moduleName: string; methodName: string }>
28
+ >((dict, moduleName) => {
29
+ this.runtime.assertIsValidModuleName(moduleName);
30
+
31
+ runtime.resolve(moduleName).runtimeMethodNames.forEach((methodName) => {
32
+ dict[this.getMethodId(moduleName, methodName).toString()] = {
33
+ moduleName,
34
+ methodName,
35
+ };
36
+ });
37
+
38
+ return dict;
39
+ }, {});
40
+ }
41
+
42
+ /**
43
+ * The purpose of this method is to provide a dictionary where
44
+ * we can look up properties like methodId and invocationType
45
+ * for each runtimeMethod using their module name and method name
46
+ */
47
+ public methodIdMap(): RuntimeMethodIdMapping {
48
+ const methodIdResolver =
49
+ this.runtime.dependencyContainer.resolve<MethodIdResolver>(
50
+ "MethodIdResolver"
51
+ );
52
+
53
+ const rawMappings = this.runtime.moduleNames.flatMap((moduleName) => {
54
+ const module = this.runtime.resolve(moduleName);
55
+ return module.runtimeMethodNames.map((method) => {
56
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
57
+ const type = Reflect.getMetadata(
58
+ runtimeMethodTypeMetadataKey,
59
+ module,
60
+ method
61
+ ) as RuntimeMethodInvocationType | undefined;
62
+
63
+ if (type !== undefined) {
64
+ return {
65
+ name: `${moduleName}.${method}`,
66
+ methodId: methodIdResolver.getMethodId(moduleName, method),
67
+ type,
68
+ } as const;
69
+ }
70
+
71
+ return undefined;
72
+ });
73
+ });
74
+
75
+ return rawMappings
76
+ .filter(filterNonUndefined)
77
+ .reduce<RuntimeMethodIdMapping>((acc, entry) => {
78
+ acc[entry.name] = {
79
+ methodId: entry.methodId,
80
+ type: entry.type,
81
+ };
82
+ return acc;
83
+ }, {});
84
+ }
85
+
86
+ public getMethodNameFromId(methodId: bigint): [string, string] | undefined {
87
+ const methodPath = this.dictionary[methodId.toString()];
88
+
89
+ if (methodPath === undefined) {
90
+ return undefined;
91
+ }
92
+
93
+ const { moduleName, methodName } = methodPath;
94
+
95
+ this.runtime.assertIsValidModuleName(moduleName);
96
+
97
+ return [moduleName, methodName];
98
+ }
99
+
100
+ public getMethodId(moduleName: string, methodName: string): bigint {
101
+ this.runtime.assertIsValidModuleName(moduleName);
102
+
103
+ return Poseidon.hash([
104
+ stringToField(moduleName),
105
+ stringToField(methodName),
106
+ ]).toBigInt();
107
+ }
108
+ }