diff --git a/packages/cwait/src/onchain/executor.ts b/packages/cwait/src/onchain/executor.ts index 82035cc..3660010 100644 --- a/packages/cwait/src/onchain/executor.ts +++ b/packages/cwait/src/onchain/executor.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { Context, extractContractArgs, @@ -15,6 +16,7 @@ import { ResolvedOp, Task } from '../types'; import { context, getRawContext, setRawContext } from './context'; import { getAwaitedOps } from './promisifiedOps/awaited'; import { pushResolvedOp } from './promisifiedOps/resolved'; +import { extractRootParent, getRoot, setRoot } from './utils'; let abortExecution: ((result: boolean) => void) | null = null; @@ -22,112 +24,141 @@ const handleState = () => { const ctx = getRawContext(); const resolvedOps = extractContractArgs(ctx.tx); - const methodArgs = getMethodArguments(ctx) as [unknown, unknown[], ResolvedOp[]]; + const methodArgs = getMethodArguments(ctx) as [unknown, unknown[], ResolvedOp[], string?]; const initialArgs = methodArgs[1] ?? []; const previousOps = methodArgs[2] ?? []; const allResolvedOps = [...previousOps, ...resolvedOps]; + const root = methodArgs[3]; + pushResolvedOp(allResolvedOps); return { args: initialArgs, methodName: methodArgs[0] as string, ops: allResolvedOps, - isChildCall: previousOps.length > 0, + isChildCall: resolvedOps.length > 0, + root, }; }; +type Method = (...args: any[]) => Promise; + +type Resolver = ( + currentMethod: Method, + args: any[], + resolve: (value: boolean | PromiseLike) => void, + reject: (reason?: any) => void, + isChildCall: boolean, + rootParent?: string, + root?: string +) => void; + +const resolver: Resolver = (currentMethod, args, resolve, reject, isChildCall, root) => { + if (!isChildCall) { + extractRootParent(resolver.name); + } else { + if (!root) { + console.log('executor-root-not-set'); + throw new Error('Root root is not set'); + } + + setRoot(root); + } + + currentMethod(...args).then( + () => { + console.log('executor-resolved'); + resolve(true); + }, + () => { + console.log('executor-rejected'); + reject(); + } + ); +}; + export const executor = - // eslint-disable-next-line @typescript-eslint/no-explicit-any (method: (...args: any[]) => Promise) => - async (ctx: Context): Promise => { - console.log('executor-start'); - setRawContext(ctx); + async (ctx: Context): Promise => { + console.log('executor-start'); + setRawContext(ctx); - const { args, methodName, ops, isChildCall } = handleState(); + const { args, methodName, ops, isChildCall, root } = handleState(); - if (isChildCall && !isSelfCall(ctx)) { - throw new Error('Only contract itself can call it'); - } + if (isChildCall && !isSelfCall(ctx)) { + throw new Error('Only contract itself can call it'); + } - const execution = new Promise((resolve, reject) => { - abortExecution = resolve; + const execution = new Promise((resolve, reject) => { + abortExecution = resolve; - method(...args).then( - () => { - console.log('executor-resolved'); - resolve(true); - }, - () => { - console.log('executor-rejected'); - reject(); - } - ); - }); + resolver(method, args, resolve, reject, isChildCall, root); + }).catch((error) => { + console.error(error); + throw error; + }); - //@ts-expect-error - // eslint-disable-next-line @typescript-eslint/no-unsafe-call - os.setTimeout(() => { - abortExecution?.(false); - }, 0); + //@ts-expect-error + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + os.setTimeout(() => { + abortExecution?.(false); + }, 0); - const isFullyExecuted = await execution; + const isFullyExecuted = await execution; - console.log('executor-executed'); + console.log('executor-executed'); - const { authInfo, availableCweb } = getCallParameters(ctx); + const { authInfo, availableCweb } = getCallParameters(ctx); - const awaitedOps = getAwaitedOps(); + const awaitedOps = getAwaitedOps(); - if (!awaitedOps.length) { - return [constructContinueTx(ctx, [constructDataUnverified({ isFullyExecuted })]), ...queue.gateway.unlock(ctx)]; - } + if (!awaitedOps.length) { + return [constructContinueTx(ctx, [constructDataUnverified({ isFullyExecuted })]), ...queue.gateway.unlock(ctx)]; + } - const callArgs: Task[] = []; + const callArgs: Task[] = []; - if (!isFullyExecuted) { + if (!isFullyExecuted) { + callArgs.push(awaitedOps.pop()!); + } + + if (callArgs[0]?.isBatch) { + while (awaitedOps.at(-1)?.isBatch) { callArgs.push(awaitedOps.pop()!); } + } - if (callArgs[0]?.isBatch) { - while (awaitedOps.at(-1)?.isBatch) { - callArgs.push(awaitedOps.pop()!); - } - } + const callFee = 700n + BigInt(callArgs.length) * 100n; + const opTxFee = awaitedOps.length ? 100n + BigInt(awaitedOps.length) * 100n : 0n; - const callFee = 700n + BigInt(callArgs.length) * 100n; - const opTxFee = awaitedOps.length ? 100n + BigInt(awaitedOps.length) * 100n : 0n; - - return [ - constructContinueTx( - ctx, - awaitedOps.map(({ op }) => op), - [ - { - callInfo: { - ref: constructContractRef(context.issuer, []), - methodInfo: { - methodName, - methodArgs: [ - args, - [ - ...ops, - ...Array(awaitedOps.length).fill({ SlotOp: 0 } satisfies ResolvedOp), - ] satisfies ResolvedOp[], - isFullyExecuted, - callArgs, - awaitedOps, - ], - }, - contractInfo: { - providedCweb: availableCweb - callFee - opTxFee, - authenticated: authInfo, - }, - contractArgs: callArgs.map(({ op }) => op), + return [ + constructContinueTx( + ctx, + awaitedOps.map(({ op }) => op), + [ + { + callInfo: { + ref: constructContractRef(context.issuer, []), + methodInfo: { + methodName, + methodArgs: [ + args, + [...ops, ...Array(awaitedOps.length).fill({ SlotOp: 0 } satisfies ResolvedOp)] satisfies ResolvedOp[], + getRoot(), + callArgs, + awaitedOps, + ], }, + contractInfo: { + providedCweb: availableCweb - callFee - opTxFee, + authenticated: authInfo, + }, + contractArgs: callArgs.map(({ op }) => op), }, - ] - ), - ]; - }; + }, + ] + ), + ]; + }; diff --git a/packages/cwait/src/onchain/utils/callstack.ts b/packages/cwait/src/onchain/utils/callstack.ts index ea22049..3b603b0 100644 --- a/packages/cwait/src/onchain/utils/callstack.ts +++ b/packages/cwait/src/onchain/utils/callstack.ts @@ -1,10 +1,56 @@ -export const getStack = ({ skip = 0 }: { skip?: number } = {}) => - new Error().stack - ?.split('\n') +let rootParent = ''; +let root = ''; + +export const extractRootParent = (name: string) => { + const stack = getStackItems(); + + if (stack[0] === name) { + rootParent = name; + + return; + } +}; + +export const getStackItems = ({ skip = 0 }: { skip?: number } = {}) => { + const slugs = new Error() + .stack!.split('\n') .slice(2 + skip) .map((line) => { const match = line.match(/at\s+([^\s(]+)/); return match ? match[1] : ''; }) - .filter((name) => name && name !== 'Promise') - .join('@') || ''; + .filter((name) => name && !['', 'Promise'].includes(name)); + + return slugs; +}; + +export const getStack = ({ skip = 0 }: { skip?: number } = {}) => { + const items = getStackItems({ skip }); + + if (rootParent) { + const rootIndex = items.findLastIndex((item) => item === rootParent); + + if (rootIndex !== -1) { + const pureSlugs = items.slice(0, rootIndex); + + return pureSlugs.join('@') || '@'; + } + } + + if (!root) { + console.log('set root'); + console.log(JSON.stringify(items)); + console.log(items.at(-1)); + root = items.at(-1) ?? ''; + } + + return items.join('@') || '@'; +}; + +export const getRoot = () => { + return root; +}; + +export const setRoot = (value: string) => { + root = value; +}; diff --git a/packages/ui/.env b/packages/ui/.env index c702916..85fe43a 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="0xd0b364db47a35710320a815fbcae80435ae83e0a1730e70529f1b0b2a6b7bfd5" \ No newline at end of file +VITE_CONTRACT_ADDRESS="0x6fd41997fb840f91cc9d3b1bf69015dc2cef975015b52d53bf04f4380ddd66c4" \ No newline at end of file