From d5aaecce180b828588a935ebaeb1b989375e696b Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 5 Jun 2025 12:35:20 +0300 Subject: [PATCH] wip --- .cweb-config/dapp-ecosystem-test.yaml | 9 +- .../src/onchain/__tests__/handlers.test.ts | 93 +++++++++++++++++++ .../src/onchain/__tests__/helpers.ts | 35 +++++++ packages/cwait/src/onchain/context/context.ts | 4 +- .../cwait/src/onchain/context/extractOps.ts | 6 +- .../cwait/src/onchain/executor/executor.ts | 2 +- .../cwait/src/onchain/features/cwait/cwait.ts | 4 +- .../cwait/src/onchain/runtime/execLoop.ts | 21 ++++- packages/cwait/src/types.ts | 6 +- 9 files changed, 165 insertions(+), 15 deletions(-) create mode 100644 packages/contract.cm/src/onchain/__tests__/handlers.test.ts create mode 100644 packages/contract.cm/src/onchain/__tests__/helpers.ts diff --git a/.cweb-config/dapp-ecosystem-test.yaml b/.cweb-config/dapp-ecosystem-test.yaml index 779766e..dd5f4a7 100644 --- a/.cweb-config/dapp-ecosystem-test.yaml +++ b/.cweb-config/dapp-ecosystem-test.yaml @@ -14,9 +14,11 @@ includes: blake3: 321b1f88930aead7fe47961c8664005b3441f6502824769bf21eb06c3e5aaba4 use: - - dex-app.cm v0.0.67+test - - eth_offer_maker 0.0.67+devnet - - jump-listener.cm v0.0.5 + - jump-listener.cm v0.1.8 + - jump_listener_devnet v0.1.8 + - jump-forwarder.cm v0.1.5 + - jump_forwarder_devnet v0.1.5 + interpreters: {} @@ -31,3 +33,4 @@ contract_instances: template: contract.cm v0.0.1 parameters: content: [] + diff --git a/packages/contract.cm/src/onchain/__tests__/handlers.test.ts b/packages/contract.cm/src/onchain/__tests__/handlers.test.ts new file mode 100644 index 0000000..379d8be --- /dev/null +++ b/packages/contract.cm/src/onchain/__tests__/handlers.test.ts @@ -0,0 +1,93 @@ +import path from 'node:path'; + +import { constructContractIssuer, constructContractRef, type PreparedCallInfo } from '@coinweb/contract-kit'; +import { UnitTest, type ExecInfo, type RoundInfo, type UnitTestContext, type DbWriteOp } from '@coinweb/testing-sdk'; + +import { AddWordArgs, FEE, PUBLIC_METHODS } from '../../offchain/shared'; + +import { CONTRACT_INSTANCE_ID, TEST_PRIVATE_KEY, TEST_PUBLIC_KEY, waitRoundsCount } from './helpers'; + +const addWordRound = (creatorAccount: Buffer): RoundInfo => { + const self = constructContractIssuer(CONTRACT_INSTANCE_ID); + + const kvDeleteCallInfo: PreparedCallInfo = { + ref: constructContractRef(self, []), + methodInfo: { methodName: PUBLIC_METHODS.ADD_WORD, methodArgs: ['TEST_HELLO'] satisfies AddWordArgs }, + contractArgs: [], + contractInfo: { providedCweb: FEE.ADD_WORD, authenticated: null }, // null = authenticated account in tests + }; + + const withFunds = { type: { privateKey: creatorAccount } }; + + return { + txsInfo: { + // @ts-ignore + txs: [{ callInfo: kvDeleteCallInfo, withFunds }], + l1_events: [], + }, + claims: [], + blocks_on: [], + }; +}; + +const runTest = async (context: UnitTestContext, input: ExecInfo, checkFn: (results: DbWriteOp[]) => void) => { + const test = new UnitTest(context); + await test.load(path.join(import.meta.dirname, '../../../tests_data/state.json')); + await test.run(input, { checkFn }); +}; + +describe('PUBLIC_METHODS.ADD_WORD Tests', () => { + let creatorAccount: { publicKey: Buffer; privateKey: Buffer }; + + beforeAll(() => { + creatorAccount = { + privateKey: Buffer.from(TEST_PRIVATE_KEY, 'hex'), + publicKey: Buffer.from(TEST_PUBLIC_KEY, 'hex'), + }; + }); + + it('Should successfully execute', async () => { + const input: ExecInfo = { + rounds: [...waitRoundsCount(50), addWordRound(creatorAccount.privateKey), ...waitRoundsCount(2)], + }; + + const checkFn = (results: DbWriteOp[]) => { + expect(results).toBeDefined(); + + // @ts-ignore + // const errors = findAllValuesRecursivelyByKey<{ ContractError: ContractError }>(results, 'Invalid'); + // expect(errors.length).toBe(0); + + // @ts-ignore + // const storeOps = findAllValuesRecursivelyByKey(results, 'StoreOp'); + // expect(storeOps.length).toBeGreaterThan(0); + + // const existingPayload = storeOps.find((s) => isEqual(s.key.first_part, createFirstPartDefault())); + + // expect(existingPayload).toBeDefined(); + // expect(existingPayload?.key).toStrictEqual(createDefaultKey(Number.MAX_SAFE_INTEGER)); + // expect(existingPayload?.key.first_part).toStrictEqual(createFirstPartDefault()); + + // const { id, data, owner, tx } = existingPayload?.body as StoreKvPayload; + // expect(id).toBeDefined(); + // expect(data).toBeDefined(); + // expect(owner).toBeDefined(); + // expect(tx).toBeDefined(); + // expect(tx?.timestamp).toBeDefined(); + // expect(tx?.coinwebTxId).toBeDefined(); + // expect(Number(id)).toStrictEqual(Number.MAX_SAFE_INTEGER); + // expect(owner).toStrictEqual({ + // auth: 'EcdsaContract', + // payload: TEST_PUBLIC_KEY, + // }); + }; + + const context: UnitTestContext = { + name: 'add_word', + testPath: path.join(import.meta.dirname, './tests_data/add_word'), + verbose: true, + }; + + await runTest(context, input, checkFn); + }); +}); diff --git a/packages/contract.cm/src/onchain/__tests__/helpers.ts b/packages/contract.cm/src/onchain/__tests__/helpers.ts new file mode 100644 index 0000000..47473e4 --- /dev/null +++ b/packages/contract.cm/src/onchain/__tests__/helpers.ts @@ -0,0 +1,35 @@ +import path from 'node:path'; + +import { getInstanceFromIndex, type RoundInfo } from '@coinweb/testing-sdk'; + +const onchainPackage = (await import('../../../dist/out/package.json')) as { + name: string; +}; + +export const CONTRACT_INSTANCE = await getInstanceFromIndex({ + path: path.resolve(import.meta.dirname, '../../../tests_data/index.yaml'), + instance_alias: 'cwait-contract 0.0.1-test', +}); + +export const CONTRACT_INSTANCE_ID = '0x'.concat(CONTRACT_INSTANCE.instance_id.replace(/0x/, '')); + +export const CONTRACT_TEMPLATE_ID = `0x${onchainPackage.name.substring(5)}`; + +export const TEST_PRIVATE_KEY = '31c70848e4e3aaffcf91f134853ec966e913aa9a813115bcb81512e7625f46a9'; +export const TEST_PUBLIC_KEY = '03951f89fe78e13f295d96eb7afa1e0da726df7d58f9c84f7144e5febc30efeec4'; + +export const waitRoundsCount = (roundsCount: number): RoundInfo[] => { + const emptyRound: RoundInfo = { + txsInfo: { + txs: [], + l1_events: [], + }, + claims: [], + blocks_on: [], + }; + const rounds: RoundInfo[] = []; + for (let i = 0; i < roundsCount; i++) { + rounds.push(emptyRound); + } + return rounds; +}; diff --git a/packages/cwait/src/onchain/context/context.ts b/packages/cwait/src/onchain/context/context.ts index d140bc0..8cedf7e 100644 --- a/packages/cwait/src/onchain/context/context.ts +++ b/packages/cwait/src/onchain/context/context.ts @@ -58,7 +58,7 @@ export const handleContext = (ctx: Context) => { caller, thisId, parentId, - saveResult, + shouldSaveResult, takenFundsIds = [], execOpsIndexes = [], ] = getMethodArgs(); @@ -70,7 +70,7 @@ export const handleContext = (ctx: Context) => { initialContext.parentId = parentId; initialContext.methodName = methodName; initialContext.initialArgs = initialArgs ?? []; - initialContext.needSaveResult = saveResult ?? false; + initialContext.needSaveResult = shouldSaveResult ?? false; const { authInfo } = getCallParameters(getRawContext()); initialContext.user = (authInfo && extractUser(authInfo)) ?? caller ?? null; diff --git a/packages/cwait/src/onchain/context/extractOps.ts b/packages/cwait/src/onchain/context/extractOps.ts index 153dc96..2bb917e 100644 --- a/packages/cwait/src/onchain/context/extractOps.ts +++ b/packages/cwait/src/onchain/context/extractOps.ts @@ -86,7 +86,11 @@ export const extractOps = ({ throw new Error('Wrong subcall result'); } - extractedOps.push({ ChildOp: 0 }, ...(nextAfterBlock.TakeOp.result as TypedClaim).body); + extractedOps.push({ + ChildOp: { + ops: (nextAfterBlock.TakeOp.result as TypedClaim).body, + }, + }); i += 2; continue; diff --git a/packages/cwait/src/onchain/executor/executor.ts b/packages/cwait/src/onchain/executor/executor.ts index 23146d4..8c396f8 100644 --- a/packages/cwait/src/onchain/executor/executor.ts +++ b/packages/cwait/src/onchain/executor/executor.ts @@ -29,7 +29,7 @@ export const executor = (method: (...args: any[]) => Promise) => { }, 0); try { - setNextExec(() => method(...context.initialArgs)); + setNextExec(() => method(...context.initialArgs), []); const isFullyExecuted = await execLoop(); console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish-end'); return constructTx(isFullyExecuted); diff --git a/packages/cwait/src/onchain/features/cwait/cwait.ts b/packages/cwait/src/onchain/features/cwait/cwait.ts index 6fe51a5..e8f1870 100644 --- a/packages/cwait/src/onchain/features/cwait/cwait.ts +++ b/packages/cwait/src/onchain/features/cwait/cwait.ts @@ -54,7 +54,7 @@ export const cwait = Promise Promise asyncCallback(...args), op.ChildOp.ops); } throw new Error('Exec or Child operation not found'); diff --git a/packages/cwait/src/onchain/runtime/execLoop.ts b/packages/cwait/src/onchain/runtime/execLoop.ts index da06ae4..6630337 100644 --- a/packages/cwait/src/onchain/runtime/execLoop.ts +++ b/packages/cwait/src/onchain/runtime/execLoop.ts @@ -1,3 +1,7 @@ +import { ResolvedOp } from '../../types'; + +import { pushResolvedOp } from './resolvedOps'; + let abortExecution: ((isFullyExecuted: boolean) => void) | null = null; export const stopExecution = (isFullyExecuted = false) => { @@ -5,10 +9,18 @@ export const stopExecution = (isFullyExecuted = false) => { abortExecution?.(isFullyExecuted); }; -type ExecTask = () => Promise; +type ExecTask = () => Promise; +type Exec = { task: ExecTask; ops: ResolvedOp[] }; -let execQueue: ExecTask[] = []; -export const setNextExec = (task: ExecTask) => (execQueue = [task]); +const execQueue: Exec[] = []; +export const setNextExec = (task: ExecTask, ops: ResolvedOp[]) => { + return new Promise((resolve, reject) => { + execQueue.push({ + task: () => task().then(resolve, reject), + ops, + }); + }); +}; export const execLoop = async (): Promise => { const nextExec = execQueue.pop(); @@ -17,7 +29,8 @@ export const execLoop = async (): Promise => { const execution = new Promise((resolve, reject) => { abortExecution = resolve; - nextExec().then( + pushResolvedOp(nextExec.ops); + nextExec.task().then( () => { resolve(true); }, diff --git a/packages/cwait/src/types.ts b/packages/cwait/src/types.ts index 45fc95c..ca22d77 100644 --- a/packages/cwait/src/types.ts +++ b/packages/cwait/src/types.ts @@ -37,7 +37,9 @@ export type ResolvedExecOp = { }; export type ResolvedChildOp = { - ChildOp: 0; + ChildOp: { + ops: ResolvedOp[]; + }; }; export type ResolvedLockOp = { @@ -100,7 +102,7 @@ export type ExecutorMethodArgs = [ caller?: User, thisId?: string, parentId?: string, - saveResult?: boolean, + shouldSaveResult?: boolean, takenFundsIds?: string[], execOpsIndexes?: number[], ];