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
23 changes: 17 additions & 6 deletions src/web3mail/sendEmail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ import {
senderNameSchema,
throwIfMissing,
} from '../utils/validators.js';
import {
checkUserVoucher,
filterWorkerpoolOrders,
} from './sendEmail.models.js';
import { SendEmailParams, SendEmailResponse } from './types.js';
import {
DappAddressConsumer,
DappWhitelistAddressConsumer,
Expand All @@ -35,6 +30,11 @@ import {
IpfsNodeConfigConsumer,
SubgraphConsumer,
} from './internalTypes.js';
import {
checkUserVoucher,
filterWorkerpoolOrders,
} from './sendEmail.models.js';
import { SendEmailParams, SendEmailResponse } from './types.js';

export type SendEmail = typeof sendEmail;

Expand All @@ -56,6 +56,7 @@ export const sendEmail = async ({
senderName,
protectedData,
useVoucher = false,
allowDeposit = false,
}: IExecConsumer &
SubgraphConsumer &
DappAddressConsumer &
Expand Down Expand Up @@ -120,6 +121,10 @@ export const sendEmail = async ({
.label('useVoucher')
.validateSync(useVoucher);

const vAllowDeposit = booleanSchema()
.label('allowDeposit')
.validateSync(allowDeposit);

// Check protected data schema through subgraph
const isValidProtectedData = await checkProtectedDataValidity(
graphQLClient,
Expand Down Expand Up @@ -316,14 +321,20 @@ export const sendEmail = async ({
const requestorder = await iexec.order.signRequestorder(requestorderToSign);

// Match orders and compute task ID
// TODO: Remove @ts-ignore once iexec SDK is updated to a version that includes allowDeposit in matchOrders types
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - allowDeposit is supported at runtime but not yet in TypeScript types
const { dealid: dealId } = await iexec.order.matchOrders(
{
apporder: apporder,
datasetorder: datasetorder,
workerpoolorder: workerpoolorder,
requestorder: requestorder,
},
{ useVoucher: vUseVoucher }
// TODO: Remove @ts-ignore once iexec SDK is updated to a version that includes allowDeposit in matchOrders types
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - allowDeposit is supported at runtime but not yet in TypeScript types
{ useVoucher: vUseVoucher, allowDeposit: vAllowDeposit }
);

const taskId = await iexec.deal.computeTaskId(dealId, 0);
Expand Down
10 changes: 10 additions & 0 deletions src/web3mail/sendEmailCampaign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
addressOrEnsSchema,
campaignRequestSchema,
throwIfMissing,
booleanSchema,
} from '../utils/validators.js';
import {
CampaignRequest,
Expand All @@ -19,6 +20,7 @@ export const sendEmailCampaign = async ({
dataProtector = throwIfMissing(),
workerpoolAddressOrEns = throwIfMissing(),
campaignRequest,
allowDeposit = false,
}: DataProtectorConsumer &
SendEmailCampaignParams): Promise<SendEmailCampaignResponse> => {
const vCampaignRequest = campaignRequestSchema()
Expand All @@ -31,6 +33,10 @@ export const sendEmailCampaign = async ({
.label('workerpoolAddressOrEns')
.validateSync(workerpoolAddressOrEns);

const vAllowDeposit = booleanSchema()
.label('allowDeposit')
.validateSync(allowDeposit);

if (
vCampaignRequest.workerpool !== NULL_ADDRESS &&
vCampaignRequest.workerpool.toLowerCase() !==
Expand All @@ -43,10 +49,14 @@ export const sendEmailCampaign = async ({

try {
// Process the prepared bulk request
// TODO: Remove @ts-ignore once @iexec/dataprotector is updated to a version that includes allowDeposit in ProcessBulkRequestParams types
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore - allowDeposit is supported at runtime but not yet in TypeScript types
const processBulkRequestResponse = await dataProtector.processBulkRequest({
bulkRequest: vCampaignRequest,
workerpool: vWorkerpoolAddressOrEns,
waitForResult: false,
allowDeposit: vAllowDeposit,
});

return processBulkRequestResponse;
Expand Down
8 changes: 7 additions & 1 deletion src/web3mail/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { BulkRequest } from '@iexec/dataprotector';
import { EnhancedWallet } from 'iexec';
import { IExecConfigOptions } from 'iexec/IExecConfig';
import type { BulkRequest } from '@iexec/dataprotector';

export type Web3SignerProvider = EnhancedWallet;

Expand Down Expand Up @@ -64,6 +64,7 @@ export type SendEmailParams = {
appMaxPrice?: number;
workerpoolMaxPrice?: number;
useVoucher?: boolean;
allowDeposit?: boolean;
};

export type FetchMyContactsParams = {
Expand Down Expand Up @@ -180,6 +181,11 @@ export type SendEmailCampaignParams = {
* Workerpool address or ENS to use for processing
*/
workerpoolAddressOrEns?: AddressOrENS;
/**
* If true, allows automatic deposit of funds when balance is insufficient
* @default false
*/
allowDeposit?: boolean;
};

export type SendEmailCampaignResponse = {
Expand Down
88 changes: 80 additions & 8 deletions tests/e2e/sendEmail.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import {
} from '@iexec/dataprotector';
import { beforeAll, describe, expect, it } from '@jest/globals';
import { HDNodeWallet } from 'ethers';
import { IExec } from 'iexec';
import { NULL_ADDRESS } from 'iexec/utils';
import {
DEFAULT_CHAIN_ID,
getChainDefaultConfig,
} from '../../src/config/config.js';
import { IExecWeb3mail, WorkflowError } from '../../src/index.js';
import {
MAX_EXPECTED_BLOCKTIME,
Expand All @@ -22,12 +28,6 @@ import {
getTestWeb3SignerProvider,
waitSubgraphIndexing,
} from '../test-utils.js';
import { IExec } from 'iexec';
import { NULL_ADDRESS } from 'iexec/utils';
import {
DEFAULT_CHAIN_ID,
getChainDefaultConfig,
} from '../../src/config/config.js';

describe('web3mail.sendEmail()', () => {
let consumerWallet: HDNodeWallet;
Expand All @@ -41,14 +41,14 @@ describe('web3mail.sendEmail()', () => {
let learnProdWorkerpoolAddress: string;
const iexecOptions = getTestIExecOption();
const prodWorkerpoolPublicPrice = 1000;

const workerpoolprice = 1_000;
beforeAll(async () => {
// (default) prod workerpool (not free) always available
await createAndPublishWorkerpoolOrder(
TEST_CHAIN.prodWorkerpool,
TEST_CHAIN.prodWorkerpoolOwnerWallet,
NULL_ADDRESS,
1_000,
workerpoolprice,
prodWorkerpoolPublicPrice
);
// learn prod pool (free) assumed always available
Expand Down Expand Up @@ -657,4 +657,76 @@ describe('web3mail.sendEmail()', () => {
});
});
});

describe('allowDeposit', () => {
let protectData: ProtectedDataWithSecretProps;
consumerWallet = getRandomWallet();
const dataPricePerAccess = 1000;
let web3mailConsumerInstance: IExecWeb3mail;
beforeAll(async () => {
protectData = await dataProtector.protectData({
data: { email: '[email protected]' },
name: 'test do not use',
});
await dataProtector.grantAccess({
authorizedApp: getChainDefaultConfig(DEFAULT_CHAIN_ID).dappAddress,
protectedData: protectData.address,
authorizedUser: consumerWallet.address, // consumer wallet
numberOfAccess: 1000,
pricePerAccess: dataPricePerAccess,
});
await waitSubgraphIndexing();
web3mailConsumerInstance = new IExecWeb3mail(
...getTestConfig(consumerWallet.privateKey)
);
}, 2 * MAX_EXPECTED_BLOCKTIME);
it(
'should throw error if insufficient total balance to cover task cost and allowDeposit is false',
async () => {
let error;
try {
await web3mailConsumerInstance.sendEmail({
emailSubject: 'e2e mail object for test',
emailContent: 'e2e mail content for test',
protectedData: protectData.address,
dataMaxPrice: dataPricePerAccess,
workerpoolMaxPrice: workerpoolprice,
allowDeposit: false,
});
} catch (err) {
error = err;
}
expect(error).toBeInstanceOf(WorkflowError);
expect(error).toBeInstanceOf(Error);
expect(error.message).toBe('Failed to sendEmail');
const causeMsg =
error.errorCause?.message ||
error.cause?.message ||
error.cause ||
error.errorCause;
expect(String(causeMsg)).toContain(
"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"
);
},
3 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME
);

it.skip(
'should send email after depositing sufficient funds to cover task cost when allowDeposit is true',
async () => {
const result = await web3mailConsumerInstance.sendEmail({
emailSubject: 'e2e mail object for test',
emailContent: 'e2e mail content for test',
protectedData: protectData.address,
dataMaxPrice: dataPricePerAccess,
workerpoolMaxPrice: workerpoolprice,
allowDeposit: true,
});
expect(result).toBeDefined();
expect(result).toHaveProperty('taskId');
expect(result).toHaveProperty('dealId');
},
3 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME
);
});
});