feat: implement generator
This commit is contained in:
parent
c95c4e5e3f
commit
7fdda647bf
@ -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<WordClaimBody> | null = yield readOp(createWordKey(id));
|
||||
|
||||
const newWord = (wordClaim?.body.word ?? '') + '!!!';
|
||||
const newId = hashCode(newWord);
|
||||
yield storeOp(constructClaim(createWordKey(newId), { word: newWord }, '0x0'));
|
||||
}
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -1,3 +0,0 @@
|
||||
export * from './types';
|
||||
export * from './methods';
|
||||
export * from './wrappers';
|
||||
@ -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<void> {
|
||||
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);
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
declare namespace os {
|
||||
function readdir(path: string): [any[], number];
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
declare namespace std {
|
||||
function open(path: string, mode: string): any;
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import { Context, NewTx } from '@coinweb/contract-kit';
|
||||
|
||||
export type MethodCallback = (context: Context) => Promise<NewTx[]> | NewTx[];
|
||||
|
||||
export type ContractHandlers = {
|
||||
handlers: { [key: string]: MethodCallback };
|
||||
};
|
||||
@ -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);
|
||||
};
|
||||
}
|
||||
@ -1,3 +1,2 @@
|
||||
export * from './contract-kit';
|
||||
export * from './onchain';
|
||||
export * from './types';
|
||||
|
||||
@ -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<void>) =>
|
||||
async (ctx: Context): Promise<NewTx[]> => {
|
||||
(method: (...args: any[]) => Generator) =>
|
||||
(ctx: Context): NewTx[] => {
|
||||
setRawContext(ctx);
|
||||
|
||||
const { args, methodName, ops } = handleState();
|
||||
|
||||
const execution = new Promise<void>((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<unknown>;
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
@ -1,3 +1,6 @@
|
||||
export * from './awaited';
|
||||
export * from './block';
|
||||
export * from './read';
|
||||
export * from './resolved';
|
||||
export * from './store';
|
||||
export * from './take';
|
||||
|
||||
@ -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 = <TClaim extends Claim = TypedClaim>(key: ClaimKey) => {
|
||||
const { op, isOp } = shiftResolvedOp();
|
||||
export const readOp = <TClaim extends Claim = TypedClaim>(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<TClaim | null>((resolve) => {
|
||||
if (isOp) {
|
||||
const claim = op && ((extractRead(op)?.[0] ?? null) as TClaim | null);
|
||||
resolve(claim);
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -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<Claim>((resolve) => {
|
||||
if (isOp) {
|
||||
resolve(result);
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@ -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 = <TClaim extends Claim = TypedClaim>(key: ClaimKey) => {
|
||||
const { op, isOp } = shiftResolvedOp();
|
||||
export const takeOp = <TClaim extends Claim = TypedClaim>(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<TClaim | null>((resolve) => {
|
||||
if (isOp) {
|
||||
const claim = op && extractTake(op);
|
||||
resolve(claim as TClaim | null);
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
1
packages/cwait/src/onchain/signal.ts
Normal file
1
packages/cwait/src/onchain/signal.ts
Normal file
@ -0,0 +1 @@
|
||||
export const signal = Symbol('signal');
|
||||
@ -1,4 +1,4 @@
|
||||
VITE_API_URL='https://api-cloud.coinweb.io/wallet'
|
||||
VITE_EXPLORER_URL='https://explorer.coinweb.io'
|
||||
|
||||
VITE_CONTRACT_ADDRESS="0x51c4d9ea120f185aab78a2fa74eba43f4e6449a261d6d369fd077012a202aa03"
|
||||
VITE_CONTRACT_ADDRESS="0xe1e81e77f901f630ab760a84b6bb84c96c3a4ec996a287efac2c8502e94a287f"
|
||||
Loading…
x
Reference in New Issue
Block a user