Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions packages/sdk/src/lib/dataProtectorCore/processBulkRequest.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { sumTags } from 'iexec/utils';
import { SCONE_TAG } from '../../config/config.js';
import {
WorkflowError,
processProtectedDataErrorMessage,
handleIfProtocolError,
processProtectedDataErrorMessage,
ValidationError,
WorkflowError,
} from '../../utils/errors.js';
import {
checkUserVoucher,
Expand Down Expand Up @@ -50,6 +50,7 @@
bulkRequest,
workerpool,
useVoucher = false,
allowDeposit = false,
voucherOwner,
path,
pemPrivateKey,
Expand All @@ -57,7 +58,7 @@
onStatusUpdate = () => {},
}: IExecConsumer & DefaultWorkerpoolConsumer & Params): Promise<
ProcessBulkRequestResponse<Params>
> => {

Check warning on line 61 in packages/sdk/src/lib/dataProtectorCore/processBulkRequest.ts

View workflow job for this annotation

GitHub Actions / check-code

Refactor this function to reduce its Cognitive Complexity from 27 to the 15 allowed
const vRequestorder = bulkRequestSchema()
.label('bulkRequest')
.required()
Expand All @@ -66,6 +67,9 @@
.default(defaultWorkerpool) // Default workerpool if none is specified
.label('workerpool')
.validateSync(workerpool);
const vAllowDeposit = booleanSchema()
.label('allowDeposit')
.validateSync(allowDeposit);
const vUseVoucher = booleanSchema()
.label('useVoucher')
.validateSync(useVoucher);
Expand Down Expand Up @@ -215,6 +219,7 @@
const matchOptions: MatchOptions = {
useVoucher: vUseVoucher,
...(vVoucherOwner ? { voucherAddress: userVoucher?.address } : {}),
allowDeposit: vAllowDeposit,
};

const {
Expand Down
12 changes: 8 additions & 4 deletions packages/sdk/src/lib/dataProtectorCore/processProtectedData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
SCONE_TAG,
} from '../../config/config.js';
import {
WorkflowError,
processProtectedDataErrorMessage,
handleIfProtocolError,
processProtectedDataErrorMessage,
ValidationError,
WorkflowError,
} from '../../utils/errors.js';
import {
checkUserVoucher,
filterWorkerpoolOrders,
} from '../../utils/processProtectedData.models.js';
import { pushRequesterSecret } from '../../utils/pushRequesterSecret.js';
import {
getPemFormattedKeyPair,
formatPemPublicKeyForSMS,
getPemFormattedKeyPair,
} from '../../utils/rsa.js';
import {
addressOrEnsSchema,
Expand Down Expand Up @@ -64,6 +64,7 @@
inputFiles,
secrets,
workerpool,
allowDeposit = false,
useVoucher = false,
voucherOwner,
encryptResult = false,
Expand All @@ -72,7 +73,7 @@
onStatusUpdate = () => {},
}: IExecConsumer & DefaultWorkerpoolConsumer & Params): Promise<
ProcessProtectedDataResponse<Params>
> => {

Check warning on line 76 in packages/sdk/src/lib/dataProtectorCore/processProtectedData.ts

View workflow job for this annotation

GitHub Actions / check-code

Refactor this function to reduce its Cognitive Complexity from 36 to the 15 allowed
const vProtectedData = addressOrEnsSchema()
.required()
.label('protectedData')
Expand Down Expand Up @@ -103,6 +104,9 @@
.default(defaultWorkerpool) // Default workerpool if none is specified
.label('workerpool')
.validateSync(workerpool);
const vAllowDeposit = booleanSchema()
.label('allowDeposit')
.validateSync(allowDeposit);
const vUseVoucher = booleanSchema()
.label('useVoucher')
.validateSync(useVoucher);
Expand Down Expand Up @@ -369,9 +373,9 @@
};
const matchOptions: MatchOptions = {
useVoucher: vUseVoucher,
allowDeposit: vAllowDeposit,
...(vVoucherOwner ? { voucherAddress: userVoucher?.address } : {}),
};

