diff --git a/eslint.config.mjs b/eslint.config.mjs index 99d2cb2..dba45fa 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -81,7 +81,7 @@ export default tseslint.config( caughtErrorsIgnorePattern: '^_', }, ], - '@typescript-eslint/no-explicit-any': 'error', + '@typescript-eslint/no-explicit-any': ['error', { ignoreRestArgs: true }], '@typescript-eslint/ban-ts-comment': 'off', '@typescript-eslint/no-misused-promises': ['error', { checksVoidReturn: false }], '@typescript-eslint/no-empty-object-type': 'off', diff --git a/packages/contract.cm/src/onchain/addWord.ts b/packages/contract.cm/src/onchain/addWord.ts index 715dbda..46d7e1f 100644 --- a/packages/contract.cm/src/onchain/addWord.ts +++ b/packages/contract.cm/src/onchain/addWord.ts @@ -1,5 +1,5 @@ import { constructClaim } from '@coinweb/contract-kit'; -import { readOp, storeOp, cwait } from 'cwait'; +import { readOp, storeOp, cwait, queue } from 'cwait'; import { TypedClaim } from '../../../lib/dist/shared/types'; import { AddWordArgs, createWordKey, WordClaimBody } from '../offchain/shared'; @@ -26,6 +26,16 @@ export const addWord = cwait(async (...[word]: AddWordArgs) => { console.log('await storeOp'); await storeOp(constructClaim(createWordKey(id), { word }, '0x0')); + await queue(async () => { + console.log('await readOp'); + const res = await readOp>(createWordKey(id)); + + console.log('await storeOp2'); + await storeOp( + constructClaim(createWordKey(id + id), { word: (res?.body.word ?? '') + (res?.body.word ?? '') }, '0x0') + ); + }); + console.log('await extraLogic'); const wordClaim = await extraLogic(id); diff --git a/packages/contract.cm/src/onchain/contract.ts b/packages/contract.cm/src/onchain/contract.ts index 1b5d48d..800500b 100644 --- a/packages/contract.cm/src/onchain/contract.ts +++ b/packages/contract.cm/src/onchain/contract.ts @@ -1,25 +1,9 @@ -import { SELF_REGISTER_HANDLER_NAME } from '@coinweb/contract-kit'; -import { selfRegisterHandler } from '@coinweb/self-register'; -import { addMethodHandler, ContractHandlers, executeHandler, executor } from 'cwait'; +import { constructCwebMain } from 'cwait'; import { PUBLIC_METHODS } from '../offchain/shared'; import { addWord } from './addWord'; -const createModule = (): ContractHandlers => { - const module: ContractHandlers = { handlers: {} }; - - addMethodHandler(module, PUBLIC_METHODS.ADD_WORD, executor(addWord)); - - addMethodHandler(module, SELF_REGISTER_HANDLER_NAME, selfRegisterHandler); - - return module; -}; - -export const cwebMain = () => { - return (async () => { - const module = createModule(); - - await executeHandler(module); - })(); -}; +export const cwebMain = constructCwebMain({ + [PUBLIC_METHODS.ADD_WORD]: addWord, +}); diff --git a/packages/cwait/src/onchain/context/context.ts b/packages/cwait/src/onchain/context/context.ts index 273a71a..e892496 100644 --- a/packages/cwait/src/onchain/context/context.ts +++ b/packages/cwait/src/onchain/context/context.ts @@ -92,4 +92,7 @@ export const context = { get needSaveResult() { return getMethodArgs()[6] ?? false; }, + get isQueue() { + return getMethodArgs()[8] ?? false; + }, }; diff --git a/packages/cwait/src/onchain/createContract.ts b/packages/cwait/src/onchain/createContract.ts new file mode 100644 index 0000000..0b844c1 --- /dev/null +++ b/packages/cwait/src/onchain/createContract.ts @@ -0,0 +1,22 @@ +import { ContractHandlers as ContractHandlersOrig, SELF_REGISTER_HANDLER_NAME } from '@coinweb/contract-kit'; +import { selfRegisterHandler } from '@coinweb/self-register'; +import { queue } from 'lib/onchain'; + +import { addMethodHandler, ContractHandlers, executeHandler } from '../contract-kit'; + +import { executor } from './executor'; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const constructCwebMain = (methods: Record Promise>) => async () => { + const module: ContractHandlers = { handlers: {} }; + + Object.entries(methods).forEach(([name, handler]) => { + addMethodHandler(module, name, executor(handler)); + }); + + addMethodHandler(module, SELF_REGISTER_HANDLER_NAME, selfRegisterHandler); + + queue.applyQueue(module as ContractHandlersOrig, []); + + await executeHandler(module); +}; diff --git a/packages/cwait/src/onchain/promisifiedOps/cwait.ts b/packages/cwait/src/onchain/cwait.ts similarity index 77% rename from packages/cwait/src/onchain/promisifiedOps/cwait.ts rename to packages/cwait/src/onchain/cwait.ts index 4af8ba2..8b9d0b5 100644 --- a/packages/cwait/src/onchain/promisifiedOps/cwait.ts +++ b/packages/cwait/src/onchain/cwait.ts @@ -1,18 +1,15 @@ import { constructStore } from '@coinweb/contract-kit/dist/esm/operations/store'; -import { constructResultClaim } from '../claims/result'; -import { context } from '../context'; -import { stopExecution } from '../executor'; -import { opMarker } from '../global'; -import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../utils'; -import { uuid } from '../utils'; - -import { getAwaitedTasks, pushAwaitedTask } from './awaited'; -import { getUsedOps, saveUsedOps, shiftResolvedOp } from './resolved'; +import { constructResultClaim } from './claims'; +import { context } from './context'; +import { stopExecution } from './executor'; +import { opMarker } from './global'; +import { getAwaitedTasks, pushAwaitedTask } from './promisifiedOps/awaited'; +import { getUsedOps, saveUsedOps, shiftResolvedOp } from './promisifiedOps/resolved'; +import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp, uuid } from './utils'; let isRootDetected = false; -// eslint-disable-next-line @typescript-eslint/no-explicit-any export const cwait = Promise>(asyncCallback: TAsyncCallback) => { console.log('cwait: ', asyncCallback.name); let isRoot = false; diff --git a/packages/cwait/src/onchain/index.ts b/packages/cwait/src/onchain/index.ts index 7be55b9..2a9dcc7 100644 --- a/packages/cwait/src/onchain/index.ts +++ b/packages/cwait/src/onchain/index.ts @@ -1,3 +1,6 @@ export * from './context'; export * from './executor'; export * from './promisifiedOps'; +export * from './createContract'; +export * from './cwait'; +export * from './queue'; diff --git a/packages/cwait/src/onchain/promisifiedOps/index.ts b/packages/cwait/src/onchain/promisifiedOps/index.ts index 672baa5..4247f6a 100644 --- a/packages/cwait/src/onchain/promisifiedOps/index.ts +++ b/packages/cwait/src/onchain/promisifiedOps/index.ts @@ -1,4 +1,3 @@ export * from './awaited'; -export * from './cwait'; export * from './ops'; export * from './resolved'; diff --git a/packages/cwait/src/onchain/promisifiedTxs/index.ts b/packages/cwait/src/onchain/promisifiedTxs/index.ts deleted file mode 100644 index 6b03561..0000000 --- a/packages/cwait/src/onchain/promisifiedTxs/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './jumpTx'; -export * from './tx'; diff --git a/packages/cwait/src/onchain/promisifiedTxs/jumpTx.ts b/packages/cwait/src/onchain/promisifiedTxs/jumpTx.ts deleted file mode 100644 index 6ef6f86..0000000 --- a/packages/cwait/src/onchain/promisifiedTxs/jumpTx.ts +++ /dev/null @@ -1 +0,0 @@ -export const jumpTx = () => null; diff --git a/packages/cwait/src/onchain/promisifiedTxs/tx.ts b/packages/cwait/src/onchain/promisifiedTxs/tx.ts deleted file mode 100644 index 62a9327..0000000 --- a/packages/cwait/src/onchain/promisifiedTxs/tx.ts +++ /dev/null @@ -1 +0,0 @@ -export const tx = () => null; diff --git a/packages/cwait/src/onchain/queue/index.ts b/packages/cwait/src/onchain/queue/index.ts new file mode 100644 index 0000000..cadd6a9 --- /dev/null +++ b/packages/cwait/src/onchain/queue/index.ts @@ -0,0 +1 @@ +export * from './queue'; diff --git a/packages/cwait/src/onchain/queue/queue.ts b/packages/cwait/src/onchain/queue/queue.ts new file mode 100644 index 0000000..17614a3 --- /dev/null +++ b/packages/cwait/src/onchain/queue/queue.ts @@ -0,0 +1,21 @@ +const queues = new Set(); + +let queueId = 0; + +export const queue = Promise>(callback: TCallback) => { + const id = queueId++; + queues.add(id); + + const promise = new Promise((resolve) => { + callback().finally(() => { + queues.delete(id); + resolve(); + }); + }); + + return promise; +}; + +export const isQueueLocked = () => { + return queues.size > 0; +}; diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts index 0458c90..f44a1b0 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts @@ -5,16 +5,20 @@ import { FullCallInfo, NewTx, passCwebFrom, + PreparedCallInfo, PreparedOperation, + prepareQueueCall, sendCwebInterface, } from '@coinweb/contract-kit'; import { CwebBlock } from '@coinweb/contract-kit/dist/esm/operations/block'; import { GBlock } from '@coinweb/contract-kit/dist/types/operations/generics'; +import { queue, queueCostFee, queueUnlockFee } from 'lib/onchain'; import { ExecutorMethodArgs, ResolvedOp, ResolvedSlotOp, Task } from '../../../types'; import { constructFundsClaimRangRead, constructFundsClaimStore } from '../../claims/funds'; import { constructResultBlockFilter, constructResultClaimTake, resultKey } from '../../claims/result'; import { context, getRawContext } from '../../context'; +import { isQueueLocked } from '../../queue/queue'; import { isPreparedBlockOp, isPreparedExecOp } from '../typeGuards'; export const prepareInThreadTxs = ({ @@ -91,6 +95,8 @@ export const prepareInThreadTxs = ({ id, context.thisId, true, + [], + isQueueLocked(), ] satisfies ExecutorMethodArgs, }, contractInfo: { @@ -150,32 +156,52 @@ export const prepareInThreadTxs = ({ txFee += 800n + outThreadFee + BigInt(takeOps.length) * 100n; callsPrepared++; + const isNeedToLockQueue = !context.isQueue && isQueueLocked(); + const isNeedToUnlockQueue = context.isQueue && !isQueueLocked(); + + if (isNeedToLockQueue) { + console.log('isNeedToLockQueue'); + txFee += queueCostFee; + } + + if (isNeedToUnlockQueue) { + console.log('isNeedToUnlockQueue'); + txFee += queueUnlockFee; + } + + let callInfo: PreparedCallInfo = { + ref: constructContractRef(context.issuer, []), + methodInfo: { + methodName: context.methodName, + methodArgs: [ + context.initialArgs, + [...context.ops, ...resolvedSlotOps], + context.user, + context.thisId, + context.parentId, + context.needSaveResult, + takeOps.map((op) => (op.TakeOp.key.second_part as [string])[0]), + isQueueLocked(), + ] satisfies ExecutorMethodArgs, + }, + contractInfo: { + providedCweb: cwebPerCall - txFee + storedCweb, + authenticated: null, + }, + contractArgs: [constructFundsClaimRangRead(context.thisId), ...callArgs], + }; + + if (isNeedToLockQueue) { + callInfo = prepareQueueCall(context.issuer, callInfo); + } + returnTxs.push( constructContinueTx( getRawContext(), [passCwebFrom(context.issuer, cwebPerCall - outThreadFee), ...takeOps], [ { - callInfo: { - ref: constructContractRef(context.issuer, []), - methodInfo: { - methodName: context.methodName, - methodArgs: [ - context.initialArgs, - [...context.ops, ...resolvedSlotOps], - context.user, - context.thisId, - context.parentId, - context.needSaveResult, - takeOps.map((op) => (op.TakeOp.key.second_part as [string])[0]), - ] satisfies ExecutorMethodArgs, - }, - contractInfo: { - providedCweb: cwebPerCall - txFee + storedCweb, - authenticated: null, - }, - contractArgs: [constructFundsClaimRangRead(context.thisId), ...callArgs], - }, + callInfo, }, ] ) @@ -184,6 +210,10 @@ export const prepareInThreadTxs = ({ if (childCalls.length) { returnTxs.push(constructContinueTx(getRawContext(), childBlocks, childCalls)); } + + if (isNeedToUnlockQueue) { + returnTxs.push(...queue.gateway.unlock(getRawContext())); + } } } diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts index 390b5af..d9d1d5e 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts @@ -8,6 +8,7 @@ import { import { ExecutorMethodArgs, Task } from '../../../types'; import { context, getRawContext } from '../../context'; +import { isQueueLocked } from '../../queue/queue'; import { isPreparedExecOp } from '../typeGuards'; export const prepareOutThreadTxs = ({ @@ -51,6 +52,8 @@ export const prepareOutThreadTxs = ({ id, context.parentId ?? context.thisId, false, + [], + isQueueLocked(), ] satisfies ExecutorMethodArgs, }, contractInfo: { diff --git a/packages/cwait/src/types.ts b/packages/cwait/src/types.ts index a5b7fb3..ead417f 100644 --- a/packages/cwait/src/types.ts +++ b/packages/cwait/src/types.ts @@ -45,4 +45,5 @@ export type ExecutorMethodArgs = [ parentId?: string, saveResult?: boolean, takenFundsIds?: string[], + isQueue?: boolean, ]; diff --git a/packages/lib/src/onchain/features/queue.ts b/packages/lib/src/onchain/features/queue.ts index f4fb5b2..ea973e4 100644 --- a/packages/lib/src/onchain/features/queue.ts +++ b/packages/lib/src/onchain/features/queue.ts @@ -1,5 +1,7 @@ import { Queue } from '@coinweb/contract-kit'; -export const queue = new Queue({ name: 'queue', executionDepth: 2 }); +export const queue = new Queue({ name: 'queue', executionDepth: 4 }); export const queueUnlockFee = 3000n; + +export const queueCostFee = Queue.queueCostFee(); diff --git a/packages/ui/.env b/packages/ui/.env index 6a36b70..6ed9bff 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="0x8c89cb42634cfe892290845d907af8ec2d482cead42afaef3ccc3cea4446fce5" +VITE_CONTRACT_ADDRESS="0xa1bd5281d39b1182db65565d90919aff15adf65ac8ba43d984048485df5a6d8d"