@layerzerolabs/gated-transaction 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +46 -0
- package/.turbo/turbo-lint.log +4 -0
- package/.turbo/turbo-test.log +14 -0
- package/dist/5INFRBMB.js +14 -0
- package/dist/5INFRBMB.js.map +1 -0
- package/dist/AYIRG6WY.cjs +17 -0
- package/dist/AYIRG6WY.cjs.map +1 -0
- package/dist/BSVO6MXQ.cjs +84 -0
- package/dist/BSVO6MXQ.cjs.map +1 -0
- package/dist/GLQGPA3F.js +74 -0
- package/dist/GLQGPA3F.js.map +1 -0
- package/dist/PQPGPKJO.js +174 -0
- package/dist/PQPGPKJO.js.map +1 -0
- package/dist/UQ2RA5VK.cjs +181 -0
- package/dist/UQ2RA5VK.cjs.map +1 -0
- package/dist/VUOMXK5T.js +6 -0
- package/dist/VUOMXK5T.js.map +1 -0
- package/dist/YJF4D23A.cjs +8 -0
- package/dist/YJF4D23A.cjs.map +1 -0
- package/dist/gatedTx.cjs +17 -0
- package/dist/gatedTx.cjs.map +1 -0
- package/dist/gatedTx.d.ts +21 -0
- package/dist/gatedTx.d.ts.map +1 -0
- package/dist/gatedTx.js +4 -0
- package/dist/gatedTx.js.map +1 -0
- package/dist/index.cjs +47 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/resolver.cjs +19 -0
- package/dist/resolver.cjs.map +1 -0
- package/dist/resolver.d.ts +25 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +6 -0
- package/dist/resolver.js.map +1 -0
- package/dist/types.cjs +29 -0
- package/dist/types.cjs.map +1 -0
- package/dist/types.d.ts +143 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/package.json +45 -0
- package/src/gatedTx.ts +37 -0
- package/src/index.ts +3 -0
- package/src/resolver.ts +311 -0
- package/src/types.ts +213 -0
- package/test/resolver.test.ts +250 -0
- package/tsconfig.json +28 -0
- package/tsup.config.ts +8 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { WorkflowFunctions } from '@layerzerolabs/common-workflow';
|
|
4
|
+
|
|
5
|
+
import { constructGatedTransaction, getIdForGatedTransaction } from '../src/gatedTx';
|
|
6
|
+
import { resolveGatedTransactions } from '../src/resolver';
|
|
7
|
+
import type { GatedTransaction } from '../src/types';
|
|
8
|
+
import { GatedTransactionStatus } from '../src/types';
|
|
9
|
+
|
|
10
|
+
type Handler = {
|
|
11
|
+
initial?: (...args: any[]) => any | Promise<any>;
|
|
12
|
+
final?: (...args: any[]) => any | Promise<any>;
|
|
13
|
+
throwsInitial?: any;
|
|
14
|
+
throwsFinal?: any;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const makeRegistry = (handlers: Record<string, Handler>) => ({
|
|
18
|
+
callByPointer: async (fn: string, params: any[]) => {
|
|
19
|
+
const h = handlers[fn];
|
|
20
|
+
if (!h) throw new Error(`No handler for ${fn}`);
|
|
21
|
+
const isFinal = params.length > 0;
|
|
22
|
+
if (isFinal) {
|
|
23
|
+
if (h.throwsFinal !== undefined) throw h.throwsFinal;
|
|
24
|
+
if (h.final) return await h.final(...params);
|
|
25
|
+
if (h.initial) return await h.initial(...params);
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (h.throwsInitial !== undefined) throw h.throwsInitial;
|
|
29
|
+
if (h.initial) return await h.initial(...params);
|
|
30
|
+
return undefined;
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const makeTx = (name: string, functionPointer: string, expected: any, deps?: GatedTransaction[]) =>
|
|
35
|
+
constructGatedTransaction({
|
|
36
|
+
name,
|
|
37
|
+
transaction: { name } as any,
|
|
38
|
+
check: {
|
|
39
|
+
functionPointer: functionPointer as any,
|
|
40
|
+
params: [],
|
|
41
|
+
expectedResult: { operator: '=', comparisonValue: expected },
|
|
42
|
+
},
|
|
43
|
+
uniqueIdKeys: { name },
|
|
44
|
+
dependencies: deps,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const makeProcessedTx = () =>
|
|
48
|
+
({
|
|
49
|
+
chainName: 'test-chain',
|
|
50
|
+
timestamps: { created: Date.now() },
|
|
51
|
+
}) as any;
|
|
52
|
+
|
|
53
|
+
const makeFn = (): WorkflowFunctions =>
|
|
54
|
+
({
|
|
55
|
+
condition: async (predicate) => {
|
|
56
|
+
while (true) {
|
|
57
|
+
if (predicate()) {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
await new Promise((res) => setTimeout(res, 100));
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
}) as WorkflowFunctions;
|
|
64
|
+
|
|
65
|
+
describe('resolveGatedTransactions - dependency and edge cases', () => {
|
|
66
|
+
test('internal dependency NO_OP enables dependent to execute and succeed', async () => {
|
|
67
|
+
const registry = makeRegistry({
|
|
68
|
+
checkA: { initial: () => true }, // A already satisfied
|
|
69
|
+
checkB: {
|
|
70
|
+
initial: () => false, // B needs sending
|
|
71
|
+
final: () => true, // After sending, satisfied
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const processTx = vi.fn(async (_tx) => makeProcessedTx());
|
|
76
|
+
|
|
77
|
+
const txA = makeTx('A', 'checkA', true);
|
|
78
|
+
const txB = makeTx('B', 'checkB', true, [txA]);
|
|
79
|
+
|
|
80
|
+
const [resA, resB] = await resolveGatedTransactions({
|
|
81
|
+
activityRegistry: registry as any,
|
|
82
|
+
gatedTxes: [txA, txB],
|
|
83
|
+
processTx,
|
|
84
|
+
fn: makeFn(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
expect(resA.result.status).toBe(GatedTransactionStatus.NO_OP);
|
|
88
|
+
expect(resB.result.status).toBe(GatedTransactionStatus.SUCCESS);
|
|
89
|
+
expect(processTx).toHaveBeenCalledTimes(1);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('internal dependency SUCCESS enables dependent to execute', async () => {
|
|
93
|
+
const registry = makeRegistry({
|
|
94
|
+
checkA: { initial: () => false, final: () => true },
|
|
95
|
+
checkB: { initial: () => false, final: () => true },
|
|
96
|
+
});
|
|
97
|
+
const processTx = vi.fn(async (_tx) => makeProcessedTx());
|
|
98
|
+
|
|
99
|
+
const txA = makeTx('A', 'checkA', true);
|
|
100
|
+
const txB = makeTx('B', 'checkB', true, [txA]);
|
|
101
|
+
|
|
102
|
+
const [resA, resB] = await resolveGatedTransactions({
|
|
103
|
+
activityRegistry: registry as any,
|
|
104
|
+
gatedTxes: [txA, txB],
|
|
105
|
+
processTx,
|
|
106
|
+
fn: makeFn(),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
expect(resA.result.status).toBe(GatedTransactionStatus.SUCCESS);
|
|
110
|
+
expect(resB.result.status).toBe(GatedTransactionStatus.SUCCESS);
|
|
111
|
+
expect(processTx).toHaveBeenCalledTimes(2);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('internal dependency TRANSACTION_FAILED causes dependent DEPENDENCY_FAILED', async () => {
|
|
115
|
+
const registry = makeRegistry({
|
|
116
|
+
checkA: { initial: () => false, final: () => false },
|
|
117
|
+
checkB: { initial: () => false, final: () => true },
|
|
118
|
+
});
|
|
119
|
+
const processTx = vi.fn(async (_tx) => {
|
|
120
|
+
throw new Error('failed to submit');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const txA = makeTx('A', 'checkA', true);
|
|
124
|
+
const txB = makeTx('B', 'checkB', true, [txA]);
|
|
125
|
+
|
|
126
|
+
const [resA, resB] = await resolveGatedTransactions({
|
|
127
|
+
activityRegistry: registry as any,
|
|
128
|
+
gatedTxes: [txA, txB],
|
|
129
|
+
processTx,
|
|
130
|
+
fn: makeFn(),
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(resA.result.status).toBe(GatedTransactionStatus.TRANSACTION_FAILED);
|
|
134
|
+
expect(resB.result.status).toBe(GatedTransactionStatus.DEPENDENCY_FAILED);
|
|
135
|
+
expect(processTx).toHaveBeenCalledTimes(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('external dependency satisfied by precheck allows dependent to execute', async () => {
|
|
139
|
+
const registry = makeRegistry({
|
|
140
|
+
checkExt: { initial: () => true },
|
|
141
|
+
checkB: { initial: () => false, final: () => true },
|
|
142
|
+
});
|
|
143
|
+
const processTx = vi.fn(async (_tx) => makeProcessedTx());
|
|
144
|
+
|
|
145
|
+
const ext = makeTx('Ext', 'checkExt', true);
|
|
146
|
+
const txB = makeTx('B', 'checkB', true, [ext]);
|
|
147
|
+
|
|
148
|
+
const [resB] = await resolveGatedTransactions({
|
|
149
|
+
activityRegistry: registry as any,
|
|
150
|
+
gatedTxes: [txB],
|
|
151
|
+
processTx,
|
|
152
|
+
fn: makeFn(),
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
expect(resB.result.status).toBe(GatedTransactionStatus.SUCCESS);
|
|
156
|
+
expect(processTx).toHaveBeenCalledTimes(1);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test('external dependency unsatisfied by precheck causes dependent DEPENDENCY_FAILED', async () => {
|
|
160
|
+
const registry = makeRegistry({
|
|
161
|
+
checkExt: { initial: () => false },
|
|
162
|
+
checkB: { initial: () => false, final: () => true },
|
|
163
|
+
});
|
|
164
|
+
const processTx = vi.fn(async (_tx) => makeProcessedTx());
|
|
165
|
+
|
|
166
|
+
const ext = makeTx('Ext', 'checkExt', true);
|
|
167
|
+
const txB = makeTx('B', 'checkB', true, [ext]);
|
|
168
|
+
|
|
169
|
+
const [resB] = await resolveGatedTransactions({
|
|
170
|
+
activityRegistry: registry as any,
|
|
171
|
+
gatedTxes: [txB],
|
|
172
|
+
processTx,
|
|
173
|
+
fn: makeFn(),
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
expect(resB.result.status).toBe(GatedTransactionStatus.DEPENDENCY_FAILED);
|
|
177
|
+
expect(processTx).toHaveBeenCalledTimes(0);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test('confirmation denial throws before sending', async () => {
|
|
181
|
+
const registry = makeRegistry({ checkA: { initial: () => false } });
|
|
182
|
+
const processTx = vi.fn(async (_tx) => makeProcessedTx());
|
|
183
|
+
const txA = makeTx('A', 'checkA', true);
|
|
184
|
+
|
|
185
|
+
const result = await resolveGatedTransactions({
|
|
186
|
+
activityRegistry: registry as any,
|
|
187
|
+
gatedTxes: [txA],
|
|
188
|
+
processTx,
|
|
189
|
+
userInteractionCallbacks: { confirmationCallback: async () => false },
|
|
190
|
+
fn: makeFn(),
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
expect(result[0].result.status).toStrictEqual(GatedTransactionStatus.DENIED);
|
|
194
|
+
expect(processTx).toHaveBeenCalledTimes(0);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test('same name + chain, different cache keys', async () => {
|
|
198
|
+
const registry = {
|
|
199
|
+
callByPointer: async (_fn: string, params: any[]) => {
|
|
200
|
+
// final check when params length > 0
|
|
201
|
+
return params.length > 0 ? true : false;
|
|
202
|
+
},
|
|
203
|
+
} as any;
|
|
204
|
+
|
|
205
|
+
const processTx = vi.fn(
|
|
206
|
+
async (_tx) =>
|
|
207
|
+
({
|
|
208
|
+
chainName: 'test-chain',
|
|
209
|
+
timestamps: { created: Date.now() },
|
|
210
|
+
}) as any,
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const tx1 = constructGatedTransaction({
|
|
214
|
+
name: 'X',
|
|
215
|
+
transaction: { name: 'X', chainName: 'chainA' } as any,
|
|
216
|
+
check: {
|
|
217
|
+
functionPointer: 'checkX' as any,
|
|
218
|
+
params: [],
|
|
219
|
+
expectedResult: { operator: '=', comparisonValue: true },
|
|
220
|
+
},
|
|
221
|
+
uniqueIdKeys: { key: '1' },
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const tx2 = constructGatedTransaction({
|
|
225
|
+
name: 'X',
|
|
226
|
+
transaction: { name: 'X', chainName: 'chainA' } as any,
|
|
227
|
+
check: {
|
|
228
|
+
functionPointer: 'checkX' as any,
|
|
229
|
+
params: [],
|
|
230
|
+
expectedResult: { operator: '=', comparisonValue: true },
|
|
231
|
+
},
|
|
232
|
+
uniqueIdKeys: { key: '2' },
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const id1 = getIdForGatedTransaction(tx1);
|
|
236
|
+
const id2 = getIdForGatedTransaction(tx2);
|
|
237
|
+
expect(id1).not.toBe(id2);
|
|
238
|
+
|
|
239
|
+
const [r1, r2] = await resolveGatedTransactions({
|
|
240
|
+
activityRegistry: registry as any,
|
|
241
|
+
gatedTxes: [tx1, tx2],
|
|
242
|
+
processTx,
|
|
243
|
+
fn: makeFn(),
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
expect(r1.result.status).toBe(GatedTransactionStatus.SUCCESS);
|
|
247
|
+
expect(r2.result.status).toBe(GatedTransactionStatus.SUCCESS);
|
|
248
|
+
expect(processTx).toHaveBeenCalledTimes(2);
|
|
249
|
+
});
|
|
250
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "@layerzerolabs/typescript-configuration/tsconfig.base.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "./src",
|
|
5
|
+
"outDir": "./dist",
|
|
6
|
+
"strictPropertyInitialization": false,
|
|
7
|
+
"noUnusedLocals": false,
|
|
8
|
+
"noUnusedParameters": false,
|
|
9
|
+
"jsx": "react-jsx"
|
|
10
|
+
},
|
|
11
|
+
"exclude": [
|
|
12
|
+
"node_modules",
|
|
13
|
+
"**/__mocks__/*",
|
|
14
|
+
"**/__tests__/*",
|
|
15
|
+
"**/*.spec.ts",
|
|
16
|
+
"**/*.test.ts",
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"include": ["src/**/*"],
|
|
20
|
+
"ts-node": {
|
|
21
|
+
"files": true,
|
|
22
|
+
"experimentalResolverFeatures": true,
|
|
23
|
+
"compilerOptions": {
|
|
24
|
+
"module": "CommonJS",
|
|
25
|
+
"types": ["node"]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|