@human-protocol/sdk 1.0.0 → 1.0.2

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.
@@ -0,0 +1,716 @@
1
+ import { getPublicURL } from './../src/storage';
2
+ import { EscrowStatus, Job } from '../src';
3
+ import { upload } from '../src/storage';
4
+ import { toFullDigit } from '../src/utils';
5
+ import {
6
+ DEFAULT_GAS_PAYER_ADDR,
7
+ DEFAULT_GAS_PAYER_PRIVKEY,
8
+ DEFAULT_HMTOKEN_ADDR,
9
+ DEFAULT_STAKING_ADDR,
10
+ NOT_TRUSTED_OPERATOR_PRIVKEY,
11
+ REPUTATION_ORACLE_PRIVKEY,
12
+ TRUSTED_OPERATOR1_ADDR,
13
+ TRUSTED_OPERATOR1_PRIVKEY,
14
+ TRUSTED_OPERATOR2_ADDR,
15
+ WORKER1_ADDR,
16
+ WORKER2_ADDR,
17
+ WORKER3_ADDR,
18
+ } from './utils/constants';
19
+ import { manifest } from './utils/manifest';
20
+
21
+ jest.mock('../src/storage', () => ({
22
+ ...jest.requireActual('../src/storage'),
23
+ upload: jest.fn().mockResolvedValue({
24
+ key: 'uploaded-key',
25
+ hash: 'uploaded-hash',
26
+ }),
27
+ download: jest.fn().mockResolvedValue({
28
+ results: 0,
29
+ }),
30
+ getPublicURL: jest.fn().mockResolvedValue('public-url'),
31
+ }));
32
+
33
+ const setupJob = async (job: Job) => {
34
+ await job.initialize();
35
+ await job.stake(1);
36
+ await job.launch();
37
+ await job.setup();
38
+ };
39
+
40
+ describe('Test Job', () => {
41
+ describe('New job', () => {
42
+ let job: Job;
43
+
44
+ beforeEach(() => {
45
+ job = new Job({
46
+ gasPayer: DEFAULT_GAS_PAYER_PRIVKEY,
47
+ reputationOracle: REPUTATION_ORACLE_PRIVKEY,
48
+ manifest: manifest,
49
+ hmTokenAddr: DEFAULT_HMTOKEN_ADDR,
50
+ stakingAddr: DEFAULT_STAKING_ADDR,
51
+ logLevel: 'error',
52
+ });
53
+ });
54
+
55
+ afterEach(() => {
56
+ jest.clearAllMocks();
57
+ });
58
+
59
+ it('Should be able to initializes the job by deploying escrow factory', async () => {
60
+ const initialized = await job.initialize();
61
+ expect(initialized).toBe(true);
62
+
63
+ expect(await job.contractData?.factory?.address).not.toBeNull();
64
+ });
65
+
66
+ it('Should be able to launch the job after staking', async () => {
67
+ // Fail to launch the job before initialization
68
+ expect(await job.launch()).toBe(false);
69
+
70
+ await job.initialize();
71
+ await job.stake(1);
72
+
73
+ expect(await job.launch()).toBe(true);
74
+ expect(await job.status()).toBe(EscrowStatus.Launched);
75
+ });
76
+
77
+ it('Should be able to setup the job', async () => {
78
+ // Fail to setup the job before launch
79
+ expect(await job.setup()).toBe(false);
80
+
81
+ await job.initialize();
82
+ await job.stake(1);
83
+
84
+ await job.launch();
85
+
86
+ expect(await job.setup()).toBe(true);
87
+ });
88
+
89
+ it('Should be able to add trusted handlers', async () => {
90
+ await job.initialize();
91
+ await job.stake(1);
92
+
93
+ await job.launch();
94
+
95
+ expect(await job.isTrustedHandler(DEFAULT_GAS_PAYER_ADDR)).toBe(true);
96
+
97
+ expect(
98
+ await job.addTrustedHandlers([
99
+ TRUSTED_OPERATOR1_ADDR,
100
+ TRUSTED_OPERATOR2_ADDR,
101
+ ])
102
+ ).toBe(true);
103
+
104
+ expect(await job.isTrustedHandler(TRUSTED_OPERATOR1_ADDR)).toBe(true);
105
+ expect(await job.isTrustedHandler(TRUSTED_OPERATOR2_ADDR)).toBe(true);
106
+ });
107
+
108
+ it('Should be able to bulk payout workers', async () => {
109
+ await setupJob(job);
110
+
111
+ expect(
112
+ await job.bulkPayout(
113
+ [
114
+ {
115
+ address: WORKER1_ADDR,
116
+ amount: 20,
117
+ },
118
+ {
119
+ address: WORKER2_ADDR,
120
+ amount: 50,
121
+ },
122
+ ],
123
+ {}
124
+ )
125
+ ).toBe(true);
126
+
127
+ // The escrow contract is still in Partial state as there's still balance left.
128
+ expect((await job.balance())?.toString()).toBe(
129
+ toFullDigit(30).toString()
130
+ );
131
+ expect(await job.status()).toBe(EscrowStatus.Partial);
132
+
133
+ // Trying to pay more than the contract balance results in failure.
134
+ expect(
135
+ await job.bulkPayout(
136
+ [
137
+ {
138
+ address: WORKER3_ADDR,
139
+ amount: 50,
140
+ },
141
+ ],
142
+ {}
143
+ )
144
+ ).toBe(false);
145
+
146
+ // Paying the remaining amount empties the escrow and updates the status correctly.
147
+ expect(
148
+ await job.bulkPayout(
149
+ [
150
+ {
151
+ address: WORKER3_ADDR,
152
+ amount: 30,
153
+ },
154
+ ],
155
+ {}
156
+ )
157
+ ).toBe(true);
158
+ expect((await job.balance())?.toString()).toBe(toFullDigit(0).toString());
159
+ expect(await job.status()).toBe(EscrowStatus.Paid);
160
+ });
161
+
162
+ it('Should encrypt result, when bulk paying out workers', async () => {
163
+ await setupJob(job);
164
+
165
+ jest.clearAllMocks();
166
+ const finalResults = { results: 0 };
167
+ await job.bulkPayout(
168
+ [
169
+ {
170
+ address: WORKER1_ADDR,
171
+ amount: 100,
172
+ },
173
+ ],
174
+ finalResults,
175
+ true
176
+ );
177
+
178
+ expect(upload).toHaveBeenCalledWith(
179
+ job.storageAccessData,
180
+ finalResults,
181
+ job.providerData?.reputationOracle?.publicKey,
182
+ true,
183
+ false
184
+ );
185
+ expect(upload).toHaveBeenCalledTimes(1);
186
+ });
187
+
188
+ it('Should not encrypt result, when bulk paying out workers', async () => {
189
+ await setupJob(job);
190
+
191
+ jest.clearAllMocks();
192
+ const finalResults = { results: 0 };
193
+ await job.bulkPayout(
194
+ [
195
+ {
196
+ address: WORKER1_ADDR,
197
+ amount: 100,
198
+ },
199
+ ],
200
+ finalResults,
201
+ false
202
+ );
203
+
204
+ expect(upload).toHaveBeenCalledWith(
205
+ job.storageAccessData,
206
+ finalResults,
207
+ job.providerData?.reputationOracle?.publicKey,
208
+ false,
209
+ false
210
+ );
211
+ expect(upload).toHaveBeenCalledTimes(1);
212
+ });
213
+
214
+ it('Should store result in private storage, when bulk paying out workers', async () => {
215
+ await setupJob(job);
216
+
217
+ jest.clearAllMocks();
218
+ const finalResults = { results: 0 };
219
+ await job.bulkPayout(
220
+ [
221
+ {
222
+ address: WORKER1_ADDR,
223
+ amount: 100,
224
+ },
225
+ ],
226
+ finalResults,
227
+ false,
228
+ false
229
+ );
230
+
231
+ expect(upload).toHaveBeenCalledWith(
232
+ job.storageAccessData,
233
+ finalResults,
234
+ job.providerData?.reputationOracle?.publicKey,
235
+ false,
236
+ false
237
+ );
238
+ expect(upload).toHaveBeenCalledTimes(1);
239
+ });
240
+
241
+ it('Should store result in public storage, when bulk paying out workers', async () => {
242
+ await setupJob(job);
243
+
244
+ jest.clearAllMocks();
245
+ const finalResults = { results: 0 };
246
+ await job.bulkPayout(
247
+ [
248
+ {
249
+ address: WORKER1_ADDR,
250
+ amount: 50,
251
+ },
252
+ ],
253
+ finalResults,
254
+ false,
255
+ true
256
+ );
257
+
258
+ expect(upload).toHaveBeenCalledWith(
259
+ job.storageAccessData,
260
+ finalResults,
261
+ job.providerData?.reputationOracle?.publicKey,
262
+ false,
263
+ true
264
+ );
265
+ expect(upload).toHaveBeenCalledTimes(1);
266
+ expect(getPublicURL).toHaveBeenCalledTimes(1);
267
+ });
268
+
269
+ it('Should return final result', async () => {
270
+ await setupJob(job);
271
+
272
+ const finalResults = { results: 0 };
273
+ await job.bulkPayout(
274
+ [
275
+ {
276
+ address: WORKER1_ADDR,
277
+ amount: 100,
278
+ },
279
+ ],
280
+ finalResults,
281
+ true
282
+ );
283
+
284
+ expect(JSON.stringify(await job.finalResults())).toBe(
285
+ JSON.stringify(finalResults)
286
+ );
287
+ });
288
+
289
+ it('Should be able to abort the job', async () => {
290
+ await setupJob(job);
291
+
292
+ expect(await job.abort()).toBe(true);
293
+ });
294
+
295
+ it('Should be able to abort partially paid job', async () => {
296
+ await setupJob(job);
297
+
298
+ const finalResults = { results: 0 };
299
+ await job.bulkPayout(
300
+ [
301
+ {
302
+ address: WORKER1_ADDR,
303
+ amount: 50,
304
+ },
305
+ ],
306
+ finalResults,
307
+ true
308
+ );
309
+
310
+ expect(await job.abort()).toBe(true);
311
+ });
312
+
313
+ it('Should not be able to abort fully paid job', async () => {
314
+ await setupJob(job);
315
+
316
+ const finalResults = { results: 0 };
317
+ await job.bulkPayout(
318
+ [
319
+ {
320
+ address: WORKER1_ADDR,
321
+ amount: 100,
322
+ },
323
+ ],
324
+ finalResults,
325
+ true
326
+ );
327
+
328
+ expect(await job.abort()).toBe(false);
329
+ });
330
+
331
+ it('Should be able to cancel the job', async () => {
332
+ await setupJob(job);
333
+
334
+ expect(await job.cancel()).toBe(true);
335
+ expect((await job.balance())?.toString()).toBe(toFullDigit(0).toString());
336
+ });
337
+
338
+ it('Should be able to cancel partially paid job', async () => {
339
+ await setupJob(job);
340
+
341
+ const finalResults = { results: 0 };
342
+ await job.bulkPayout(
343
+ [
344
+ {
345
+ address: WORKER1_ADDR,
346
+ amount: 50,
347
+ },
348
+ ],
349
+ finalResults,
350
+ true
351
+ );
352
+
353
+ expect(await job.cancel()).toBe(true);
354
+ expect((await job.balance())?.toString()).toBe(toFullDigit(0).toString());
355
+ });
356
+
357
+ it('Should not be able to cancel paid job', async () => {
358
+ await setupJob(job);
359
+
360
+ const finalResults = { results: 0 };
361
+ await job.bulkPayout(
362
+ [
363
+ {
364
+ address: WORKER1_ADDR,
365
+ amount: 100,
366
+ },
367
+ ],
368
+ finalResults,
369
+ true
370
+ );
371
+
372
+ expect(await job.cancel()).toBe(false);
373
+ });
374
+ });
375
+
376
+ describe('Access existing job from trusted handler', () => {
377
+ let job: Job;
378
+
379
+ beforeEach(async () => {
380
+ const originalJob = new Job({
381
+ gasPayer: DEFAULT_GAS_PAYER_PRIVKEY,
382
+ reputationOracle: REPUTATION_ORACLE_PRIVKEY,
383
+ manifest: manifest,
384
+ hmTokenAddr: DEFAULT_HMTOKEN_ADDR,
385
+ stakingAddr: DEFAULT_STAKING_ADDR,
386
+ trustedHandlers: [TRUSTED_OPERATOR1_PRIVKEY],
387
+ logLevel: 'error',
388
+ });
389
+
390
+ await setupJob(originalJob);
391
+
392
+ job = new Job({
393
+ gasPayer: NOT_TRUSTED_OPERATOR_PRIVKEY,
394
+ hmTokenAddr: DEFAULT_HMTOKEN_ADDR,
395
+ reputationOracle: REPUTATION_ORACLE_PRIVKEY,
396
+ escrowAddr: originalJob.contractData?.escrowAddr,
397
+ factoryAddr: originalJob.contractData?.factoryAddr,
398
+ trustedHandlers: [TRUSTED_OPERATOR1_PRIVKEY],
399
+ logLevel: 'error',
400
+ });
401
+ });
402
+
403
+ afterEach(() => {
404
+ jest.clearAllMocks();
405
+ });
406
+
407
+ it('Should be able to initializes the job by accessing existing escrow', async () => {
408
+ expect(await job.initialize()).toBe(true);
409
+
410
+ expect(await job.manifestData?.manifestlink?.url).toBe('uploaded-key');
411
+ expect(await job.manifestData?.manifestlink?.hash).toBe('uploaded-hash');
412
+ });
413
+
414
+ it('Should not be able to launch the job again', async () => {
415
+ await job.initialize();
416
+
417
+ expect(await job.launch()).toBe(false);
418
+ expect(await job.status()).toBe(EscrowStatus.Pending);
419
+ });
420
+
421
+ it('Should not be able to setup the job again', async () => {
422
+ await job.initialize();
423
+
424
+ expect(await job.setup()).toBe(false);
425
+
426
+ expect((await job.balance())?.toString()).toBe(
427
+ toFullDigit(100).toString()
428
+ );
429
+ expect(await job.manifestData?.manifestlink?.url).toBe('uploaded-key');
430
+ expect(await job.manifestData?.manifestlink?.hash).toBe('uploaded-hash');
431
+ });
432
+
433
+ it('Should be able to add trusted handlers', async () => {
434
+ await job.initialize();
435
+
436
+ expect(await job.isTrustedHandler(DEFAULT_GAS_PAYER_ADDR)).toBe(true);
437
+
438
+ expect(
439
+ await job.addTrustedHandlers([
440
+ TRUSTED_OPERATOR1_ADDR,
441
+ TRUSTED_OPERATOR2_ADDR,
442
+ ])
443
+ ).toBe(true);
444
+
445
+ expect(await job.isTrustedHandler(TRUSTED_OPERATOR1_ADDR)).toBe(true);
446
+ expect(await job.isTrustedHandler(TRUSTED_OPERATOR2_ADDR)).toBe(true);
447
+ });
448
+
449
+ it('Should be able to bulk payout workers', async () => {
450
+ await job.initialize();
451
+
452
+ expect(
453
+ await job.bulkPayout(
454
+ [
455
+ {
456
+ address: WORKER1_ADDR,
457
+ amount: 20,
458
+ },
459
+ {
460
+ address: WORKER2_ADDR,
461
+ amount: 50,
462
+ },
463
+ ],
464
+ {}
465
+ )
466
+ ).toBe(true);
467
+
468
+ // The escrow contract is still in Partial state as there's still balance left.
469
+ expect((await job.balance())?.toString()).toBe(
470
+ toFullDigit(30).toString()
471
+ );
472
+ expect(await job.status()).toBe(EscrowStatus.Partial);
473
+
474
+ // Trying to pay more than the contract balance results in failure.
475
+ expect(
476
+ await job.bulkPayout(
477
+ [
478
+ {
479
+ address: WORKER3_ADDR,
480
+ amount: 50,
481
+ },
482
+ ],
483
+ {}
484
+ )
485
+ ).toBe(false);
486
+
487
+ // Paying the remaining amount empties the escrow and updates the status correctly.
488
+ expect(
489
+ await job.bulkPayout(
490
+ [
491
+ {
492
+ address: WORKER3_ADDR,
493
+ amount: 30,
494
+ },
495
+ ],
496
+ {}
497
+ )
498
+ ).toBe(true);
499
+ expect((await job.balance())?.toString()).toBe(toFullDigit(0).toString());
500
+ expect(await job.status()).toBe(EscrowStatus.Paid);
501
+ });
502
+
503
+ it('Should encrypt result, when bulk paying out workers', async () => {
504
+ await job.initialize();
505
+
506
+ jest.clearAllMocks();
507
+ const finalResults = { results: 0 };
508
+ await job.bulkPayout(
509
+ [
510
+ {
511
+ address: WORKER1_ADDR,
512
+ amount: 100,
513
+ },
514
+ ],
515
+ finalResults,
516
+ true
517
+ );
518
+
519
+ expect(upload).toHaveBeenCalledWith(
520
+ job.storageAccessData,
521
+ finalResults,
522
+ job.providerData?.reputationOracle?.publicKey,
523
+ true,
524
+ false
525
+ );
526
+ expect(upload).toHaveBeenCalledTimes(1);
527
+ });
528
+
529
+ it('Should not encrypt result, when bulk paying out workers', async () => {
530
+ await job.initialize();
531
+
532
+ jest.clearAllMocks();
533
+ const finalResults = { results: 0 };
534
+ await job.bulkPayout(
535
+ [
536
+ {
537
+ address: WORKER1_ADDR,
538
+ amount: 100,
539
+ },
540
+ ],
541
+ finalResults,
542
+ false
543
+ );
544
+
545
+ expect(upload).toHaveBeenCalledWith(
546
+ job.storageAccessData,
547
+ finalResults,
548
+ job.providerData?.reputationOracle?.publicKey,
549
+ false,
550
+ false
551
+ );
552
+ expect(upload).toHaveBeenCalledTimes(1);
553
+ });
554
+
555
+ it('Should store result in private storage, when bulk paying out workers', async () => {
556
+ await job.initialize();
557
+
558
+ jest.clearAllMocks();
559
+ const finalResults = { results: 0 };
560
+ await job.bulkPayout(
561
+ [
562
+ {
563
+ address: WORKER1_ADDR,
564
+ amount: 100,
565
+ },
566
+ ],
567
+ finalResults,
568
+ false,
569
+ false
570
+ );
571
+
572
+ expect(upload).toHaveBeenCalledWith(
573
+ job.storageAccessData,
574
+ finalResults,
575
+ job.providerData?.reputationOracle?.publicKey,
576
+ false,
577
+ false
578
+ );
579
+ expect(upload).toHaveBeenCalledTimes(1);
580
+ });
581
+
582
+ it('Should store result in public storage, when bulk paying out workers', async () => {
583
+ await job.initialize();
584
+
585
+ jest.clearAllMocks();
586
+ const finalResults = { results: 0 };
587
+ await job.bulkPayout(
588
+ [
589
+ {
590
+ address: WORKER1_ADDR,
591
+ amount: 50,
592
+ },
593
+ ],
594
+ finalResults,
595
+ false,
596
+ true
597
+ );
598
+
599
+ expect(upload).toHaveBeenCalledWith(
600
+ job.storageAccessData,
601
+ finalResults,
602
+ job.providerData?.reputationOracle?.publicKey,
603
+ false,
604
+ true
605
+ );
606
+ expect(upload).toHaveBeenCalledTimes(1);
607
+ expect(getPublicURL).toHaveBeenCalledTimes(1);
608
+ });
609
+
610
+ it('Should return final result', async () => {
611
+ await job.initialize();
612
+
613
+ const finalResults = { results: 0 };
614
+ await job.bulkPayout(
615
+ [
616
+ {
617
+ address: WORKER1_ADDR,
618
+ amount: 100,
619
+ },
620
+ ],
621
+ finalResults,
622
+ true
623
+ );
624
+
625
+ expect(JSON.stringify(await job.finalResults())).toBe(
626
+ JSON.stringify(finalResults)
627
+ );
628
+ });
629
+
630
+ it('Should be able to abort the job', async () => {
631
+ await job.initialize();
632
+
633
+ expect(await job.abort()).toBe(true);
634
+ });
635
+
636
+ it('Should be able to abort partially paid job', async () => {
637
+ await job.initialize();
638
+
639
+ const finalResults = { results: 0 };
640
+ await job.bulkPayout(
641
+ [
642
+ {
643
+ address: WORKER1_ADDR,
644
+ amount: 50,
645
+ },
646
+ ],
647
+ finalResults,
648
+ true
649
+ );
650
+
651
+ expect(await job.abort()).toBe(true);
652
+ });
653
+
654
+ it('Should not be able to abort fully paid job', async () => {
655
+ await job.initialize();
656
+
657
+ const finalResults = { results: 0 };
658
+ await job.bulkPayout(
659
+ [
660
+ {
661
+ address: WORKER1_ADDR,
662
+ amount: 100,
663
+ },
664
+ ],
665
+ finalResults,
666
+ true
667
+ );
668
+
669
+ expect(await job.abort()).toBe(false);
670
+ });
671
+
672
+ it('Should be able to cancel the job', async () => {
673
+ await job.initialize();
674
+
675
+ expect(await job.cancel()).toBe(true);
676
+ expect((await job.balance())?.toString()).toBe(toFullDigit(0).toString());
677
+ });
678
+
679
+ it('Should be able to cancel partially paid job', async () => {
680
+ await job.initialize();
681
+
682
+ const finalResults = { results: 0 };
683
+ await job.bulkPayout(
684
+ [
685
+ {
686
+ address: WORKER1_ADDR,
687
+ amount: 50,
688
+ },
689
+ ],
690
+ finalResults,
691
+ true
692
+ );
693
+
694
+ expect(await job.cancel()).toBe(true);
695
+ expect((await job.balance())?.toString()).toBe(toFullDigit(0).toString());
696
+ });
697
+
698
+ it('Should not be able to cancel paid job', async () => {
699
+ await job.initialize();
700
+
701
+ const finalResults = { results: 0 };
702
+ await job.bulkPayout(
703
+ [
704
+ {
705
+ address: WORKER1_ADDR,
706
+ amount: 100,
707
+ },
708
+ ],
709
+ finalResults,
710
+ true
711
+ );
712
+
713
+ expect(await job.cancel()).toBe(false);
714
+ });
715
+ });
716
+ });