@human-protocol/sdk 1.1.0 → 1.1.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.
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.1.0",
4
+ "version": "1.1.2",
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 10 && jest --runInBand\"",
18
18
  "lint": "eslint .",
19
19
  "lint:fix": "eslint . --fix",
20
20
  "format": "prettier --write '**/*.{ts,json}'"
@@ -38,15 +38,12 @@
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",
45
45
  "ethers": "^5.7.2",
46
46
  "secp256k1": "^4.0.3",
47
47
  "winston": "^3.8.2"
48
- },
49
- "peerDependencies": {
50
- "@human-protocol/core": "^1.0.9"
51
48
  }
52
49
  }
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
@@ -101,7 +104,7 @@ export class Job {
101
104
  alchemy: alchemyKey,
102
105
  infura: infuraKey,
103
106
  })
104
- : new ethers.providers.JsonRpcProvider();
107
+ : new ethers.providers.JsonRpcProvider('http://127.0.0.1:8545');
105
108
 
106
109
  this.providerData = {
107
110
  provider,
@@ -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,20 @@ 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
- this.contractData.hmTokenAddr,
194
+ this.contractData.stakingAddr,
180
195
  this.providerData?.gasPayer
181
196
  );
182
197
  this.contractData.factoryAddr = this.contractData.factory.address;
@@ -197,6 +212,31 @@ export class Job {
197
212
  this.providerData?.gasPayer
198
213
  );
199
214
 
215
+ this._logger.info('Checking if staking is configured...');
216
+ const stakingAddr = await this.contractData.factory.staking();
217
+ if (!stakingAddr) {
218
+ this._logError(new Error('Factory is not configured with staking'));
219
+ this.contractData.factory = undefined;
220
+
221
+ return false;
222
+ }
223
+ this._logger.info('Getting staking...');
224
+ this.contractData.staking = await getStaking(
225
+ stakingAddr,
226
+ this.providerData?.gasPayer
227
+ );
228
+ this.contractData.stakingAddr = stakingAddr;
229
+
230
+ this._logger.info('Checking if reward pool is configured...');
231
+ const rewardPoolAddr = await this.contractData.staking.rewardPool();
232
+ if (!rewardPoolAddr) {
233
+ this._logError(new Error('Staking is not configured with reward pool'));
234
+ this.contractData.staking = undefined;
235
+ this.contractData.factory = undefined;
236
+
237
+ return false;
238
+ }
239
+
200
240
  this._logger.info('Checking if escrow exists in the factory...');
