feat: add base mutex methods

This commit is contained in:
Alex 2025-04-25 15:41:29 +03:00
parent cb4a2fb33d
commit 0d5850ec25
19 changed files with 452 additions and 1 deletions

View File

@ -5,12 +5,18 @@ import { addMethodHandler, ContractHandlers, executeHandler, executor } from 'cw
import { PUBLIC_METHODS } from '../offchain/shared'; import { PUBLIC_METHODS } from '../offchain/shared';
import { addWord } from './addWord'; import { addWord } from './addWord';
import { mutexLock } from './mutex/lock';
import { mutexUnlock, mutexUnlockExec } from './mutex/unlock';
const createModule = (): ContractHandlers => { const createModule = (): ContractHandlers => {
const module: ContractHandlers = { handlers: {} }; const module: ContractHandlers = { handlers: {} };
addMethodHandler(module, PUBLIC_METHODS.ADD_WORD, executor(addWord)); addMethodHandler(module, PUBLIC_METHODS.ADD_WORD, executor(addWord));
addMethodHandler(module, '_lock', mutexLock);
addMethodHandler(module, '_unlock', mutexUnlock);
addMethodHandler(module, '_unlock_exec', mutexUnlockExec);
addMethodHandler(module, SELF_REGISTER_HANDLER_NAME, selfRegisterHandler); addMethodHandler(module, SELF_REGISTER_HANDLER_NAME, selfRegisterHandler);
return module; return module;

View File

@ -0,0 +1 @@
export const mutexAccess = () => {};

View File

@ -0,0 +1,43 @@
import {
constructClaim,
constructClaimKey,
constructContinueTx,
constructContractIssuer,
constructContractRef,
constructStore,
Context,
extractContractInfo,
getContractId,
} from '@coinweb/contract-kit';
export const mutexLock = (context: Context) => {
const { providedCweb } = extractContractInfo(context.tx);
const issuer = constructContractIssuer(getContractId(context.tx));
return [
constructContinueTx(
context,
[
constructStore(
constructClaim(constructClaimKey('mutex', context.call.txid.slice(0, 8)), { word: 'CLASH' }, '0x0')
),
],
[
{
callInfo: {
ref: constructContractRef(issuer, []),
methodInfo: {
methodName: '_unlock',
methodArgs: [],
},
contractInfo: {
providedCweb: providedCweb ? providedCweb - 1000n : 1000n,
authenticated: null,
},
contractArgs: [],
},
},
]
),
];
};

View File

@ -0,0 +1,59 @@
import {
constructContinueTx,
constructContractIssuer,
constructContractRef,
constructRangeRead,
constructTake,
Context,
extractContractArgs,
extractContractInfo,
extractRead,
getContractId,
} from '@coinweb/contract-kit';
export const mutexUnlock = (context: Context) => {
const { providedCweb } = extractContractInfo(context.tx);
const issuer = constructContractIssuer(getContractId(context.tx));
return [
constructContinueTx(
context,
[],
[
{
callInfo: {
ref: constructContractRef(issuer, []),
methodInfo: {
methodName: '_unlock_exec',
methodArgs: [],
},
contractInfo: {
providedCweb: (providedCweb ? providedCweb - 1000n : 1000n) / 2n,
authenticated: null,
},
contractArgs: [constructRangeRead(issuer, 'mutex', {}, 10000)],
},
},
{
callInfo: {
ref: constructContractRef(issuer, []),
methodInfo: {
methodName: '_unlock',
methodArgs: [],
},
contractInfo: {
providedCweb: (providedCweb ? providedCweb - 1000n : 1000n) / 2n,
authenticated: null,
},
contractArgs: [],
},
},
]
),
];
};
export const mutexUnlockExec = (context: Context) => {
const claim = extractContractArgs(context.tx)[0]!;
const take = extractRead(claim)![0];
return [constructContinueTx(context, [constructTake(take.content.key)])];
};

View File

@ -0,0 +1,26 @@
import {
constructClaim,
constructClaimKey,
constructStore,
constructTake,
CwebStore,
GStore,
} from '@coinweb/contract-kit';
import { MutexAccessResult, MutexAccessStatus } from '../types';
export const mutexAccessKey = 'mutex_access';
export const constructMutexAccessFirstPart = () => [mutexAccessKey];
export const constructMutexAccessClaimKey = (uniqueId: string) =>
constructClaimKey(constructMutexAccessFirstPart(), [uniqueId]);
export const constructMutexAccessClaim = (uniqueId: string, status: MutexAccessStatus) =>
constructClaim(constructMutexAccessClaimKey(uniqueId), { status } satisfies MutexAccessResult, '0x0');
export const constructMutexAccessClaimStore = (uniqueId: string, status: MutexAccessStatus): GStore<CwebStore> =>
constructStore(constructMutexAccessClaim(uniqueId, status));
export const constructMutexAccessClaimTake = (uniqueId: string) =>
constructTake(constructMutexAccessClaimKey(uniqueId));

View File

@ -0,0 +1,2 @@
export * from './access';
export * from './lock';

View File

@ -0,0 +1,70 @@
import {
constructClaim,
constructClaimKey,
constructContractIssuer,
constructRangeRead,
constructStore,
Context,
getContractId,
GRead,
} from '@coinweb/contract-kit';
import { CwebRead } from '@coinweb/contract-kit/dist/esm/operations/read';
import { toHex } from 'lib/shared';
import { rangeReadLimit } from '../settings';
import { LockedKey, MutexLockState } from '../types';
export const mutexLockKey = 'mutex_lock';
export const constructMutexLockFirstPart = () => [mutexLockKey];
export const constructMutexLockClaimKey = (lockId: string, timestamp: number) =>
constructClaimKey(constructMutexLockFirstPart(), [timestamp, lockId]);
export const constructMutexLockClaim = ({
fee,
keys,
locked,
lockId,
timestamp,
processId,
}: {
lockId: string;
timestamp: number;
locked: boolean;
keys: LockedKey[];
fee: bigint;
processId: string;
}) =>
constructClaim(
constructMutexLockClaimKey(lockId, timestamp),
{ locked, keys, processId } satisfies MutexLockState,
toHex(fee)
);
export const constructMutexLockClaimStore = ({
fee,
keys,
locked,
lockId,
timestamp,
processId,
}: {
fee: bigint;
keys: LockedKey[];
locked: boolean;
lockId: string;
timestamp: number;
processId: string;
}) => constructStore(constructMutexLockClaim({ fee, keys, locked, lockId, timestamp, processId }));
export const constructMutexLockClaimRangeRead = (context: Context): GRead<CwebRead> =>
constructRangeRead(
constructContractIssuer(getContractId(context.tx)),
constructMutexLockFirstPart(),
{},
rangeReadLimit
);
// export const constructMutexLockClaimTake = (lockId: string, timestamp: number) =>
// constructTake(constructMutexLockClaimKey(lockId, timestamp));

View File

@ -0,0 +1,20 @@
import {
execLockMethodName,
getAccessMethodName,
lockMethodName,
mutexExecLock,
mutexGetAccess,
mutexLock,
mutexUnlock,
unlockMethodName,
} from './methods';
export * from './claims';
export * from './types';
export const mutexMethods = {
[execLockMethodName]: mutexExecLock,
[getAccessMethodName]: mutexGetAccess,
[lockMethodName]: mutexLock,
[unlockMethodName]: mutexUnlock,
};

View File

@ -0,0 +1,75 @@
import {
constructClaim,
constructContinueTx,
constructContractRef,
constructStore,
constructTake,
Context,
extractContractArgs,
extractRead,
passCwebFrom,
} from '@coinweb/contract-kit';
import { getCallParameters, getContractIssuer } from 'lib/onchain';
import { toHex, TypedClaim } from 'lib/shared';
import { lockFee } from '../settings';
import { MutexLockState } from '../types';
import { isMatchLockKeys } from '../utils';
import { lockMethodName } from './names';
export const mutexExecLock = (context: Context) => {
const { availableCweb } = getCallParameters(context);
const issuer = getContractIssuer(context);
const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map(
({ content }) => content as TypedClaim<MutexLockState>
);
if (!lockQueue) {
throw new Error('No lock queue found');
}
const alreadyLockedKeys = lockQueue.filter(({ body }) => body.locked).map(({ key }) => key);
const lockCandidate = lockQueue.find(
({ body, key }) => !body.locked && !alreadyLockedKeys.some((lockedKey) => isMatchLockKeys(lockedKey, key))
);
if (!lockCandidate) {
return [];
}
return [
constructContinueTx(
context,
[
passCwebFrom(issuer, availableCweb),
constructTake(lockCandidate.key),
constructStore(
constructClaim(
lockCandidate.key,
{ ...lockCandidate.body, locked: true },
toHex(BigInt(lockCandidate.fees_stored) - lockFee)
)
),
],
[
{
callInfo: {
ref: constructContractRef(issuer, []),
methodInfo: {
methodName: lockMethodName,
methodArgs: [],
},
contractInfo: {
providedCweb: lockFee,
authenticated: null,
},
contractArgs: [],
},
},
]
),
];
};

View File

@ -0,0 +1,32 @@
import { constructContinueTx, Context, extractContractArgs, extractRead } from '@coinweb/contract-kit';
import { getContractArguments } from 'lib/onchain';
import { TypedClaim } from 'lib/shared';
import { constructMutexAccessClaimStore } from '../claims';
import { MutexAccessStatus, MutexGetAccessArgs, MutexLockState } from '../types';
import { isMatchLockKeys } from '../utils';
export const mutexGetAccess = (context: Context) => {
const [claimKey, processId, uniqueId] = getContractArguments<MutexGetAccessArgs>(context);
const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map(
({ content }) => content as TypedClaim<MutexLockState>
);
if (!lockQueue) {
throw new Error('No lock queue found');
}
const isLockedByOtherProcess = lockQueue.some(
({ body }) => body.locked && body.processId !== processId && body.keys.some((key) => isMatchLockKeys(key, claimKey))
);
return [
constructContinueTx(context, [
constructMutexAccessClaimStore(
uniqueId,
isLockedByOtherProcess ? MutexAccessStatus.DENIED : MutexAccessStatus.GRANTED
),
]),
];
};

View File

@ -0,0 +1,5 @@
export * from './execLock';
export * from './getAccess';
export * from './lock';
export * from './names';
export * from './unlock';

View File

@ -0,0 +1,34 @@
import { constructContinueTx, constructContractRef, Context } from '@coinweb/contract-kit';
import { getCallParameters, getContractIssuer } from 'lib/onchain';
import { constructMutexLockClaimRangeRead } from '../claims';
import { execLockMethodName } from './names';
export const mutexLock = (context: Context) => {
const { availableCweb } = getCallParameters(context);
const issuer = getContractIssuer(context);
return [
constructContinueTx(
context,
[],
[
{
callInfo: {
ref: constructContractRef(issuer, []),
methodInfo: {
methodName: execLockMethodName,
methodArgs: [],
},
contractInfo: {
providedCweb: availableCweb - 700n,
authenticated: null,
},
contractArgs: [constructMutexLockClaimRangeRead(context)],
},
},
]
),
];
};

View File

@ -0,0 +1,4 @@
export const lockMethodName = '_mutex_lock';
export const execLockMethodName = '_mutex_execLock';
export const getAccessMethodName = '_mutex_get_access';
export const unlockMethodName = '_mutex_unlock';

View File

@ -0,0 +1,38 @@
import { constructContinueTx, constructContractRef, constructTake, Context, passCwebFrom } from '@coinweb/contract-kit';
import { getCallParameters, getContractArguments, getContractIssuer } from 'lib/onchain';
import { constructMutexLockClaimKey } from '../claims';
import { lockFee } from '../settings';
import { MutexUnlockArgs } from '../types';
import { lockMethodName } from './names';
export const mutexUnlock = (context: Context) => {
const { availableCweb } = getCallParameters(context);
const issuer = getContractIssuer(context);
const [lockId, timestamp] = getContractArguments<MutexUnlockArgs>(context);
return [
constructContinueTx(
context,
[passCwebFrom(issuer, availableCweb), constructTake(constructMutexLockClaimKey(lockId, timestamp))],
[
{
callInfo: {
ref: constructContractRef(issuer, []),
methodInfo: {
methodName: lockMethodName,
methodArgs: [],
},
contractInfo: {
providedCweb: lockFee,
authenticated: null,
},
contractArgs: [],
},
},
]
),
];
};

View File

@ -0,0 +1,4 @@
export const lockFee = 5000n;
export const unlockFee = 1000n + lockFee;
export const rangeReadLimit = 10000;

View File

@ -0,0 +1,22 @@
import { ClaimKey } from '@coinweb/contract-kit';
export type LockedKey = Omit<ClaimKey, 'second_part'> & Partial<Pick<ClaimKey, 'second_part'>>;
export type MutexLockState = {
locked: boolean;
keys: LockedKey[];
processId: string;
};
export enum MutexAccessStatus {
GRANTED = 'granted',
DENIED = 'denied',
}
export type MutexAccessResult = {
status: MutexAccessStatus;
};
export type MutexGetAccessArgs = [claimKey: ClaimKey, processId: string, uniqueId: string];
export type MutexUnlockArgs = [lockId: string, timestamp: number];

View File

@ -0,0 +1 @@
export * from './isMatchLockKeys';

View File

@ -0,0 +1,9 @@
import { LockedKey } from '../types';
export const isMatchLockKeys = (lockKey1: LockedKey, lockKey2: LockedKey) => {
if (!lockKey1.second_part || !lockKey2.second_part) {
return lockKey1.first_part === lockKey2.first_part;
}
return lockKey1.first_part === lockKey2.first_part && lockKey1.second_part === lockKey2.second_part;
};

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="0x8c89cb42634cfe892290845d907af8ec2d482cead42afaef3ccc3cea4446fce5" VITE_CONTRACT_ADDRESS="0xbf9ca1687e3441bed1afd495405778a9c06c2f5173829d47d9b87dfb150063d8"