From 7fdda647bfc098aea92f660e3f54b56017b61487 Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 3 Apr 2025 19:21:48 +0300 Subject: [PATCH] feat: implement generator --- packages/contract.cm/src/onchain/addWord.ts | 16 ++-- packages/contract.cm/src/onchain/contract.ts | 16 +++- packages/cwait/src/contract-kit/index.ts | 3 - packages/cwait/src/contract-kit/methods.ts | 59 -------------- .../cwait/src/contract-kit/quickjs/os.d.ts | 3 - .../cwait/src/contract-kit/quickjs/std.d.ts | 3 - .../contract-kit/{store.d.ts => types.d.ts} | 0 packages/cwait/src/contract-kit/types.ts | 7 -- packages/cwait/src/contract-kit/wrappers.ts | 17 ---- packages/cwait/src/index.ts | 1 - packages/cwait/src/onchain/executor.ts | 80 +++++++++++-------- packages/cwait/src/onchain/ops/index.ts | 3 + packages/cwait/src/onchain/ops/read.ts | 29 +++---- packages/cwait/src/onchain/ops/store.ts | 35 ++++---- packages/cwait/src/onchain/ops/take.ts | 29 +++---- packages/cwait/src/onchain/signal.ts | 1 + packages/ui/.env | 2 +- 17 files changed, 118 insertions(+), 186 deletions(-) delete mode 100644 packages/cwait/src/contract-kit/index.ts delete mode 100644 packages/cwait/src/contract-kit/methods.ts delete mode 100644 packages/cwait/src/contract-kit/quickjs/os.d.ts delete mode 100644 packages/cwait/src/contract-kit/quickjs/std.d.ts rename packages/cwait/src/contract-kit/{store.d.ts => types.d.ts} (100%) delete mode 100644 packages/cwait/src/contract-kit/types.ts delete mode 100644 packages/cwait/src/contract-kit/wrappers.ts create mode 100644 packages/cwait/src/onchain/signal.ts diff --git a/packages/contract.cm/src/onchain/addWord.ts b/packages/contract.cm/src/onchain/addWord.ts index ed5173a..e8422f4 100644 --- a/packages/contract.cm/src/onchain/addWord.ts +++ b/packages/contract.cm/src/onchain/addWord.ts @@ -1,7 +1,7 @@ import { constructClaim } from '@coinweb/contract-kit'; -import { storeOp } from 'cwait'; +import { readOp, storeOp, TypedClaim } from 'cwait'; -import { AddWordArgs, createWordKey } from '../offchain/shared'; +import { AddWordArgs, createWordKey, WordClaimBody } from '../offchain/shared'; function hashCode(str: string): string { let hash = 0; @@ -15,7 +15,13 @@ function hashCode(str: string): string { return `${hash.toString(16)}`; } -export const addWord = async (...[word]: AddWordArgs) => { +export function* addWord(...[word]: AddWordArgs) { const id = hashCode(word); - storeOp(constructClaim(createWordKey(id), { word }, '0x0')); -}; + yield storeOp(constructClaim(createWordKey(id), { word }, '0x0')); + + const wordClaim: TypedClaim | null = yield readOp(createWordKey(id)); + + const newWord = (wordClaim?.body.word ?? '') + '!!!'; + const newId = hashCode(newWord); + yield storeOp(constructClaim(createWordKey(newId), { word: newWord }, '0x0')); +} diff --git a/packages/contract.cm/src/onchain/contract.ts b/packages/contract.cm/src/onchain/contract.ts index d44f0ba..e9e2378 100644 --- a/packages/contract.cm/src/onchain/contract.ts +++ b/packages/contract.cm/src/onchain/contract.ts @@ -1,8 +1,16 @@ -import { SELF_REGISTER_HANDLER_NAME, ContractHandlers as CKContractHandlers } from '@coinweb/contract-kit'; +import { + SELF_REGISTER_HANDLER_NAME, + ContractHandlers as CKContractHandlers, + MethodCallback, + selfCallWrapper, + addMethodHandler, + executeHandler, + ContractHandlers, +} from '@coinweb/contract-kit'; import { selfRegisterHandler } from '@coinweb/self-register'; -import { addMethodHandler, ContractHandlers, executeHandler, executor, MethodCallback, selfCallWrapper } from 'cwait'; import { queue } from 'lib/onchain'; +import { executor } from '../../../cwait/dist/onchain'; import { PUBLIC_METHODS } from '../offchain/shared'; import { addWord } from './addWord'; @@ -23,8 +31,8 @@ const createModule = (): ContractHandlers => { return module; }; -export const cwebMain = async () => { +export const cwebMain = () => { const module = createModule(); - await executeHandler(module); + executeHandler(module); }; diff --git a/packages/cwait/src/contract-kit/index.ts b/packages/cwait/src/contract-kit/index.ts deleted file mode 100644 index 14eaf29..0000000 --- a/packages/cwait/src/contract-kit/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './types'; -export * from './methods'; -export * from './wrappers'; diff --git a/packages/cwait/src/contract-kit/methods.ts b/packages/cwait/src/contract-kit/methods.ts deleted file mode 100644 index 7dfbef8..0000000 --- a/packages/cwait/src/contract-kit/methods.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { extractContinuations, Context } from '@coinweb/contract-kit'; -import { - getContextCall, - getContextGenesis, - getContextSystem, - getContextTx, - writeToResultFile, -} from '@coinweb/contract-kit/dist/esm/context'; -import { getMethodName } from '@coinweb/contract-kit/dist/esm/method'; - -import { ContractHandlers, MethodCallback } from './types'; - -/** - * Adds a method handler for a specific method name. - * @param contract_module - Contract module containing the method. - * @param methodName - The name of the method. - * @param handler - The method callback to add. - */ -export function addMethodHandler(contract_module: ContractHandlers, methodName: string, handler: MethodCallback): void { - contract_module.handlers[methodName] = handler; -} - -/** - * Retrieves the method handler for a specific method name. - * @param contract_module - Contract module containing the method. - * @param methodName - The name of the method. - * @returns The method callback for the specified method name. - * @throws Will throw an error if no handler is specified for the method name. - */ -export function getMethodHandler(contract_module: ContractHandlers, methodName: string): MethodCallback { - const handler = contract_module.handlers[methodName]; - if (!handler) { - throw Error('Handler not specified for this method name'); - } - return handler; -} - -/** - * Executes the handler for the method specified in the transaction context. - * The results (new transactions) are written to the result file. - * @param contractModule - Contract module containing the method. - */ -export async function executeHandler(contractModule: ContractHandlers): Promise { - const contextTx = getContextTx(); - const contextCall = getContextCall(); - const genesis = getContextGenesis(); - const system = getContextSystem(); - const context: Context = { - tx: contextTx, - call: contextCall, - genesis, - system, - continuations: extractContinuations(contextTx), - }; - const method = getMethodName(context); - const handler = getMethodHandler(contractModule, method); - const txs = await handler(context); - writeToResultFile(txs); -} diff --git a/packages/cwait/src/contract-kit/quickjs/os.d.ts b/packages/cwait/src/contract-kit/quickjs/os.d.ts deleted file mode 100644 index b59d7cb..0000000 --- a/packages/cwait/src/contract-kit/quickjs/os.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare namespace os { - function readdir(path: string): [any[], number]; -} diff --git a/packages/cwait/src/contract-kit/quickjs/std.d.ts b/packages/cwait/src/contract-kit/quickjs/std.d.ts deleted file mode 100644 index 5b3b5ca..0000000 --- a/packages/cwait/src/contract-kit/quickjs/std.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare namespace std { - function open(path: string, mode: string): any; -} diff --git a/packages/cwait/src/contract-kit/store.d.ts b/packages/cwait/src/contract-kit/types.d.ts similarity index 100% rename from packages/cwait/src/contract-kit/store.d.ts rename to packages/cwait/src/contract-kit/types.d.ts diff --git a/packages/cwait/src/contract-kit/types.ts b/packages/cwait/src/contract-kit/types.ts deleted file mode 100644 index fe7d359..0000000 --- a/packages/cwait/src/contract-kit/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Context, NewTx } from '@coinweb/contract-kit'; - -export type MethodCallback = (context: Context) => Promise | NewTx[]; - -export type ContractHandlers = { - handlers: { [key: string]: MethodCallback }; -}; diff --git a/packages/cwait/src/contract-kit/wrappers.ts b/packages/cwait/src/contract-kit/wrappers.ts deleted file mode 100644 index ff5b595..0000000 --- a/packages/cwait/src/contract-kit/wrappers.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Context, isSelfCall } from '@coinweb/contract-kit'; - -import { MethodCallback } from './types'; - -/** - * Wraps a method callback to ensure that it can only be called by the contract itself. - * @param handler - The method callback to wrap. - * @returns A new method callback that throws an error if the call is not from the contract itself. - */ -export function selfCallWrapper(handler: MethodCallback): MethodCallback { - return async (context: Context) => { - if (!isSelfCall(context)) { - throw new Error('Only contract itself can call it'); - } - return handler(context); - }; -} diff --git a/packages/cwait/src/index.ts b/packages/cwait/src/index.ts index 55987ee..040849e 100644 --- a/packages/cwait/src/index.ts +++ b/packages/cwait/src/index.ts @@ -1,3 +1,2 @@ -export * from './contract-kit'; export * from './onchain'; export * from './types'; diff --git a/packages/cwait/src/onchain/executor.ts b/packages/cwait/src/onchain/executor.ts index fca42c3..8c729cf 100644 --- a/packages/cwait/src/onchain/executor.ts +++ b/packages/cwait/src/onchain/executor.ts @@ -12,38 +12,52 @@ import { getCallParameters, queue } from 'lib/onchain'; import { context, getRawContext, setRawContext } from './context'; import { getAwaitedOps } from './ops/awaited'; import { pushResolvedOp } from './ops/resolved'; - -let abortExecution: (() => void) | null = null; +import { signal } from './signal'; const handleState = () => { const ctx = getRawContext(); const resolvedOps = extractContractArgs(ctx.tx); - const currentArgs = getMethodArguments(ctx) as [unknown, unknown[], ResolvedOperation[]]; + const methodArgs = getMethodArguments(ctx) as [unknown, unknown[], ResolvedOperation[]]; - const initialArgs = currentArgs[1]; - const allResolvedOps = [...currentArgs[2], ...resolvedOps]; + const initialArgs = methodArgs[1] ?? []; + const previousOps = methodArgs[2] ?? []; + const allResolvedOps = [...previousOps, ...resolvedOps]; pushResolvedOp(allResolvedOps); - return { args: initialArgs, methodName: currentArgs[0] as string, ops: allResolvedOps }; + return { args: initialArgs, methodName: methodArgs[0] as string, ops: allResolvedOps }; }; export const executor = // eslint-disable-next-line @typescript-eslint/no-explicit-any - (method: (...args: any[]) => Promise) => - async (ctx: Context): Promise => { + (method: (...args: any[]) => Generator) => + (ctx: Context): NewTx[] => { setRawContext(ctx); const { args, methodName, ops } = handleState(); - const execution = new Promise((resolve, reject) => { - abortExecution = resolve; + const execution = method(...args); - method(...args).then(resolve, reject); - }); + let value: unknown; + let isValue = false; - await execution; + while (true) { + let result: IteratorResult; + + if (!isValue) { + result = execution.next(); + isValue = true; + } else { + result = execution.next(value); + } + + value = result.value; + + if (result.done || result.value === signal) { + break; + } + } const { authInfo, availableCweb } = getCallParameters(ctx); @@ -56,29 +70,25 @@ export const executor = const txFee = 700n + BigInt(awaitedOps.length) * 100n; return [ - constructContinueTx(ctx, awaitedOps, [ - { - callInfo: { - ref: constructContractRef(context.issuer, []), - methodInfo: { - methodName, - methodArgs: [args, ops], + constructContinueTx( + ctx, + [], + [ + { + callInfo: { + ref: constructContractRef(context.issuer, []), + methodInfo: { + methodName, + methodArgs: [args, ops], + }, + contractInfo: { + providedCweb: availableCweb - txFee, + authenticated: authInfo, + }, + contractArgs: awaitedOps, }, - contractInfo: { - providedCweb: availableCweb - txFee, - authenticated: authInfo, - }, - contractArgs: [], }, - }, - ]), + ] + ), ]; }; - -export const abort = () => { - if (!abortExecution) { - throw new Error('Abort not found'); - } - - abortExecution(); -}; diff --git a/packages/cwait/src/onchain/ops/index.ts b/packages/cwait/src/onchain/ops/index.ts index ffdadda..3c9ab12 100644 --- a/packages/cwait/src/onchain/ops/index.ts +++ b/packages/cwait/src/onchain/ops/index.ts @@ -1,3 +1,6 @@ +export * from './awaited'; export * from './block'; +export * from './read'; +export * from './resolved'; export * from './store'; export * from './take'; diff --git a/packages/cwait/src/onchain/ops/read.ts b/packages/cwait/src/onchain/ops/read.ts index fbf2492..7ae5fd6 100644 --- a/packages/cwait/src/onchain/ops/read.ts +++ b/packages/cwait/src/onchain/ops/read.ts @@ -2,26 +2,27 @@ import { Claim, ClaimKey, constructRead, extractRead, isResolvedRead } from '@co import { TypedClaim } from '../../types'; import { context } from '../context'; -import { abort } from '../executor'; +import { signal } from '../signal'; import { pushAwaitedOp } from './awaited'; import { shiftResolvedOp } from './resolved'; -export const readOp = (key: ClaimKey) => { - const { op, isOp } = shiftResolvedOp(); +export const readOp = (key: ClaimKey): TClaim | null => { + const resolvedOp = shiftResolvedOp(); - if (!isOp) { + if (!resolvedOp.isOp) { pushAwaitedOp(constructRead(context.issuer, key)); - } else if (op && !isResolvedRead(op)) { + // @ts-expect-error + return signal; + } else { + if (!resolvedOp.op) { + return resolvedOp.op; + } + + if (isResolvedRead(resolvedOp.op)) { + return (extractRead(resolvedOp.op)?.[0].content as TClaim | undefined) ?? null; + } + throw new Error('Read operation not found'); } - - return new Promise((resolve) => { - if (isOp) { - const claim = op && ((extractRead(op)?.[0] ?? null) as TClaim | null); - resolve(claim); - } else { - abort(); - } - }); }; diff --git a/packages/cwait/src/onchain/ops/store.ts b/packages/cwait/src/onchain/ops/store.ts index 0357ec5..84a1008 100644 --- a/packages/cwait/src/onchain/ops/store.ts +++ b/packages/cwait/src/onchain/ops/store.ts @@ -1,31 +1,26 @@ import { Claim, constructStore, isResolvedStore } from '@coinweb/contract-kit'; -import { extractStore } from '@coinweb/contract-kit/dist/esm/operations/store'; -import { abort } from '../executor'; +import { signal } from '../signal'; import { pushAwaitedOp } from './awaited'; import { shiftResolvedOp } from './resolved'; -export const storeOp = (claim: Claim) => { - const { op, isOp } = shiftResolvedOp(); +export const storeOp = (claim: Claim): Claim | null => { + const resolvedOp = shiftResolvedOp(); - if (!isOp) { + if (!resolvedOp.isOp) { pushAwaitedOp(constructStore(claim)); - } else if (op && !isResolvedStore(op)) { + // @ts-expect-error + return signal; + } else { + if (!resolvedOp.op) { + return resolvedOp.op; + } + + if (isResolvedStore(resolvedOp.op)) { + return resolvedOp.op.StoreOp; + } + throw new Error('Store operation not found'); } - - const result = op && extractStore(op); - - if (!result) { - throw new Error('Wrong store operation'); - } - - return new Promise((resolve) => { - if (isOp) { - resolve(result); - } else { - abort(); - } - }); }; diff --git a/packages/cwait/src/onchain/ops/take.ts b/packages/cwait/src/onchain/ops/take.ts index 9a2f9d8..48a26b0 100644 --- a/packages/cwait/src/onchain/ops/take.ts +++ b/packages/cwait/src/onchain/ops/take.ts @@ -1,26 +1,27 @@ import { constructTake, extractTake, isResolvedTake, Claim, ClaimKey } from '@coinweb/contract-kit'; import { TypedClaim } from '../../types'; -import { abort } from '../executor'; +import { signal } from '../signal'; import { pushAwaitedOp } from './awaited'; import { shiftResolvedOp } from './resolved'; -export const takeOp = (key: ClaimKey) => { - const { op, isOp } = shiftResolvedOp(); +export const takeOp = (key: ClaimKey): TClaim | null => { + const resolvedOp = shiftResolvedOp(); - if (!isOp) { + if (!resolvedOp.isOp) { pushAwaitedOp(constructTake(key)); - } else if (op && !isResolvedTake(op)) { + // @ts-expect-error + return signal; + } else { + if (!resolvedOp.op) { + return resolvedOp.op; + } + + if (isResolvedTake(resolvedOp.op)) { + return extractTake(resolvedOp.op) as TClaim; + } + throw new Error('Take operation not found'); } - - return new Promise((resolve) => { - if (isOp) { - const claim = op && extractTake(op); - resolve(claim as TClaim | null); - } else { - abort(); - } - }); }; diff --git a/packages/cwait/src/onchain/signal.ts b/packages/cwait/src/onchain/signal.ts new file mode 100644 index 0000000..eb285dc --- /dev/null +++ b/packages/cwait/src/onchain/signal.ts @@ -0,0 +1 @@ +export const signal = Symbol('signal'); diff --git a/packages/ui/.env b/packages/ui/.env index c000542..9384c13 100644 --- a/packages/ui/.env +++ b/packages/ui/.env @@ -1,4 +1,4 @@ VITE_API_URL='https://api-cloud.coinweb.io/wallet' VITE_EXPLORER_URL='https://explorer.coinweb.io' -VITE_CONTRACT_ADDRESS="0x51c4d9ea120f185aab78a2fa74eba43f4e6449a261d6d369fd077012a202aa03" \ No newline at end of file +VITE_CONTRACT_ADDRESS="0xe1e81e77f901f630ab760a84b6bb84c96c3a4ec996a287efac2c8502e94a287f" \ No newline at end of file