201
241
  const hasEscrow = await this.contractData?.factory.hasEscrow(
202
242
  this.contractData?.escrowAddr
@@ -274,26 +314,33 @@ export class Job {
274
314
 
275
315
  this._logger.info('Launching escrow...');
276
316
 
277
- const txReceipt = await this.contractData?.factory?.createEscrow(
278
- this.providerData?.trustedHandlers?.map(
279
- (trustedHandler) => trustedHandler.address
280
- ) || []
281
- );
317
+ try {
318
+ const txReceipt = await this.contractData?.factory?.createEscrow(
319
+ this.contractData.hmTokenAddr,
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 &&
@@ -338,6 +385,8 @@ export class Job {
338
385
  this.manifestData?.manifest?.reputation_oracle_addr || '';
339
386
  const recordingOracleAddr =
340
387
  this.manifestData?.manifest?.recording_oracle_addr || '';
388
+ const requestedSolutions =
389
+ this.manifestData?.manifest?.job_total_tasks || 0;
341
390
 
342
391
  this._logger.info(
343
392
  `Transferring ${this.amount} HMT to ${this.contractData.escrow.address}...`
@@ -391,7 +440,8 @@ export class Job {
391
440
  reputationOracleStake,
392
441
  recordingOracleStake,
393
442
  this.manifestData?.manifestlink?.url,
394
- this.manifestData?.manifestlink?.hash
443
+ this.manifestData?.manifestlink?.hash,
444
+ requestedSolutions
395
445
  );
396
446
 
397
447
  if (!contractSetup) {
@@ -633,6 +683,184 @@ export class Job {
633
683
  return (await this.status()) === EscrowStatus.Complete;
634
684
  }
635
685
 
686
+ /**
687
+ * **Stake HMTokens**
688
+ *
689
+ * @param {number} amount - Amount to stake
690
+ * @param {string | undefined} from - Address to stake
691
+ * @returns {Promise<boolean>} - True if the token is staked
692
+ */
693
+ async stake(amount: number, from?: string) {
694
+ if (!this.contractData?.staking) {
695
+ this._logError(ErrorStakingMissing);
696
+ return false;
697
+ }
698
+ if (!this.contractData.hmToken) {
699
+ this._logError(ErrorHMTokenMissing);
700
+ return false;
701
+ }
702
+
703
+ const operator = this._findOperator(from);
704
+
705
+ if (!operator) {
706
+ this._logError(new Error('Unknown wallet'));
707
+ return false;
708
+ }
709
+
710
+ try {
711
+ const approved = await this.contractData.hmToken
712
+ .connect(operator)
713
+ .approve(this.contractData.staking.address, toFullDigit(amount));
714
+
715
+ if (!approved) {
716
+ throw new Error('Not approved');
717
+ }
718
+ } catch {
719
+ this._logError(new Error('Error approving HMTokens for staking'));
720
+ return false;
721
+ }
722
+
723
+ try {
724
+ await this.contractData.staking
725
+ .connect(operator)
726
+ .stake(toFullDigit(amount));
727
+ } catch {
728
+ this._logError(new Error(`Error executing transaction from ${from}`));
729
+ return false;
730
+ }
731
+ return true;
732
+ }
733
+
734
+ /**
735
+ * **Unstake HMTokens**
736
+ *
737
+ * @param {number} amount - Amount to unstake
738
+ * @param {string | undefined} from - Address to unstake
739
+ * @returns {Promise<boolean>} - True if the token is unstaked
740
+ */
741
+ async unstake(amount: number, from?: string) {
742
+ if (!this.contractData?.staking) {
743
+ this._logError(ErrorStakingMissing);
744
+ return false;
745
+ }
746
+
747
+ const operator = this._findOperator(from);
748
+
749
+ if (!operator) {
750
+ this._logError(new Error('Unknown wallet'));
751
+ return false;
752
+ }
753
+
754
+ try {
755
+ await this.contractData.staking
756
+ .connect(operator)
757
+ .unstake(toFullDigit(amount));
758
+ } catch {
759
+ this._logError(new Error(`Error executing transaction from ${from}`));
760
+ return false;
761
+ }
762
+ return true;
763
+ }
764
+
765
+ /**
766
+ * **Withdraw unstaked HMTokens**
767
+ *
768
+ * @param {string | undefined} from - Address to withdraw
769
+ * @returns {Promise<boolean>} - True if the token is withdrawn
770
+ */
771
+ async withdraw(from?: string) {
772
+ if (!this.contractData?.staking) {
773
+ this._logError(ErrorStakingMissing);
774
+ return false;
775
+ }
776
+
777
+ const operator = this._findOperator(from);
778
+
779
+ if (!operator) {
780
+ this._logError(new Error('Unknown wallet'));
781
+ return false;
782
+ }
783
+
784
+ try {
785
+ await this.contractData.staking.connect(operator).withdraw();
786
+ } catch {
787
+ this._logError(new Error(`Error executing transaction from ${from}`));
788
+ return false;
789
+ }
790
+ return true;
791
+ }
792
+
793
+ /**
794
+ * **Allocate HMTokens staked to the job**
795
+ *
796
+ * @param {number} amount - Amount to allocate
797
+ * @param {string | undefined} - Address to allocate with
798
+ * @returns {Promise<boolean>} - True if the token is allocated
799
+ */
800
+ async allocate(amount: number, from?: string) {
801
+ if (!this.contractData?.staking) {
802
+ this._logError(ErrorStakingMissing);
803
+ return false;
804
+ }
805
+
806
+ if (!this.contractData.escrowAddr) {
807
+ this._logError(ErrorJobNotLaunched);
808
+ return false;
809
+ }
810
+
811
+ const operator = this._findOperator(from);
812
+
813
+ if (!operator) {
814
+ this._logError(new Error('Unknown wallet'));
815
+ return false;
816
+ }
817
+
818
+ try {
819
+ await this.contractData.staking
820
+ .connect(operator)
821
+ .allocate(this.contractData.escrowAddr, toFullDigit(amount));
822
+ } catch {
823
+ this._logError(new Error(`Error executing transaction from ${from}`));
824
+ return false;
825
+ }
826
+ return true;
827
+ }
828
+
829
+ /**
830
+ * **Unallocate HMTokens from the job**
831
+ *
832
+ * @param {string | undefined} - Address to close allocation with
833
+ * @returns {Promise<boolean>} - True if the token is unallocated.
834
+ */
835
+ async closeAllocation(from?: string) {
836
+ if (!this.contractData?.staking) {
837
+ this._logError(ErrorStakingMissing);
838
+ return false;
839
+ }
840
+
841
+ if (!this.contractData.escrowAddr) {
842
+ this._logError(ErrorJobNotLaunched);
843
+ return false;
844
+ }
845
+
846
+ const operator = this._findOperator(from);
847
+
848
+ if (!operator) {
849
+ this._logError(new Error('Unknown wallet'));
850
+ return false;
851
+ }
852
+
853
+ try {
854
+ await this.contractData.staking
855
+ .connect(operator)
856
+ .closeAllocation(this.contractData.escrowAddr);
857
+ } catch {
858
+ this._logError(new Error(`Error executing transaction from ${from}`));
859
+ return false;
860
+ }
861
+ return true;
862
+ }
863
+
636
864
  /**
637
865
  * **Get current status of the escrow**
638
866
  *
@@ -803,8 +1031,10 @@ export class Job {
803
1031
  await contract.connect(trustedHandler).functions[functionName](...args);
804
1032
  return true;
805
1033
  } catch (err) {
806
- new Error(
807
- 'Error executing the transaction from all of the trusted handlers. Stop continue executing...'
1034
+ this._logError(
1035
+ new Error(
1036
+ 'Error executing the transaction from all of the trusted handlers. Stop continue executing...'
1037
+ )
808
1038
  );
809
1039
  }
810
1040
  }
@@ -813,9 +1043,25 @@ export class Job {
813
1043
 
814
1044
  /**
815
1045
  * **Error log**
1046
+ *
816
1047
  * @param {Error} error - Occured error
817
1048
  */
818
- private async _logError(error: Error) {
1049
+ private _logError(error: Error) {
819
1050
  this._logger.error(error.message);
820
1051
  }
1052
+
1053
+ /**
1054
+ * **Find operator to execute tx**
1055
+ *
1056
+ * @param {string} addr - Address of the operator
1057
+ * @returns {ethers.Wallet | undefined} - Operator wallet
1058
+ */
1059
+ private _findOperator(addr?: string): ethers.Wallet | undefined {
1060
+ return addr
1061
+ ? [
1062
+ this.providerData?.gasPayer,
1063
+ ...(this.providerData?.trustedHandlers || []),
1064
+ ].find((account?: ethers.Wallet) => account?.address === addr)
1065
+ : this.providerData?.gasPayer;
1066
+ }
821
1067
  }
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
  /**
@@ -30,17 +34,18 @@ export const getHmToken = async (
30
34
  /**
31
35
  * **Deploy EscrowFactory contract**
32
36
  *
33
- * @param {string} hmTokenAddr HMToken address
37
+ * @param {string} stakingAddr Staking address
34
38
  * @param {ethers.Signer | undefined} signer Deployer signer
35
39
  * @returns {Promise<EscrowFactory>} Deployed contract instance
36
40
  */
37
41
  export const deployEscrowFactory = async (
38
- hmTokenAddr: string,
42
+ stakingAddr: string,
39
43
  signer?: ethers.Signer
40
44
  ): Promise<EscrowFactory> => {
41
45
  const factory = new EscrowFactory__factory(signer);
42
46
 
43
- const contract = await factory.deploy(hmTokenAddr);
47
+ const contract = await factory.deploy();
48
+ await contract.initialize(stakingAddr);
44
49
 
45
50
  return contract;
46
51
  };
@@ -81,6 +86,68 @@ export const getEscrow = async (
81
86
  return contract;
82
87
  };
83
88
 
89
+ /**
90
+ * **Deploy Staking contract**
91
+ *
92
+ * @param {string} hmTokenAddr HMToken address
93
+ * @param {number} minimumStake Minimum amount to stake
94
+ * @param {number} lockPeriod Lock period after unstake
95
+ * @param {ethers.Signer | undefined} signer Deployer signer
96
+ * @returns {Promise<Staking>} Deployed contract instance
97
+ */
98
+ export const deployStaking = async (
99
+ hmTokenAddr: string,
100
+ minimumStake: number,
101
+ lockPeriod: number,
102
+ signer?: ethers.Signer
103
+ ): Promise<Staking> => {
104
+ const staking = new Staking__factory(signer);
105
+ const contract = await staking.deploy();
106
+ await contract.initialize(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();
146
+ await contract.initialize(hmTokenAddr, stakingAddr, fee);
147
+
148
+ return contract;
149
+ };
150
+
84
151
  /**
85
152
  * **Get specific amount representation in given decimals**
86
153
  *
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,6 +46,7 @@ describe('Test Job', () => {
39
46
  reputationOracle: REPUTATION_ORACLE_PRIVKEY,
40
47
  manifest: manifest,
41
48
  hmTokenAddr: DEFAULT_HMTOKEN_ADDR,
49
+ stakingAddr: DEFAULT_STAKING_ADDR,
42
50
  logLevel: 'debug',
43
51
  });
44
52
  });
@@ -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
+ '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0';
6
+
4
7
  export const DEFAULT_GAS_PAYER_ADDR =
5
8
  '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266';
6
9
  export const DEFAULT_GAS_PAYER_PRIVKEY =