const { dealid: dealId, txHash } = await iexec.order.matchOrders(
orders,
matchOptions
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/lib/types/commonTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ export interface SearchableDataSchema
export type MatchOptions = {
useVoucher: boolean;
voucherAddress?: string;
allowDeposit?: boolean;
};

export type DefaultWorkerpoolConsumer = {
Expand Down
12 changes: 12 additions & 0 deletions packages/sdk/src/lib/types/coreTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,12 @@ export type ProcessProtectedDataParams = {
*/
workerpool?: AddressOrENS;

/**
* A boolean that indicates whether to allow automatic deposit from wallet when account balance is insufficient to cover the cost of the task.
* @default false
*/
allowDeposit?: boolean;

/**
* A boolean that indicates whether to use a voucher or no.
*/
Expand Down Expand Up @@ -567,6 +573,12 @@ export type ProcessBulkRequestParams = {
*/
useVoucher?: boolean;

/**
* A boolean that indicates whether to allow automatic deposit from wallet when account balance is insufficient to cover the cost of the bulk request.
* @default false
*/
allowDeposit?: boolean;

/**
* Override the voucher contract to use, must be combined with useVoucher: true the user must be authorized by the voucher's owner to use it.
*/
Expand Down
181 changes: 181 additions & 0 deletions packages/sdk/tests/e2e/dataProtectorCore/processProtectedData.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
MAX_EXPECTED_WEB2_SERVICES_TIME,
deployRandomApp,
getTestConfig,
setNRlcBalance,
} from '../../test-utils.js';

describe('dataProtectorCore.processProtectedData() (waitForResult: false)', () => {
Expand Down Expand Up @@ -392,4 +393,184 @@ describe('dataProtectorCore.processProtectedData() (waitForResult: false)', () =
2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME
);
});

describe('allowDeposit', () => {
let payableWorkerpoolAddress: string;
const workerpoolprice = 1000;
beforeAll(async () => {
const workerpoolOwnerWallet = Wallet.createRandom();
const [ethProvider, options] = getTestConfig(
workerpoolOwnerWallet.privateKey
);

const iexecWorkerpoolOwner = new IExec(
{ ethProvider },
options.iexecOptions
);

await setNRlcBalance(workerpoolOwnerWallet.address, 100 * 10e9);
await iexecWorkerpoolOwner.account.deposit(100 * 10e9);
const { address: deployedWorkerpoolAddress } =
await iexecWorkerpoolOwner.workerpool.deployWorkerpool({
description: 'payable test workerpool',
owner: await iexecWorkerpoolOwner.wallet.getAddress(),
});
payableWorkerpoolAddress = deployedWorkerpoolAddress;

await iexecWorkerpoolOwner.order
.createWorkerpoolorder({
workerpool: deployedWorkerpoolAddress,
category: 0,
workerpoolprice,
volume: 1000,
tag: ['tee', 'scone'],
})
.then(iexecWorkerpoolOwner.order.signWorkerpoolorder)
.then(iexecWorkerpoolOwner.order.publishWorkerpoolorder);
});
it(
'should throw error when insufficient funds and allowDeposit is false',
async () => {
const { processProtectedData } = await import(
'../../../src/lib/dataProtectorCore/processProtectedData.js'
);

// wallet has enough nRLC
await setNRlcBalance(wallet.address, workerpoolprice * 10e9);
// but account has no enough funds to process the data (less than workerpoolprice)
await iexec.account.deposit(1 * 10e9);

let caughtError: Error | undefined;
try {
await processProtectedData({
iexec,
protectedData: protectedData.address,
app: appAddress,
defaultWorkerpool: payableWorkerpoolAddress,
workerpool: payableWorkerpoolAddress,
workerpoolMaxPrice: 100000,
secrets: {
1: 'ProcessProtectedData test subject',
2: 'email content for test processData',
},
args: '_args_test_process_data_',
path: 'computed.json',
waitForResult: false,
});
} catch (firstError) {
try {
await processProtectedData({
iexec,
protectedData: protectedData.address,
app: appAddress,
defaultWorkerpool: payableWorkerpoolAddress,
workerpool: payableWorkerpoolAddress,
workerpoolMaxPrice: 100000,
secrets: {
1: 'ProcessProtectedData test subject',
2: 'email content for test processData',
},
args: '_args_test_process_data_',
path: 'computed.json',
waitForResult: false,
});
} catch (secondError) {
caughtError = secondError as Error;
}
}

expect(caughtError).toBeDefined();
expect(caughtError).toBeInstanceOf(Error);
expect(caughtError?.message).toBe('Failed to process protected data');
const causeMsg =
(caughtError as any)?.errorCause?.message ||
(caughtError as any)?.cause?.message ||
(caughtError as any)?.cause ||
(caughtError as any)?.errorCause;

expect(causeMsg).toBe(
`Cost per task (${workerpoolprice} nRlc) is greater than requester account stake (0). Orders can't be matched. If you are the requester, you should deposit to top up your account`
);
},
2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME
);

it(
'should process protected data when no funds are deposited and allowDeposit is true',
async () => {
const { processProtectedData } = await import(
'../../../src/lib/dataProtectorCore/processProtectedData.js'
);
// wallet has enough nRLC but account has no funds
await setNRlcBalance(wallet.address, workerpoolprice * 10e9);

const walletBefore = await iexec.wallet.checkBalances(
await iexec.wallet.getAddress()
);
const res = await processProtectedData({
iexec,
protectedData: protectedData.address,

app: appAddress,
defaultWorkerpool: workerpoolAddress,
workerpool: workerpoolAddress,
secrets: {
1: 'ProcessProtectedData test subject',
2: 'email content for test processData',
},
args: '_args_test_process_data_',
path: 'computed.json',
waitForResult: false,
allowDeposit: true,
});
const walletAfter = await iexec.wallet.checkBalances(
await iexec.wallet.getAddress()
);
expect(walletAfter.nRLC.lt(walletBefore.nRLC)).toBe(true);
expect(res.dealId).toEqual(expect.any(String));
expect(res.taskId).toEqual(expect.any(String));
expect(res.txHash).toEqual(expect.any(String));
},
2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME
);

it(
'should process protected data when insufficient funds are deposited and allowDeposit is true',
async () => {
const { processProtectedData } = await import(
'../../../src/lib/dataProtectorCore/processProtectedData.js'
);
// wallet has enough nRLC but account has insufficient funds
await setNRlcBalance(wallet.address, workerpoolprice * 10e9);
await iexec.account.deposit(10 * 10e9);

const walletBefore = await iexec.wallet.checkBalances(
await iexec.wallet.getAddress()
);
const res = await processProtectedData({
iexec,
protectedData: protectedData.address,
app: appAddress,
defaultWorkerpool: workerpoolAddress,
workerpool: workerpoolAddress,
secrets: {
1: 'ProcessProtectedData test subject',
2: 'email content for test processData',
},
args: '_args_test_process_data_',
path: 'computed.json',
waitForResult: false,
allowDeposit: true,
});
const walletAfter = await iexec.wallet.checkBalances(
await iexec.wallet.getAddress()
);
expect(walletAfter.nRLC.lt(walletBefore.nRLC)).toBe(true);
expect(res.dealId).toEqual(expect.any(String));
expect(res.taskId).toEqual(expect.any(String));
expect(res.txHash).toEqual(expect.any(String));
},
2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME
);
});
});
Loading