@human-protocol/sdk 1.0.1 → 1.0.25

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@human-protocol/sdk",
3
3
  "description": "Human Protocol SDK",
4
- "version": "1.0.1",
4
+ "version": "1.0.25",
5
5
  "files": [
6
6
  "src",
7
7
  "dist",
@@ -14,7 +14,7 @@
14
14
  "clean": "rm -rf ./dist",
15
15
  "build": "npm run clean && tsc",
16
16
  "prepublish": "npm run build",
17
- "test": "./scripts/run-test.sh",
17
+ "test": "concurrently -k -s first -g --hide 0 \"yarn workspace @human-protocol/core local\" \"sleep 5 && jest --runInBand\"",
18
18
  "lint": "eslint .",
19
19
  "lint:fix": "eslint . --fix",
20
20
  "format": "prettier --write '**/*.{ts,json}'"
@@ -38,7 +38,7 @@
38
38
  ]
39
39
  },
40
40
  "dependencies": {
41
- "@human-protocol/core": "^1.0.9",
41
+ "@human-protocol/core": "^1.0.12",
42
42
  "aws-sdk": "^2.1255.0",
43
43
  "crypto": "^1.0.1",
44
44
  "dotenv": "^16.0.3",
@@ -47,6 +47,6 @@
47
47
  "winston": "^3.8.2"
48
48
  },
49
49
  "peerDependencies": {
50
- "@human-protocol/core": "^1.0.9"
50
+ "@human-protocol/core": "^1.0.12"
51
51
  }
52
- }
52
+ }
package/src/error.ts CHANGED
@@ -36,3 +36,8 @@ export const ErrorHMTokenMissing = new Error('HMToken is missing');
36
36
  export const ErrorStorageAccessDataMissing = new Error(
37
37
  'Storage access data is missing'
38
38
  );
39
+
40
+ /**
41
+ * @constant {Error} - The Staking contract is missing.
42
+ */
43
+ export const ErrorStakingMissing = new Error('Staking contract is missing');
package/src/job.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  getEscrow,
21
21
  getEscrowFactory,
22
22
  getHmToken,
23
+ getStaking,
23
24
  toFullDigit,
24
25
  } from './utils';
