Compare commits

...

1 Commits

Author SHA1 Message Date
a0516f67ae wip: use callstack 2025-04-15 10:14:51 +03:00
3 changed files with 158 additions and 81 deletions

View File

@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { import {
Context, Context,
extractContractArgs, extractContractArgs,
@ -15,6 +16,7 @@ import { ResolvedOp, Task } from '../types';
import { context, getRawContext, setRawContext } from './context'; import { context, getRawContext, setRawContext } from './context';
import { getAwaitedOps } from './promisifiedOps/awaited'; import { getAwaitedOps } from './promisifiedOps/awaited';
import { pushResolvedOp } from './promisifiedOps/resolved'; import { pushResolvedOp } from './promisifiedOps/resolved';
import { extractRootParent, getRoot, setRoot } from './utils';
let abortExecution: ((result: boolean) => void) | null = null; let abortExecution: ((result: boolean) => void) | null = null;
@ -22,112 +24,141 @@ const handleState = () => {
const ctx = getRawContext(); const ctx = getRawContext();
const resolvedOps = extractContractArgs(ctx.tx); 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 initialArgs = methodArgs[1] ?? [];
const previousOps = methodArgs[2] ?? []; const previousOps = methodArgs[2] ?? [];
const allResolvedOps = [...previousOps, ...resolvedOps]; const allResolvedOps = [...previousOps, ...resolvedOps];
const root = methodArgs[3];
pushResolvedOp(allResolvedOps); pushResolvedOp(allResolvedOps);
return { return {
args: initialArgs, args: initialArgs,
methodName: methodArgs[0] as string, methodName: methodArgs[0] as string,
ops: allResolvedOps, ops: allResolvedOps,
isChildCall: previousOps.length > 0, isChildCall: resolvedOps.length > 0,
root,
}; };
}; };
type Method = (...args: any[]) => Promise<void>;
type Resolver = (
currentMethod: Method,
args: any[],
resolve: (value: boolean | PromiseLike<boolean>) => 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 = export const executor =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(method: (...args: any[]) => Promise<void>) => (method: (...args: any[]) => Promise<void>) =>
async (ctx: Context): Promise<NewTx[]> => { async (ctx: Context): Promise<NewTx[]> => {
console.log('executor-start'); console.log('executor-start');
setRawContext(ctx); setRawContext(ctx);
const { args, methodName, ops, isChildCall } = handleState(); const { args, methodName, ops, isChildCall, root } = handleState();
if (isChildCall && !isSelfCall(ctx)) { if (isChildCall && !isSelfCall(ctx)) {
throw new Error('Only contract itself can call it'); throw new Error('Only contract itself can call it');
} }
const execution = new Promise<boolean>((resolve, reject) => { const execution = new Promise<boolean>((resolve, reject) => {
abortExecution = resolve; abortExecution = resolve;
method(...args).then( resolver(method, args, resolve, reject, isChildCall, root);
() => { }).catch((error) => {
console.log('executor-resolved'); console.error(error);
resolve(true); throw error;
}, });
() => {
console.log('executor-rejected');
reject();
}
);
});
//@ts-expect-error //@ts-expect-error
// eslint-disable-next-line @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-call
os.setTimeout(() => { os.setTimeout(() => {
abortExecution?.(false); abortExecution?.(false);
}, 0); }, 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) { if (!awaitedOps.length) {
return [constructContinueTx(ctx, [constructDataUnverified({ isFullyExecuted })]), ...queue.gateway.unlock(ctx)]; 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()!); callArgs.push(awaitedOps.pop()!);
} }
}
if (callArgs[0]?.isBatch) { const callFee = 700n + BigInt(callArgs.length) * 100n;
while (awaitedOps.at(-1)?.isBatch) { const opTxFee = awaitedOps.length ? 100n + BigInt(awaitedOps.length) * 100n : 0n;
callArgs.push(awaitedOps.pop()!);
}
}
const callFee = 700n + BigInt(callArgs.length) * 100n; return [
const opTxFee = awaitedOps.length ? 100n + BigInt(awaitedOps.length) * 100n : 0n; constructContinueTx(
ctx,
return [ awaitedOps.map(({ op }) => op),
constructContinueTx( [
ctx, {
awaitedOps.map(({ op }) => op), callInfo: {
[ ref: constructContractRef(context.issuer, []),
{ methodInfo: {
callInfo: { methodName,
ref: constructContractRef(context.issuer, []), methodArgs: [
methodInfo: { args,
methodName, [...ops, ...Array(awaitedOps.length).fill({ SlotOp: 0 } satisfies ResolvedOp)] satisfies ResolvedOp[],
methodArgs: [ getRoot(),
args, callArgs,
[ awaitedOps,
...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),
}, },
contractInfo: {
providedCweb: availableCweb - callFee - opTxFee,
authenticated: authInfo,
},
contractArgs: callArgs.map(({ op }) => op),
}, },
] },
), ]
]; ),
}; ];
};

View File

@ -1,10 +1,56 @@
export const getStack = ({ skip = 0 }: { skip?: number } = {}) => let rootParent = '';
new Error().stack let root = '';
?.split('\n')
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) .slice(2 + skip)
.map((line) => { .map((line) => {
const match = line.match(/at\s+([^\s(]+)/); const match = line.match(/at\s+([^\s(]+)/);
return match ? match[1] : ''; return match ? match[1] : '';
}) })
.filter((name) => name && name !== 'Promise') .filter((name) => name && !['<anonymous>', 'Promise'].includes(name));
.join('@') || '';
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;
};

View File

@ -1,4 +1,4 @@
VITE_API_URL='https://api-cloud.coinweb.io/wallet' VITE_API_URL='https://api-cloud.coinweb.io/wallet'
VITE_EXPLORER_URL='https://explorer.coinweb.io' VITE_EXPLORER_URL='https://explorer.coinweb.io'
VITE_CONTRACT_ADDRESS="0xd0b364db47a35710320a815fbcae80435ae83e0a1730e70529f1b0b2a6b7bfd5" VITE_CONTRACT_ADDRESS="0x6fd41997fb840f91cc9d3b1bf69015dc2cef975015b52d53bf04f4380ddd66c4"