This commit is contained in:
Alex 2025-06-05 12:35:20 +03:00
parent d110ffee46
commit d5aaecce18
9 changed files with 165 additions and 15 deletions

View File

@ -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: []

View File

@ -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<CwebStore>(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);
});
});

View File

@ -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;
};

View File

@ -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;

View File

@ -86,7 +86,11 @@ export const extractOps = ({
throw new Error('Wrong subcall result');
}
extractedOps.push({ ChildOp: 0 }, ...(nextAfterBlock.TakeOp.result as TypedClaim<ResolvedOp[]>).body);
extractedOps.push({
ChildOp: {
ops: (nextAfterBlock.TakeOp.result as TypedClaim<ResolvedOp[]>).body,
},
});
i += 2;
continue;

View File

@ -29,7 +29,7 @@ export const executor = (method: (...args: any[]) => Promise<void>) => {
}, 0);
try {
setNextExec(() => method(...context.initialArgs));
setNextExec(() => method(...context.initialArgs), []);
const isFullyExecuted = await execLoop();
console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish-end');
return constructTx(isFullyExecuted);

View File

@ -54,7 +54,7 @@ export const cwait = <TAsyncCallback extends (...args: any[]) => Promise<unknown
pushAwaitedTask(constructStore(constructResultClaim(op.ExecOp.id, getUsedOps())));
}
});
}, []);
stopExecution(); //Check: maybe does no affect
@ -62,7 +62,7 @@ export const cwait = <TAsyncCallback extends (...args: any[]) => Promise<unknown
}
if (isResolvedChildOp(op)) {
return asyncCallback(...args);
return setNextExec(() => asyncCallback(...args), op.ChildOp.ops);
}
throw new Error('Exec or Child operation not found');

View File

@ -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<void>;
type ExecTask = () => Promise<unknown>;
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<boolean> => {
const nextExec = execQueue.pop();
@ -17,7 +29,8 @@ export const execLoop = async (): Promise<boolean> => {
const execution = new Promise<boolean>((resolve, reject) => {
abortExecution = resolve;
nextExec().then(
pushResolvedOp(nextExec.ops);
nextExec.task().then(
() => {
resolve(true);
},

View File

@ -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[],
];