25
26
  import {
@@ -29,6 +30,7 @@ import {
29
30
  ErrorJobNotLaunched,
30
31
  ErrorManifestMissing,
31
32
  ErrorReputationOracleMissing,
33
+ ErrorStakingMissing,
32
34
  ErrorStorageAccessDataMissing,
33
35
  } from './error';
34
36
  import { createLogger } from './logger';
@@ -94,6 +96,7 @@ export class Job {
94
96
  storageEndpoint,
95
97
  storagePublicBucket,
96
98
  storageBucket,
99
+ stakingAddr,
97
100
  logLevel = 'info',
98
101
  }: JobArguments) {
99
102
  const provider = network
@@ -134,6 +137,7 @@ export class Job {
134
137
  hmTokenAddr,
135
138
  escrowAddr,
136
139
  factoryAddr,
140
+ stakingAddr,
137
141
  };
138
142
 
139
143
  this.manifestData = { manifest };
@@ -174,9 +178,21 @@ export class Job {
174
178
  return false;
175
179
  }
176
180
 
181
+ if (!this.contractData.stakingAddr) {
182
+ this._logError(new Error('Staking contract is missing'));
183
+ return false;
184
+ }
185
+
186
+ this._logger.info('Getting staking...');
187
+ this.contractData.staking = await getStaking(
188
+ this.contractData.stakingAddr,
189
+ this.providerData?.gasPayer
190
+ );
191
+
177
192
  this._logger.info('Deploying escrow factory...');
178
193
  this.contractData.factory = await deployEscrowFactory(
179
194
  this.contractData.hmTokenAddr,
195
+ this.contractData.stakingAddr,
180
196
  this.providerData?.gasPayer
181
197
  );
182
198
  this.contractData.factoryAddr = this.contractData.factory.address;
@@ -197,6 +213,31 @@ export class Job {
197
213
  this.providerData?.gasPayer
198
214
  );
199
215
 
216
+ this._logger.info('Checking if staking is configured...');
217
+ const stakingAddr = await this.contractData.factory.staking();
218
+ if (!stakingAddr) {
219
+ this._logError(new Error('Factory is not configured with staking'));
220
+ this.contractData.factory = undefined;
221
+
222
+ return false;
223
+ }
224
+ this._logger.info('Getting staking...');
225
+ this.contractData.staking = await getStaking(
226
+ stakingAddr,
227
+ this.providerData?.gasPayer
228
+ );
229
+ this.contractData.stakingAddr = stakingAddr;
230
+
231
+ this._logger.info('Checking if reward pool is configured...');
232
+ const rewardPoolAddr = await this.contractData.staking.rewardPool();
233
+ if (!rewardPoolAddr) {
234
+ this._logError(new Error('Staking is not configured with reward pool'));
235
+ this.contractData.staking = undefined;
236
+ this.contractData.factory = undefined;
237
+
238
+ return false;
239
+ }
240
+
200
241
  this._logger.info('Checking if escrow exists in the factory...');
201
242
  const hasEscrow = await this.contractData?.factory.hasEscrow(
202
243
  this.contractData?.escrowAddr
@@ -274,26 +315,32 @@ export class Job {
274
315
 
275
316
  this._logger.info('Launching escrow...');
276
317
 
277
- const txReceipt = await this.contractData?.factory?.createEscrow(
278
- this.providerData?.trustedHandlers?.map(
279
- (trustedHandler) => trustedHandler.address
280
- ) || []
281
- );
318
+ try {
319
+ const txReceipt = await this.contractData?.factory?.createEscrow(
320
+ this.providerData?.trustedHandlers?.map(
321
+ (trustedHandler) => trustedHandler.address
322
+ ) || []
323
+ );
282
324
 
283
- const txResponse = await txReceipt?.wait();
325
+ const txResponse = await txReceipt?.wait();
284
326
 
285
- const event = txResponse?.events?.find(
286
- (event) => event.event === 'Launched'
287
- );
327
+ const event = txResponse?.events?.find(
328
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
329
+ (event: any) => event.event === 'Launched'
330
+ );
288
331
 
289
- const escrowAddr = event?.args?.[1];
290
- this._logger.info(`Escrow is deployed at ${escrowAddr}.`);
332
+ const escrowAddr = event?.args?.[1];
333
+ this._logger.info(`Escrow is deployed at ${escrowAddr}.`);
291
334
 
292
- this.contractData.escrowAddr = escrowAddr;
293
- this.contractData.escrow = await getEscrow(
294
- escrowAddr,
295
- this.providerData?.gasPayer
296
- );
335
+ this.contractData.escrowAddr = escrowAddr;
336
+ this.contractData.escrow = await getEscrow(
337
+ escrowAddr,
338
+ this.providerData?.gasPayer
339
+ );
340
+ } catch {
341
+ this._logError(new Error('Error creating escrow...'));
342
+ return false;
343
+ }
297
344
 
298
345
  return (
299
346
  (await this.status()) == EscrowStatus.Launched &&
@@ -633,6 +680,184 @@ export class Job {
633
680
  return (await this.status()) === EscrowStatus.Complete;
634
681
  }
635
682
 
683
+ /**
684
+ * **Stake HMTokens**
685
+ *
686
+ * @param {number} amount - Amount to stake
687
+ * @param {string | undefined} from - Address to stake
688
+ * @returns {Promise<boolean>} - True if the token is staked
689
+ */
690
+ async stake(amount: number, from?: string) {
691
+ if (!this.contractData?.staking) {
692
+ this._logError(ErrorStakingMissing);
693
+ return false;
694
+ }
695
+ if (!this.contractData.hmToken) {
696
+ this._logError(ErrorHMTokenMissing);
697
+ return false;
698
+ }
699
+
700
+ const operator = this._findOperator(from);
701
+
702
+ if (!operator) {
703
+ this._logError(new Error('Unknown wallet'));
704
+ return false;
705
+ }
706
+
707
+ try {
708
+ const approved = await this.contractData.hmToken
709
+ .connect(operator)
710
+ .approve(this.contractData.staking.address, toFullDigit(amount));
711
+
712
+ if (!approved) {
713
+ throw new Error('Not approved');
714
+ }
715
+ } catch {
716
+ this._logError(new Error('Error approving HMTokens for staking'));
717
+ return false;
718
+ }
719
+
720
+ try {
721
+ await this.contractData.staking
722
+ .connect(operator)
723
+ .stake(toFullDigit(amount));
724
+ } catch {
725
+ this._logError(new Error(`Error executing transaction from ${from}`));
726
+ return false;
727
+ }
728
+ return true;
729
+ }
730
+
731
+ /**
732
+ * **Unstake HMTokens**
733
+ *
734
+ * @param {number} amount - Amount to unstake
735
+ * @param {string | undefined} from - Address to unstake
736
+ * @returns {Promise<boolean>} - True if the token is unstaked
737
+ */
738
+ async unstake(amount: number, from?: string) {
739
+ if (!this.contractData?.staking) {
740
+ this._logError(ErrorStakingMissing);
741
+ return false;
742
+ }
743
+
744
+ const operator = this._findOperator(from);
745
+
746
+ if (!operator) {
747
+ this._logError(new Error('Unknown wallet'));
748
+ return false;
749
+ }
750
+
751
+ try {
752
+ await this.contractData.staking
753
+ .connect(operator)
754
+ .unstake(toFullDigit(amount));
755
+ } catch {
756
+ this._logError(new Error(`Error executing transaction from ${from}`));
757
+ return false;
758
+ }
759
+ return true;
760
+ }
761
+
762
+ /**
763
+ * **Withdraw unstaked HMTokens**
764
+ *
765
+ * @param {string | undefined} from - Address to withdraw
766
+ * @returns {Promise<boolean>} - True if the token is withdrawn
767
+ */
768
+ async withdraw(from?: string) {
769
+ if (!this.contractData?.staking) {
770
+ this._logError(ErrorStakingMissing);
771
+ return false;
772
+ }
773
+
774
+ const operator = this._findOperator(from);
775
+
776
+ if (!operator) {
777
+ this._logError(new Error('Unknown wallet'));
778
+ return false;
779
+ }
780
+
781
+ try {
782
+ await this.contractData.staking.connect(operator).withdraw();
783
+ } catch {
784
+ this._logError(new Error(`Error executing transaction from ${from}`));
785
+ return false;
786
+ }
787
+ return true;
788
+ }
789
+
790
+ /**
791
+ * **Allocate HMTokens staked to the job**
792
+ *
793
+ * @param {number} amount - Amount to allocate
794
+ * @param {string | undefined} - Address to allocate with
795
+ * @returns {Promise<boolean>} - True if the token is allocated
796
+ */
797
+ async allocate(amount: number, from?: string) {
798
+ if (!this.contractData?.staking) {
799
+ this._logError(ErrorStakingMissing);
800
+ return false;
801
+ }
802
+
803
+ if (!this.contractData.escrowAddr) {
804
+ this._logError(ErrorJobNotLaunched);
805
+ return false;
806
+ }
807
+
808
+ const operator = this._findOperator(from);
809
+
810
+ if (!operator) {
811
+ this._logError(new Error('Unknown wallet'));
812
+ return false;
813
+ }
814
+
815
+ try {
816
+ await this.contractData.staking
817
+ .connect(operator)
818
+ .allocate(this.contractData.escrowAddr, toFullDigit(amount));
819
+ } catch {
820
+ this._logError(new Error(`Error executing transaction from ${from}`));
821
+ return false;
822
+ }
823
+ return true;
824
+ }
825
+
826
+ /**
827
+ * **Unallocate HMTokens from the job**
828
+ *
829
+ * @param {string | undefined} - Address to close allocation with
830
+ * @returns {Promise<boolean>} - True if the token is unallocated.
831
+ */
832
+ async closeAllocation(from?: string) {
833
+ if (!this.contractData?.staking) {
834
+ this._logError(ErrorStakingMissing);
835
+ return false;
836
+ }
837
+
838
+ if (!this.contractData.escrowAddr) {
839
+ this._logError(ErrorJobNotLaunched);
840
+ return false;
841
+ }
842
+
843
+ const operator = this._findOperator(from);
844
+
845
+ if (!operator) {
846
+ this._logError(new Error('Unknown wallet'));
847
+ return false;
848
+ }
849
+
850
+ try {
851
+ await this.contractData.staking
852
+ .connect(operator)
853
+ .closeAllocation(this.contractData.escrowAddr);
854
+ } catch {
855
+ this._logError(new Error(`Error executing transaction from ${from}`));
856
+ return false;
857
+ }
858
+ return true;
859
+ }
860
+
636
861
  /**
637
862
  * **Get current status of the escrow**
638
863
  *
@@ -803,8 +1028,10 @@ export class Job {
803
1028
  await contract.connect(trustedHandler).functions[functionName](...args);
804
1029
  return true;
805
1030
  } catch (err) {
806
- new Error(
807
- 'Error executing the transaction from all of the trusted handlers. Stop continue executing...'
1031
+ this._logError(
1032
+ new Error(
1033
+ 'Error executing the transaction from all of the trusted handlers. Stop continue executing...'
1034
+ )
808
1035
  );
809
1036
  }
810
1037
  }
@@ -813,9 +1040,25 @@ export class Job {
813
1040
 
814
1041
  /**
815
1042
  * **Error log**
1043
+ *
816
1044
  * @param {Error} error - Occured error
817
1045
  */
818
- private async _logError(error: Error) {
1046
+ private _logError(error: Error) {
819
1047
  this._logger.error(error.message);
820
1048
  }
1049
+
1050
+ /**
1051
+ * **Find operator to execute tx**
1052
+ *
1053
+ * @param {string} addr - Address of the operator
1054
+ * @returns {ethers.Wallet | undefined} - Operator wallet
1055
+ */
1056
+ private _findOperator(addr?: string): ethers.Wallet | undefined {
1057
+ return addr
1058
+ ? [
1059
+ this.providerData?.gasPayer,
1060
+ ...(this.providerData?.trustedHandlers || []),
1061
+ ].find((account?: ethers.Wallet) => account?.address === addr)
1062
+ : this.providerData?.gasPayer;
1063
+ }
821
1064
  }
package/src/storage.ts CHANGED
@@ -76,7 +76,7 @@ export const getKeyFromURL = (url: string): string => {
76
76
  * @param {StorageAccessData} storageAccessData - Cloud storage access data
77
77
  * @param {string} key - Key of result object
78
78
  * @param {string} privateKey - Private key to decode encrypted content
79
- * @param {string} isPublic - Whether the objest is using public bucket, or private bucket
79
+ * @param {boolean} isPublic - Whether the objest is using public bucket, or private bucket
80
80
  * @returns {Promise<Result>} - Downloaded result
81
81
  */
82
82
  export const download = async (
@@ -104,14 +104,15 @@ export const download = async (
104
104
  * @param {StorageAccessData} storageAccessData - Cloud storage access data
105
105
  * @param {Result} result - Result to upload
106
106
  * @param {string} publicKey - Public key to encrypt data if necessary
107
- * @param {string} encrypt - Whether to encrypt the result, or not
108
- * @param {string} isPublic - Whether to use public bucket, or private bucket
107
+ * @param {boolean} _encrypt - Whether to encrypt the result, or not
108
+ * @param {boolean} isPublic - Whether to use public bucket, or private bucket
109
109
  * @returns {Promise<UploadResult>} - Uploaded result with key/hash
110
110
  */
111
111
  export const upload = async (
112
112
  storageAccessData: StorageAccessData,
113
113
  result: Result,
114
114
  publicKey: string,
115
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
115
116
  encrypt = true,
116
117
  isPublic = false
117
118
  ): Promise<UploadResult> => {
package/src/types.ts CHANGED
@@ -2,6 +2,7 @@ import {
2
2
  Escrow,
3
3
  EscrowFactory,
4
4
  HMToken,
5
+ Staking,
5
6
  } from '@human-protocol/core/typechain-types';
6
7
  import { ethers } from 'ethers';
7
8
 
@@ -567,6 +568,14 @@ export type ContractData = {
567
568
  * HMToken contract instance
568
569
  */
569
570
  hmToken?: HMToken;
571
+ /**
572
+ * Staking contract address
573
+ */
574
+ stakingAddr?: string;
575
+ /**
576
+ * Staking contract instance
577
+ */
578
+ staking?: Staking;
570
579
  };
571
580
 
572
581
  /**
@@ -627,6 +636,10 @@ export type JobArguments = {
627
636
  /**
628
637
  * Factory contract address
629
638
  */
639
+ stakingAddr?: string;
640
+ /**
641
+ * Staking contract address
642
+ */
630
643
  factoryAddr?: string;
631
644
  /**
632
645
  * Escrow contract address
package/src/utils.ts CHANGED
@@ -7,6 +7,10 @@ import {
7
7
  EscrowFactory__factory,
8
8
  HMToken,
9
9
  HMToken__factory,
10
+ Staking,
11
+ Staking__factory,
12
+ RewardPool,
13
+ RewardPool__factory,
10
14
  } from '@human-protocol/core/typechain-types';
11
15
 
12
16
  /**
@@ -31,16 +35,18 @@ export const getHmToken = async (
31
35
  * **Deploy EscrowFactory contract**
32
36
  *
33
37
  * @param {string} hmTokenAddr HMToken address
38
+ * @param {string} stakingAddr Staking address
34
39
  * @param {ethers.Signer | undefined} signer Deployer signer
35
40
  * @returns {Promise<EscrowFactory>} Deployed contract instance
36
41
  */
37
42
  export const deployEscrowFactory = async (
38
43
  hmTokenAddr: string,
44
+ stakingAddr: string,
39
45
  signer?: ethers.Signer
40
46
  ): Promise<EscrowFactory> => {
41
47
  const factory = new EscrowFactory__factory(signer);
42
48
 
43
- const contract = await factory.deploy(hmTokenAddr);
49
+ const contract = await factory.deploy(hmTokenAddr, stakingAddr);
44
50
 
45
51
  return contract;
46
52
  };
@@ -81,6 +87,66 @@ export const getEscrow = async (
81
87
  return contract;
82
88
  };
83
89
 
90
+ /**
91
+ * **Deploy Staking contract**
92
+ *
93
+ * @param {string} hmTokenAddr HMToken address
94
+ * @param {number} minimumStake Minimum amount to stake
95
+ * @param {number} lockPeriod Lock period after unstake
96
+ * @param {ethers.Signer | undefined} signer Deployer signer
97
+ * @returns {Promise<Staking>} Deployed contract instance
98
+ */
99
+ export const deployStaking = async (
100
+ hmTokenAddr: string,
101
+ minimumStake: number,
102
+ lockPeriod: number,
103
+ signer?: ethers.Signer
104
+ ): Promise<Staking> => {
105
+ const staking = new Staking__factory(signer);
106
+ const contract = await staking.deploy(hmTokenAddr, minimumStake, lockPeriod);
107
+
108
+ return contract;
109
+ };
110
+
111
+ /**
112
+ * **Get Staking contract instance at given address**
113
+ *
114
+ * @param {string} stakingAddr Staking contract address
115
+ * @param {ethers.Signer | undefined} signer Deployer signer
116
+ * @returns {Promise<Staking>} Attached contract instance
117
+ */
118
+ export const getStaking = async (
119
+ stakingAddr: string,
120
+ signer?: ethers.Signer
121
+ ): Promise<Staking> => {
122
+ const factory = new Staking__factory(signer);
123
+
124
+ const contract = await factory.attach(stakingAddr);
125
+
126
+ return contract;
127
+ };
128
+
129
+ /**
130
+ * **Deploy RewardPool contract**
131
+ *
132
+ * @param {string} hmTokenAddr HMToken address
133
+ * @param {string} stakingAddr Staking address
134
+ * @param {number} fee Reward fee of the protocol
135
+ * @param {ethers.Signer | undefined} signer Deployer signer
136
+ * @returns {Promise<Staking>} Deployed contract instance
137
+ */
138
+ export const deployRewardPool = async (
139
+ hmTokenAddr: string,
140
+ stakingAddr: string,
141
+ fee: number,
142
+ signer?: ethers.Signer
143
+ ): Promise<RewardPool> => {
144
+ const rewardPool = new RewardPool__factory(signer);
145
+ const contract = await rewardPool.deploy(hmTokenAddr, stakingAddr, fee);
146
+
147
+ return contract;
148
+ };
149
+
84
150
  /**
85
151
  * **Get specific amount representation in given decimals**
86
152
  *
package/test/job.test.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  DEFAULT_GAS_PAYER_ADDR,
7
7
  DEFAULT_GAS_PAYER_PRIVKEY,
8
8
  DEFAULT_HMTOKEN_ADDR,
9
+ DEFAULT_STAKING_ADDR,
9
10
  NOT_TRUSTED_OPERATOR_PRIVKEY,
10
11
  REPUTATION_ORACLE_PRIVKEY,
11
12
  TRUSTED_OPERATOR1_ADDR,
@@ -29,6 +30,12 @@ jest.mock('../src/storage', () => ({
29
30
  getPublicURL: jest.fn().mockResolvedValue('public-url'),
30
31
  }));
31
32
 
33
+ const setupJob = async (job: Job) => {
34
+ await job.initialize();
35
+ await job.launch();
36
+ await job.setup();
37
+ };
38
+
32
39
  describe('Test Job', () => {
33
40
  describe('New job', () => {
34
41
  let job: Job;
@@ -39,7 +46,8 @@ describe('Test Job', () => {
39
46
  reputationOracle: REPUTATION_ORACLE_PRIVKEY,
40
47
  manifest: manifest,
41
48
  hmTokenAddr: DEFAULT_HMTOKEN_ADDR,
42
- logLevel: 'debug',
49
+ stakingAddr: DEFAULT_STAKING_ADDR,
50
+ logLevel: 'error',
43
51
  });
44
52
  });
45
53
 
@@ -54,11 +62,11 @@ describe('Test Job', () => {
54
62
  expect(await job.contractData?.factory?.address).not.toBeNull();
55
63
  });
56
64
 
57
- it('Should be able to launch the job', async () => {
58
- // Fail to launch the job before initialization
65
+ it('Should be able to launch the job after staking', async () => {
66
+ expect(await job.initialize()).toBe(true);
59
67
  expect(await job.launch()).toBe(false);
60
68
 
61
- await job.initialize();
69
+ await job.stake(1);
62
70
 
63
71
  expect(await job.launch()).toBe(true);
64
72
  expect(await job.status()).toBe(EscrowStatus.Launched);
@@ -92,9 +100,7 @@ describe('Test Job', () => {
92
100
  });
93
101
 
94
102
  it('Should be able to bulk payout workers', async () => {
95
- await job.initialize();
96
- await job.launch();
97
- await job.setup();
103
+ await setupJob(job);
98
104
 
99
105
  expect(
100
106
  await job.bulkPayout(
@@ -148,9 +154,7 @@ describe('Test Job', () => {
148
154
  });
149
155
 
150
156
  it('Should encrypt result, when bulk paying out workers', async () => {
151
- await job.initialize();
152
- await job.launch();
153
- await job.setup();
157
+ await setupJob(job);
154
158
 
155
159
  jest.clearAllMocks();
156
160
  const finalResults = { results: 0 };
@@ -176,9 +180,7 @@ describe('Test Job', () => {
176
180
  });
177
181
 
178
182
  it('Should not encrypt result, when bulk paying out workers', async () => {
179
- await job.initialize();
180
- await job.launch();
181
- await job.setup();
183
+ await setupJob(job);
182
184
 
183
185
  jest.clearAllMocks();
184
186
  const finalResults = { results: 0 };
@@ -204,9 +206,7 @@ describe('Test Job', () => {
204
206
  });
205
207
 
206
208
  it('Should store result in private storage, when bulk paying out workers', async () => {
207
- await job.initialize();
208
- await job.launch();
209
- await job.setup();
209
+ await setupJob(job);
210
210
 
211
211
  jest.clearAllMocks();
212
212
  const finalResults = { results: 0 };
@@ -233,9 +233,7 @@ describe('Test Job', () => {
233
233
  });
234
234
 
235
235
  it('Should store result in public storage, when bulk paying out workers', async () => {
236
- await job.initialize();
237
- await job.launch();
238
- await job.setup();
236
+ await setupJob(job);
239
237
 
240
238
  jest.clearAllMocks();
241
239
  const finalResults = { results: 0 };
@@ -263,9 +261,7 @@ describe('Test Job', () => {
263
261
  });
264
262
 
265
263
  it('Should return final result', async () => {
266
- await job.initialize();
267
- await job.launch();
268
- await job.setup();
264
+ await setupJob(job);
269
265
 
270
266
  const finalResults = { results: 0 };
271
267
  await job.bulkPayout(
@@ -285,17 +281,13 @@ describe('Test Job', () => {
285
281
  });
286
282
 
287
283
  it('Should be able to abort the job', async () => {
288
- await job.initialize();
289
- await job.launch();
290
- await job.setup();
284
+ await setupJob(job);
291
285
 
292
286
  expect(await job.abort()).toBe(true);
293
287
  });
294
288
 
295
289
  it('Should be able to abort partially paid job', async () => {
296
- await job.initialize();
297
- await job.launch();
298
- await job.setup();
290
+ await setupJob(job);
299
291
 
300
292
  const finalResults = { results: 0 };
301
293
  await job.bulkPayout(
@@ -313,9 +305,7 @@ describe('Test Job', () => {
313
305
  });
314
306
 
315
307
  it('Should not be able to abort fully paid job', async () => {
316
- await job.initialize();
317
- await job.launch();
318
- await job.setup();
308
+ await setupJob(job);
319
309
 
320
310
  const finalResults = { results: 0 };
321
311
  await job.bulkPayout(
@@ -333,18 +323,14 @@ describe('Test Job', () => {
333
323
  });
334
324
 
335
325
  it('Should be able to cancel the job', async () => {
336
- await job.initialize();
337
- await job.launch();
338
- await job.setup();
326
+ await setupJob(job);
339
327
 
340
328
  expect(await job.cancel()).toBe(true);
341
329
  expect((await job.balance())?.toString()).toBe(toFullDigit(0).toString());
342
330
  });
343
331
 
344
332
  it('Should be able to cancel partially paid job', async () => {
345
- await job.initialize();
346
- await job.launch();
347
- await job.setup();
333
+ await setupJob(job);
348
334
 
349
335
  const finalResults = { results: 0 };
350
336
  await job.bulkPayout(
@@ -363,9 +349,7 @@ describe('Test Job', () => {
363
349
  });
364
350
 
365
351
  it('Should not be able to cancel paid job', async () => {
366
- await job.initialize();
367
- await job.launch();
368
- await job.setup();
352
+ await setupJob(job);
369
353
 
370
354
  const finalResults = { results: 0 };
371
355
  await job.bulkPayout(
@@ -381,6 +365,81 @@ describe('Test Job', () => {
381
365
 
382
366
  expect(await job.cancel()).toBe(false);
383
367
  });
368
+
369
+ it('Should be able to allocate token to the job', async () => {
370
+ await job.initialize();
371
+
372
+ expect(await job.launch()).toBe(true);
373
+ expect(await job.status()).toBe(EscrowStatus.Launched);
374
+
375
+ expect(await job.allocate(1)).toBe(true);
376
+ });
377
+
378
+ it('Should be able to launch another job after allocating portion of the stake', async () => {
379
+ await job.initialize();
380
+ await job.stake(2);
381
+
382
+ expect(await job.launch()).toBe(true);
383
+ expect(await job.status()).toBe(EscrowStatus.Launched);
384
+
385
+ expect(await job.allocate(1)).toBe(true);
386
+
387
+ const newJob = new Job({
388
+ gasPayer: DEFAULT_GAS_PAYER_PRIVKEY,
389
+ reputationOracle: REPUTATION_ORACLE_PRIVKEY,
390
+ manifest: manifest,
391
+ hmTokenAddr: DEFAULT_HMTOKEN_ADDR,
392
+ stakingAddr: DEFAULT_STAKING_ADDR,
393
+ logLevel: 'error',
394
+ });
395
+
396
+ await newJob.initialize();
397
+ expect(await newJob.launch()).toBe(true);
398
+ });
399
+
400
+ it('Should not be able to launch another job after allocating all of the stake', async () => {
401
+ await job.initialize();
402
+
403
+ expect(await job.launch()).toBe(true);
404
+ expect(await job.status()).toBe(EscrowStatus.Launched);
405
+
406
+ expect(await job.allocate(1)).toBe(true);
407
+
408
+ const newJob = new Job({
409
+ gasPayer: DEFAULT_GAS_PAYER_PRIVKEY,
410
+ reputationOracle: REPUTATION_ORACLE_PRIVKEY,
411
+ manifest: manifest,
412
+ hmTokenAddr: DEFAULT_HMTOKEN_ADDR,
413
+ stakingAddr: DEFAULT_STAKING_ADDR,
414
+ logLevel: 'error',
415
+ });
416
+
417
+ await newJob.initialize();
418
+ expect(await newJob.launch()).toBe(false);
419
+ });
420
+
421
+ it('Should be able to launch another job after staking more tokens', async () => {
422
+ await job.initialize();
423
+ await job.stake(1);
424
+
425
+ expect(await job.launch()).toBe(true);
426
+ expect(await job.status()).toBe(EscrowStatus.Launched);
427
+
428
+ expect(await job.allocate(1)).toBe(true);
429
+
430
+ const newJob = new Job({
431
+ gasPayer: DEFAULT_GAS_PAYER_PRIVKEY,
432
+ reputationOracle: REPUTATION_ORACLE_PRIVKEY,
433
+ manifest: manifest,
434
+ hmTokenAddr: DEFAULT_HMTOKEN_ADDR,
435
+ stakingAddr: DEFAULT_STAKING_ADDR,
436
+ logLevel: 'error',
437
+ });
438
+
439
+ await newJob.initialize();
440
+ await newJob.stake(1);
441
+ expect(await newJob.launch()).toBe(true);
442
+ });
384
443
  });
385
444
 
386
445
  describe('Access existing job from trusted handler', () => {
@@ -392,12 +451,14 @@ describe('Test Job', () => {
392
451
  reputationOracle: REPUTATION_ORACLE_PRIVKEY,
393
452
  manifest: manifest,
394
453
  hmTokenAddr: DEFAULT_HMTOKEN_ADDR,
454
+ stakingAddr: DEFAULT_STAKING_ADDR,
395
455
  trustedHandlers: [TRUSTED_OPERATOR1_PRIVKEY],
396
456
  logLevel: 'error',
397
457
  });
398
458
 
399
459
  await originalJob.initialize();
400
460
  await originalJob.launch();
461
+ await originalJob.stake(1);
401
462
  await originalJob.setup();
402
463
 
403
464
  job = new Job({
@@ -407,7 +468,7 @@ describe('Test Job', () => {
407
468
  escrowAddr: originalJob.contractData?.escrowAddr,
408
469
  factoryAddr: originalJob.contractData?.factoryAddr,
409
470
  trustedHandlers: [TRUSTED_OPERATOR1_PRIVKEY],
410
- logLevel: 'debug',
471
+ logLevel: 'error',
411
472
  });
412
473
  });
413
474
 
@@ -416,8 +477,7 @@ describe('Test Job', () => {
416
477
  });
417
478
 
418
479
  it('Should be able to initializes the job by accessing existing escrow', async () => {
419
- const initialized = await job.initialize();
420
- expect(initialized).toBe(true);
480
+ expect(await job.initialize()).toBe(true);
421
481
 
422
482
  expect(await job.manifestData?.manifestlink?.url).toBe('uploaded-key');
423
483
  expect(await job.manifestData?.manifestlink?.hash).toBe('uploaded-hash');
@@ -430,7 +490,7 @@ describe('Test Job', () => {
430
490
  expect(await job.status()).toBe(EscrowStatus.Pending);
431
491
  });
432
492
 
433
- it('Should be able to setup the job again', async () => {
493
+ it('Should not be able to setup the job again', async () => {
434
494
  await job.initialize();
435
495
 
436
496
  expect(await job.setup()).toBe(false);
@@ -444,7 +504,6 @@ describe('Test Job', () => {
444
504
 
445
505
  it('Should be able to add trusted handlers', async () => {
446
506
  await job.initialize();
447
- await job.launch();
448
507
 
449
508
  expect(await job.isTrustedHandler(DEFAULT_GAS_PAYER_ADDR)).toBe(true);
450
509
 
@@ -461,8 +520,6 @@ describe('Test Job', () => {
461
520
 
462
521
  it('Should be able to bulk payout workers', async () => {
463
522
  await job.initialize();
464
- await job.launch();
465
- await job.setup();
466
523
 
467
524
  expect(
468
525
  await job.bulkPayout(
@@ -517,8 +574,6 @@ describe('Test Job', () => {
517
574
 
518
575
  it('Should encrypt result, when bulk paying out workers', async () => {
519
576
  await job.initialize();
520
- await job.launch();
521
- await job.setup();
522
577
 
523
578
  jest.clearAllMocks();
524
579
  const finalResults = { results: 0 };
@@ -545,8 +600,6 @@ describe('Test Job', () => {
545
600
 
546
601
  it('Should not encrypt result, when bulk paying out workers', async () => {
547
602
  await job.initialize();
548
- await job.launch();
549
- await job.setup();
550
603
 
551
604
  jest.clearAllMocks();
552
605
  const finalResults = { results: 0 };
@@ -573,8 +626,6 @@ describe('Test Job', () => {
573
626
 
574
627
  it('Should store result in private storage, when bulk paying out workers', async () => {
575
628
  await job.initialize();
576
- await job.launch();
577
- await job.setup();
578
629
 
579
630
  jest.clearAllMocks();
580
631
  const finalResults = { results: 0 };
@@ -602,8 +653,6 @@ describe('Test Job', () => {
602
653
 
603
654
  it('Should store result in public storage, when bulk paying out workers', async () => {
604
655
  await job.initialize();
605
- await job.launch();
606
- await job.setup();
607
656
 
608
657
  jest.clearAllMocks();
609
658
  const finalResults = { results: 0 };
@@ -632,8 +681,6 @@ describe('Test Job', () => {
632
681
 
633
682
  it('Should return final result', async () => {
634
683
  await job.initialize();
635
- await job.launch();
636
- await job.setup();
637
684
 
638
685
  const finalResults = { results: 0 };
639
686
  await job.bulkPayout(
@@ -654,16 +701,12 @@ describe('Test Job', () => {
654
701
 
655
702
  it('Should be able to abort the job', async () => {
656
703
  await job.initialize();
657
- await job.launch();
658
- await job.setup();
659
704
 
660
705
  expect(await job.abort()).toBe(true);
661
706
  });
662
707
 
663
708
  it('Should be able to abort partially paid job', async () => {
664
709
  await job.initialize();
665
- await job.launch();
666
- await job.setup();
667
710
 
668
711
  const finalResults = { results: 0 };
669
712
  await job.bulkPayout(
@@ -682,8 +725,6 @@ describe('Test Job', () => {
682
725
 
683
726
  it('Should not be able to abort fully paid job', async () => {
684
727
  await job.initialize();
685
- await job.launch();
686
- await job.setup();
687
728
 
688
729
  const finalResults = { results: 0 };
689
730
  await job.bulkPayout(
@@ -702,8 +743,6 @@ describe('Test Job', () => {
702
743
 
703
744
  it('Should be able to cancel the job', async () => {
704
745
  await job.initialize();
705
- await job.launch();
706
- await job.setup();
707
746
 
708
747
  expect(await job.cancel()).toBe(true);
709
748
  expect((await job.balance())?.toString()).toBe(toFullDigit(0).toString());
@@ -711,8 +750,6 @@ describe('Test Job', () => {
711
750
 
712
751
  it('Should be able to cancel partially paid job', async () => {
713
752
  await job.initialize();
714
- await job.launch();
715
- await job.setup();
716
753
 
717
754
  const finalResults = { results: 0 };
718
755
  await job.bulkPayout(
@@ -732,8 +769,6 @@ describe('Test Job', () => {
732
769
 
733
770
  it('Should not be able to cancel paid job', async () => {
734
771
  await job.initialize();
735
- await job.launch();
736
- await job.setup();
737
772
 
738
773
  const finalResults = { results: 0 };
739
774
  await job.bulkPayout(
@@ -749,5 +784,34 @@ describe('Test Job', () => {
749
784
 
750
785
  expect(await job.cancel()).toBe(false);
751
786
  });
787
+
788
+ it('Should not be able to allocate to job without staking', async () => {
789
+ await job.initialize();
790
+ expect(await job.allocate(1, TRUSTED_OPERATOR1_ADDR)).toBe(false);
791
+ });
792
+
793
+ it('Should be able to allocate to job after staking', async () => {
794
+ await job.initialize();
795
+ await job.stake(1, TRUSTED_OPERATOR1_ADDR);
796
+
797
+ expect(await job.allocate(1, TRUSTED_OPERATOR1_ADDR)).toBe(true);
798
+ });
799
+
800
+ it('Should be able to launch another job after staking', async () => {
801
+ await job.initialize();
802
+ await job.stake(1, TRUSTED_OPERATOR1_ADDR);
803
+
804
+ const newJob = new Job({
805
+ gasPayer: TRUSTED_OPERATOR1_PRIVKEY,
806
+ reputationOracle: REPUTATION_ORACLE_PRIVKEY,
807
+ manifest: manifest,
808
+ hmTokenAddr: DEFAULT_HMTOKEN_ADDR,
809
+ stakingAddr: DEFAULT_STAKING_ADDR,
810
+ logLevel: 'error',
811
+ });
812
+
813
+ await newJob.initialize();
814
+ expect(await newJob.launch()).toBe(true);
815
+ });
752
816
  });
753
817
  });
@@ -1,6 +1,9 @@
1
1
  export const DEFAULT_HMTOKEN_ADDR =
2
2
  '0x5FbDB2315678afecb367f032d93F642f64180aa3';
3
3
 
4
+ export const DEFAULT_STAKING_ADDR =
5
+ '0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512';
6
+
4
7
  export const DEFAULT_GAS_PAYER_ADDR =
5
8
  '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
6
9
  export const DEFAULT_GAS_PAYER_PRIVKEY =