From 75bac0ba958ec914fa6292645e87889ed4ba1c32 Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Mon, 27 Apr 2026 08:06:09 +0200 Subject: [PATCH 01/13] feat: migrate from bellecour (SGX/SCONE) to Arbitrum (TDX) Drop bellecour chain config and SGX sconify pipeline. Set Arbitrum Sepolia as default chain, add Compass URL resolution for both Arbitrum networks, remove ENS resolution (unsupported on Arbitrum), and update TEE tag from 'scone' to 'tdx' across SDK and tests. --- .github/workflows/dapp-deploy.yml | 108 ---- dapp/sconify.sh | 38 -- deployment-dapp/src/revokeSellOrderScript.ts | 2 +- .../src/singleFunction/deployApp.ts | 18 - package.json | 2 +- src/config/config.ts | 15 +- src/web3mail/IExecWeb3mail.ts | 4 +- src/web3mail/fetchUserContacts.ts | 7 +- src/web3mail/sendEmail.models.ts | 1 - src/web3mail/sendEmailCampaign.ts | 5 +- src/web3mail/types.ts | 6 + tests/docker-compose.yml | 186 +++--- tests/e2e/constructor.test.ts | 28 +- tests/e2e/fetchMyContacts.test.ts | 60 +- tests/e2e/fetchUserContacts.test.ts | 45 +- tests/e2e/prepareEmailCampaign.test.ts | 21 +- tests/e2e/sendEmail.test.ts | 552 +++++------------- tests/e2e/sendEmailCampaign.test.ts | 227 +++---- tests/mock/compass/data.json | 24 + tests/mock/server/http500.nginx.conf | 6 + .../prepare-bellecour-fork-for-tests.js | 296 ---------- tests/scripts/prepare-forks-for-tests.js | 233 ++++++++ tests/scripts/prepare-test-env.js | 64 +- tests/test-utils.ts | 203 ++++++- tests/unit/fetchMyContacts.test.ts | 23 +- tests/unit/sendEmail.test.ts | 31 +- tests/utils/mockAllForSendEmail.ts | 2 +- 27 files changed, 980 insertions(+), 1227 deletions(-) delete mode 100755 dapp/sconify.sh create mode 100644 tests/mock/compass/data.json create mode 100644 tests/mock/server/http500.nginx.conf delete mode 100644 tests/scripts/prepare-bellecour-fork-for-tests.js create mode 100644 tests/scripts/prepare-forks-for-tests.js diff --git a/.github/workflows/dapp-deploy.yml b/.github/workflows/dapp-deploy.yml index fd62aa42..a1d77c1d 100644 --- a/.github/workflows/dapp-deploy.yml +++ b/.github/workflows/dapp-deploy.yml @@ -73,34 +73,6 @@ jobs: dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }} dockerhub-password: ${{ secrets.DOCKERHUB_PAT }} - sconify: - if: startsWith(github.event.inputs.environment, 'bellecour-') - uses: iExecBlockchainComputing/github-actions-workflows/.github/workflows/sconify.yml@sconify-v2.0.0 - needs: [docker-publish, extract-tag] - with: - image-name: 'iexechub/web3mail-dapp' - image-tag: ${{ needs.extract-tag.outputs.clean_tag }} - sconify-debug: false - sconify-prod: true - docker-registry: docker.io - sconify-version: ${{ vars.SCONIFY_VERSION }} - binary: /usr/local/bin/node - command: node - host-path: | - /etc/hosts - /etc/resolv.conf - binary-fs: true - fs-dir: /app - heap: 1G - dlopen: 1 - mprotect: 0 - secrets: - docker-username: ${{ secrets.DOCKERHUB_USERNAME }} - docker-password: ${{ secrets.DOCKERHUB_PAT }} - scontain-username: ${{ secrets.SCONTAIN_REGISTRY_USERNAME }} - scontain-password: ${{ secrets.SCONTAIN_REGISTRY_PAT }} - scone-signing-key: ${{ secrets.SCONIFY_SIGNING_PRIVATE_KEY }} - deploy-tdx-dapp: if: startsWith(github.event.inputs.environment, 'arbitrum-') needs: [extract-tag, docker-publish] @@ -168,83 +140,3 @@ jobs: cd node_modules/whitelist-smart-contract export ADDRESS_TO_ADD=$(cat ../../deployment-dapp/.app-address) npm run addResourceToWhitelist -- --network ${{ vars.WHITELIST_NETWORK_NAME }} - - deploy-scone-dapp: - if: startsWith(github.event.inputs.environment, 'bellecour-') - needs: [extract-tag, sconify] - runs-on: ubuntu-latest - environment: ${{ inputs.environment }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.19.0' - cache: 'npm' - - - name: Install dependencies - run: | - npm ci - cd node_modules/whitelist-smart-contract - npm install --save-dev ts-node - cd ../../deployment-dapp - npm ci - - - name: Deploy SCONE dapp contract - env: - WALLET_PRIVATE_KEY: ${{ secrets.WEB3MAIL_APP_OWNER_PRIVATEKEY }} - DOCKER_IMAGE_TAG: ${{ needs.sconify.outputs.prod-image-tag }} - CHECKSUM: ${{ needs.sconify.outputs.prod-checksum }} - FINGERPRINT: ${{ needs.sconify.outputs.prod-mrenclave }} - RPC_URL: ${{ secrets.RPC_URL }} - SCONIFY_VERSION: ${{ vars.SCONIFY_VERSION }} - run: | - cd deployment-dapp - npm run deploy-dapp - - - name: Push dapp secret - env: - WALLET_PRIVATE_KEY: ${{ secrets.WEB3MAIL_APP_OWNER_PRIVATEKEY }} - MJ_APIKEY_PUBLIC: ${{ secrets.MAILJET_APIKEY_PUBLIC }} - MJ_APIKEY_PRIVATE: ${{ secrets.MAILJET_APIKEY_PRIVATE }} - MJ_SENDER: ${{ secrets.MAILJET_SENDER }} - MAILGUN_APIKEY: ${{ secrets.MAILGUN_APIKEY }} - WEB3MAIL_WHITELISTED_APPS: ${{ vars.WEB3MAIL_WHITELISTED_APPS }} - POCO_SUBGRAPH_URL: ${{ vars.POCO_SUBGRAPH_URL }} - RPC_URL: ${{ secrets.RPC_URL }} - SCONIFY_VERSION: ${{ vars.SCONIFY_VERSION }} - run: | - cd deployment-dapp - npm run push-dapp-secret - - - name: Publish free sell order - env: - WALLET_PRIVATE_KEY: ${{ secrets.WEB3MAIL_APP_OWNER_PRIVATEKEY }} - PRICE: ${{ vars.SELL_ORDER_PRICE }} - VOLUME: ${{ vars.SELL_ORDER_VOLUME }} - RPC_URL: ${{ secrets.RPC_URL }} - TEE_FRAMEWORK: ${{ vars.TEE_FRAMEWORK }} - run: | - cd deployment-dapp - npm run publish-sell-order - - - name: Add resource to whitelist - env: - CONTRACT_ADDRESS: ${{ vars.WEB3MAIL_WHITELIST_CONTRACT_ADDRESS }} - PRIVATE_KEY: ${{ secrets.WEB3MAIL_APP_OWNER_PRIVATEKEY }} - RPC_URL: ${{ secrets.RPC_URL }} - run: | - cd node_modules/whitelist-smart-contract - export ADDRESS_TO_ADD=$(cat ../../deployment-dapp/.app-address) - npm run addResourceToWhitelist -- --network ${{ vars.WHITELIST_NETWORK_NAME }} - - - name: Configure ENS - if: ${{ vars.DAPP_ENS_NAME }} - env: - WALLET_PRIVATE_KEY: ${{ secrets.WEB3MAIL_APP_OWNER_PRIVATEKEY }} - DAPP_ENS_NAME: ${{ vars.DAPP_ENS_NAME }} - run: | - cd deployment-dapp - npm run configure-ens diff --git a/dapp/sconify.sh b/dapp/sconify.sh deleted file mode 100755 index 5ac7bb63..00000000 --- a/dapp/sconify.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash - -# declare the app entrypoint -ENTRYPOINT="node /app/app.js" -USER_DOCKER_HUB="your_docker_hub_username" -# Declare image related variables -IMG_NAME=web3mail -NON_TEE_TAG=non-tee -DEBUG_TAG=1.0.0-debug -IMG_FROM=${USER_DOCKER_HUB}/${IMG_NAME}:${NON_TEE_TAG} -IMG_TO=${USER_DOCKER_HUB}/${IMG_NAME}:${DEBUG_TAG} - - -# build the regular non-TEE image -docker build . -t ${IMG_FROM} -docker pull registry.scontain.com:5050/sconecuratedimages/node:14.4.0-alpine3.11 - -# Run the sconifier to build the TEE image based on the non-TEE image -docker run -it --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - registry.scontain.com:5050/scone-production/iexec-sconify-image:5.7.5-v8 \ - sconify_iexec \ - --name=${IMG_NAME} \ - --from=${IMG_FROM} \ - --to=${IMG_TO} \ - --binary-fs \ - --fs-dir=/app \ - --host-path=/etc/hosts \ - --host-path=/etc/resolv.conf \ - --binary=/usr/local/bin/node \ - --heap=1G \ - --dlopen=1 \ - --no-color \ - --verbose \ - --command=${ENTRYPOINT} \ - && echo -e "\n------------------\n" \ - && echo "successfully built TEE docker image => ${IMG_TO}" \ - && echo "application mrenclave.fingerprint is $(docker run --rm -e SCONE_HASH=1 ${IMG_TO})" diff --git a/deployment-dapp/src/revokeSellOrderScript.ts b/deployment-dapp/src/revokeSellOrderScript.ts index b3e97c8f..8c49a8f2 100644 --- a/deployment-dapp/src/revokeSellOrderScript.ts +++ b/deployment-dapp/src/revokeSellOrderScript.ts @@ -17,7 +17,7 @@ const main = async () => { // validate params const orderHash = await orderHashSchema().validate(ORDER_HASH); - //revoke sell order for Tee app (scone) + //revoke sell order for Tee app const txHash = await revokeSellOrder(iexec, orderHash); if (!txHash) throw Error(`Failed to revoke app sell order: ${orderHash}`); }; diff --git a/deployment-dapp/src/singleFunction/deployApp.ts b/deployment-dapp/src/singleFunction/deployApp.ts index 7e9e6aef..9e4dd682 100644 --- a/deployment-dapp/src/singleFunction/deployApp.ts +++ b/deployment-dapp/src/singleFunction/deployApp.ts @@ -28,30 +28,12 @@ export const deployApp = async ({ const name = APP_NAME; const type = APP_TYPE; - let mrenclave; - - // TODO: to be deleted after migration to TDX - if (sconifyVersion) { - console.log( - `Using SCONE framework with SCONIFY version: ${sconifyVersion}` - ); - mrenclave = { - framework: 'SCONE', // workaround framework not auto capitalized - version: `v${sconifyVersion.split('.').slice(0, 2).join('.')}`, // extracts "vX.Y" from "X.Y.Z-vN" format (e.g., "5.9.1-v16" → "v5.9") - entrypoint: 'node --disable-wasm-trap-handler /app/app.js', - heapSize: 1073741824, // 1GB - fingerprint, - }; - } - const app = { owner: await iexec.wallet.getAddress(), name, type, multiaddr: `${dockerNamespace}/${dockerRepository}:${dockerTag}`, checksum, - // TODO: to be deleted after migration to TDX - mrenclave, }; console.log(`Deploying app:\n${JSON.stringify(app, undefined, 2)}`); const { address, txHash } = await iexec.app.deployApp(app); diff --git a/package.json b/package.json index 88394d30..85e93c81 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "scripts": { "build": "rm -rf dist && tsc --project tsconfig.build.json", "check-types": "tsc --noEmit", - "test:prepare": "node tests/scripts/prepare-bellecour-fork-for-tests.js && node tests/scripts/prepare-iexec.js", + "test:prepare": "node tests/scripts/prepare-forks-for-tests.js", "test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --testMatch \"**/tests/**/*.test.ts\" --forceExit --coverage", "test:unit": "NODE_OPTIONS=--experimental-vm-modules jest --testMatch \"**/tests/unit/**/*.test.ts\" -b", "test:unit:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --testMatch \"**/tests/unit/**/*.unit.ts\" --coverage", diff --git a/src/config/config.ts b/src/config/config.ts index 4a8628b7..41184e23 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -5,11 +5,12 @@ export const DEFAULT_CONTENT_TYPE = 'text/plain'; export const ANY_DATASET_ADDRESS = 'any'; export const CALLBACK_WEB3MAIL = '0x5f936db7ad6d29371808e42a87015595d90509ba'; -export const DEFAULT_CHAIN_ID = 134; +export const DEFAULT_CHAIN_ID = 421614; interface ChainConfig { name: string; dappAddress?: string; + compassUrl?: string; prodWorkerpoolAddress: string; dataProtectorSubgraph: string; ipfsUploadUrl: string; @@ -19,19 +20,10 @@ interface ChainConfig { } const CHAIN_CONFIG: Record = { - 134: { - name: 'bellecour', - dappAddress: 'web3mail.apps.iexec.eth', - prodWorkerpoolAddress: 'prod-v8-bellecour.main.pools.iexec.eth', - dataProtectorSubgraph: - 'https://thegraph.iex.ec/subgraphs/name/bellecour/dataprotector-v2', - ipfsUploadUrl: '/dns4/ipfs-upload.v8-bellecour.iex.ec/https', - ipfsGateway: 'https://ipfs-gateway.v8-bellecour.iex.ec', - whitelistSmartContract: '0x781482C39CcE25546583EaC4957Fb7Bf04C277D2', - }, 421614: { name: 'arbitrum-sepolia-testnet', dappAddress: undefined, // ENS not supported on this network, address will be resolved from Compass + compassUrl: 'https://compass.arbitrum-sepolia-testnet.iex.ec', prodWorkerpoolAddress: '0x2956f0cb779904795a5f30d3b3ea88b714c3123f', // TDX workerpool dataProtectorSubgraph: 'https://thegraph.arbitrum-sepolia-testnet.iex.ec/api/subgraphs/id/5YjRPLtjS6GH6bB4yY55Qg4HzwtRGQ8TaHtGf9UBWWd', @@ -42,6 +34,7 @@ const CHAIN_CONFIG: Record = { 42161: { name: 'arbitrum-mainnet', dappAddress: undefined, // ENS not supported on this network, address will be resolved from Compass + compassUrl: 'https://compass.arbitrum-mainnet.iex.ec', prodWorkerpoolAddress: '0x8ef2ec3ef9535d4b4349bfec7d8b31a580e60244', // TDX workerpool dataProtectorSubgraph: 'https://thegraph.arbitrum.iex.ec/api/subgraphs/id/Ep5zs5zVr4tDiVuQJepUu51e5eWYJpka624X4DMBxe3u', diff --git a/src/web3mail/IExecWeb3mail.ts b/src/web3mail/IExecWeb3mail.ts index 7c70ba21..6311f90d 100644 --- a/src/web3mail/IExecWeb3mail.ts +++ b/src/web3mail/IExecWeb3mail.ts @@ -71,7 +71,7 @@ export class IExecWeb3mail { ethProvider?: EthersCompatibleProvider, options?: Web3MailConfigOptions ) { - this.ethProvider = ethProvider || 'bellecour'; + this.ethProvider = ethProvider || 'arbitrum-sepolia-testnet'; this.options = options || {}; } @@ -193,7 +193,7 @@ export class IExecWeb3mail { this.options?.dappAddressOrENS || chainDefaultConfig?.dappAddress || (await resolveDappAddressFromCompass( - await iexec.config.resolveCompassURL(), + this.options?.compassUrl ?? chainDefaultConfig?.compassUrl ?? '', chainId )); const dappWhitelistAddress = diff --git a/src/web3mail/fetchUserContacts.ts b/src/web3mail/fetchUserContacts.ts index 9e7ceb03..46db8fa8 100644 --- a/src/web3mail/fetchUserContacts.ts +++ b/src/web3mail/fetchUserContacts.ts @@ -9,7 +9,6 @@ import { addressOrEnsSchema, addressSchema, booleanSchema, - isEnsTest, throwIfMissing, } from '../utils/validators.js'; import { Contact, FetchUserContactsParams } from './types.js'; @@ -70,10 +69,8 @@ export const fetchUserContacts = async ({ const orders = dappOrders.concat(whitelistOrders); const myContacts: Omit[] = []; - let web3DappResolvedAddress = vDappAddressOrENS; - if (isEnsTest(vDappAddressOrENS)) { - web3DappResolvedAddress = await iexec.ens.resolveName(vDappAddressOrENS); - } + const web3DappResolvedAddress = vDappAddressOrENS; + orders.forEach((order) => { if ( order.order.apprestrict.toLowerCase() === diff --git a/src/web3mail/sendEmail.models.ts b/src/web3mail/sendEmail.models.ts index be608b71..3e1568a0 100644 --- a/src/web3mail/sendEmail.models.ts +++ b/src/web3mail/sendEmail.models.ts @@ -1,6 +1,5 @@ import { Address, BN } from 'iexec'; import { PublishedWorkerpoolorder } from 'iexec/IExecOrderbookModule'; -// import { VoucherInfo } from 'iexec/IExecVoucherModule'; // To import from 'iexec' once exported type VoucherInfo = { diff --git a/src/web3mail/sendEmailCampaign.ts b/src/web3mail/sendEmailCampaign.ts index 15f4b37d..c3672597 100644 --- a/src/web3mail/sendEmailCampaign.ts +++ b/src/web3mail/sendEmailCampaign.ts @@ -57,13 +57,12 @@ export const sendEmailCampaign = async ({ } // Handle protocol errors - this will throw if it's an ApiCallError - // handleIfProtocolError transforms ApiCallError into a WorkflowError with isProtocolError=true - handleIfProtocolError(error); + handleIfProtocolError(error as Error); // For all other errors throw new WorkflowError({ message: 'Failed to sendEmailCampaign', - errorCause: error, + errorCause: error as Error, }); } }; diff --git a/src/web3mail/types.ts b/src/web3mail/types.ts index 18cee01b..d73fafd9 100644 --- a/src/web3mail/types.ts +++ b/src/web3mail/types.ts @@ -135,6 +135,12 @@ export type Web3MailConfigOptions = { */ ipfsGateway?: string; + /** + * Override the Compass URL used to resolve the dapp address. + * If not provided, the default Compass URL for the chain will be used. + */ + compassUrl?: string; + /** * if true allows using a provider connected to an experimental networks (default false) * diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index fd6faec6..53abcbbd 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -1,27 +1,57 @@ services: - bellecour-fork: + arbitrum-sepolia-fork: restart: 'no' + platform: linux/amd64 image: ghcr.io/foundry-rs/foundry:v1.0.0 entrypoint: anvil - command: '--host 0.0.0.0 --port 8545 --block-time 1 --hardfork berlin --fork-url $BELLECOUR_FORK_URL --fork-block-number $BELLECOUR_FORK_BLOCK --chain-id 134 --gas-limit 6700000 --gas-price 0' + command: '--host 0.0.0.0 --port 8545 --block-time 1 --fork-url $ARBITRUM_SEPOLIA_FORK_URL --fork-block-number $ARBITRUM_SEPOLIA_FORK_BLOCK --chain-id 421614' expose: - 8545 ports: - - 8545:8545 + - 8555:8545 healthcheck: + # check port 8545 is open without nc test: (echo >/dev/tcp/$(hostname)/8545) &>/dev/null interval: 10s timeout: 5s retries: 3 start_period: 30s - sms: + unknown-chain-fork: + restart: 'no' + platform: linux/amd64 + image: ghcr.io/foundry-rs/foundry:v1.0.0 + entrypoint: anvil + command: '--host 0.0.0.0 --port 8545 --block-time 1 --fork-url $ARBITRUM_SEPOLIA_FORK_URL --fork-block-number $ARBITRUM_SEPOLIA_FORK_BLOCK --chain-id 421615' + expose: + - 8545 + ports: + - 8565:8545 + healthcheck: + # check port 8545 is open without nc + test: (echo >/dev/tcp/$(hostname)/8545) &>/dev/null + interval: 10s + timeout: 5s + retries: 3 + start_period: 30s + + service-internal-error: + image: nginx:alpine + volumes: + - $PWD/mock/server/http500.nginx.conf:/etc/nginx/conf.d/default.conf:ro + expose: + - 80 + ports: + - 5500:80 + + sms-arbitrum-sepolia: + platform: linux/amd64 image: iexechub/iexec-sms:8.7.0 restart: unless-stopped environment: JAVA_TOOL_OPTIONS: '-Xmx256M' - IEXEC_SMS_BLOCKCHAIN_NODE_ADDRESS: http://bellecour-fork:8545 - IEXEC_HUB_ADDRESS: '0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f' + IEXEC_SMS_BLOCKCHAIN_NODE_ADDRESS: http://arbitrum-sepolia-fork:8545 + IEXEC_HUB_ADDRESS: '0xB2157BF2fAb286b2A4170E3491Ac39770111Da3E' IEXEC_SMS_TEE_RUNTIME_FRAMEWORK: scone IEXEC_SMS_IMAGE_LAS_IMAGE: 'las-image' IEXEC_TEE_WORKER_PRE_COMPUTE_IMAGE: 'pre-compute-image' @@ -29,32 +59,37 @@ services: IEXEC_TEE_WORKER_POST_COMPUTE_IMAGE: 'post-compute-image' IEXEC_TEE_WORKER_POST_COMPUTE_FINGERPRINT: 'post-compute-fingerprint' ports: - - 13300:13300 + - 13350:13300 healthcheck: test: curl -f localhost:13300/actuator/health || exit 1 + interval: 10s + timeout: 10s + retries: 10 + start_period: 120s depends_on: - bellecour-fork: + arbitrum-sepolia-fork: condition: service_healthy - result-proxy: + result-proxy-arbitrum-sepolia: + platform: linux/amd64 image: iexechub/iexec-result-proxy:7.1.0 restart: unless-stopped environment: - IEXEC_PRIVATE_CHAIN_ADDRESS: http://bellecour-fork:8545 - IEXEC_PUBLIC_CHAIN_ADDRESS: http://bellecour-fork:8545 - IEXEC_HUB_ADDRESS: '0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f' + IEXEC_PRIVATE_CHAIN_ADDRESS: http://arbitrum-sepolia-fork:8545 + IEXEC_PUBLIC_CHAIN_ADDRESS: http://arbitrum-sepolia-fork:8545 + IEXEC_HUB_ADDRESS: '0xB2157BF2fAb286b2A4170E3491Ac39770111Da3E' MONGO_HOST: result-proxy-mongo MONGO_PORT: 13202 IEXEC_IPFS_HOST: ipfs ports: - - 13200:13200 + - 13250:13200 depends_on: - bellecour-fork: + arbitrum-sepolia-fork: condition: service_healthy result-proxy-mongo: condition: service_started ipfs: - condition: service_started + condition: service_healthy result-proxy-mongo: restart: unless-stopped @@ -63,6 +98,8 @@ services: command: -c "mongod --bind_ip_all --port 13202" expose: - 13202 + ports: + - 13202:13202 ipfs: restart: unless-stopped @@ -81,7 +118,7 @@ services: start_period: 30s market-mongo: - image: mongo:7.0.6 + image: mongo:6.0.3 restart: unless-stopped expose: - 27017 @@ -96,51 +133,75 @@ services: - 6379 ports: - 6379:6379 + healthcheck: + test: nc -z 0.0.0.0 6379 + interval: 10s + timeout: 5s + retries: 3 + start_period: 30s - market-watcher: - image: iexechub/iexec-market-watcher:7.0.0 + market-watcher-arbitrum-sepolia: + platform: linux/amd64 + image: iexechub/iexec-market-watcher:7.0.1 restart: unless-stopped environment: - CHAIN: BELLECOUR - START_BLOCK: $BELLECOUR_FORK_BLOCK - ETH_WS_HOST: ws://bellecour-fork:8545 - ETH_RPC_HOST: http://bellecour-fork:8545 + CHAIN: ARBITRUM_SEPOLIA + START_BLOCK: $ARBITRUM_SEPOLIA_FORK_BLOCK + CHAIN_ID: 421614 + IEXEC_ADDRESS: '0xB2157BF2fAb286b2A4170E3491Ac39770111Da3E' + IS_NATIVE: 'false' + ETH_WS_HOST: ws://arbitrum-sepolia-fork:8545 + ETH_RPC_HOST: http://arbitrum-sepolia-fork:8545 MONGO_HOST: market-mongo REDIS_HOST: market-redis depends_on: - bellecour-fork: + arbitrum-sepolia-fork: condition: service_healthy market-redis: - condition: service_started + condition: service_healthy market-mongo: condition: service_started - market-api: - image: iexechub/iexec-market-api:7.1.0 + market-api-arbitrum-sepolia: + platform: linux/amd64 + image: iexechub/iexec-market-api:7.1.2 restart: unless-stopped ports: - - 3000:3000 + - 3050:3000 expose: - 3000 environment: - CHAINS: BELLECOUR_FORK - BELLECOUR_FORK_ETH_RPC_HOST: http://bellecour-fork:8545 - BELLECOUR_FORK_CHAIN_ID: 134 - BELLECOUR_FORK_IS_NATIVE: 'true' - BELLECOUR_FORK_IEXEC_ADDRESS: '0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f' + CHAINS: ARBITRUM_SEPOLIA_FORK + ARBITRUM_SEPOLIA_FORK_ETH_RPC_HOST: http://arbitrum-sepolia-fork:8545 + ARBITRUM_SEPOLIA_FORK_CHAIN_ID: 421614 + ARBITRUM_SEPOLIA_FORK_IS_NATIVE: 'false' + ARBITRUM_SEPOLIA_FORK_IEXEC_ADDRESS: '0xB2157BF2fAb286b2A4170E3491Ac39770111Da3E' MONGO_HOST: market-mongo REDIS_HOST: market-redis RATE_LIMIT_MAX: 10000 RATE_LIMIT_PERIOD: 60000 MAX_OPEN_ORDERS_PER_WALLET: 1000 depends_on: - bellecour-fork: + arbitrum-sepolia-fork: condition: service_healthy market-redis: - condition: service_started + condition: service_healthy market-mongo: condition: service_started + compass-arbitrum-sepolia: + platform: linux/amd64 + image: iexechub/compass:v0.1.1 + restart: unless-stopped + environment: + DATA_FILE_PATH: /app/data.json + volumes: + - $PWD/mock/compass/data.json:/app/data.json:ro + ports: + - 8069:3000 + healthcheck: + test: nc -w 1 0.0.0.0 3000 + graphnode-postgres: image: postgres:12 restart: unless-stopped @@ -162,19 +223,15 @@ services: start_period: 30s graphnode: + platform: linux/amd64 image: graphprotocol/graph-node:v0.34.1 restart: unless-stopped expose: - 8000 - 8020 ports: - # GraphQL HTTP - 8000:8000 - # GraphQL WS - # - 8001:8001 - # admin RPC - 8020:8020 - # # metrics - 8040:8040 environment: postgres_host: graphnode-postgres @@ -183,10 +240,11 @@ services: postgres_pass: password postgres_db: graphnode-db ipfs: ipfs:5001 - ethereum: bellecour:http://bellecour-fork:8545 - GRAPH_ETHEREUM_GENESIS_BLOCK_NUMBER: $BELLECOUR_FORK_BLOCK + # Alias arbitrum-sepolia-fork as "bellecour" so the subgraph template (hardcoded network name) works + ethereum: 'bellecour:http://arbitrum-sepolia-fork:8545' + GRAPH_ETHEREUM_GENESIS_BLOCK_NUMBER: $ARBITRUM_SEPOLIA_FORK_BLOCK depends_on: - bellecour-fork: + arbitrum-sepolia-fork: condition: service_healthy graphnode-postgres: condition: service_healthy @@ -200,6 +258,7 @@ services: start_period: 30s dataprotector-subgraph-deployer: + platform: linux/amd64 image: iexechub/dataprotector-subgraph-deployer:3.0.0 restart: 'no' depends_on: @@ -209,46 +268,33 @@ services: condition: service_started environment: ENV: prod - START_BLOCK: $BELLECOUR_FORK_BLOCK - GRAPHNODE_URL: http://graphnode:8020 - IPFS_URL: http://ipfs:5001 - - voucher-subgraph-deployer: - restart: 'no' - # https://github.com/iExecBlockchainComputing/iexec-voucher-subgraph - image: iexechub/voucher-subgraph-deployer:1.0.0 - environment: - RPC_URL: http://bellecour-fork:8545 + START_BLOCK: $ARBITRUM_SEPOLIA_FORK_BLOCK + DATAPROTECTOR_ADDRESS: '0x168eAF6C33a77E3caD9db892452f51a5D91df621' + APP_REGISTRY_ADDRESS: '0x9950D94FB074182eE93fF79a50CD698C4983281F' + DATASET_REGISTRY_ADDRESS: '0x07cc4E1Ea30dD02796795876509a3bfC5053128d' GRAPHNODE_URL: http://graphnode:8020 IPFS_URL: http://ipfs:5001 - VOUCHER_HUB_ADDRESS: '0x3137B6DF4f36D338b82260eDBB2E7bab034AFEda' - VOUCHER_HUB_START_BLOCK: $BELLECOUR_FORK_BLOCK - depends_on: - bellecour-fork: - condition: service_healthy - graphnode: - condition: service_healthy - ipfs: - condition: service_healthy stack-ready: image: bash command: - echo "all services ready" depends_on: - bellecour-fork: + arbitrum-sepolia-fork: condition: service_healthy - graphnode: + sms-arbitrum-sepolia: condition: service_healthy - sms: - condition: service_healthy - market-watcher: + market-watcher-arbitrum-sepolia: condition: service_started - market-api: + market-api-arbitrum-sepolia: condition: service_started - result-proxy: + result-proxy-arbitrum-sepolia: condition: service_started + compass-arbitrum-sepolia: + condition: service_healthy + graphnode: + condition: service_healthy dataprotector-subgraph-deployer: condition: service_completed_successfully - voucher-subgraph-deployer: - condition: service_completed_successfully + unknown-chain-fork: + condition: service_healthy diff --git a/tests/e2e/constructor.test.ts b/tests/e2e/constructor.test.ts index 25f34b67..1be16a83 100644 --- a/tests/e2e/constructor.test.ts +++ b/tests/e2e/constructor.test.ts @@ -113,7 +113,9 @@ describe('IExecWeb3mail()', () => { expect(whitelistAddress).toStrictEqual( customDappWhitelistAddress.toLowerCase() ); - expect(await iexec.config.resolveSmsURL()).toBe(smsURL); + expect(await iexec.config.resolveSmsURL({ teeFramework: 'tdx' })).toBe( + smsURL + ); expect(await iexec.config.resolveIexecGatewayURL()).toBe(iexecGatewayURL); }); @@ -141,7 +143,7 @@ describe('IExecWeb3mail()', () => { MAX_EXPECTED_WEB2_SERVICES_TIME ); - describe.skip('When instantiating SDK with an experimental network', () => { + describe('When instantiating SDK with an experimental network', () => { const experimentalNetworkSigner = getWeb3Provider( Wallet.createRandom().privateKey, { @@ -151,19 +153,21 @@ describe('IExecWeb3mail()', () => { ); describe('Without allowExperimentalNetworks', () => { - it('should throw a configuration error', async () => { - const web3mail = new IExecWeb3mail(experimentalNetworkSigner); - await expect(web3mail.init()).rejects.toThrow( - 'Missing required configuration for chainId 421614: dataProtectorSubgraph, whitelistSmartContract, ipfsGateway, prodWorkerpoolAddress, ipfsUploadUrl' - ); - }); + it( + 'should not throw a configuration error', + async () => { + const web3mail = new IExecWeb3mail(experimentalNetworkSigner); + + // Pour une fonction async, on utilise resolves + await expect(web3mail.init()).resolves.not.toThrow(); + }, + MAX_EXPECTED_WEB2_SERVICES_TIME + ); }); describe('With allowExperimentalNetworks: true', () => { it('should resolve the configuration', async () => { - const web3mail = new IExecWeb3mail(experimentalNetworkSigner, { - allowExperimentalNetworks: true, - }); + const web3mail = new IExecWeb3mail(experimentalNetworkSigner); await expect(web3mail.init()).resolves.toBeUndefined(); expect(web3mail).toBeInstanceOf(IExecWeb3mail); }); @@ -233,7 +237,7 @@ describe('IExecWeb3mail()', () => { const chainConfig = getChainDefaultConfig(chainId, { allowExperimentalNetworks: true, }); - expect(chainConfig.dappAddress).toBeUndefined(); // ENS not supported on this network + expect(chainConfig?.dappAddress).toBeUndefined(); // ENS not supported on this network const web3mail = new IExecWeb3mail( getWeb3Provider(Wallet.createRandom().privateKey, { diff --git a/tests/e2e/fetchMyContacts.test.ts b/tests/e2e/fetchMyContacts.test.ts index ecbc8579..0ca25daf 100644 --- a/tests/e2e/fetchMyContacts.test.ts +++ b/tests/e2e/fetchMyContacts.test.ts @@ -12,24 +12,25 @@ import { MAX_EXPECTED_WEB2_SERVICES_TIME, deployRandomDataset, getTestConfig, + getTestDappAddress, getTestWeb3SignerProvider, getTestIExecOption, + setBalance, waitSubgraphIndexing, } from '../test-utils.js'; import IExec from 'iexec/IExec'; -import { - DEFAULT_CHAIN_ID, - getChainDefaultConfig, -} from '../../src/config/config.js'; describe('web3mail.fetchMyContacts()', () => { let wallet: HDNodeWallet; let web3mail: IExecWeb3mail; let dataProtector: IExecDataProtectorCore; let protectedData: ProtectedDataWithSecretProps; + let dappAddress: string; beforeAll(async () => { wallet = Wallet.createRandom(); + await setBalance(wallet.address, 10n ** 18n); + dappAddress = await getTestDappAddress(); dataProtector = new IExecDataProtectorCore( ...getTestConfig(wallet.privateKey) ); @@ -45,22 +46,17 @@ describe('web3mail.fetchMyContacts()', () => { 'pass with a granted access for a specific requester', async () => { await dataProtector.grantAccess({ - authorizedApp: getChainDefaultConfig(DEFAULT_CHAIN_ID).dappAddress, + authorizedApp: dappAddress, protectedData: protectedData.address, authorizedUser: wallet.address, }); + await waitSubgraphIndexing(); const res = await web3mail.fetchMyContacts(); - const foundContactForASpecificRequester = res.find((obj) => { - return obj.address === protectedData.address.toLocaleLowerCase(); - }); - expect( - foundContactForASpecificRequester && - foundContactForASpecificRequester.address - ).toBeDefined(); - expect( - foundContactForASpecificRequester && - foundContactForASpecificRequester.address - ).toBe(protectedData.address.toLocaleLowerCase()); + const foundContact = res.find( + (obj) => obj.address === protectedData.address.toLowerCase() + ); + expect(foundContact?.address).toBeDefined(); + expect(foundContact?.address).toBe(protectedData.address.toLowerCase()); }, MAX_EXPECTED_WEB2_SERVICES_TIME ); @@ -69,22 +65,19 @@ describe('web3mail.fetchMyContacts()', () => { 'pass with a granted access for any requester', async () => { const grantedAccessForAnyRequester = await dataProtector.grantAccess({ - authorizedApp: getChainDefaultConfig(DEFAULT_CHAIN_ID).dappAddress, + authorizedApp: dappAddress, protectedData: protectedData.address, authorizedUser: NULL_ADDRESS, }); + await waitSubgraphIndexing(); const res = await web3mail.fetchMyContacts(); - const foundContactForAnyRequester = res.find( + const foundContact = res.find( (obj) => obj.address === protectedData.address.toLowerCase() ); - expect( - foundContactForAnyRequester && foundContactForAnyRequester.address - ).toBeDefined(); - expect( - foundContactForAnyRequester && foundContactForAnyRequester.address - ).toBe(protectedData.address.toLocaleLowerCase()); + expect(foundContact?.address).toBeDefined(); + expect(foundContact?.address).toBe(protectedData.address.toLowerCase()); //revoke access to not appear as contact for anyone const revoke = await dataProtector.revokeOneAccess( @@ -105,10 +98,10 @@ describe('web3mail.fetchMyContacts()', () => { iexecOptions ); const dataset = await deployRandomDataset(iexec); - const encryptionKey = await iexec.dataset.generateEncryptionKey(); + const encryptionKey = iexec.dataset.generateEncryptionKey(); await iexec.dataset.pushDatasetSecret(dataset.address, encryptionKey); await dataProtector.grantAccess({ - authorizedApp: getChainDefaultConfig(DEFAULT_CHAIN_ID).dappAddress, + authorizedApp: dappAddress, protectedData: dataset.address, authorizedUser: wallet.address, }); @@ -130,7 +123,7 @@ describe('web3mail.fetchMyContacts()', () => { await waitSubgraphIndexing(); await dataProtector.grantAccess({ - authorizedApp: getChainDefaultConfig(DEFAULT_CHAIN_ID).dappAddress, + authorizedApp: dappAddress, protectedData: notValidProtectedData.address, authorizedUser: wallet.address, }); @@ -149,7 +142,6 @@ describe('web3mail.fetchMyContacts()', () => { describe('bulkOnly parameter', () => { let protectedDataWithBulk: any; let protectedDataWithoutBulk: any; - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); beforeAll(async () => { protectedDataWithBulk = await dataProtector.protectData({ @@ -166,17 +158,15 @@ describe('web3mail.fetchMyContacts()', () => { it( 'should return only contacts with bulk access when bulkOnly is true', async () => { - // Grant access with allowBulk: true await dataProtector.grantAccess({ - authorizedApp: defaultConfig.dappAddress, + authorizedApp: dappAddress, protectedData: protectedDataWithBulk.address, authorizedUser: wallet.address, allowBulk: true, }); - // Grant access with allowBulk: false (or default) await dataProtector.grantAccess({ - authorizedApp: defaultConfig.dappAddress, + authorizedApp: dappAddress, protectedData: protectedDataWithoutBulk.address, authorizedUser: wallet.address, allowBulk: false, @@ -184,12 +174,10 @@ describe('web3mail.fetchMyContacts()', () => { await waitSubgraphIndexing(); - // Fetch contacts with bulkOnly: true const contactsWithBulkOnly = await web3mail.fetchMyContacts({ bulkOnly: true, }); - // Should only include the contact with bulk access const bulkContact = contactsWithBulkOnly.find( (contact) => contact.address === protectedDataWithBulk.address.toLowerCase() @@ -210,12 +198,10 @@ describe('web3mail.fetchMyContacts()', () => { it( 'should return all contacts when bulkOnly is false', async () => { - // Fetch contacts with bulkOnly: false (default) const contactsWithoutBulkOnly = await web3mail.fetchMyContacts({ bulkOnly: false, }); - // Should include both contacts const bulkContact = contactsWithoutBulkOnly.find( (contact) => contact.address === protectedDataWithBulk.address.toLowerCase() @@ -234,10 +220,8 @@ describe('web3mail.fetchMyContacts()', () => { it( 'should return all contacts when bulkOnly is not specified (default)', async () => { - // Fetch contacts without specifying bulkOnly (defaults to false) const contactsDefault = await web3mail.fetchMyContacts(); - // Should include both contacts const bulkContact = contactsDefault.find( (contact) => contact.address === protectedDataWithBulk.address.toLowerCase() diff --git a/tests/e2e/fetchUserContacts.test.ts b/tests/e2e/fetchUserContacts.test.ts index e3c9a94c..59f3342e 100644 --- a/tests/e2e/fetchUserContacts.test.ts +++ b/tests/e2e/fetchUserContacts.test.ts @@ -14,6 +14,8 @@ import { MAX_EXPECTED_SUBGRAPH_INDEXING_TIME, MAX_EXPECTED_WEB2_SERVICES_TIME, getTestConfig, + getTestDappAddress, + setBalance, waitSubgraphIndexing, } from '../test-utils.js'; @@ -23,9 +25,12 @@ describe('web3mail.fetchMyContacts()', () => { let dataProtector: IExecDataProtectorCore; let protectedData1: ProtectedDataWithSecretProps; let protectedData2: ProtectedDataWithSecretProps; + let dappAddress: string; beforeAll(async () => { wallet = Wallet.createRandom(); + await setBalance(wallet.address, 10n ** 18n); + dappAddress = await getTestDappAddress(); dataProtector = new IExecDataProtectorCore( ...getTestConfig(wallet.privateKey) ); @@ -91,13 +96,11 @@ describe('web3mail.fetchMyContacts()', () => { 'should return the user contacts for both app and whitelist', async () => { const userWithAccess = Wallet.createRandom().address; - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); - expect(defaultConfig).not.toBeNull(); - const authorizedApp = defaultConfig!.dappAddress; - const authorizedWhitelist = defaultConfig!.whitelistSmartContract; + const authorizedWhitelist = + getChainDefaultConfig(DEFAULT_CHAIN_ID)!.whitelistSmartContract; await dataProtector.grantAccess({ - authorizedApp: authorizedApp, + authorizedApp: dappAddress, protectedData: protectedData1.address, authorizedUser: userWithAccess, }); @@ -108,6 +111,8 @@ describe('web3mail.fetchMyContacts()', () => { authorizedUser: userWithAccess, }); + await waitSubgraphIndexing(); + const contacts = await web3mail.fetchUserContacts({ userAddress: userWithAccess, isUserStrict: true, @@ -122,22 +127,21 @@ describe('web3mail.fetchMyContacts()', () => { async () => { const user1 = Wallet.createRandom().address; const user2 = Wallet.createRandom().address; - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); - expect(defaultConfig).not.toBeNull(); - const authorizedApp = defaultConfig!.dappAddress; await dataProtector.grantAccess({ - authorizedApp: authorizedApp, + authorizedApp: dappAddress, protectedData: protectedData1.address, authorizedUser: user1, }); await dataProtector.grantAccess({ - authorizedApp: authorizedApp, + authorizedApp: dappAddress, protectedData: protectedData2.address, authorizedUser: user2, }); + await waitSubgraphIndexing(); + const contactUser1 = await web3mail.fetchUserContacts({ userAddress: user1, }); @@ -153,15 +157,15 @@ describe('web3mail.fetchMyContacts()', () => { 'Test that the protected data can be accessed by authorized user', async () => { const userWithAccess = Wallet.createRandom().address; - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); - expect(defaultConfig).not.toBeNull(); - const authorizedApp = defaultConfig!.dappAddress; await dataProtector.grantAccess({ - authorizedApp: authorizedApp, + authorizedApp: dappAddress, protectedData: protectedData1.address, authorizedUser: userWithAccess, }); + + await waitSubgraphIndexing(); + const contacts = await web3mail.fetchUserContacts({ userAddress: userWithAccess, }); @@ -216,12 +220,8 @@ describe('web3mail.fetchMyContacts()', () => { // Call getTestConfig to get the default configuration const [ethProvider, defaultOptions] = getTestConfig(wallet.privateKey); - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); - expect(defaultConfig).not.toBeNull(); - const authorizedApp = defaultConfig!.dappAddress; - await dataProtector.grantAccess({ - authorizedApp: authorizedApp, + authorizedApp: dappAddress, protectedData: protectedData1.address, authorizedUser: ethProvider.address, }); @@ -270,12 +270,9 @@ describe('web3mail.fetchMyContacts()', () => { it( 'should return only contacts with bulk access when bulkOnly is true', async () => { - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); - expect(defaultConfig).not.toBeNull(); - // Grant access with allowBulk: true await dataProtector.grantAccess({ - authorizedApp: defaultConfig!.dappAddress, + authorizedApp: dappAddress, protectedData: protectedDataWithBulk.address, authorizedUser: userWithAccess, allowBulk: true, @@ -283,7 +280,7 @@ describe('web3mail.fetchMyContacts()', () => { // Grant access with allowBulk: false (or default) await dataProtector.grantAccess({ - authorizedApp: defaultConfig!.dappAddress, + authorizedApp: dappAddress, protectedData: protectedDataWithoutBulk.address, authorizedUser: userWithAccess, allowBulk: false, diff --git a/tests/e2e/prepareEmailCampaign.test.ts b/tests/e2e/prepareEmailCampaign.test.ts index 5846d3fc..3680e457 100644 --- a/tests/e2e/prepareEmailCampaign.test.ts +++ b/tests/e2e/prepareEmailCampaign.test.ts @@ -4,10 +4,6 @@ import { } from '@iexec/dataprotector'; import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; import { HDNodeWallet } from 'ethers'; -import { - DEFAULT_CHAIN_ID, - getChainDefaultConfig, -} from '../../src/config/config.js'; import { Contact, IExecWeb3mail } from '../../src/index.js'; import { MAX_EXPECTED_BLOCKTIME, @@ -17,8 +13,10 @@ import { createAndPublishAppOrders, getRandomWallet, getTestConfig, + getTestDappAddress, getTestIExecOption, getTestWeb3SignerProvider, + setBalance, waitSubgraphIndexing, } from '../test-utils.js'; import { IExec } from 'iexec'; @@ -33,23 +31,22 @@ describe('web3mail.prepareEmailCampaign()', () => { let validProtectedData3: ProtectedDataWithSecretProps; const iexecOptions = getTestIExecOption(); const prodWorkerpoolPublicPrice = 1000; - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); + let dappAddress: string; beforeAll(async () => { // Create app orders providerWallet = getRandomWallet(); + dappAddress = await getTestDappAddress(); const ethProvider = getTestWeb3SignerProvider( TEST_CHAIN.appOwnerWallet.privateKey ); const resourceProvider = new IExec({ ethProvider }, iexecOptions); - await createAndPublishAppOrders( - resourceProvider, - defaultConfig!.dappAddress - ); + await createAndPublishAppOrders(resourceProvider, dappAddress); dataProtector = new IExecDataProtectorCore( ...getTestConfig(providerWallet.privateKey) ); + await setBalance(providerWallet.address, 10n ** 18n); // create valid protected data validProtectedData1 = await dataProtector.protectData({ @@ -75,21 +72,21 @@ describe('web3mail.prepareEmailCampaign()', () => { // Grant access with allowBulk for bulk processing await dataProtector.grantAccess({ - authorizedApp: defaultConfig.dappAddress, + authorizedApp: dappAddress, protectedData: validProtectedData1.address, authorizedUser: consumerWallet.address, allowBulk: true, }); await dataProtector.grantAccess({ - authorizedApp: defaultConfig.dappAddress, + authorizedApp: dappAddress, protectedData: validProtectedData2.address, authorizedUser: consumerWallet.address, allowBulk: true, }); await dataProtector.grantAccess({ - authorizedApp: defaultConfig.dappAddress, + authorizedApp: dappAddress, protectedData: validProtectedData3.address, authorizedUser: consumerWallet.address, allowBulk: true, diff --git a/tests/e2e/sendEmail.test.ts b/tests/e2e/sendEmail.test.ts index cdd6d8bc..7c581318 100644 --- a/tests/e2e/sendEmail.test.ts +++ b/tests/e2e/sendEmail.test.ts @@ -2,24 +2,23 @@ import { IExecDataProtectorCore, ProtectedDataWithSecretProps, } from '@iexec/dataprotector'; -import { beforeAll, describe, expect, it } from '@jest/globals'; +import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; import { HDNodeWallet } from 'ethers'; import { IExecWeb3mail, WorkflowError } from '../../src/index.js'; import { MAX_EXPECTED_BLOCKTIME, - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME, MAX_EXPECTED_WEB2_SERVICES_TIME, TEST_CHAIN, - addVoucherEligibleAsset, createAndPublishAppOrders, createAndPublishWorkerpoolOrder, - createVoucher, - createVoucherType, ensureSufficientStake, getRandomWallet, getTestConfig, + getTestDappAddress, getTestIExecOption, getTestWeb3SignerProvider, + setBalance, + setEthForGas, waitSubgraphIndexing, } from '../test-utils.js'; import { IExec } from 'iexec'; @@ -38,50 +37,49 @@ describe('web3mail.sendEmail()', () => { let invalidProtectedData: ProtectedDataWithSecretProps; let consumerIExecInstance: IExec; let prodWorkerpoolAddress: string; - let learnProdWorkerpoolAddress: string; + let paidOnlyWorkerpoolAddress: string; + let dappAddress: string; const iexecOptions = getTestIExecOption(); const prodWorkerpoolPublicPrice = 1000; beforeAll(async () => { - // (default) prod workerpool (not free) always available + // paid workerpool order for the "not free" tests await createAndPublishWorkerpoolOrder( TEST_CHAIN.prodWorkerpool, TEST_CHAIN.prodWorkerpoolOwnerWallet, NULL_ADDRESS, 1_000, - prodWorkerpoolPublicPrice - ); - // learn prod pool (free) assumed always available - await createAndPublishWorkerpoolOrder( - TEST_CHAIN.learnProdWorkerpool, - TEST_CHAIN.learnProdWorkerpoolOwnerWallet, - NULL_ADDRESS, - 0, - 10_000 + 1_000_000 ); + // apporder always available providerWallet = getRandomWallet(); + dappAddress = await getTestDappAddress(); const ethProvider = getTestWeb3SignerProvider( TEST_CHAIN.appOwnerWallet.privateKey ); const resourceProvider = new IExec({ ethProvider }, iexecOptions); - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); - await createAndPublishAppOrders( - resourceProvider, - defaultConfig!.dappAddress - ); + await createAndPublishAppOrders(resourceProvider, dappAddress); - learnProdWorkerpoolAddress = await resourceProvider.ens.resolveName( - TEST_CHAIN.learnProdWorkerpool - ); - prodWorkerpoolAddress = await resourceProvider.ens.resolveName( - TEST_CHAIN.prodWorkerpool + prodWorkerpoolAddress = TEST_CHAIN.prodWorkerpool; + paidOnlyWorkerpoolAddress = TEST_CHAIN.paidOnlyWorkerpool; + + // paid-only workerpool: never publish free orders here so negative tests + // that expect "no workerpool order" or "insufficient stake" stay reliable + // across repeated test:e2e runs (market orders persist in MongoDB). + await createAndPublishWorkerpoolOrder( + TEST_CHAIN.paidOnlyWorkerpool, + TEST_CHAIN.paidOnlyWorkerpoolOwnerWallet, + NULL_ADDRESS, + 1_000, + 1_000_000 ); //create valid protected data dataProtector = new IExecDataProtectorCore( ...getTestConfig(providerWallet.privateKey) ); + await setBalance(providerWallet.address, 10n ** 18n); validProtectedData = await dataProtector.protectData({ data: { email: 'example@test.com' }, name: 'test do not use', @@ -97,6 +95,8 @@ describe('web3mail.sendEmail()', () => { beforeEach(async () => { // use a fresh wallet for calling sendEmail consumerWallet = getRandomWallet(); + // matchOrders pays gas in ETH; free workerpool (0 nRLC stake) never hits ensureSufficientStake deposit path + await setEthForGas(consumerWallet.address); const consumerEthProvider = getTestWeb3SignerProvider( consumerWallet.privateKey ); @@ -105,7 +105,7 @@ describe('web3mail.sendEmail()', () => { iexecOptions ); await dataProtector.grantAccess({ - authorizedApp: getChainDefaultConfig(DEFAULT_CHAIN_ID).dappAddress, + authorizedApp: dappAddress, protectedData: validProtectedData.address, authorizedUser: consumerWallet.address, // consumer wallet numberOfAccess: 1000, @@ -124,6 +124,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, + workerpoolAddressOrEns: paidOnlyWorkerpoolAddress, }) .catch((e) => (error = e)); expect(error).toBeDefined(); @@ -145,6 +146,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, + workerpoolAddressOrEns: paidOnlyWorkerpoolAddress, workerpoolMaxPrice: prodWorkerpoolPublicPrice, }) .catch((e) => (error = e)); @@ -189,7 +191,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: invalidProtectedData.address, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, }) ).rejects.toThrow( new Error( @@ -215,7 +217,8 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: protectedData.address, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolMaxPrice: prodWorkerpoolPublicPrice, }) ).rejects.toThrow( new WorkflowError({ @@ -250,7 +253,7 @@ describe('web3mail.sendEmail()', () => { try { await invalidWeb3mail.sendEmail({ protectedData: validProtectedData.address, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, emailSubject: 'My email subject', emailContent: 'My email content', }); @@ -267,394 +270,143 @@ describe('web3mail.sendEmail()', () => { 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME ); - it( - 'should successfully send email when using a free prod workerpool', - async () => { - const sendEmailResponse = await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: 'e2e mail content for test', - protectedData: validProtectedData.address, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, - }); - expect(sendEmailResponse).toStrictEqual({ - taskId: expect.any(String), - dealId: expect.any(String), - }); - }, - 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME - ); - - it( - 'should successfully send email with granted access to whitelist address', - async () => { - //create valid protected data - const protectedDataForWhitelist = await dataProtector.protectData({ - data: { email: 'example@test.com' }, - name: 'test do not use', - }); - await waitSubgraphIndexing(); - - //grant access to whitelist - await dataProtector.grantAccess({ - authorizedApp: - getChainDefaultConfig(DEFAULT_CHAIN_ID).whitelistSmartContract, //whitelist address - protectedData: protectedDataForWhitelist.address, - authorizedUser: consumerWallet.address, // consumer wallet - numberOfAccess: 1000, - }); - - const sendEmailResponse = await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: 'e2e mail content for test', - protectedData: protectedDataForWhitelist.address, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, - }); - expect(sendEmailResponse).toStrictEqual({ - taskId: expect.any(String), - dealId: expect.any(String), - }); - }, - 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME - ); - - it( - 'should successfully send email with content type html', - async () => { - const sendEmailResponse = await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: - '

Test html

test paragraph

', - protectedData: validProtectedData.address, - contentType: 'text/html', - workerpoolAddressOrEns: learnProdWorkerpoolAddress, - }); - expect(sendEmailResponse).toStrictEqual({ - taskId: expect.any(String), - dealId: expect.any(String), - }); - }, - 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME - ); - - it( - 'should successfully send email with a valid senderName', - async () => { - const sendEmailResponse = await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: 'e2e mail content for test', - protectedData: validProtectedData.address, - senderName: 'Product Team', - workerpoolAddressOrEns: learnProdWorkerpoolAddress, - }); - expect(sendEmailResponse).toStrictEqual({ - taskId: expect.any(String), - dealId: expect.any(String), - }); - }, - 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME - ); - - it( - 'should successfully send email with email content size < 512 kilo-bytes', - async () => { - const desiredSizeInBytes = 500000; // 500 kilo-bytes - const characterToRepeat = 'A'; - const LARGE_CONTENT = characterToRepeat.repeat(desiredSizeInBytes); - - const sendEmailResponse = await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: LARGE_CONTENT, - protectedData: validProtectedData.address, - senderName: 'Product Team', - workerpoolAddressOrEns: learnProdWorkerpoolAddress, - }); - expect(sendEmailResponse).toStrictEqual({ - taskId: expect.any(String), - dealId: expect.any(String), - }); - }, - 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME - ); - - it( - 'should successfully send email with a valid label', - async () => { - const sendEmailResponse = await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: 'e2e mail content for test', - protectedData: validProtectedData.address, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, - label: 'ID1234678', - }); - expect(sendEmailResponse).toStrictEqual({ - taskId: expect.any(String), - dealId: expect.any(String), - }); - // TODO check label in created deal - }, - 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME - ); + describe('when using the free prod workerpool', () => { + beforeAll(async () => { + await createAndPublishWorkerpoolOrder( + TEST_CHAIN.prodWorkerpool, + TEST_CHAIN.prodWorkerpoolOwnerWallet, + NULL_ADDRESS, + 0, + 1_000 + ); + }, MAX_EXPECTED_BLOCKTIME); - describe('when useVoucher:true', () => { it( - 'should throw error if no voucher available for the requester', + 'should successfully send email when using a free prod workerpool', async () => { - let error; - try { - await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: 'e2e mail content for test', - protectedData: validProtectedData.address, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, - workerpoolMaxPrice: 1000, - useVoucher: true, - }); - } catch (err) { - error = err; - } - expect(error).toBeDefined(); - expect(error.message).toBe( - 'Oops, it seems your wallet is not associated with any voucher. Check on https://builder.iex.ec/' - ); + const sendEmailResponse = await web3mail.sendEmail({ + emailSubject: 'e2e mail object for test', + emailContent: 'e2e mail content for test', + protectedData: validProtectedData.address, + workerpoolAddressOrEns: prodWorkerpoolAddress, + }); + expect(sendEmailResponse).toStrictEqual({ + taskId: expect.any(String), + dealId: expect.any(String), + }); }, 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME ); + it( - 'should throw error if workerpool is not sponsored by the voucher', + 'should successfully send email with granted access to whitelist address', async () => { - const voucherType = await createVoucherType({ - description: 'test voucher type', - duration: 60 * 60, + //create valid protected data + const protectedDataForWhitelist = await dataProtector.protectData({ + data: { email: 'example@test.com' }, + name: 'test do not use', }); - await createVoucher({ - owner: consumerWallet.address, - voucherType, - value: 1000, + await waitSubgraphIndexing(); + + //grant access to whitelist + await dataProtector.grantAccess({ + authorizedApp: + getChainDefaultConfig(DEFAULT_CHAIN_ID)?.whitelistSmartContract, //whitelist address + protectedData: protectedDataForWhitelist.address, + authorizedUser: consumerWallet.address, // consumer wallet + numberOfAccess: 1000, }); - let error; - try { - await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: 'e2e mail content for test', - protectedData: validProtectedData.address, - // workerpoolAddressOrEns: prodWorkerpoolAddress, // default - workerpoolMaxPrice: 1000, - useVoucher: true, - }); - } catch (err) { - error = err; - } - expect(error).toBeDefined(); - expect(error.message).toBe('Failed to sendEmail'); - expect(error.cause.message).toBe( - 'Found some workerpool orders but none can be sponsored by your voucher.' - ); + const sendEmailResponse = await web3mail.sendEmail({ + emailSubject: 'e2e mail object for test', + emailContent: 'e2e mail content for test', + protectedData: protectedDataForWhitelist.address, + workerpoolAddressOrEns: prodWorkerpoolAddress, + }); + expect(sendEmailResponse).toStrictEqual({ + taskId: expect.any(String), + dealId: expect.any(String), + }); }, 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME ); - describe('when voucher balance covers the full workerpool price', () => { - it( - 'should create a deal for send email', - async () => { - // payable workerpool - const voucherType = await createVoucherType({ - description: 'test voucher type', - duration: 60 * 60, - }); - await addVoucherEligibleAsset(prodWorkerpoolAddress, voucherType); - const voucherValue = 1000; - await createVoucher({ - owner: consumerWallet.address, - voucherType, - value: voucherValue, - }); - await waitSubgraphIndexing(); - const sendEmailResponse = await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: 'e2e mail content for test', - protectedData: validProtectedData.address, - // workerpoolAddressOrEns: prodWorkerpoolAddress, // default - useVoucher: true, - }); - expect(sendEmailResponse).toStrictEqual({ - taskId: expect.any(String), - dealId: expect.any(String), - }); - }, - 2 * MAX_EXPECTED_BLOCKTIME + - MAX_EXPECTED_WEB2_SERVICES_TIME + - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME - ); - }); - - describe('when voucher balance does not cover the full workerpool price', () => { - describe('but workerpoolMaxPrice covers the non sponsored amount', () => { - it( - 'let call iexec.matchOrders', - async () => { - const voucherType = await createVoucherType({ - description: 'test voucher type', - duration: 60 * 60, - }); - await addVoucherEligibleAsset(prodWorkerpoolAddress, voucherType); - - const voucherRemainingValue = 500; - const workerpoolOrderPrice = 600; - const nonSponsoredAmount = - workerpoolOrderPrice - voucherRemainingValue; - - // voucher with balance insufficient to cover workerpool price - await Promise.all([ - createVoucher({ - owner: consumerWallet.address, - voucherType, - value: voucherRemainingValue, - skipOrders: true, - }), - createAndPublishWorkerpoolOrder( - TEST_CHAIN.prodWorkerpool, - TEST_CHAIN.prodWorkerpoolOwnerWallet, - consumerWallet.address, - workerpoolOrderPrice - ), - ]); - await waitSubgraphIndexing(); - - let error; - try { - await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: 'e2e mail content for test', - protectedData: validProtectedData.address, - // workerpoolAddressOrEns: prodWorkerpoolAddress, // default - workerpoolMaxPrice: nonSponsoredAmount, - useVoucher: true, - }); - } catch (err) { - error = err; - } - expect(error).toBeDefined(); - expect(error.message).toBe('Failed to sendEmail'); - expect(error.cause.message).toBe( - `Orders can't be matched. Please approve an additional ${nonSponsoredAmount} for voucher usage.` - ); - }, - 2 * MAX_EXPECTED_BLOCKTIME + - MAX_EXPECTED_WEB2_SERVICES_TIME + - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME - ); - it( - 'should create task if user approves the non sponsored amount', - async () => { - const voucherType = await createVoucherType({ - description: 'test voucher type', - duration: 60 * 60, - }); - await addVoucherEligibleAsset(prodWorkerpoolAddress, voucherType); - - const voucherRemainingValue = 500; - const workerpoolOrderPrice = 600; - const nonSponsoredAmount = - workerpoolOrderPrice - voucherRemainingValue; + it( + 'should successfully send email with content type html', + async () => { + const sendEmailResponse = await web3mail.sendEmail({ + emailSubject: 'e2e mail object for test', + emailContent: + '

Test html

test paragraph

', + protectedData: validProtectedData.address, + contentType: 'text/html', + workerpoolAddressOrEns: prodWorkerpoolAddress, + }); + expect(sendEmailResponse).toStrictEqual({ + taskId: expect.any(String), + dealId: expect.any(String), + }); + }, + 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME + ); - // voucher with balance insufficient to cover workerpool price - const [voucherAddress] = await Promise.all([ - createVoucher({ - owner: consumerWallet.address, - voucherType, - value: voucherRemainingValue, - skipOrders: true, - }), - createAndPublishWorkerpoolOrder( - TEST_CHAIN.prodWorkerpool, - TEST_CHAIN.prodWorkerpoolOwnerWallet, - consumerWallet.address, - workerpoolOrderPrice - ), - ]); - await waitSubgraphIndexing(); - await ensureSufficientStake( - consumerIExecInstance, - nonSponsoredAmount - ); - await consumerIExecInstance.account.approve( - nonSponsoredAmount, - voucherAddress - ); - const sendEmailResponse = await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: 'e2e mail content for test', - protectedData: validProtectedData.address, - // workerpoolAddressOrEns: prodWorkerpoolAddress, // default - workerpoolMaxPrice: nonSponsoredAmount, - useVoucher: true, - }); - expect(sendEmailResponse).toStrictEqual({ - taskId: expect.any(String), - dealId: expect.any(String), - }); - }, - 2 * MAX_EXPECTED_BLOCKTIME + - MAX_EXPECTED_WEB2_SERVICES_TIME + - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME - ); - }); - describe('and workerpoolMaxPrice does NOT covers the non sponsored amount', () => { - it( - 'should throws an error No Workerpool order found for the desired price', - async () => { - const voucherType = await createVoucherType({ - description: 'test voucher type', - duration: 60 * 60, - }); - await addVoucherEligibleAsset(prodWorkerpoolAddress, voucherType); + it( + 'should successfully send email with a valid senderName', + async () => { + const sendEmailResponse = await web3mail.sendEmail({ + emailSubject: 'e2e mail object for test', + emailContent: 'e2e mail content for test', + protectedData: validProtectedData.address, + senderName: 'Product Team', + workerpoolAddressOrEns: prodWorkerpoolAddress, + }); + expect(sendEmailResponse).toStrictEqual({ + taskId: expect.any(String), + dealId: expect.any(String), + }); + }, + 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME + ); - const voucherRemainingValue = 500; - const workerpoolOrderPrice = 600; + it( + 'should successfully send email with email content size < 512 kilo-bytes', + async () => { + const desiredSizeInBytes = 500000; // 500 kilo-bytes + const characterToRepeat = 'A'; + const LARGE_CONTENT = characterToRepeat.repeat(desiredSizeInBytes); - // voucher with balance insufficient to cover workerpool price - await Promise.all([ - createVoucher({ - owner: consumerWallet.address, - voucherType, - value: voucherRemainingValue, - skipOrders: true, - }), - createAndPublishWorkerpoolOrder( - TEST_CHAIN.prodWorkerpool, - TEST_CHAIN.prodWorkerpoolOwnerWallet, - consumerWallet.address, - workerpoolOrderPrice - ), - ]); - await waitSubgraphIndexing(); + const sendEmailResponse = await web3mail.sendEmail({ + emailSubject: 'e2e mail object for test', + emailContent: LARGE_CONTENT, + protectedData: validProtectedData.address, + senderName: 'Product Team', + workerpoolAddressOrEns: prodWorkerpoolAddress, + }); + expect(sendEmailResponse).toStrictEqual({ + taskId: expect.any(String), + dealId: expect.any(String), + }); + }, + 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME + ); - let error; - try { - await web3mail.sendEmail({ - emailSubject: 'e2e mail object for test', - emailContent: 'e2e mail content for test', - protectedData: validProtectedData.address, - // workerpoolAddressOrEns: prodWorkerpoolAddress, // default - // workerpoolMaxPrice: 0, // default - useVoucher: true, - }); - } catch (err) { - error = err; - } - expect(error).toBeDefined(); - expect(error.message).toBe('Failed to sendEmail'); - expect(error.cause.message).toBe( - `No Workerpool order found for the desired price` - ); - }, - 2 * MAX_EXPECTED_BLOCKTIME + - MAX_EXPECTED_WEB2_SERVICES_TIME + - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME - ); - }); - }); + it( + 'should successfully send email with a valid label', + async () => { + const sendEmailResponse = await web3mail.sendEmail({ + emailSubject: 'e2e mail object for test', + emailContent: 'e2e mail content for test', + protectedData: validProtectedData.address, + workerpoolAddressOrEns: prodWorkerpoolAddress, + label: 'ID1234678', + }); + expect(sendEmailResponse).toStrictEqual({ + taskId: expect.any(String), + dealId: expect.any(String), + }); + // TODO check label in created deal + }, + 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME + ); }); }); diff --git a/tests/e2e/sendEmailCampaign.test.ts b/tests/e2e/sendEmailCampaign.test.ts index d112c706..52469363 100644 --- a/tests/e2e/sendEmailCampaign.test.ts +++ b/tests/e2e/sendEmailCampaign.test.ts @@ -5,10 +5,6 @@ import { import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; import { HDNodeWallet } from 'ethers'; import { ValidationError } from 'yup'; -import { - DEFAULT_CHAIN_ID, - getChainDefaultConfig, -} from '../../src/config/config.js'; import { Contact, IExecWeb3mail, @@ -24,8 +20,11 @@ import { ensureSufficientStake, getRandomWallet, getTestConfig, + getTestDappAddress, getTestIExecOption, getTestWeb3SignerProvider, + setBalance, + sleep, waitSubgraphIndexing, } from '../test-utils.js'; import { IExec } from 'iexec'; @@ -40,11 +39,10 @@ describe('web3mail.sendEmailCampaign()', () => { let validProtectedData2: ProtectedDataWithSecretProps; let validProtectedData3: ProtectedDataWithSecretProps; let consumerIExecInstance: IExec; - let learnProdWorkerpoolAddress: string; - let prodWorkerpoolAddress: string; + const prodWorkerpoolAddress: string = TEST_CHAIN.prodWorkerpool; const iexecOptions = getTestIExecOption(); const prodWorkerpoolPublicPrice = 1000; - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); + let dappAddress: string; beforeAll(async () => { // (default) prod workerpool (not free) always available @@ -53,38 +51,23 @@ describe('web3mail.sendEmailCampaign()', () => { TEST_CHAIN.prodWorkerpoolOwnerWallet, NULL_ADDRESS, 1_000, - prodWorkerpoolPublicPrice - ); - // learn prod pool (free) assumed always available - await createAndPublishWorkerpoolOrder( - TEST_CHAIN.learnProdWorkerpool, - TEST_CHAIN.learnProdWorkerpoolOwnerWallet, - NULL_ADDRESS, - 0, - 10_000 + 1_000_000 ); + // apporder always available providerWallet = getRandomWallet(); + dappAddress = await getTestDappAddress(); const ethProvider = getTestWeb3SignerProvider( TEST_CHAIN.appOwnerWallet.privateKey ); const resourceProvider = new IExec({ ethProvider }, iexecOptions); - await createAndPublishAppOrders( - resourceProvider, - defaultConfig!.dappAddress - ); - - learnProdWorkerpoolAddress = await resourceProvider.ens.resolveName( - TEST_CHAIN.learnProdWorkerpool - ); - prodWorkerpoolAddress = await resourceProvider.ens.resolveName( - TEST_CHAIN.prodWorkerpool - ); + await createAndPublishAppOrders(resourceProvider, dappAddress); // Create valid protected data dataProtector = new IExecDataProtectorCore( ...getTestConfig(providerWallet.privateKey) ); + await setBalance(providerWallet.address, 10n ** 18n); validProtectedData1 = await dataProtector.protectData({ data: { email: 'user1@example.com' }, @@ -116,21 +99,21 @@ describe('web3mail.sendEmailCampaign()', () => { // Grant access with allowBulk for bulk processing await dataProtector.grantAccess({ - authorizedApp: defaultConfig.dappAddress, + authorizedApp: dappAddress, protectedData: validProtectedData1.address, authorizedUser: consumerWallet.address, allowBulk: true, }); await dataProtector.grantAccess({ - authorizedApp: defaultConfig.dappAddress, + authorizedApp: dappAddress, protectedData: validProtectedData2.address, authorizedUser: consumerWallet.address, allowBulk: true, }); await dataProtector.grantAccess({ - authorizedApp: defaultConfig.dappAddress, + authorizedApp: dappAddress, protectedData: validProtectedData3.address, authorizedUser: consumerWallet.address, allowBulk: true, @@ -242,59 +225,35 @@ describe('web3mail.sendEmailCampaign()', () => { workerpoolAddressOrEns: prodWorkerpoolAddress, }); - // Verify the result - expect(result).toBeDefined(); - expect('tasks' in result).toBe(true); - expect(result.tasks).toBeDefined(); - expect(Array.isArray(result.tasks)).toBe(true); - expect(result.tasks.length).toBeGreaterThan(0); - result.tasks.forEach((task) => { - expect(task.taskId).toBeDefined(); - expect(task.dealId).toBeDefined(); - expect(task.bulkIndex).toBeDefined(); - }); - }, - 30 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME + 60_000 - ); - }); - - describe('when using a free prod workerpool', () => { - it( - 'should successfully send email campaign', - async () => { - // Prepare campaign first - const contacts: Contact[] = await web3mail.fetchMyContacts({ - bulkOnly: true, - }); - expect(contacts.length).toBeGreaterThanOrEqual(3); - - const bulkOrders = contacts.map((contact) => contact.grantedAccess); - - const campaignRequest = await web3mail.prepareEmailCampaign({ - emailSubject: 'Bulk test subject', - emailContent: 'Bulk test message', - senderName: 'Bulk test sender', - grantedAccesses: bulkOrders, - maxProtectedDataPerTask: 3, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, - }); - - // Send campaign - const result = await web3mail.sendEmailCampaign({ - campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, - }); - - // Verify the result - expect(result).toBeDefined(); - expect('tasks' in result).toBe(true); - expect(result.tasks).toBeDefined(); - expect(Array.isArray(result.tasks)).toBe(true); - expect(result.tasks.length).toBeGreaterThan(0); - result.tasks.forEach((task) => { + // Market watcher indexes asynchronously — if tasks is empty, wait then reconstruct from deals + let tasks = result.tasks; + if (tasks.length === 0) { + await sleep(5_000); + const hash = await consumerIExecInstance.order.hashRequestorder( + campaignRequest.campaignRequest + ); + const { deals } = + await consumerIExecInstance.deal.fetchDealsByRequestorder(hash, { + pageSize: 10, + }); + tasks = await Promise.all( + deals.flatMap((deal) => + Array.from({ length: deal.botSize }, async (_, i) => ({ + taskId: await consumerIExecInstance.deal.computeTaskId( + deal.dealid, + deal.botFirst + i + ), + dealId: deal.dealid, + bulkIndex: deal.botFirst + i, + })) + ) + ); + } + expect(tasks.length).toBeGreaterThan(0); + tasks.forEach((task) => { expect(task.taskId).toBeDefined(); expect(task.dealId).toBeDefined(); - expect(task.bulkIndex).toBeDefined(); + expect(typeof task.bulkIndex).toBe('number'); }); }, 30 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME + 60_000 @@ -309,7 +268,7 @@ describe('web3mail.sendEmailCampaign()', () => { await web3mail .sendEmailCampaign({ campaignRequest: undefined as any, // Testing missing campaignRequest - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, }) .catch((e) => (error = e)); @@ -338,7 +297,7 @@ describe('web3mail.sendEmailCampaign()', () => { emailContent: 'Bulk test message', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, }); let error: Error; @@ -394,12 +353,12 @@ describe('web3mail.sendEmailCampaign()', () => { emailContent: 'Bulk test message', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, }); await invalidWeb3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, }); } catch (err) { error = err as Web3mailWorkflowError; @@ -419,6 +378,11 @@ describe('web3mail.sendEmailCampaign()', () => { it( 'should successfully send campaign prepared by prepareEmailCampaign', async () => { + await ensureSufficientStake( + consumerIExecInstance, + prodWorkerpoolPublicPrice + ); + // Fetch contacts with allowBulk access const contacts: Contact[] = await web3mail.fetchMyContacts({ bulkOnly: true, @@ -434,9 +398,9 @@ describe('web3mail.sendEmailCampaign()', () => { senderName: 'Integration Test', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolMaxPrice: prodWorkerpoolPublicPrice, }); - // Verify campaign request was created expect(campaignRequest).toBeDefined(); expect(campaignRequest.campaignRequest).toBeDefined(); @@ -444,15 +408,39 @@ describe('web3mail.sendEmailCampaign()', () => { // Send the campaign const result = await web3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, }); - // Verify the result - expect(result).toBeDefined(); - expect('tasks' in result).toBe(true); - expect(result.tasks).toBeDefined(); - expect(Array.isArray(result.tasks)).toBe(true); - expect(result.tasks.length).toBeGreaterThan(0); + // Market watcher indexes asynchronously — if tasks is empty, wait then reconstruct from deals + let tasks = result.tasks; + if (tasks.length === 0) { + await sleep(5_000); + const hash = await consumerIExecInstance.order.hashRequestorder( + campaignRequest.campaignRequest + ); + const { deals } = + await consumerIExecInstance.deal.fetchDealsByRequestorder(hash, { + pageSize: 10, + }); + tasks = await Promise.all( + deals.flatMap((deal) => + Array.from({ length: deal.botSize }, async (_, i) => ({ + taskId: await consumerIExecInstance.deal.computeTaskId( + deal.dealid, + deal.botFirst + i + ), + dealId: deal.dealid, + bulkIndex: deal.botFirst + i, + })) + ) + ); + } + expect(tasks.length).toBeGreaterThan(0); + tasks.forEach((task) => { + expect(task.taskId).toBeDefined(); + expect(task.dealId).toBeDefined(); + expect(typeof task.bulkIndex).toBe('number'); + }); }, 30 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME + 60_000 ); @@ -460,6 +448,12 @@ describe('web3mail.sendEmailCampaign()', () => { it( 'should handle multiple protected data per task correctly', async () => { + // 3 contacts / maxProtectedDataPerTask:2 = 2 tasks → 2 × workerpoolprice + await ensureSufficientStake( + consumerIExecInstance, + prodWorkerpoolPublicPrice * 2 + ); + // Fetch contacts const contacts: Contact[] = await web3mail.fetchMyContacts({ bulkOnly: true, @@ -467,29 +461,52 @@ describe('web3mail.sendEmailCampaign()', () => { expect(contacts.length).toBeGreaterThanOrEqual(3); const bulkOrders = contacts.map((contact) => contact.grantedAccess); - // Prepare campaign with maxProtectedDataPerTask: 2 const campaignRequest = await web3mail.prepareEmailCampaign({ emailSubject: 'Bulk test subject', emailContent: 'Bulk test message', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 2, // Process 2 emails per task - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolMaxPrice: prodWorkerpoolPublicPrice, }); - // Send campaign const result = await web3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddressOrEns: prodWorkerpoolAddress, }); - // Verify tasks were created - expect(result).toBeDefined(); - expect('tasks' in result).toBe(true); - expect(result.tasks.length).toBeGreaterThan(0); - - // With 3 protected data and maxProtectedDataPerTask: 2, we should have at least 2 tasks - expect(result.tasks.length).toBeGreaterThanOrEqual(1); + // Market watcher indexes asynchronously — if tasks is empty, wait then reconstruct from deals + let tasks = result.tasks; + if (tasks.length === 0) { + await sleep(5_000); + const hash = await consumerIExecInstance.order.hashRequestorder( + campaignRequest.campaignRequest + ); + const { deals } = + await consumerIExecInstance.deal.fetchDealsByRequestorder(hash, { + pageSize: 10, + }); + tasks = await Promise.all( + deals.flatMap((deal) => + Array.from({ length: deal.botSize }, async (_, i) => ({ + taskId: await consumerIExecInstance.deal.computeTaskId( + deal.dealid, + deal.botFirst + i + ), + dealId: deal.dealid, + bulkIndex: deal.botFirst + i, + })) + ) + ); + } + // 3 contacts / maxProtectedDataPerTask:2 = 2 tasks across 1+ deals + expect(tasks.length).toBeGreaterThanOrEqual(1); + tasks.forEach((task) => { + expect(task.taskId).toBeDefined(); + expect(task.dealId).toBeDefined(); + expect(typeof task.bulkIndex).toBe('number'); + }); }, 30 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME + 60_000 ); diff --git a/tests/mock/compass/data.json b/tests/mock/compass/data.json new file mode 100644 index 00000000..4bc8cfa6 --- /dev/null +++ b/tests/mock/compass/data.json @@ -0,0 +1,24 @@ +[ + { + "chainId": "421614", + "description": "arbitrum-sepolia-fork", + "iapps": [ + { + "key": "web3mail-tdx", + "address": "0x09d59e1B696D0cb69f46bf762412636E8652aB58" + } + ], + "workerpools": [ + { + "name": "unreachable-workerpool", + "apiUrl": "https://unreachable-workerpool.iex.ec", + "address": "0xB967057a21dc6A66A29721d96b8Aa7454B7c383F" + }, + { + "name": "workerpool-http-500", + "apiUrl": "http://localhost:5500", + "address": "0x2956f0cb779904795a5f30d3b3ea88b714c3123f" + } + ] + } +] diff --git a/tests/mock/server/http500.nginx.conf b/tests/mock/server/http500.nginx.conf new file mode 100644 index 00000000..54751ad0 --- /dev/null +++ b/tests/mock/server/http500.nginx.conf @@ -0,0 +1,6 @@ +server { + listen 80; + location / { + return 500; + } +} diff --git a/tests/scripts/prepare-bellecour-fork-for-tests.js b/tests/scripts/prepare-bellecour-fork-for-tests.js deleted file mode 100644 index 7c92a1a0..00000000 --- a/tests/scripts/prepare-bellecour-fork-for-tests.js +++ /dev/null @@ -1,296 +0,0 @@ -import { - Contract, - EnsPlugin, - JsonRpcProvider, - JsonRpcSigner, - Network, - formatEther, - keccak256, - toBeHex, -} from 'ethers'; - -const VOUCHER_HUB_ADDRESS = '0x3137B6DF4f36D338b82260eDBB2E7bab034AFEda'; -const TARGET_VOUCHER_MANAGER_WALLET = - '0x44cA21A3c4efE9B1A0268e2e9B2547E7d9C8f19C'; // Should be same wallet as TEST_CHAIN.voucherManagerWallet -const LEARN_WORKERPOOL_OWNER_WALLET = - '0x02D0e61355e963210d0DE382e6BA09781181bB94'; -const PROD_WORKERPOOL_OWNER_WALLET = - '0x1Ff6AfF580e8Ca738F76485E0914C2aCaDa7B462'; -const APP_OWNER_WALLET = '0x626D65C778fB98f813C25F84249E3012B80e8d91'; -const LEARN_WORKERPOOL_ENS = 'prod-v8-learn.main.pools.iexec.eth'; -const PROD_WORKERPOOL_ENS = 'prod-v8-bellecour.main.pools.iexec.eth'; -const WEB3_MAIL_DAPP_ADDRESS_ENS = 'web3mail.apps.iexec.eth'; - -const rpcURL = 'http://127.0.0.1:8545'; - -const provider = new JsonRpcProvider( - rpcURL, - new Network('bellecour-fork', 134).attachPlugin( - new EnsPlugin('0x5f5B93fca68c9C79318d1F3868A354EE67D8c006', 134) - ), - { - pollingInterval: 1000, // speed up tests - } -); - -const LEARN_WORKERPOOL = await provider.resolveName(LEARN_WORKERPOOL_ENS); -const PROD_WORKERPOOL = await provider.resolveName(PROD_WORKERPOOL_ENS); -const WEB3_MAIL_DAPP_ADDRESS = await provider.resolveName( - WEB3_MAIL_DAPP_ADDRESS_ENS -); - -const setBalance = async (address, weiAmount) => { - await fetch(rpcURL, { - method: 'POST', - body: JSON.stringify({ - method: 'anvil_setBalance', - params: [address, toBeHex(weiAmount)], - id: 1, - jsonrpc: '2.0', - }), - headers: { - 'Content-Type': 'application/json', - }, - }); - const balance = await provider.getBalance(address); - console.log(`${address} wallet balance is now ${formatEther(balance)} RLC`); -}; - -const impersonate = async (address) => { - await fetch(rpcURL, { - method: 'POST', - body: JSON.stringify({ - method: 'anvil_impersonateAccount', - params: [address], - id: 1, - jsonrpc: '2.0', - }), - headers: { - 'Content-Type': 'application/json', - }, - }); - console.log(`impersonating ${address}`); -}; - -const stopImpersonate = async (address) => { - await fetch(rpcURL, { - method: 'POST', - body: JSON.stringify({ - method: 'anvil_stopImpersonatingAccount', - params: [address], - id: 1, - jsonrpc: '2.0', - }), - headers: { - 'Content-Type': 'application/json', - }, - }); - console.log(`stop impersonating ${address}`); -}; - -const getVoucherManagementRoles = async (targetManager) => { - const voucherHubContract = new Contract( - VOUCHER_HUB_ADDRESS, - [ - { - inputs: [], - name: 'defaultAdmin', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes32', - name: 'role', - type: 'bytes32', - }, - { - internalType: 'address', - name: 'account', - type: 'address', - }, - ], - name: 'grantRole', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'bytes32', - name: 'role', - type: 'bytes32', - }, - { - internalType: 'address', - name: 'account', - type: 'address', - }, - ], - name: 'hasRole', - outputs: [ - { - internalType: 'bool', - name: '', - type: 'bool', - }, - ], - stateMutability: 'view', - type: 'function', - }, - ], - provider - ); - - const defaultAdmin = await voucherHubContract.defaultAdmin(); - - console.log('VoucherHub defaultAdmin:', defaultAdmin); - - await impersonate(defaultAdmin); - - const MINTER_ROLE = keccak256(Buffer.from('MINTER_ROLE')); - - const MANAGER_ROLE = keccak256(Buffer.from('MANAGER_ROLE')); - - await voucherHubContract - .connect(new JsonRpcSigner(provider, defaultAdmin)) - .grantRole(MINTER_ROLE, targetManager, { gasPrice: 0 }) - .then((tx) => tx.wait()); - - await voucherHubContract - .connect(new JsonRpcSigner(provider, defaultAdmin)) - .grantRole(MANAGER_ROLE, targetManager, { - gasPrice: 0, - }) - .then((tx) => tx.wait()); - - await stopImpersonate(defaultAdmin); - - console.log( - `${targetManager} has role MINTER_ROLE: ${await voucherHubContract.hasRole( - MINTER_ROLE, - targetManager - )}` - ); - - console.log( - `${targetManager} has role MANAGER_ROLE: ${await voucherHubContract.hasRole( - MANAGER_ROLE, - targetManager - )}` - ); -}; - -const getIExecResourceOwnership = async (resourceAddress, targetOwner) => { - const RESOURCE_ABI = [ - { - inputs: [], - name: 'owner', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - { - inputs: [], - name: 'registry', - outputs: [ - { - internalType: 'contract IRegistry', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - ]; - const RESOURCE_REGISTRY_ABI = [ - { - inputs: [ - { - internalType: 'address', - name: 'from', - type: 'address', - }, - { - internalType: 'address', - name: 'to', - type: 'address', - }, - { - internalType: 'uint256', - name: 'tokenId', - type: 'uint256', - }, - ], - name: 'safeTransferFrom', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - ]; - - const resourceContract = new Contract( - resourceAddress, - RESOURCE_ABI, - provider - ); - - const resourceOwner = await resourceContract.owner(); - const resourceRegistryAddress = await resourceContract.registry(); - const resourceRegistryContract = new Contract( - resourceRegistryAddress, - RESOURCE_REGISTRY_ABI, - provider - ); - - await impersonate(resourceOwner); - await resourceRegistryContract - .connect(new JsonRpcSigner(provider, resourceOwner)) - .safeTransferFrom(resourceOwner, targetOwner, resourceAddress, { - gasPrice: 0, - }) - .then((tx) => tx.wait()); - await stopImpersonate(resourceOwner); - - const newOwner = await resourceContract.owner(); - console.log(`resource ${resourceAddress} is now owned by ${newOwner}`); -}; - -const main = async () => { - console.log(`preparing bellecour-fork at ${rpcURL}`); - - // prepare Voucher - await setBalance(TARGET_VOUCHER_MANAGER_WALLET, 1000000n * 10n ** 18n); - await getVoucherManagementRoles(TARGET_VOUCHER_MANAGER_WALLET); - - // prepare workerpools - await getIExecResourceOwnership( - LEARN_WORKERPOOL, - LEARN_WORKERPOOL_OWNER_WALLET - ); - await getIExecResourceOwnership( - PROD_WORKERPOOL, - PROD_WORKERPOOL_OWNER_WALLET - ); - - // prepare web3mail app for tests - await getIExecResourceOwnership(WEB3_MAIL_DAPP_ADDRESS, APP_OWNER_WALLET); -}; - -main(); diff --git a/tests/scripts/prepare-forks-for-tests.js b/tests/scripts/prepare-forks-for-tests.js new file mode 100644 index 00000000..6ef1e7eb --- /dev/null +++ b/tests/scripts/prepare-forks-for-tests.js @@ -0,0 +1,233 @@ +import { + Contract, + JsonRpcProvider, + JsonRpcSigner, + Wallet, + formatEther, + toBeHex, +} from 'ethers'; + +const TARGET_POCO_ADMIN_WALLET = '0x7bd4783FDCAD405A28052a0d1f11236A741da593'; + +// Must match TEST_CHAIN.appOwnerWallet in test-utils.ts +const APP_OWNER_WALLET_ADDRESS = new Wallet( + '0xa911b93e50f57c156da0b8bff2277d241bcdb9345221a3e246a99c6e7cedcde5' +).address; // 0x626D65C778fB98f813C25F84249E3012B80e8d91 + +// Web3mail dapp (compass mock) — fixed address; no ENS on 421614 fork +const DAPP_ADDRESS = '0x09d59e1B696D0cb69f46bf762412636E8652aB58'; + +// Must match TEST_CHAIN.prodWorkerpool +const PROD_WORKERPOOL_ADDRESS = '0x2956f0cb779904795a5f30d3b3ea88b714c3123f'; +// Must match TEST_CHAIN.paidOnlyWorkerpool — never gets free orders published +const PAID_ONLY_WORKERPOOL_ADDRESS = '0xB967057a21dc6A66A29721d96b8Aa7454B7c383F'; +// Default Arbitrum Sepolia pool (e2e "learn" / free) — same TEST_CHAIN.learnProdWorkerpool +// const LEARN_WORKERPOOL_ADDRESS = +// '0xB967057a21dc6A66A29721d96b8Aa7454B7c383F'; +// Must match TEST_CHAIN.prodWorkerpoolOwnerWallet +const PROD_WORKERPOOL_OWNER_TEST = new Wallet( + '0x6a12f56d7686e85ab0f46eb3c19cb0c75bfabf8fb04e595654fc93ad652fa7bc' +).address; + +const getProvider = (rpcUrl) => + new JsonRpcProvider(rpcUrl, undefined, { + pollingInterval: 100, // fast polling for tests + }); + +const setBalance = (rpcUrl) => async (address, weiAmount) => { + console.log(`setting balance of ${address} to ${formatEther(weiAmount)}`); + console.log('rpcUrl', rpcUrl); + await fetch(rpcUrl, { + method: 'POST', + body: JSON.stringify({ + method: 'anvil_setBalance', + params: [address, toBeHex(weiAmount)], + id: 1, + jsonrpc: '2.0', + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + const balance = await getProvider(rpcUrl).getBalance(address); + console.log(`${address} wallet balance is now ${formatEther(balance)}`); +}; + +const impersonate = (rpcUrl) => async (address) => { + await fetch(rpcUrl, { + method: 'POST', + body: JSON.stringify({ + method: 'anvil_impersonateAccount', + params: [address], + id: 1, + jsonrpc: '2.0', + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + console.log(`impersonating ${address}`); +}; + +const stopImpersonate = (rpcUrl) => async (address) => { + await fetch(rpcUrl, { + method: 'POST', + body: JSON.stringify({ + method: 'anvil_stopImpersonatingAccount', + params: [address], + id: 1, + jsonrpc: '2.0', + }), + headers: { + 'Content-Type': 'application/json', + }, + }); + console.log(`stop impersonating ${address}`); +}; + +const getIExecHubOwnership = + (rpcUrl, legacyTx = false) => + async (hubAddress, targetOwner) => { + const iexecContract = new Contract( + hubAddress, + [ + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + constant: true, + }, + { + inputs: [ + { internalType: 'address', name: 'newOwner', type: 'address' }, + ], + name: 'transferOwnership', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ], + getProvider(rpcUrl) + ); + const iexecOwner = await iexecContract.owner(); + await setBalance(rpcUrl)(iexecOwner, 1n * 10n ** 18n); // give some ETH to the owner to be able to send the transaction + await impersonate(rpcUrl)(iexecOwner); + await iexecContract + .connect(new JsonRpcSigner(getProvider(rpcUrl), iexecOwner)) + .transferOwnership(targetOwner, legacyTx ? { gasPrice: 0 } : {}) + .then((tx) => tx.wait()); + await stopImpersonate(rpcUrl)(iexecOwner); + + const newOwner = await iexecContract.owner(); + console.log(`IExecHub proxy at ${hubAddress} is now owned by ${newOwner}`); + }; + +/** + * prepare-bellecour-fork-for-tests.js: IExec v8 resource = ERC-721 in `registry()`. + * Transfer the token to the test wallet (same as Bellecour learn/prod + web3mail app). + * Arbitrum Sepolia: fixed `resourceAddress`, gasLimit for the fork. + */ +const getIExecResourceOwnership = + (rpcUrl) => async (resourceAddress, targetOwner) => { + const RESOURCE_ABI = [ + { + inputs: [], + name: 'owner', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'registry', + outputs: [ + { internalType: 'contract IRegistry', name: '', type: 'address' }, + ], + stateMutability: 'view', + type: 'function', + }, + ]; + const RESOURCE_REGISTRY_ABI = [ + { + inputs: [ + { internalType: 'address', name: 'from', type: 'address' }, + { internalType: 'address', name: 'to', type: 'address' }, + { internalType: 'uint256', name: 'tokenId', type: 'uint256' }, + ], + name: 'safeTransferFrom', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + ]; + + const provider = getProvider(rpcUrl); + const resourceContract = new Contract( + resourceAddress, + RESOURCE_ABI, + provider + ); + const resourceOwner = await resourceContract.owner(); + if (resourceOwner.toLowerCase() === targetOwner.toLowerCase()) { + console.log( + `resource ${resourceAddress} is already owned by ${targetOwner}` + ); + return; + } + const resourceRegistryAddress = await resourceContract.registry(); + const resourceRegistryContract = new Contract( + resourceRegistryAddress, + RESOURCE_REGISTRY_ABI, + provider + ); + const tokenId = BigInt(resourceAddress); + await setBalance(rpcUrl)(resourceOwner, 1n * 10n ** 18n); + await impersonate(rpcUrl)(resourceOwner); + const txOpts = { gasLimit: 4_000_000n }; + await resourceRegistryContract + .connect(new JsonRpcSigner(provider, resourceOwner)) + .safeTransferFrom(resourceOwner, targetOwner, tokenId, txOpts) + .then((tx) => tx.wait()); + await stopImpersonate(rpcUrl)(resourceOwner); + + const newOwner = await resourceContract.owner(); + console.log(`resource ${resourceAddress} is now owned by ${newOwner}`); + }; + +const main = async () => { + const arbitrumSepoliaForkRpcUrl = 'http://localhost:8555'; + console.log( + `preparing arbitrum-sepolia-fork at ${arbitrumSepoliaForkRpcUrl}` + ); + await setBalance(arbitrumSepoliaForkRpcUrl)( + TARGET_POCO_ADMIN_WALLET, + 1000000n * 10n ** 18n + ); + await getIExecHubOwnership(arbitrumSepoliaForkRpcUrl)( + '0xB2157BF2fAb286b2A4170E3491Ac39770111Da3E', + TARGET_POCO_ADMIN_WALLET + ); + await setBalance(arbitrumSepoliaForkRpcUrl)( + APP_OWNER_WALLET_ADDRESS, + 1n * 10n ** 18n + ); + await getIExecResourceOwnership(arbitrumSepoliaForkRpcUrl)( + DAPP_ADDRESS, + APP_OWNER_WALLET_ADDRESS + ); + await getIExecResourceOwnership(arbitrumSepoliaForkRpcUrl)( + PROD_WORKERPOOL_ADDRESS, + PROD_WORKERPOOL_OWNER_TEST + ); + await getIExecResourceOwnership(arbitrumSepoliaForkRpcUrl)( + PAID_ONLY_WORKERPOOL_ADDRESS, + PROD_WORKERPOOL_OWNER_TEST + ); +}; + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/tests/scripts/prepare-test-env.js b/tests/scripts/prepare-test-env.js index 7cdb445b..12f2bb6a 100644 --- a/tests/scripts/prepare-test-env.js +++ b/tests/scripts/prepare-test-env.js @@ -1,33 +1,51 @@ import { writeFileSync } from 'fs'; -const forkUrl = 'https://bellecour.iex.ec'; +const arbitrumSepoliaForkUrl = + process.env.ARBITRUM_SEPOLIA_FORK_URL || + 'https://sepolia-rollup.arbitrum.io/rpc'; -fetch(forkUrl, { - method: 'POST', - body: JSON.stringify({ - jsonrpc: 2.0, - method: 'eth_blockNumber', - params: [], - id: 1, - }), -}) - .then((res) => res.json()) - .then((jsonRes) => { - const forkBlockNumber = parseInt(jsonRes.result.substring(2), 16); +const arbitrumSepoliaForkBlock = await getCurrentBlockNumber( + arbitrumSepoliaForkUrl +); - console.log('Creating .env file for docker-compose test-stack'); - writeFileSync( - '.env', - `############ THIS FILE IS GENERATED ############ +console.log('Creating .env file for docker-compose test-stack'); +writeFileSync( + '.env', + `############ THIS FILE IS GENERATED ############ # run "node prepare-test-env.js" to regenerate # ################################################ # blockchain node to use as the reference for the local fork -BELLECOUR_FORK_URL=${forkUrl} +ARBITRUM_SEPOLIA_FORK_URL=${arbitrumSepoliaForkUrl} # block number to fork from -BELLECOUR_FORK_BLOCK=${forkBlockNumber}` - ); +ARBITRUM_SEPOLIA_FORK_BLOCK=${arbitrumSepoliaForkBlock}` +); + +async function getCurrentBlockNumber(forkUrl) { + const blockNumber = await fetch(forkUrl, { + method: 'POST', + body: JSON.stringify({ + jsonrpc: '2.0', + method: 'eth_blockNumber', + params: [], + id: 1, + }), + headers: { + 'Content-Type': 'application/json', + }, }) - .catch((e) => { - throw Error(`Failed to get current block number from ${forkUrl}: ${e}`); - }); + .then((res) => res.json()) + .then((jsonRes) => { + console.log( + `Current block number of ${forkUrl} is ${JSON.stringify(jsonRes)}` + ); + const forkBlockNumber = parseInt(jsonRes.result.substring(2), 16); + return forkBlockNumber; + }) + .catch((e) => { + throw new Error( + `Failed to get current block number from ${forkUrl}: ${e}` + ); + }); + return blockNumber; +} diff --git a/tests/test-utils.ts b/tests/test-utils.ts index b4a2a29c..6f6b81a4 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -1,6 +1,14 @@ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck -import { Wallet, JsonRpcProvider, ethers, Contract } from 'ethers'; +import { + Wallet, + JsonRpcProvider, + ethers, + Contract, + AbiCoder, + keccak256, + toBeHex, +} from 'ethers'; import { Web3MailConfigOptions, Web3SignerProvider, @@ -9,43 +17,134 @@ import { IExec, utils } from 'iexec'; import { randomInt } from 'crypto'; import { getSignerFromPrivateKey } from 'iexec/utils'; +export const TEST_COMPASS_URL = 'http://127.0.0.1:8069'; + +const TEST_POOL_SIGNER = new Wallet( + '0x6a12f56d7686e85ab0f46eb3c19cb0c75bfabf8fb04e595654fc93ad652fa7bc' +); + export const TEST_CHAIN = { + name: 'arbitrum-sepolia' as const, + isNative: false, + useGas: true, ipfsGateway: 'http://127.0.0.1:8080', ipfsNode: 'http://127.0.0.1:5001', - rpcURL: 'http://127.0.0.1:8545', - chainId: '134', - smsURL: 'http://127.0.0.1:13300', - resultProxyURL: 'http://127.0.0.1:13200', - iexecGatewayURL: 'http://127.0.0.1:3000', + ipfsGatewayURL: 'http://127.0.0.1:8080', + ipfsNodeURL: 'http://127.0.0.1:5001', + rpcURL: 'http://127.0.0.1:8555', + chainId: '421614', + smsURL: 'http://127.0.0.1:13350', + resultProxyURL: 'http://127.0.0.1:13250', + iexecGatewayURL: 'http://127.0.0.1:3050', + compassUrl: TEST_COMPASS_URL, voucherHubAddress: '0x3137B6DF4f36D338b82260eDBB2E7bab034AFEda', voucherManagerWallet: new Wallet( '0x2c906d4022cace2b3ee6c8b596564c26c4dcadddf1e949b769bcb0ad75c40c33' ), voucherSubgraphURL: 'http://127.0.0.1:8000/subgraphs/name/bellecour/iexec-voucher', - learnProdWorkerpool: 'prod-v8-learn.main.pools.iexec.eth', - learnProdWorkerpoolOwnerWallet: new Wallet( - '0x800e01919eadf36f110f733decb1cc0f82e7941a748e89d7a3f76157f6654bb3' - ), - prodWorkerpool: 'prod-v8-bellecour.main.pools.iexec.eth', - prodWorkerpoolOwnerWallet: new Wallet( - '0x6a12f56d7686e85ab0f46eb3c19cb0c75bfabf8fb04e595654fc93ad652fa7bc' - ), + prodWorkerpool: '0x2956f0cb779904795a5f30d3b3ea88b714c3123f', + prodWorkerpoolOwnerWallet: TEST_POOL_SIGNER, + // Used exclusively for tests that require NO free orders to be present. + // Never publish price=0 orders against this address. + paidOnlyWorkerpool: '0xB967057a21dc6A66A29721d96b8Aa7454B7c383F', + paidOnlyWorkerpoolOwnerWallet: TEST_POOL_SIGNER, appOwnerWallet: new Wallet( '0xa911b93e50f57c156da0b8bff2277d241bcdb9345221a3e246a99c6e7cedcde5' ), - provider: new JsonRpcProvider('http://127.0.0.1:8545', undefined, { - pollingInterval: 1000, // speed up tests + provider: new JsonRpcProvider('http://127.0.0.1:8555', undefined, { + pollingInterval: 100, }), - hubAddress: '0x3eca1B216A7DF1C7689aEb259fFB83ADFB894E7f', + hubAddress: '0xB2157BF2fAb286b2A4170E3491Ac39770111Da3E', + defaultInitBalance: 1n * 10n ** 18n, +}; + +const HUB_TOKEN_ABI = [ + { + inputs: [], + name: 'token', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +] as const; + +const ERC20_BALANCE_SLOT = + '0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00'; + +const anvilSetStorageAt = async ( + contract: string, + slot: string, + value: string +) => { + await fetch(TEST_CHAIN.rpcURL, { + method: 'POST', + body: JSON.stringify({ + method: 'anvil_setStorageAt', + params: [contract, slot, value], + id: 1, + jsonrpc: '2.0', + }), + headers: { 'Content-Type': 'application/json' }, + }); +}; + +const anvilSetNRlcTokenBalance = async ( + address: string, + targetNRlcBalance: bigint +) => { + const hubAddress = TEST_CHAIN.hubAddress; + const rlcAddress = await new Contract( + hubAddress, + HUB_TOKEN_ABI, + TEST_CHAIN.provider + ).token(); + const balanceSlot = keccak256( + AbiCoder.defaultAbiCoder().encode( + ['address', 'uint256'], + [address, ERC20_BALANCE_SLOT] + ) + ); + await anvilSetStorageAt( + rlcAddress, + balanceSlot, + toBeHex(targetNRlcBalance, 32) + ); }; export const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); -export const MAX_EXPECTED_SUBGRAPH_INDEXING_TIME = 5_000; +export const MAX_EXPECTED_SUBGRAPH_INDEXING_TIME = 10_000; -export const waitSubgraphIndexing = () => - sleep(MAX_EXPECTED_SUBGRAPH_INDEXING_TIME); +const DATAPROTECTOR_SUBGRAPH_URL = + 'http://127.0.0.1:8000/subgraphs/name/DataProtector-v2'; + +export const waitSubgraphIndexing = async ( + timeoutMs = 60_000 +): Promise => { + const provider = new JsonRpcProvider(TEST_CHAIN.rpcURL); + const targetBlock = await provider.getBlockNumber(); + + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + try { + const res = await fetch(DATAPROTECTOR_SUBGRAPH_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ query: '{ _meta { block { number } } }' }), + }); + const json = await res.json(); + const indexedBlock: number = json?.data?._meta?.block?.number ?? 0; + if (indexedBlock >= targetBlock) return; + } catch { + // subgraph not ready yet, keep polling + } + await sleep(1_000); + } + throw new Error( + `waitSubgraphIndexing: subgraph did not index block ${targetBlock} within ${timeoutMs}ms` + ); +}; export const getRequiredFieldMessage = (field: string = 'this') => `${field} is a required field`; @@ -91,10 +190,22 @@ export const getTestConfig = ( ipfsNode: 'http://127.0.0.1:5001', dataProtectorSubgraph: 'http://127.0.0.1:8000/subgraphs/name/DataProtector-v2', + compassUrl: TEST_COMPASS_URL, }; return [ethProvider, options]; }; +export const getTestDappAddress = async (): Promise => { + const chainId = Number(TEST_CHAIN.chainId); + const res = await fetch( + `${TEST_COMPASS_URL}/${chainId}/iapps/web3mail-tdx` + ).then((r) => r.json()); + if (!res?.address) { + throw new Error(`Could not resolve dapp address from test compass`); + } + return res.address as string; +}; + export const getEventFromLogs = (eventName, logs, { strict = true }) => { const eventFound = logs.find((log) => log.eventName === eventName); if (!eventFound) { @@ -134,7 +245,7 @@ export const createAndPublishAppOrders = async ( await resourceProvider.order .createApporder({ app: appAddress, - tag: ['tee', 'scone'], + tag: ['tee', 'tdx'], volume: 100, appprice: 0, }) @@ -164,14 +275,29 @@ export const setNRlcBalance = async ( address: string, nRlcTargetBalance: ethers.BigNumberish ) => { - const weiAmount = BigInt(`${nRlcTargetBalance}`) * 10n ** 9n; // 1 nRLC is 10^9 wei - await setBalance(address, weiAmount); + const n = BigInt(`${nRlcTargetBalance}`); + if (TEST_CHAIN.isNative) { + const weiAmount = n * 10n ** 9n; + await setBalance(address, weiAmount); + } else { + await anvilSetNRlcTokenBalance(address, n); + } +}; + +export const setEthForGas = async ( + address: string, + wei: bigint = TEST_CHAIN.defaultInitBalance +) => { + if (!TEST_CHAIN.isNative) { + await setBalance(address, wei); + } }; export const createVoucherType = async ({ description = 'test', duration = 1000, } = {}) => { + await setEthForGas(TEST_CHAIN.voucherManagerWallet.address); const VOUCHER_HUB_ABI = [ { inputs: [ @@ -240,6 +366,9 @@ export const ensureSufficientStake = async ( ) => { const walletAddress = await iexec.wallet.getAddress(); const account = await iexec.account.checkBalance(walletAddress); + if (!TEST_CHAIN.isNative) { + await setEthForGas(walletAddress); + } if (BigInt(account.stake.toString()) < BigInt(requiredStake.toString())) { await setNRlcBalance(walletAddress, requiredStake); @@ -311,6 +440,7 @@ export const createAndPublishWorkerpoolOrder = async ( ); const iexec = new IExec({ ethProvider }, getTestIExecOption()); const requiredStake = volume * workerpoolprice; + await setEthForGas(workerpoolOwnerWallet.address); await ensureSufficientStake(iexec, requiredStake); const workerpoolorder = await iexec.order.createWorkerpoolorder({ @@ -319,7 +449,7 @@ export const createAndPublishWorkerpoolOrder = async ( requesterrestrict, volume, workerpoolprice, - tag: ['tee', 'scone'], + tag: ['tee', 'tdx'], }); await iexec.order .signWorkerpoolorder(workerpoolorder) @@ -400,18 +530,28 @@ export const createVoucher = async ({ { hubAddress: TEST_CHAIN.hubAddress } ); - // ensure RLC balance - await setNRlcBalance(await iexec.wallet.getAddress(), value); + const voucherManagerAddress = await iexec.wallet.getAddress(); + await setEthForGas(voucherManagerAddress); + + await setNRlcBalance(voucherManagerAddress, value); - // deposit RLC to voucherHub const contractClient = await iexec.config.resolveContractsClient(); const iexecContract = contractClient.getIExecContract(); + const nRlc = BigInt(value); try { - await iexecContract.depositFor(TEST_CHAIN.voucherHubAddress, { - value: BigInt(value) * 10n ** 9n, - gasPrice: 0, - }); + if (TEST_CHAIN.isNative) { + await iexecContract.depositFor(TEST_CHAIN.voucherHubAddress, { + value: nRlc * 10n ** 9n, + gasPrice: 0, + }); + } else { + const tx = await iexecContract.depositFor( + nRlc, + TEST_CHAIN.voucherHubAddress + ); + await tx.wait(); + } } catch (error) { console.error('Error depositing RLC:', error); throw error; @@ -483,6 +623,7 @@ export const addVoucherEligibleAsset = async (assetAddress, voucherTypeId) => { ]); const signer = TEST_CHAIN.voucherManagerWallet.connect(TEST_CHAIN.provider); + await setEthForGas(signer.address); const retryableAddEligibleAsset = async (tryCount = 1) => { try { diff --git a/tests/unit/fetchMyContacts.test.ts b/tests/unit/fetchMyContacts.test.ts index 5fc38602..4d2fc77a 100644 --- a/tests/unit/fetchMyContacts.test.ts +++ b/tests/unit/fetchMyContacts.test.ts @@ -15,6 +15,7 @@ describe('fetchMyContacts', () => { let testedModule: any; let fetchMyContacts: FetchMyContacts; const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); + const mockDappAddress = getRandomAddress().toLowerCase(); beforeAll(async () => { // import tested module after all mocked modules @@ -79,13 +80,13 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddressOrENS: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, }); const userAddress = (await iexec.wallet.getAddress()).toLowerCase(); expect(iexec.orderbook.fetchDatasetOrderbook).toHaveBeenNthCalledWith(1, { dataset: 'any', - app: defaultConfig.dappAddress.toLowerCase(), + app: mockDappAddress, requester: userAddress, isAppStrict: true, isRequesterStrict: false, @@ -140,14 +141,14 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddressOrENS: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, isUserStrict: true, }); const userAddress = (await iexec.wallet.getAddress()).toLowerCase(); expect(iexec.orderbook.fetchDatasetOrderbook).toHaveBeenNthCalledWith(1, { dataset: 'any', - app: defaultConfig.dappAddress.toLowerCase(), + app: mockDappAddress, requester: userAddress, isAppStrict: true, isRequesterStrict: true, @@ -215,7 +216,7 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddressOrENS: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, }); @@ -265,14 +266,14 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddressOrENS: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, bulkOnly: false, }); const userAddress = (await iexec.wallet.getAddress()).toLowerCase(); expect(iexec.orderbook.fetchDatasetOrderbook).toHaveBeenNthCalledWith(1, { dataset: 'any', - app: defaultConfig.dappAddress.toLowerCase(), + app: mockDappAddress, requester: userAddress, isAppStrict: true, isRequesterStrict: false, @@ -329,14 +330,14 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddressOrENS: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, bulkOnly: true, }); const userAddress = (await iexec.wallet.getAddress()).toLowerCase(); expect(iexec.orderbook.fetchDatasetOrderbook).toHaveBeenNthCalledWith(1, { dataset: 'any', - app: defaultConfig.dappAddress.toLowerCase(), + app: mockDappAddress, requester: userAddress, isAppStrict: true, isRequesterStrict: false, @@ -393,7 +394,7 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddressOrENS: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, isUserStrict: true, bulkOnly: true, @@ -402,7 +403,7 @@ describe('fetchMyContacts', () => { const userAddress = (await iexec.wallet.getAddress()).toLowerCase(); expect(iexec.orderbook.fetchDatasetOrderbook).toHaveBeenNthCalledWith(1, { dataset: 'any', - app: defaultConfig.dappAddress.toLowerCase(), + app: mockDappAddress, requester: userAddress, isAppStrict: true, isRequesterStrict: true, diff --git a/tests/unit/sendEmail.test.ts b/tests/unit/sendEmail.test.ts index 430f00dc..d20b127f 100644 --- a/tests/unit/sendEmail.test.ts +++ b/tests/unit/sendEmail.test.ts @@ -1,7 +1,7 @@ import { expect, it, jest } from '@jest/globals'; import { ValidationError } from 'yup'; import { type SendEmail } from '../../src/web3mail/sendEmail.js'; -import { getRandomAddress, TEST_CHAIN } from '../test-utils.js'; +import { getRandomAddress } from '../test-utils.js'; import { mockAllForSendEmail } from '../utils/mockAllForSendEmail.js'; import { DEFAULT_CHAIN_ID, @@ -168,54 +168,53 @@ describe('sendEmail', () => { const OVERSIZED_CONTENT = 'Test'; const protectedData = getRandomAddress().toLowerCase(); + const mockDappAddress = getRandomAddress().toLowerCase(); const iexec = mockAllForSendEmail(); const userAddress = await iexec.wallet.getAddress(); + const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); + expect(defaultConfig).not.toBeNull(); + // --- WHEN await sendEmail({ // @ts-expect-error No need for graphQLClient here graphQLClient: {}, // @ts-expect-error No need for iexec here iexec, - ipfsGateway: getChainDefaultConfig(DEFAULT_CHAIN_ID)?.ipfsGateway, - ipfsNode: getChainDefaultConfig(DEFAULT_CHAIN_ID)?.ipfsUploadUrl, - workerpoolAddressOrEns: - getChainDefaultConfig(DEFAULT_CHAIN_ID)?.prodWorkerpoolAddress, - dappAddressOrENS: getChainDefaultConfig(DEFAULT_CHAIN_ID)?.dappAddress, + ipfsGateway: defaultConfig!.ipfsGateway, + ipfsNode: defaultConfig!.ipfsUploadUrl, + workerpoolAddressOrEns: defaultConfig!.prodWorkerpoolAddress, + dappAddressOrENS: mockDappAddress, dappWhitelistAddress: - getChainDefaultConfig( - DEFAULT_CHAIN_ID - )?.whitelistSmartContract.toLowerCase(), + defaultConfig!.whitelistSmartContract.toLowerCase(), emailSubject: 'e2e mail object for test', emailContent: OVERSIZED_CONTENT, protectedData, }); // --- THEN - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); - expect(defaultConfig).not.toBeNull(); expect(iexec.orderbook.fetchWorkerpoolOrderbook).toHaveBeenNthCalledWith( 1, { - workerpool: TEST_CHAIN.prodWorkerpool, - app: defaultConfig!.dappAddress.toLowerCase(), + workerpool: defaultConfig!.prodWorkerpoolAddress, + app: mockDappAddress, dataset: protectedData, requester: userAddress, isRequesterStrict: false, - minTag: ['tee', 'scone'], + minTag: ['tee', 'tdx'], category: 0, } ); expect(iexec.orderbook.fetchWorkerpoolOrderbook).toHaveBeenNthCalledWith( 2, { - workerpool: TEST_CHAIN.prodWorkerpool, + workerpool: defaultConfig!.prodWorkerpoolAddress, app: defaultConfig!.whitelistSmartContract.toLowerCase(), dataset: protectedData, requester: userAddress, isRequesterStrict: false, - minTag: ['tee', 'scone'], + minTag: ['tee', 'tdx'], category: 0, } ); diff --git a/tests/utils/mockAllForSendEmail.ts b/tests/utils/mockAllForSendEmail.ts index b268e76a..7fdcab0f 100644 --- a/tests/utils/mockAllForSendEmail.ts +++ b/tests/utils/mockAllForSendEmail.ts @@ -25,7 +25,7 @@ export function mockAllForSendEmail() { fetchAppOrderbook: jest .fn<() => Promise<{ orders: any[] }>>() .mockResolvedValue({ - orders: [{ order: { appprice: 0, tag: ['tee', 'scone'] } }], + orders: [{ order: { appprice: 0, tag: ['tee', 'tdx'] } }], }), fetchWorkerpoolOrderbook: jest.fn().mockImplementation(() => { return Promise.resolve({ From c38bf763e9a36e4247ca7e6fbaf6a8d70ba1ee14 Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Mon, 27 Apr 2026 08:06:20 +0200 Subject: [PATCH 02/13] feat: drop voucher support (not deployed on Arbitrum) Remove useVoucher param from sendEmail(), checkUserVoucher(), VoucherInfo type, voucher-related orderbook filtering, and all test helpers (createVoucher, createVoucherType, addVoucherEligibleAsset). --- src/web3mail/sendEmail.models.ts | 70 +------ src/web3mail/sendEmail.ts | 49 +---- src/web3mail/types.ts | 1 - tests/test-utils.ts | 273 +--------------------------- tests/unit/sendEmail.models.test.ts | 264 ++++----------------------- tests/unit/sendEmail.test.ts | 2 - 6 files changed, 52 insertions(+), 607 deletions(-) diff --git a/src/web3mail/sendEmail.models.ts b/src/web3mail/sendEmail.models.ts index 3e1568a0..b3ffd9bb 100644 --- a/src/web3mail/sendEmail.models.ts +++ b/src/web3mail/sendEmail.models.ts @@ -1,88 +1,22 @@ -import { Address, BN } from 'iexec'; import { PublishedWorkerpoolorder } from 'iexec/IExecOrderbookModule'; -// To import from 'iexec' once exported -type VoucherInfo = { - owner: Address; - address: Address; - type: BN; - balance: BN; - expirationTimestamp: BN; - sponsoredApps: Address[]; - sponsoredDatasets: Address[]; - sponsoredWorkerpools: Address[]; - allowanceAmount: BN; - authorizedAccounts: Address[]; -}; - -function bnToNumber(bn: BN) { - return Number(bn.toString()); -} - -export function checkUserVoucher({ - userVoucher, -}: { - userVoucher: VoucherInfo; -}) { - if (bnToNumber(userVoucher.expirationTimestamp) < Date.now() / 1000) { - throw new Error( - 'Oops, it seems your voucher has expired. You might want to ask for a top up. Check on https://builder.iex.ec/' - ); - } - - if (bnToNumber(userVoucher.balance) === 0) { - throw new Error( - 'Oops, it seems your voucher is empty. You might want to ask for a top up. Check on https://builder.iex.ec/' - ); - } -} - export function filterWorkerpoolOrders({ workerpoolOrders, workerpoolMaxPrice, - useVoucher, - userVoucher, }: { workerpoolOrders: PublishedWorkerpoolorder[]; workerpoolMaxPrice: number; - useVoucher: boolean; - userVoucher?: VoucherInfo; }) { if (workerpoolOrders.length === 0) { return null; } - let eligibleWorkerpoolOrders = workerpoolOrders; - let maxVoucherSponsoredAmount = 0; // may be safer to use bigint - - if (useVoucher) { - if (!userVoucher) { - throw new Error( - 'useVoucher === true but userVoucher is undefined? Hum...' - ); - } - // only voucher sponsored workerpoolorders - eligibleWorkerpoolOrders = eligibleWorkerpoolOrders.filter(({ order }) => - userVoucher.sponsoredWorkerpools.includes(order.workerpool) - ); - if (eligibleWorkerpoolOrders.length === 0) { - throw new Error( - 'Found some workerpool orders but none can be sponsored by your voucher.' - ); - } - maxVoucherSponsoredAmount = bnToNumber(userVoucher.balance); - } - - const [cheapestOrder] = eligibleWorkerpoolOrders.sort( + const [cheapestOrder] = workerpoolOrders.sort( (order1, order2) => order1.order.workerpoolprice - order2.order.workerpoolprice ); - if ( - !cheapestOrder || - cheapestOrder.order.workerpoolprice > - workerpoolMaxPrice + maxVoucherSponsoredAmount - ) { + if (!cheapestOrder || cheapestOrder.order.workerpoolprice > workerpoolMaxPrice) { return null; } return cheapestOrder.order; diff --git a/src/web3mail/sendEmail.ts b/src/web3mail/sendEmail.ts index 017dfbc0..7f8262b6 100644 --- a/src/web3mail/sendEmail.ts +++ b/src/web3mail/sendEmail.ts @@ -13,7 +13,6 @@ import { checkProtectedDataValidity } from '../utils/subgraphQuery.js'; import { addressOrEnsSchema, addressSchema, - booleanSchema, contentTypeSchema, emailContentSchema, emailSubjectSchema, @@ -22,10 +21,7 @@ import { senderNameSchema, throwIfMissing, } from '../utils/validators.js'; -import { - checkUserVoucher, - filterWorkerpoolOrders, -} from './sendEmail.models.js'; +import { filterWorkerpoolOrders } from './sendEmail.models.js'; import { SendEmailParams, SendEmailResponse } from './types.js'; import { DappAddressConsumer, @@ -55,7 +51,6 @@ export const sendEmail = async ({ workerpoolMaxPrice = MAX_DESIRED_WORKERPOOL_ORDER_PRICE, senderName, protectedData, - useVoucher = false, }: IExecConsumer & SubgraphConsumer & DappAddressConsumer & @@ -116,10 +111,6 @@ export const sendEmail = async ({ .label('workerpoolMaxPrice') .validateSync(workerpoolMaxPrice); - const vUseVoucher = booleanSchema() - .label('useVoucher') - .validateSync(useVoucher); - // Check protected data schema through subgraph const isValidProtectedData = await checkProtectedDataValidity( graphQLClient, @@ -134,21 +125,6 @@ export const sendEmail = async ({ const requesterAddress = await iexec.wallet.getAddress(); - let userVoucher; - if (vUseVoucher) { - try { - userVoucher = await iexec.voucher.showUserVoucher(requesterAddress); - checkUserVoucher({ userVoucher }); - } catch (err) { - if (err?.message?.startsWith('No Voucher found for address')) { - throw new Error( - 'Oops, it seems your wallet is not associated with any voucher. Check on https://builder.iex.ec/' - ); - } - throw err; - } - } - try { // Fetch app order first to determine TEE framework const apporder = await iexec.orderbook @@ -206,8 +182,7 @@ export const sendEmail = async ({ workerpool: workerpoolAddressOrEns, app: vDappAddressOrENS, dataset: vDatasetAddress, - requester: requesterAddress, // public orders + user specific orders - isRequesterStrict: useVoucher, // If voucher, we only want user specific orders + requester: requesterAddress, minTag: workerpoolMinTag, category: 0, }), @@ -216,8 +191,7 @@ export const sendEmail = async ({ workerpool: workerpoolAddressOrEns, app: vDappWhitelistAddress, dataset: vDatasetAddress, - requester: requesterAddress, // public orders + user specific orders - isRequesterStrict: useVoucher, // If voucher, we only want user specific orders + requester: requesterAddress, minTag: workerpoolMinTag, category: 0, }), @@ -229,8 +203,6 @@ export const sendEmail = async ({ ...workerpoolOrderbookForAppWhitelist.orders, ], workerpoolMaxPrice: vWorkerpoolMaxPrice, - useVoucher: vUseVoucher, - userVoucher, }); if (!desiredPriceWorkerpoolOrder) { @@ -312,15 +284,12 @@ export const sendEmail = async ({ const requestorder = await iexec.order.signRequestorder(requestorderToSign); // Match orders and compute task ID - const { dealid: dealId } = await iexec.order.matchOrders( - { - apporder: apporder, - datasetorder: datasetorder, - workerpoolorder: workerpoolorder, - requestorder: requestorder, - }, - { useVoucher: vUseVoucher } - ); + const { dealid: dealId } = await iexec.order.matchOrders({ + apporder: apporder, + datasetorder: datasetorder, + workerpoolorder: workerpoolorder, + requestorder: requestorder, + }); const taskId = await iexec.deal.computeTaskId(dealId, 0); diff --git a/src/web3mail/types.ts b/src/web3mail/types.ts index d73fafd9..6318f168 100644 --- a/src/web3mail/types.ts +++ b/src/web3mail/types.ts @@ -63,7 +63,6 @@ export type SendEmailParams = { dataMaxPrice?: number; appMaxPrice?: number; workerpoolMaxPrice?: number; - useVoucher?: boolean; }; export type FetchMyContactsParams = { diff --git a/tests/test-utils.ts b/tests/test-utils.ts index 6f6b81a4..e9018a79 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -15,7 +15,6 @@ import { } from '../src/web3mail/types.js'; import { IExec, utils } from 'iexec'; import { randomInt } from 'crypto'; -import { getSignerFromPrivateKey } from 'iexec/utils'; export const TEST_COMPASS_URL = 'http://127.0.0.1:8069'; @@ -37,12 +36,6 @@ export const TEST_CHAIN = { resultProxyURL: 'http://127.0.0.1:13250', iexecGatewayURL: 'http://127.0.0.1:3050', compassUrl: TEST_COMPASS_URL, - voucherHubAddress: '0x3137B6DF4f36D338b82260eDBB2E7bab034AFEda', - voucherManagerWallet: new Wallet( - '0x2c906d4022cace2b3ee6c8b596564c26c4dcadddf1e949b769bcb0ad75c40c33' - ), - voucherSubgraphURL: - 'http://127.0.0.1:8000/subgraphs/name/bellecour/iexec-voucher', prodWorkerpool: '0x2956f0cb779904795a5f30d3b3ea88b714c3123f', prodWorkerpoolOwnerWallet: TEST_POOL_SIGNER, // Used exclusively for tests that require NO free orders to be present. @@ -159,11 +152,7 @@ export const MAX_EXPECTED_WEB2_SERVICES_TIME = 80_000; export const MARKET_API_CALL_TIMEOUT = 2_000; -export const timeouts = { - // utils - createVoucherType: MAX_EXPECTED_BLOCKTIME * 2, - createVoucher: MAX_EXPECTED_BLOCKTIME * 4 + MARKET_API_CALL_TIMEOUT * 2, -}; +export const timeouts = {}; export const getTestWeb3SignerProvider = ( privateKey: string = Wallet.createRandom().privateKey @@ -174,8 +163,6 @@ export const getTestIExecOption = () => ({ smsURL: TEST_CHAIN.smsURL, resultProxyURL: TEST_CHAIN.resultProxyURL, iexecGatewayURL: TEST_CHAIN.iexecGatewayURL, - voucherHubAddress: TEST_CHAIN.voucherHubAddress, - voucherSubgraphURL: TEST_CHAIN.voucherSubgraphURL, ipfsGateway: TEST_CHAIN.ipfsGateway, ipfsNode: TEST_CHAIN.ipfsNode, }); @@ -293,72 +280,6 @@ export const setEthForGas = async ( } }; -export const createVoucherType = async ({ - description = 'test', - duration = 1000, -} = {}) => { - await setEthForGas(TEST_CHAIN.voucherManagerWallet.address); - const VOUCHER_HUB_ABI = [ - { - inputs: [ - { - internalType: 'string', - name: 'description', - type: 'string', - }, - { - internalType: 'uint256', - name: 'duration', - type: 'uint256', - }, - ], - name: 'createVoucherType', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - { - anonymous: false, - inputs: [ - { - indexed: true, - internalType: 'uint256', - name: 'id', - type: 'uint256', - }, - { - indexed: false, - internalType: 'string', - name: 'description', - type: 'string', - }, - { - indexed: false, - internalType: 'uint256', - name: 'duration', - type: 'uint256', - }, - ], - name: 'VoucherTypeCreated', - type: 'event', - }, - ]; - const voucherHubContract = new Contract( - TEST_CHAIN.voucherHubAddress, - VOUCHER_HUB_ABI, - TEST_CHAIN.provider - ); - const signer = TEST_CHAIN.voucherManagerWallet.connect(TEST_CHAIN.provider); - const createVoucherTypeTxHash = await voucherHubContract - .connect(signer) - .createVoucherType(description, duration); - const txReceipt = await createVoucherTypeTxHash.wait(); - const { id } = getEventFromLogs('VoucherTypeCreated', txReceipt.logs, { - strict: true, - }).args; - - return id as bigint; -}; export const ensureSufficientStake = async ( iexec: IExec, @@ -456,195 +377,3 @@ export const createAndPublishWorkerpoolOrder = async ( .then((o) => iexec.order.publishWorkerpoolorder(o)); }; -export const WORKERPOOL_ORDER_PER_VOUCHER = 1000; - -export const createVoucher = async ({ - owner, - voucherType, - value, - skipOrders = false, -}: { - owner: string; - voucherType: ethers.BigNumberish; - value: ethers.BigNumberish; - skipOrders?: boolean; -}) => { - const VOUCHER_HUB_ABI = [ - { - inputs: [ - { - internalType: 'address', - name: 'owner', - type: 'address', - }, - { - internalType: 'uint256', - name: 'voucherType', - type: 'uint256', - }, - { - internalType: 'uint256', - name: 'value', - type: 'uint256', - }, - ], - name: 'createVoucher', - outputs: [ - { - internalType: 'address', - name: 'voucherAddress', - type: 'address', - }, - ], - stateMutability: 'nonpayable', - type: 'function', - }, - { - inputs: [ - { - internalType: 'address', - name: 'account', - type: 'address', - }, - ], - name: 'getVoucher', - outputs: [ - { - internalType: 'address', - name: '', - type: 'address', - }, - ], - stateMutability: 'view', - type: 'function', - }, - ]; - - const iexec = new IExec( - { - ethProvider: getSignerFromPrivateKey( - TEST_CHAIN.rpcURL, - TEST_CHAIN.voucherManagerWallet.privateKey - ), - }, - { hubAddress: TEST_CHAIN.hubAddress } - ); - - const voucherManagerAddress = await iexec.wallet.getAddress(); - await setEthForGas(voucherManagerAddress); - - await setNRlcBalance(voucherManagerAddress, value); - - const contractClient = await iexec.config.resolveContractsClient(); - const iexecContract = contractClient.getIExecContract(); - const nRlc = BigInt(value); - - try { - if (TEST_CHAIN.isNative) { - await iexecContract.depositFor(TEST_CHAIN.voucherHubAddress, { - value: nRlc * 10n ** 9n, - gasPrice: 0, - }); - } else { - const tx = await iexecContract.depositFor( - nRlc, - TEST_CHAIN.voucherHubAddress - ); - await tx.wait(); - } - } catch (error) { - console.error('Error depositing RLC:', error); - throw error; - } - - const voucherHubContract = new Contract( - TEST_CHAIN.voucherHubAddress, - VOUCHER_HUB_ABI, - TEST_CHAIN.provider - ); - - const signer = TEST_CHAIN.voucherManagerWallet.connect(TEST_CHAIN.provider); - - try { - const createVoucherTxHash = await voucherHubContract - .connect(signer) - .createVoucher(owner, voucherType, value); - - await createVoucherTxHash.wait(); - } catch (error) { - console.error('Error creating voucher:', error); - throw error; - } - - if (!skipOrders) { - try { - const workerpoolprice = Math.floor(value / WORKERPOOL_ORDER_PER_VOUCHER); - await createAndPublishWorkerpoolOrder( - TEST_CHAIN.prodWorkerpool, - TEST_CHAIN.prodWorkerpoolOwnerWallet, - owner, - workerpoolprice, - WORKERPOOL_ORDER_PER_VOUCHER - ); - } catch (error) { - console.error('Error publishing workerpoolorder:', error); - throw error; - } - } - - try { - return await voucherHubContract.getVoucher(owner); - } catch (error) { - console.error('Error getting voucher:', error); - throw error; - } -}; - -export const addVoucherEligibleAsset = async (assetAddress, voucherTypeId) => { - const voucherHubContract = new Contract(TEST_CHAIN.voucherHubAddress, [ - { - inputs: [ - { - internalType: 'uint256', - name: 'voucherTypeId', - type: 'uint256', - }, - { - internalType: 'address', - name: 'asset', - type: 'address', - }, - ], - name: 'addEligibleAsset', - outputs: [], - stateMutability: 'nonpayable', - type: 'function', - }, - ]); - - const signer = TEST_CHAIN.voucherManagerWallet.connect(TEST_CHAIN.provider); - await setEthForGas(signer.address); - - const retryableAddEligibleAsset = async (tryCount = 1) => { - try { - const tx = await voucherHubContract - .connect(signer) - .addEligibleAsset(voucherTypeId, assetAddress); - await tx.wait(); - } catch (error) { - console.warn( - `Error adding eligible asset to voucher (try count ${tryCount}):`, - error - ); - if (tryCount < 3) { - await sleep(3000 * tryCount); - await retryableAddEligibleAsset(tryCount + 1); - } else { - throw new Error( - `Failed to add eligible asset to voucher after ${tryCount} attempts` - ); - } - } - }; - await retryableAddEligibleAsset(); -}; diff --git a/tests/unit/sendEmail.models.test.ts b/tests/unit/sendEmail.models.test.ts index 7e173416..16936b1e 100644 --- a/tests/unit/sendEmail.models.test.ts +++ b/tests/unit/sendEmail.models.test.ts @@ -1,68 +1,7 @@ -import { Address, BN } from 'iexec'; import { PublishedWorkerpoolorder } from 'iexec/IExecOrderbookModule'; -// import { VoucherInfo } from 'iexec/IExecVoucherModule'; -import { - checkUserVoucher, - filterWorkerpoolOrders, -} from '../../src/web3mail/sendEmail.models.js'; -import { getRandomAddress } from '../test-utils.js'; - -// To import from 'iexec' once exported -type VoucherInfo = { - owner: Address; - address: Address; - type: BN; - balance: BN; - expirationTimestamp: BN; - sponsoredApps: Address[]; - sponsoredDatasets: Address[]; - sponsoredWorkerpools: Address[]; - allowanceAmount: BN; - authorizedAccounts: Address[]; -}; +import { filterWorkerpoolOrders } from '../../src/web3mail/sendEmail.models.js'; describe('sendEmail.models', () => { - describe('checkUserVoucher', () => { - describe('When voucher is expired', () => { - it('should throw an Error with the correct message', async () => { - // --- GIVEN - const userVoucher = { - expirationTimestamp: Date.now() / 1000 - 60, // Expired 1min ago - } as unknown as VoucherInfo; - - expect(() => - checkUserVoucher({ - userVoucher, - }) - ).toThrow( - new Error( - 'Oops, it seems your voucher has expired. You might want to ask for a top up. Check on https://builder.iex.ec/' - ) - ); - }); - }); - - describe('When voucher has a balance equals to 0', () => { - it('should throw an Error with the correct message', async () => { - // --- GIVEN - const userVoucher = { - expirationTimestamp: Date.now() / 1000 + 3600, // Will expire in 1h - balance: 0, - } as unknown as VoucherInfo; - - expect(() => - checkUserVoucher({ - userVoucher, - }) - ).toThrow( - new Error( - 'Oops, it seems your voucher is empty. You might want to ask for a top up. Check on https://builder.iex.ec/' - ) - ); - }); - }); - }); - describe('filterWorkerpoolOrders()', () => { describe('When workerpool orders is an empty array', () => { it('should answer with null', () => { @@ -73,8 +12,6 @@ describe('sendEmail.models', () => { const foundOrder = filterWorkerpoolOrders({ workerpoolOrders, workerpoolMaxPrice: 0, - useVoucher: false, - userVoucher: undefined, }); // --- THEN @@ -82,179 +19,58 @@ describe('sendEmail.models', () => { }); }); - describe('useVoucher === false', () => { - describe('When all orders are too expensive', () => { - it('should answer with null', () => { - // --- GIVEN - const workerpoolOrders = [ - { - order: { - workerpoolprice: 1, - }, + describe('When all orders are too expensive', () => { + it('should answer with null', () => { + // --- GIVEN + const workerpoolOrders = [ + { + order: { + workerpoolprice: 1, }, - { - order: { - workerpoolprice: 2, - }, + }, + { + order: { + workerpoolprice: 2, }, - ] as PublishedWorkerpoolorder[]; - - // --- WHEN - const foundOrder = filterWorkerpoolOrders({ - workerpoolOrders, - workerpoolMaxPrice: 0, // <-- I want a free workerpool order - useVoucher: false, - userVoucher: undefined, - }); + }, + ] as PublishedWorkerpoolorder[]; - // --- THEN - expect(foundOrder).toBeNull(); + // --- WHEN + const foundOrder = filterWorkerpoolOrders({ + workerpoolOrders, + workerpoolMaxPrice: 0, }); - }); - - describe('When one order is cheap enough', () => { - it('should answer with it', () => { - // --- GIVEN - const workerpoolOrders = [ - { - order: { - workerpoolprice: 1, - }, - }, - { - order: { - workerpoolprice: 2, - }, - }, - ] as PublishedWorkerpoolorder[]; - // --- WHEN - const foundOrder = filterWorkerpoolOrders({ - workerpoolOrders, - workerpoolMaxPrice: 1, - useVoucher: false, - userVoucher: undefined, - }); - - // --- THEN - expect(foundOrder).toBeTruthy(); - expect(foundOrder.workerpoolprice).toBe(1); - }); + // --- THEN + expect(foundOrder).toBeNull(); }); }); - describe('useVoucher === true', () => { - describe('When there are workerpool orders but workerpool is NOT included in the voucher sponsored workerpools', () => { - it('should answer with null', () => { - // --- GIVEN - const userVoucher = { - balance: 4, // Technically it should be a BN - sponsoredWorkerpools: [getRandomAddress()], - } as unknown as VoucherInfo; - const workerpoolOrders = [ - { - order: { - workerpoolprice: 3, - workerpool: getRandomAddress(), - }, - }, - { - order: { - workerpoolprice: 1, - workerpool: getRandomAddress(), - }, - }, - ] as PublishedWorkerpoolorder[]; - - expect(() => - filterWorkerpoolOrders({ - workerpoolOrders, - workerpoolMaxPrice: 0, - useVoucher: true, - userVoucher, - }) - ).toThrow( - new Error( - 'Found some workerpool orders but none can be sponsored by your voucher.' - ) - ); - }); - }); - - describe('When voucher balance is greater than asked maxPrice', () => { - it('should answer with the cheapest sponsored order', () => { - // --- GIVEN - const userVoucher = { - balance: 4, // Technically it should be a BN - sponsoredWorkerpools: [ - '0x3779Da315D935D3E3957561667236BF6859C1b0E', - ], - } as unknown as VoucherInfo; - const workerpoolOrders = [ - { - order: { - workerpoolprice: 3, - workerpool: '0x3779Da315D935D3E3957561667236BF6859C1b0E', - }, - }, - { - order: { - workerpoolprice: 1, - workerpool: '0x3779Da315D935D3E3957561667236BF6859C1b0E', - }, + describe('When one order is cheap enough', () => { + it('should answer with the cheapest one', () => { + // --- GIVEN + const workerpoolOrders = [ + { + order: { + workerpoolprice: 1, }, - { - order: { - workerpoolprice: 0, - workerpool: getRandomAddress(), - }, + }, + { + order: { + workerpoolprice: 2, }, - ] as PublishedWorkerpoolorder[]; - - // --- WHEN - const foundOrder = filterWorkerpoolOrders({ - workerpoolOrders, - workerpoolMaxPrice: 0, - useVoucher: true, - userVoucher, - }); + }, + ] as PublishedWorkerpoolorder[]; - // --- THEN - expect(foundOrder).toBeTruthy(); - expect(foundOrder.workerpoolprice).toBe(1); + // --- WHEN + const foundOrder = filterWorkerpoolOrders({ + workerpoolOrders, + workerpoolMaxPrice: 1, }); - }); - describe('When voucher balance is not enough but user wants to pay the rest (workerpoolMaxPrice)', () => { - it('should answer with the cheapest order', () => { - // --- GIVEN - const userVoucher = { - balance: 2, // Technically it should be a BN - sponsoredWorkerpools: [ - '0x3779Da315D935D3E3957561667236BF6859C1b0E', - ], - } as unknown as VoucherInfo; - const workerpoolOrders = [ - { - order: { - workerpoolprice: 3, - workerpool: '0x3779Da315D935D3E3957561667236BF6859C1b0E', - }, - }, - ] as PublishedWorkerpoolorder[]; - - // --- WHEN - const foundOrder = filterWorkerpoolOrders({ - workerpoolOrders, - workerpoolMaxPrice: 1, - useVoucher: true, - userVoucher, - }); - - // --- THEN - expect(foundOrder).toBeTruthy(); - expect(foundOrder.workerpoolprice).toBe(3); - }); + // --- THEN + expect(foundOrder).not.toBeNull(); + expect(foundOrder!.workerpoolprice).toBe(1); }); }); }); diff --git a/tests/unit/sendEmail.test.ts b/tests/unit/sendEmail.test.ts index d20b127f..76ff85d1 100644 --- a/tests/unit/sendEmail.test.ts +++ b/tests/unit/sendEmail.test.ts @@ -201,7 +201,6 @@ describe('sendEmail', () => { app: mockDappAddress, dataset: protectedData, requester: userAddress, - isRequesterStrict: false, minTag: ['tee', 'tdx'], category: 0, } @@ -213,7 +212,6 @@ describe('sendEmail', () => { app: defaultConfig!.whitelistSmartContract.toLowerCase(), dataset: protectedData, requester: userAddress, - isRequesterStrict: false, minTag: ['tee', 'tdx'], category: 0, } From 64993af05c43dfdf2ece66d0e35f54808354656c Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Mon, 27 Apr 2026 08:38:59 +0200 Subject: [PATCH 03/13] chore(dapp): arbitrum migration - Remove remaining bellecour references from CI, workflows, dapp tests, deployment utils, getWeb3Provider and test scripts --- .github/workflows/dapp-ci.yml | 2 +- .github/workflows/dapp-deploy.yml | 2 -- dapp/src/executeTask.js | 1 - dapp/tests/e2e/app.test.js | 2 +- dapp/tests/unit/decryptEmailContent.test.js | 5 ++-- .../src/singleFunction/deployApp.ts | 6 ----- deployment-dapp/src/utils/utils.ts | 2 +- src/config/config.ts | 3 --- src/utils/getWeb3Provider.ts | 2 +- src/web3mail/IExecWeb3mail.ts | 26 ++++++++++++++----- src/web3mail/fetchUserContacts.ts | 3 +-- src/web3mail/sendEmail.models.ts | 15 +++++++---- tests/scripts/prepare-forks-for-tests.js | 4 +-- tests/test-utils.ts | 2 -- 14 files changed, 40 insertions(+), 35 deletions(-) diff --git a/.github/workflows/dapp-ci.yml b/.github/workflows/dapp-ci.yml index 8eebac7a..c07cf501 100644 --- a/.github/workflows/dapp-ci.yml +++ b/.github/workflows/dapp-ci.yml @@ -40,7 +40,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - environment: ['bellecour-dev', 'arbitrum-sepolia-dev'] + environment: ['arbitrum-sepolia-dev'] environment: ${{ matrix.environment }} defaults: run: diff --git a/.github/workflows/dapp-deploy.yml b/.github/workflows/dapp-deploy.yml index a1d77c1d..a07e2089 100644 --- a/.github/workflows/dapp-deploy.yml +++ b/.github/workflows/dapp-deploy.yml @@ -9,10 +9,8 @@ on: type: choice options: # dev environments - - bellecour-dev - arbitrum-sepolia-dev # prod environments (requires a tag starting with dapp-v) - - bellecour-prod - arbitrum-sepolia-prod - arbitrum-prod diff --git a/dapp/src/executeTask.js b/dapp/src/executeTask.js index f1029e0a..c49bab4d 100644 --- a/dapp/src/executeTask.js +++ b/dapp/src/executeTask.js @@ -100,7 +100,6 @@ async function processProtectedData({ result.success = false; result.error = e.message; } - console.log(`Protected data ${index} processed:`, result); return result; } diff --git a/dapp/tests/e2e/app.test.js b/dapp/tests/e2e/app.test.js index b89ecedc..28822cf0 100644 --- a/dapp/tests/e2e/app.test.js +++ b/dapp/tests/e2e/app.test.js @@ -30,7 +30,7 @@ describe('sendEmail', () => { beforeEach(async () => { // worker env setup process.env.WEB3MAIL_IPFS_GATEWAY = - 'https://ipfs-gateway.v8-bellecour.iex.ec'; + 'https://ipfs-gateway.arbitrum-sepolia-testnet.iex.ec'; process.env.IEXEC_IN = './tests/_test_inputs_'; process.env.IEXEC_OUT = './tests/_test_outputs_/iexec_out'; // clean IEXEC_OUT diff --git a/dapp/tests/unit/decryptEmailContent.test.js b/dapp/tests/unit/decryptEmailContent.test.js index d9ba0df8..f950a116 100644 --- a/dapp/tests/unit/decryptEmailContent.test.js +++ b/dapp/tests/unit/decryptEmailContent.test.js @@ -8,7 +8,7 @@ describe('decryptContent', () => { const { IExec } = await import('iexec'); const iexec = new IExec({ - ethProvider: 'bellecour', + ethProvider: 'arbitrum-sepolia-testnet', }); const encryptionKey = iexec.dataset.generateEncryptionKey(); @@ -26,7 +26,8 @@ describe('decryptContent', () => { }); describe('downloadEncryptedContent', () => { - const DEFAULT_IPFS_GATEWAY = 'https://ipfs-gateway.v8-bellecour.iex.ec'; + const DEFAULT_IPFS_GATEWAY = + 'https://ipfs-gateway.arbitrum-sepolia-testnet.iex.ec'; beforeEach(() => { process.env.WEB3MAIL_IPFS_GATEWAY = DEFAULT_IPFS_GATEWAY; diff --git a/deployment-dapp/src/singleFunction/deployApp.ts b/deployment-dapp/src/singleFunction/deployApp.ts index 9e4dd682..82db9899 100644 --- a/deployment-dapp/src/singleFunction/deployApp.ts +++ b/deployment-dapp/src/singleFunction/deployApp.ts @@ -12,18 +12,12 @@ export const deployApp = async ({ dockerRepository = DOCKER_IMAGE_REPOSITORY, dockerTag, checksum, - // TODO: to be deleted after migration to TDX - fingerprint, - sconifyVersion, }: { iexec: IExec; dockerNamespace?: string; dockerRepository?: string; dockerTag: string; checksum?: string; - // TODO: to be deleted after migration to TDX - fingerprint?: string; - sconifyVersion?: string; }): Promise => { const name = APP_NAME; const type = APP_TYPE; diff --git a/deployment-dapp/src/utils/utils.ts b/deployment-dapp/src/utils/utils.ts index 6e8e5331..dbc14482 100644 --- a/deployment-dapp/src/utils/utils.ts +++ b/deployment-dapp/src/utils/utils.ts @@ -3,7 +3,7 @@ import { IExec, utils } from 'iexec'; export const getIExec = ( privateKey: string, - host: string = 'bellecour' + host: string = 'arbitrum-sepolia-testnet' ): IExec => { const ethProvider = utils.getSignerFromPrivateKey(host, privateKey, { providers: {}, diff --git a/src/config/config.ts b/src/config/config.ts index 41184e23..7cc6cde6 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -10,7 +10,6 @@ export const DEFAULT_CHAIN_ID = 421614; interface ChainConfig { name: string; dappAddress?: string; - compassUrl?: string; prodWorkerpoolAddress: string; dataProtectorSubgraph: string; ipfsUploadUrl: string; @@ -23,7 +22,6 @@ const CHAIN_CONFIG: Record = { 421614: { name: 'arbitrum-sepolia-testnet', dappAddress: undefined, // ENS not supported on this network, address will be resolved from Compass - compassUrl: 'https://compass.arbitrum-sepolia-testnet.iex.ec', prodWorkerpoolAddress: '0x2956f0cb779904795a5f30d3b3ea88b714c3123f', // TDX workerpool dataProtectorSubgraph: 'https://thegraph.arbitrum-sepolia-testnet.iex.ec/api/subgraphs/id/5YjRPLtjS6GH6bB4yY55Qg4HzwtRGQ8TaHtGf9UBWWd', @@ -34,7 +32,6 @@ const CHAIN_CONFIG: Record = { 42161: { name: 'arbitrum-mainnet', dappAddress: undefined, // ENS not supported on this network, address will be resolved from Compass - compassUrl: 'https://compass.arbitrum-mainnet.iex.ec', prodWorkerpoolAddress: '0x8ef2ec3ef9535d4b4349bfec7d8b31a580e60244', // TDX workerpool dataProtectorSubgraph: 'https://thegraph.arbitrum.iex.ec/api/subgraphs/id/Ep5zs5zVr4tDiVuQJepUu51e5eWYJpka624X4DMBxe3u', diff --git a/src/utils/getWeb3Provider.ts b/src/utils/getWeb3Provider.ts index a722978d..b383da90 100644 --- a/src/utils/getWeb3Provider.ts +++ b/src/utils/getWeb3Provider.ts @@ -5,7 +5,7 @@ export const getWeb3Provider = ( privateKey: string, options: { allowExperimentalNetworks?: boolean; host?: number | string } = {} ): Web3SignerProvider => { - const chainHost = options?.host ? `${options.host}` : 'bellecour'; + const chainHost = options?.host ? `${options.host}` : 'arbitrum-sepolia-testnet'; return getSignerFromPrivateKey(chainHost, privateKey, { allowExperimentalNetworks: options?.allowExperimentalNetworks, providers: {}, diff --git a/src/web3mail/IExecWeb3mail.ts b/src/web3mail/IExecWeb3mail.ts index 6311f90d..d1de9a45 100644 --- a/src/web3mail/IExecWeb3mail.ts +++ b/src/web3mail/IExecWeb3mail.ts @@ -179,6 +179,9 @@ export class IExecWeb3mail { { ipfsGatewayURL: ipfsGateway, ...this.options?.iexecOptions, + ...(this.options?.compassUrl + ? { compassURL: this.options.compassUrl } + : {}), allowExperimentalNetworks: this.options.allowExperimentalNetworks, } ); @@ -189,13 +192,24 @@ export class IExecWeb3mail { const subgraphUrl = this.options?.dataProtectorSubgraph || chainDefaultConfig?.dataProtectorSubgraph; - const dappAddressOrENS = - this.options?.dappAddressOrENS || - chainDefaultConfig?.dappAddress || - (await resolveDappAddressFromCompass( - this.options?.compassUrl ?? chainDefaultConfig?.compassUrl ?? '', + const compassUrl = await iexec.config.resolveCompassURL(); + const optionsDappAddress = this.options?.dappAddressOrENS; + const configDappAddress = chainDefaultConfig?.dappAddress; + let dappAddressOrENS: string; + if (optionsDappAddress) { + dappAddressOrENS = optionsDappAddress; + } else if (configDappAddress) { + dappAddressOrENS = configDappAddress; + } else if (compassUrl) { + dappAddressOrENS = await resolveDappAddressFromCompass( + compassUrl, chainId - )); + ); + } else { + throw new Error( + `No Compass URL available for chain ${chainId}. Provide a compassUrl in options or a dappAddressOrENS.` + ); + } const dappWhitelistAddress = this.options?.dappWhitelistAddress || chainDefaultConfig?.whitelistSmartContract; diff --git a/src/web3mail/fetchUserContacts.ts b/src/web3mail/fetchUserContacts.ts index 46db8fa8..5471ba5f 100644 --- a/src/web3mail/fetchUserContacts.ts +++ b/src/web3mail/fetchUserContacts.ts @@ -69,12 +69,11 @@ export const fetchUserContacts = async ({ const orders = dappOrders.concat(whitelistOrders); const myContacts: Omit[] = []; - const web3DappResolvedAddress = vDappAddressOrENS; orders.forEach((order) => { if ( order.order.apprestrict.toLowerCase() === - web3DappResolvedAddress.toLowerCase() || + vDappAddressOrENS.toLowerCase() || order.order.apprestrict.toLowerCase() === vDappWhitelistAddress.toLowerCase() ) { diff --git a/src/web3mail/sendEmail.models.ts b/src/web3mail/sendEmail.models.ts index b3ffd9bb..1a976199 100644 --- a/src/web3mail/sendEmail.models.ts +++ b/src/web3mail/sendEmail.models.ts @@ -11,12 +11,17 @@ export function filterWorkerpoolOrders({ return null; } - const [cheapestOrder] = workerpoolOrders.sort( - (order1, order2) => - order1.order.workerpoolprice - order2.order.workerpoolprice - ); + const [cheapestOrder] = workerpoolOrders + .slice() + .sort( + (order1, order2) => + order1.order.workerpoolprice - order2.order.workerpoolprice + ); - if (!cheapestOrder || cheapestOrder.order.workerpoolprice > workerpoolMaxPrice) { + if ( + !cheapestOrder || + cheapestOrder.order.workerpoolprice > workerpoolMaxPrice + ) { return null; } return cheapestOrder.order; diff --git a/tests/scripts/prepare-forks-for-tests.js b/tests/scripts/prepare-forks-for-tests.js index 6ef1e7eb..cad89744 100644 --- a/tests/scripts/prepare-forks-for-tests.js +++ b/tests/scripts/prepare-forks-for-tests.js @@ -125,8 +125,8 @@ const getIExecHubOwnership = }; /** - * prepare-bellecour-fork-for-tests.js: IExec v8 resource = ERC-721 in `registry()`. - * Transfer the token to the test wallet (same as Bellecour learn/prod + web3mail app). + * IExec v8 resource = ERC-721 in `registry()`. + * Transfer the token to the test wallet (same as Arbitrum Sepolia learn/prod + web3mail app). * Arbitrum Sepolia: fixed `resourceAddress`, gasLimit for the fork. */ const getIExecResourceOwnership = diff --git a/tests/test-utils.ts b/tests/test-utils.ts index e9018a79..a767eddc 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -280,7 +280,6 @@ export const setEthForGas = async ( } }; - export const ensureSufficientStake = async ( iexec: IExec, requiredStake: ethers.BigNumberish @@ -376,4 +375,3 @@ export const createAndPublishWorkerpoolOrder = async ( .signWorkerpoolorder(workerpoolorder) .then((o) => iexec.order.publishWorkerpoolorder(o)); }; - From 331fbabebdd10deb654421506e4c8b2915f98c6e Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Mon, 27 Apr 2026 09:34:17 +0200 Subject: [PATCH 04/13] style: fix prettier formatting in getWeb3Provider --- src/utils/getWeb3Provider.ts | 4 +- tests/docker-compose.yml | 9 ++- tests/e2e/constructor.test.ts | 5 +- tests/e2e/sendEmail.test.ts | 71 ++++++++++++------------ tests/mock/compass/data.json | 2 +- tests/scripts/prepare-forks-for-tests.js | 12 +--- tests/scripts/prepare-test-env.js | 4 +- tests/test-utils.ts | 4 -- 8 files changed, 56 insertions(+), 55 deletions(-) diff --git a/src/utils/getWeb3Provider.ts b/src/utils/getWeb3Provider.ts index b383da90..0c133b26 100644 --- a/src/utils/getWeb3Provider.ts +++ b/src/utils/getWeb3Provider.ts @@ -5,7 +5,9 @@ export const getWeb3Provider = ( privateKey: string, options: { allowExperimentalNetworks?: boolean; host?: number | string } = {} ): Web3SignerProvider => { - const chainHost = options?.host ? `${options.host}` : 'arbitrum-sepolia-testnet'; + const chainHost = options?.host + ? `${options.host}` + : 'arbitrum-sepolia-testnet'; return getSignerFromPrivateKey(chainHost, privateKey, { allowExperimentalNetworks: options?.allowExperimentalNetworks, providers: {}, diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 53abcbbd..fd8e1994 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -37,6 +37,7 @@ services: service-internal-error: image: nginx:alpine + platform: linux/amd64 volumes: - $PWD/mock/server/http500.nginx.conf:/etc/nginx/conf.d/default.conf:ro expose: @@ -94,6 +95,7 @@ services: result-proxy-mongo: restart: unless-stopped image: library/mongo:4.2 + platform: linux/amd64 entrypoint: '/bin/bash' command: -c "mongod --bind_ip_all --port 13202" expose: @@ -104,6 +106,7 @@ services: ipfs: restart: unless-stopped image: ipfs/go-ipfs:v0.9.1 + platform: linux/amd64 expose: - 8080 - 5001 @@ -120,6 +123,7 @@ services: market-mongo: image: mongo:6.0.3 restart: unless-stopped + platform: linux/amd64 expose: - 27017 ports: @@ -127,6 +131,7 @@ services: market-redis: image: redis:7.0.7-alpine + platform: linux/amd64 restart: unless-stopped command: redis-server --appendonly yes expose: @@ -204,6 +209,7 @@ services: graphnode-postgres: image: postgres:12 + platform: linux/amd64 restart: unless-stopped command: - 'postgres' @@ -268,7 +274,7 @@ services: condition: service_started environment: ENV: prod - START_BLOCK: $ARBITRUM_SEPOLIA_FORK_BLOCK + START_BLOCK: $ARBITRUM_SEPOLIA_INDEX_BLOCK DATAPROTECTOR_ADDRESS: '0x168eAF6C33a77E3caD9db892452f51a5D91df621' APP_REGISTRY_ADDRESS: '0x9950D94FB074182eE93fF79a50CD698C4983281F' DATASET_REGISTRY_ADDRESS: '0x07cc4E1Ea30dD02796795876509a3bfC5053128d' @@ -277,6 +283,7 @@ services: stack-ready: image: bash + platform: linux/amd64 command: - echo "all services ready" depends_on: diff --git a/tests/e2e/constructor.test.ts b/tests/e2e/constructor.test.ts index 1be16a83..f335fd7e 100644 --- a/tests/e2e/constructor.test.ts +++ b/tests/e2e/constructor.test.ts @@ -4,6 +4,7 @@ import { describe, expect, it } from '@jest/globals'; import { Wallet } from 'ethers'; import { getWeb3Provider, IExecWeb3mail } from '../../src/index.js'; import { + getTestConfig, getTestWeb3SignerProvider, MAX_EXPECTED_WEB2_SERVICES_TIME, } from '../test-utils.js'; @@ -123,9 +124,9 @@ describe('IExecWeb3mail()', () => { 'When calling a read method should work as expected', async () => { // --- GIVEN - const web3mail = new IExecWeb3mail(); - await web3mail.init(); const wallet = Wallet.createRandom(); + const web3mail = new IExecWeb3mail(...getTestConfig(wallet.privateKey)); + await web3mail.init(); // --- WHEN/THEN // fetchUserContacts should work without throwing diff --git a/tests/e2e/sendEmail.test.ts b/tests/e2e/sendEmail.test.ts index 7c581318..6af2c458 100644 --- a/tests/e2e/sendEmail.test.ts +++ b/tests/e2e/sendEmail.test.ts @@ -37,19 +37,16 @@ describe('web3mail.sendEmail()', () => { let invalidProtectedData: ProtectedDataWithSecretProps; let consumerIExecInstance: IExec; let prodWorkerpoolAddress: string; - let paidOnlyWorkerpoolAddress: string; let dappAddress: string; const iexecOptions = getTestIExecOption(); - const prodWorkerpoolPublicPrice = 1000; beforeAll(async () => { - // paid workerpool order for the "not free" tests await createAndPublishWorkerpoolOrder( TEST_CHAIN.prodWorkerpool, TEST_CHAIN.prodWorkerpoolOwnerWallet, NULL_ADDRESS, - 1_000, - 1_000_000 + 0, + 1_000 ); // apporder always available @@ -62,18 +59,6 @@ describe('web3mail.sendEmail()', () => { await createAndPublishAppOrders(resourceProvider, dappAddress); prodWorkerpoolAddress = TEST_CHAIN.prodWorkerpool; - paidOnlyWorkerpoolAddress = TEST_CHAIN.paidOnlyWorkerpool; - - // paid-only workerpool: never publish free orders here so negative tests - // that expect "no workerpool order" or "insufficient stake" stay reliable - // across repeated test:e2e runs (market orders persist in MongoDB). - await createAndPublishWorkerpoolOrder( - TEST_CHAIN.paidOnlyWorkerpool, - TEST_CHAIN.paidOnlyWorkerpoolOwnerWallet, - NULL_ADDRESS, - 1_000, - 1_000_000 - ); //create valid protected data dataProtector = new IExecDataProtectorCore( @@ -95,7 +80,6 @@ describe('web3mail.sendEmail()', () => { beforeEach(async () => { // use a fresh wallet for calling sendEmail consumerWallet = getRandomWallet(); - // matchOrders pays gas in ETH; free workerpool (0 nRLC stake) never hits ensureSufficientStake deposit path await setEthForGas(consumerWallet.address); const consumerEthProvider = getTestWeb3SignerProvider( consumerWallet.privateKey @@ -114,23 +98,50 @@ describe('web3mail.sendEmail()', () => { }); describe('when using the default (not free) prod workerpool', () => { + let paidWorkerpoolAddress: string; + const prodWorkerpoolPublicPrice = 1000; + + beforeAll(async () => { + await setEthForGas(TEST_CHAIN.prodWorkerpoolOwnerWallet.address); + const workerpoolOwnerEthProvider = getTestWeb3SignerProvider( + TEST_CHAIN.prodWorkerpoolOwnerWallet.privateKey + ); + const workerpoolOwnerIexec = new IExec( + { ethProvider: workerpoolOwnerEthProvider }, + iexecOptions + ); + const { address } = + await workerpoolOwnerIexec.workerpool.deployWorkerpool({ + owner: TEST_CHAIN.prodWorkerpoolOwnerWallet.address, + description: 'paid test workerpool', + }); + paidWorkerpoolAddress = address; + await createAndPublishWorkerpoolOrder( + paidWorkerpoolAddress, + TEST_CHAIN.prodWorkerpoolOwnerWallet, + NULL_ADDRESS, + prodWorkerpoolPublicPrice, + 10 + ); + }, 3 * MAX_EXPECTED_BLOCKTIME); + describe('when using the user does not set the workerpoolMaxPrice', () => { it( 'should throw an error No Workerpool order found for the desired price', async () => { - let error: Error; + let error!: Error; await web3mail .sendEmail({ emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, - workerpoolAddressOrEns: paidOnlyWorkerpoolAddress, + workerpoolAddressOrEns: paidWorkerpoolAddress, }) .catch((e) => (error = e)); expect(error).toBeDefined(); expect(error.message).toBe('Failed to sendEmail'); expect(error.cause).toStrictEqual( - Error(`No Workerpool order found for the desired price`) + new Error(`No Workerpool order found for the desired price`) ); }, 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME @@ -140,20 +151,20 @@ describe('web3mail.sendEmail()', () => { it( `should throw an error if the user can't pay with its account`, async () => { - let error: Error; + let error!: WorkflowError; await web3mail .sendEmail({ emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, - workerpoolAddressOrEns: paidOnlyWorkerpoolAddress, + workerpoolAddressOrEns: paidWorkerpoolAddress, workerpoolMaxPrice: prodWorkerpoolPublicPrice, }) .catch((e) => (error = e)); expect(error).toBeInstanceOf(WorkflowError); expect(error.message).toBe('Failed to sendEmail'); expect(error.cause).toStrictEqual( - Error( + new Error( `Cost per task (${prodWorkerpoolPublicPrice}) 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` ) ); @@ -171,6 +182,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, + workerpoolAddressOrEns: paidWorkerpoolAddress, workerpoolMaxPrice: prodWorkerpoolPublicPrice, }); expect(sendEmailResponse).toStrictEqual({ @@ -218,7 +230,6 @@ describe('web3mail.sendEmail()', () => { emailContent: 'e2e mail content for test', protectedData: protectedData.address, workerpoolAddressOrEns: prodWorkerpoolAddress, - workerpoolMaxPrice: prodWorkerpoolPublicPrice, }) ).rejects.toThrow( new WorkflowError({ @@ -271,16 +282,6 @@ describe('web3mail.sendEmail()', () => { ); describe('when using the free prod workerpool', () => { - beforeAll(async () => { - await createAndPublishWorkerpoolOrder( - TEST_CHAIN.prodWorkerpool, - TEST_CHAIN.prodWorkerpoolOwnerWallet, - NULL_ADDRESS, - 0, - 1_000 - ); - }, MAX_EXPECTED_BLOCKTIME); - it( 'should successfully send email when using a free prod workerpool', async () => { diff --git a/tests/mock/compass/data.json b/tests/mock/compass/data.json index 4bc8cfa6..7d862cdc 100644 --- a/tests/mock/compass/data.json +++ b/tests/mock/compass/data.json @@ -12,7 +12,7 @@ { "name": "unreachable-workerpool", "apiUrl": "https://unreachable-workerpool.iex.ec", - "address": "0xB967057a21dc6A66A29721d96b8Aa7454B7c383F" + "address": "0x2956f0cb779904795a5f30d3b3ea88b714c3123f" }, { "name": "workerpool-http-500", diff --git a/tests/scripts/prepare-forks-for-tests.js b/tests/scripts/prepare-forks-for-tests.js index cad89744..46f0c7ce 100644 --- a/tests/scripts/prepare-forks-for-tests.js +++ b/tests/scripts/prepare-forks-for-tests.js @@ -19,12 +19,8 @@ const DAPP_ADDRESS = '0x09d59e1B696D0cb69f46bf762412636E8652aB58'; // Must match TEST_CHAIN.prodWorkerpool const PROD_WORKERPOOL_ADDRESS = '0x2956f0cb779904795a5f30d3b3ea88b714c3123f'; -// Must match TEST_CHAIN.paidOnlyWorkerpool — never gets free orders published -const PAID_ONLY_WORKERPOOL_ADDRESS = '0xB967057a21dc6A66A29721d96b8Aa7454B7c383F'; -// Default Arbitrum Sepolia pool (e2e "learn" / free) — same TEST_CHAIN.learnProdWorkerpool -// const LEARN_WORKERPOOL_ADDRESS = -// '0xB967057a21dc6A66A29721d96b8Aa7454B7c383F'; -// Must match TEST_CHAIN.prodWorkerpoolOwnerWallet + +// Must match TEST_CHAIN.prodWorkerpoolOwner const PROD_WORKERPOOL_OWNER_TEST = new Wallet( '0x6a12f56d7686e85ab0f46eb3c19cb0c75bfabf8fb04e595654fc93ad652fa7bc' ).address; @@ -221,10 +217,6 @@ const main = async () => { PROD_WORKERPOOL_ADDRESS, PROD_WORKERPOOL_OWNER_TEST ); - await getIExecResourceOwnership(arbitrumSepoliaForkRpcUrl)( - PAID_ONLY_WORKERPOOL_ADDRESS, - PROD_WORKERPOOL_OWNER_TEST - ); }; main().catch((err) => { diff --git a/tests/scripts/prepare-test-env.js b/tests/scripts/prepare-test-env.js index 12f2bb6a..a77014c9 100644 --- a/tests/scripts/prepare-test-env.js +++ b/tests/scripts/prepare-test-env.js @@ -18,7 +18,9 @@ writeFileSync( # blockchain node to use as the reference for the local fork ARBITRUM_SEPOLIA_FORK_URL=${arbitrumSepoliaForkUrl} # block number to fork from -ARBITRUM_SEPOLIA_FORK_BLOCK=${arbitrumSepoliaForkBlock}` +ARBITRUM_SEPOLIA_FORK_BLOCK=${arbitrumSepoliaForkBlock} +# block number to index from (should be fork block + 1 to skip all existing ArbitrumInternalTxType which is not supported by a graphnode connected to anvil) +ARBITRUM_SEPOLIA_INDEX_BLOCK=${arbitrumSepoliaForkBlock + 1}` ); async function getCurrentBlockNumber(forkUrl) { diff --git a/tests/test-utils.ts b/tests/test-utils.ts index a767eddc..7be3db3f 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -38,10 +38,6 @@ export const TEST_CHAIN = { compassUrl: TEST_COMPASS_URL, prodWorkerpool: '0x2956f0cb779904795a5f30d3b3ea88b714c3123f', prodWorkerpoolOwnerWallet: TEST_POOL_SIGNER, - // Used exclusively for tests that require NO free orders to be present. - // Never publish price=0 orders against this address. - paidOnlyWorkerpool: '0xB967057a21dc6A66A29721d96b8Aa7454B7c383F', - paidOnlyWorkerpoolOwnerWallet: TEST_POOL_SIGNER, appOwnerWallet: new Wallet( '0xa911b93e50f57c156da0b8bff2277d241bcdb9345221a3e246a99c6e7cedcde5' ), From 9c57e3372cdc683c61f5c0d52a2f5bc5d93dc45d Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Tue, 28 Apr 2026 16:05:47 +0200 Subject: [PATCH 05/13] refactor: remove resolveCompassURL as option --- src/web3mail/IExecWeb3mail.ts | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/src/web3mail/IExecWeb3mail.ts b/src/web3mail/IExecWeb3mail.ts index d1de9a45..dfd81f8f 100644 --- a/src/web3mail/IExecWeb3mail.ts +++ b/src/web3mail/IExecWeb3mail.ts @@ -179,9 +179,6 @@ export class IExecWeb3mail { { ipfsGatewayURL: ipfsGateway, ...this.options?.iexecOptions, - ...(this.options?.compassUrl - ? { compassURL: this.options.compassUrl } - : {}), allowExperimentalNetworks: this.options.allowExperimentalNetworks, } ); @@ -192,24 +189,13 @@ export class IExecWeb3mail { const subgraphUrl = this.options?.dataProtectorSubgraph || chainDefaultConfig?.dataProtectorSubgraph; - const compassUrl = await iexec.config.resolveCompassURL(); - const optionsDappAddress = this.options?.dappAddressOrENS; - const configDappAddress = chainDefaultConfig?.dappAddress; - let dappAddressOrENS: string; - if (optionsDappAddress) { - dappAddressOrENS = optionsDappAddress; - } else if (configDappAddress) { - dappAddressOrENS = configDappAddress; - } else if (compassUrl) { - dappAddressOrENS = await resolveDappAddressFromCompass( - compassUrl, + const dappAddressOrENS = + this.options?.dappAddressOrENS || + chainDefaultConfig?.dappAddress || + (await resolveDappAddressFromCompass( + await iexec.config.resolveCompassURL(), chainId - ); - } else { - throw new Error( - `No Compass URL available for chain ${chainId}. Provide a compassUrl in options or a dappAddressOrENS.` - ); - } + )); const dappWhitelistAddress = this.options?.dappWhitelistAddress || chainDefaultConfig?.whitelistSmartContract; From e0b8387efdb3c499e9011e1161ee12b37a8e8b17 Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:00:33 +0200 Subject: [PATCH 06/13] refactor --- src/web3mail/types.ts | 6 ------ tests/e2e/constructor.test.ts | 7 ++++--- tests/e2e/sendEmailCampaign.test.ts | 5 ----- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/web3mail/types.ts b/src/web3mail/types.ts index 6318f168..bbaf1260 100644 --- a/src/web3mail/types.ts +++ b/src/web3mail/types.ts @@ -134,12 +134,6 @@ export type Web3MailConfigOptions = { */ ipfsGateway?: string; - /** - * Override the Compass URL used to resolve the dapp address. - * If not provided, the default Compass URL for the chain will be used. - */ - compassUrl?: string; - /** * if true allows using a provider connected to an experimental networks (default false) * diff --git a/tests/e2e/constructor.test.ts b/tests/e2e/constructor.test.ts index f335fd7e..733e1982 100644 --- a/tests/e2e/constructor.test.ts +++ b/tests/e2e/constructor.test.ts @@ -159,7 +159,6 @@ describe('IExecWeb3mail()', () => { async () => { const web3mail = new IExecWeb3mail(experimentalNetworkSigner); - // Pour une fonction async, on utilise resolves await expect(web3mail.init()).resolves.not.toThrow(); }, MAX_EXPECTED_WEB2_SERVICES_TIME @@ -168,7 +167,9 @@ describe('IExecWeb3mail()', () => { describe('With allowExperimentalNetworks: true', () => { it('should resolve the configuration', async () => { - const web3mail = new IExecWeb3mail(experimentalNetworkSigner); + const web3mail = new IExecWeb3mail(experimentalNetworkSigner, { + allowExperimentalNetworks: true, + }); await expect(web3mail.init()).resolves.toBeUndefined(); expect(web3mail).toBeInstanceOf(IExecWeb3mail); }); @@ -238,7 +239,7 @@ describe('IExecWeb3mail()', () => { const chainConfig = getChainDefaultConfig(chainId, { allowExperimentalNetworks: true, }); - expect(chainConfig?.dappAddress).toBeUndefined(); // ENS not supported on this network + expect(chainConfig.dappAddress).toBeUndefined(); // ENS not supported on this network const web3mail = new IExecWeb3mail( getWeb3Provider(Wallet.createRandom().privateKey, { diff --git a/tests/e2e/sendEmailCampaign.test.ts b/tests/e2e/sendEmailCampaign.test.ts index 52469363..0042fbae 100644 --- a/tests/e2e/sendEmailCampaign.test.ts +++ b/tests/e2e/sendEmailCampaign.test.ts @@ -225,7 +225,6 @@ describe('web3mail.sendEmailCampaign()', () => { workerpoolAddressOrEns: prodWorkerpoolAddress, }); - // Market watcher indexes asynchronously — if tasks is empty, wait then reconstruct from deals let tasks = result.tasks; if (tasks.length === 0) { await sleep(5_000); @@ -411,7 +410,6 @@ describe('web3mail.sendEmailCampaign()', () => { workerpoolAddressOrEns: prodWorkerpoolAddress, }); - // Market watcher indexes asynchronously — if tasks is empty, wait then reconstruct from deals let tasks = result.tasks; if (tasks.length === 0) { await sleep(5_000); @@ -448,7 +446,6 @@ describe('web3mail.sendEmailCampaign()', () => { it( 'should handle multiple protected data per task correctly', async () => { - // 3 contacts / maxProtectedDataPerTask:2 = 2 tasks → 2 × workerpoolprice await ensureSufficientStake( consumerIExecInstance, prodWorkerpoolPublicPrice * 2 @@ -476,7 +473,6 @@ describe('web3mail.sendEmailCampaign()', () => { workerpoolAddressOrEns: prodWorkerpoolAddress, }); - // Market watcher indexes asynchronously — if tasks is empty, wait then reconstruct from deals let tasks = result.tasks; if (tasks.length === 0) { await sleep(5_000); @@ -500,7 +496,6 @@ describe('web3mail.sendEmailCampaign()', () => { ) ); } - // 3 contacts / maxProtectedDataPerTask:2 = 2 tasks across 1+ deals expect(tasks.length).toBeGreaterThanOrEqual(1); tasks.forEach((task) => { expect(task.taskId).toBeDefined(); From e4f089331df1b7146426f747914cfcd7ef01667e Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Wed, 29 Apr 2026 12:00:41 +0200 Subject: [PATCH 07/13] chore: update iexec deps --- package-lock.json | 65 ++++++++++++++++++++++++++++++++++++++++++++--- package.json | 2 +- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 477a3d41..cc99be79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "buffer": "^6.0.3", "ethers": "^6.15.0", "graphql-request": "^6.1.0", - "iexec": "^8.24.0", + "iexec": "^9.0.0", "kubo-rpc-client": "^5.4.1", "yup": "^1.1.1" }, @@ -822,6 +822,63 @@ "yup": "^1.0.2" } }, + "node_modules/@iexec/dataprotector/node_modules/iexec": { + "version": "8.24.0", + "resolved": "https://registry.npmjs.org/iexec/-/iexec-8.24.0.tgz", + "integrity": "sha512-XMi+kZlRHPB5prubA7PQvhEmKxENN/5P0+gfe96eKKUWZSb3qllzi14btRE/MEmUXwsQok9kpIOq9IajUY8VQQ==", + "license": "Apache-2.0", + "dependencies": { + "@multiformats/multiaddr": "^13.0.1", + "@types/bn.js": "^5.2.0", + "bn.js": "^5.2.2", + "buffer": "^6.0.3", + "commander": "^13.1.0", + "debug": "^4.4.3", + "ethers": "^6.13.5", + "fs-extra": "^11.3.0", + "graphql-request": "^7.3.5", + "inquirer": "^13.1.0", + "is-docker": "^3.0.0", + "jszip": "^3.10.1", + "kubo-rpc-client": "^5.3.0", + "multiformats": "^13.4.2", + "node-forge": "^1.3.2", + "ora": "^9.0.0", + "prettyjson": "^1.2.5", + "query-string": "^9.1.1", + "rlc-faucet-contract": "^1.0.10", + "semver": "^7.7.3", + "update-check": "^1.5.4", + "yup": "^1.6.1" + }, + "bin": { + "iexec": "dist/esm/cli/cmd/iexec.js" + } + }, + "node_modules/@iexec/dataprotector/node_modules/iexec/node_modules/@multiformats/multiaddr": { + "version": "13.0.1", + "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.1.tgz", + "integrity": "sha512-XToN915cnfr6Lr9EdGWakGJbPT0ghpg/850HvdC+zFX8XvpLZElwa8synCiwa8TuvKNnny6m8j8NVBNCxhIO3g==", + "license": "Apache-2.0 OR MIT", + "dependencies": { + "@chainsafe/is-ip": "^2.0.1", + "multiformats": "^13.0.0", + "uint8-varint": "^2.0.1", + "uint8arrays": "^5.0.0" + } + }, + "node_modules/@iexec/dataprotector/node_modules/iexec/node_modules/graphql-request": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-7.4.0.tgz", + "integrity": "sha512-xfr+zFb/QYbs4l4ty0dltqiXIp07U6sl+tOKAb0t50/EnQek6CVVBLjETXi+FghElytvgaAWtIOt3EV7zLzIAQ==", + "license": "MIT", + "dependencies": { + "@graphql-typed-document-node/core": "^3.2.0" + }, + "peerDependencies": { + "graphql": "14 - 16" + } + }, "node_modules/@inquirer/ansi": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.3.tgz", @@ -5543,9 +5600,9 @@ "license": "BSD-3-Clause" }, "node_modules/iexec": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/iexec/-/iexec-8.24.0.tgz", - "integrity": "sha512-XMi+kZlRHPB5prubA7PQvhEmKxENN/5P0+gfe96eKKUWZSb3qllzi14btRE/MEmUXwsQok9kpIOq9IajUY8VQQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/iexec/-/iexec-9.0.0.tgz", + "integrity": "sha512-6Rh8GvxBUxuElBFUatn55RG0PdoQBLxYW73hB1LrBA4kycea526qJK5ut7Qi2xF2K5xXXYLwH8QdeioYEC6SuQ==", "license": "Apache-2.0", "dependencies": { "@multiformats/multiaddr": "^13.0.1", diff --git a/package.json b/package.json index 85e93c81..21a95b46 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "buffer": "^6.0.3", "ethers": "^6.15.0", "graphql-request": "^6.1.0", - "iexec": "^8.24.0", + "iexec": "^9.0.0", "kubo-rpc-client": "^5.4.1", "yup": "^1.1.1" }, From a5e69ca6927714237b2609fb141cd6e5b2e65fc8 Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Wed, 29 Apr 2026 14:47:38 +0200 Subject: [PATCH 08/13] chore(deps): bump iexec to 9.x - Upgrade iexec in dapp and deployment-dapp; refresh lockfiles - Drop ENS support in types/validators; use Address for dapp and whitelist - Remove redundant getSignerFromPrivateKey options (providers) - Update unit and e2e tests for new types and iexec behavior --- dapp/package-lock.json | 8 ++--- dapp/package.json | 2 +- deployment-dapp/package-lock.json | 8 ++--- deployment-dapp/package.json | 2 +- src/utils/getWeb3Provider.ts | 1 - src/utils/resolveDappAddressFromCompass.ts | 4 +-- src/utils/validators.ts | 11 ------ src/web3mail/IExecWeb3mail.ts | 39 ++++++++++---------- src/web3mail/fetchMyContacts.ts | 4 +-- src/web3mail/fetchUserContacts.ts | 42 +++++++++++----------- src/web3mail/internalTypes.ts | 4 +-- src/web3mail/prepareEmailCampaign.ts | 22 ++++++------ src/web3mail/sendEmail.ts | 35 +++++++++--------- src/web3mail/sendEmailCampaign.ts | 16 ++++----- src/web3mail/types.ts | 20 +++++------ tests/e2e/constructor.test.ts | 26 +++++++------- tests/e2e/fetchUserContacts.test.ts | 4 +-- tests/e2e/sendEmail.test.ts | 24 ++++++------- tests/e2e/sendEmailCampaign.test.ts | 31 ++++++++-------- tests/unit/fetchMyContacts.test.ts | 12 +++---- tests/unit/sendEmail.test.ts | 4 +-- tests/unit/utils/validators.test.ts | 41 +++++++++------------ 22 files changed, 163 insertions(+), 197 deletions(-) diff --git a/dapp/package-lock.json b/dapp/package-lock.json index 3c9a2977..a02cfd57 100644 --- a/dapp/package-lock.json +++ b/dapp/package-lock.json @@ -25,7 +25,7 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-import": "^2.28.1", "eslint-plugin-sonarjs": "^0.21.0", - "iexec": "^8.24.0", + "iexec": "^9.0.0", "jest": "^29.7.0", "prettier": "^2.8.8" }, @@ -4480,9 +4480,9 @@ "license": "BSD-3-Clause" }, "node_modules/iexec": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/iexec/-/iexec-8.24.0.tgz", - "integrity": "sha512-XMi+kZlRHPB5prubA7PQvhEmKxENN/5P0+gfe96eKKUWZSb3qllzi14btRE/MEmUXwsQok9kpIOq9IajUY8VQQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/iexec/-/iexec-9.0.0.tgz", + "integrity": "sha512-6Rh8GvxBUxuElBFUatn55RG0PdoQBLxYW73hB1LrBA4kycea526qJK5ut7Qi2xF2K5xXXYLwH8QdeioYEC6SuQ==", "dev": true, "license": "Apache-2.0", "dependencies": { diff --git a/dapp/package.json b/dapp/package.json index 9e801116..c75c4979 100644 --- a/dapp/package.json +++ b/dapp/package.json @@ -36,7 +36,7 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-import": "^2.28.1", "eslint-plugin-sonarjs": "^0.21.0", - "iexec": "^8.24.0", + "iexec": "^9.0.0", "jest": "^29.7.0", "prettier": "^2.8.8" } diff --git a/deployment-dapp/package-lock.json b/deployment-dapp/package-lock.json index ef0b132b..31eb2c14 100644 --- a/deployment-dapp/package-lock.json +++ b/deployment-dapp/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { - "iexec": "^8.24.0", + "iexec": "^9.0.0", "typescript": "^5.0.4", "yup": "^1.2.0" }, @@ -3605,9 +3605,9 @@ "license": "BSD-3-Clause" }, "node_modules/iexec": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/iexec/-/iexec-8.24.0.tgz", - "integrity": "sha512-XMi+kZlRHPB5prubA7PQvhEmKxENN/5P0+gfe96eKKUWZSb3qllzi14btRE/MEmUXwsQok9kpIOq9IajUY8VQQ==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/iexec/-/iexec-9.0.0.tgz", + "integrity": "sha512-6Rh8GvxBUxuElBFUatn55RG0PdoQBLxYW73hB1LrBA4kycea526qJK5ut7Qi2xF2K5xXXYLwH8QdeioYEC6SuQ==", "license": "Apache-2.0", "dependencies": { "@multiformats/multiaddr": "^13.0.1", diff --git a/deployment-dapp/package.json b/deployment-dapp/package.json index 49fc4f1a..e978f7da 100644 --- a/deployment-dapp/package.json +++ b/deployment-dapp/package.json @@ -18,7 +18,7 @@ "author": "", "license": "ISC", "dependencies": { - "iexec": "^8.24.0", + "iexec": "^9.0.0", "typescript": "^5.0.4", "yup": "^1.2.0" }, diff --git a/src/utils/getWeb3Provider.ts b/src/utils/getWeb3Provider.ts index 0c133b26..7c7abddc 100644 --- a/src/utils/getWeb3Provider.ts +++ b/src/utils/getWeb3Provider.ts @@ -10,6 +10,5 @@ export const getWeb3Provider = ( : 'arbitrum-sepolia-testnet'; return getSignerFromPrivateKey(chainHost, privateKey, { allowExperimentalNetworks: options?.allowExperimentalNetworks, - providers: {}, }); }; diff --git a/src/utils/resolveDappAddressFromCompass.ts b/src/utils/resolveDappAddressFromCompass.ts index 61c6e3b5..c766999d 100644 --- a/src/utils/resolveDappAddressFromCompass.ts +++ b/src/utils/resolveDappAddressFromCompass.ts @@ -1,10 +1,10 @@ import { CompassCallError } from 'iexec/errors'; -import { AddressOrENS } from '../web3mail/types.js'; +import { Address } from '../web3mail/types.js'; export async function resolveDappAddressFromCompass( compassUrl: string, chainId: number -): Promise { +): Promise
{ if (!compassUrl) { return undefined; } diff --git a/src/utils/validators.ts b/src/utils/validators.ts index 24d4edb2..0cc0c2ea 100644 --- a/src/utils/validators.ts +++ b/src/utils/validators.ts @@ -18,17 +18,6 @@ export const throwIfMissing = (): never => { const isUndefined = (value: unknown) => value === undefined; const isAddressTest = (value: string) => isAddress(value); -export const isEnsTest = (value: string) => - value.endsWith('.eth') && value.length > 6; - -export const addressOrEnsSchema = () => - string() - .transform((value: string) => value?.toLowerCase() || value) - .test( - 'is-address-or-ens', - '${path} should be an ethereum address or a ENS name', - (value) => isUndefined(value) || isAddressTest(value) || isEnsTest(value) - ); export const addressSchema = () => string() diff --git a/src/web3mail/IExecWeb3mail.ts b/src/web3mail/IExecWeb3mail.ts index dfd81f8f..3d562320 100644 --- a/src/web3mail/IExecWeb3mail.ts +++ b/src/web3mail/IExecWeb3mail.ts @@ -11,7 +11,7 @@ import { Contact, FetchUserContactsParams, SendEmailParams, - AddressOrENS, + Address, Web3MailConfigOptions, SendEmailResponse, Web3SignerProvider, @@ -34,8 +34,8 @@ type EthersCompatibleProvider = | string; interface Web3mailResolvedConfig { - dappAddressOrENS: AddressOrENS; - dappWhitelistAddress: AddressOrENS; + dappAddress: Address; + dappWhitelistAddress: Address; graphQLClient: GraphQLClient; ipfsNode: string; ipfsGateway: string; @@ -45,9 +45,9 @@ interface Web3mailResolvedConfig { } export class IExecWeb3mail { - private dappAddressOrENS!: AddressOrENS; + private dappAddress!: Address; - private dappWhitelistAddress!: AddressOrENS; + private dappWhitelistAddress!: Address; private graphQLClient!: GraphQLClient; @@ -78,7 +78,7 @@ export class IExecWeb3mail { async init(): Promise { if (!this.initPromise) { this.initPromise = this.resolveConfig().then((config) => { - this.dappAddressOrENS = config.dappAddressOrENS; + this.dappAddress = config.dappAddress; this.dappWhitelistAddress = config.dappWhitelistAddress; this.graphQLClient = config.graphQLClient; this.ipfsNode = config.ipfsNode; @@ -99,7 +99,7 @@ export class IExecWeb3mail { ...args, iexec: this.iexec, graphQLClient: this.graphQLClient, - dappAddressOrENS: this.dappAddressOrENS, + dappAddress: this.dappAddress, dappWhitelistAddress: this.dappWhitelistAddress, }); } @@ -111,7 +111,7 @@ export class IExecWeb3mail { ...args, iexec: this.iexec, graphQLClient: this.graphQLClient, - dappAddressOrENS: this.dappAddressOrENS, + dappAddress: this.dappAddress, dappWhitelistAddress: this.dappWhitelistAddress, }); } @@ -121,12 +121,11 @@ export class IExecWeb3mail { await isValidProvider(this.iexec); return sendEmail({ ...args, - workerpoolAddressOrEns: - args.workerpoolAddressOrEns || this.defaultWorkerpool, + workerpoolAddress: args.workerpoolAddress || this.defaultWorkerpool, iexec: this.iexec, ipfsNode: this.ipfsNode, ipfsGateway: this.ipfsGateway, - dappAddressOrENS: this.dappAddressOrENS, + dappAddress: this.dappAddress, dappWhitelistAddress: this.dappWhitelistAddress, graphQLClient: this.graphQLClient, }); @@ -136,16 +135,15 @@ export class IExecWeb3mail { args: PrepareEmailCampaignParams ): Promise { await this.init(); - await isValidProvider(this.iexec); + return prepareEmailCampaign({ ...args, - workerpoolAddressOrEns: - args.workerpoolAddressOrEns || this.defaultWorkerpool, + workerpoolAddress: args.workerpoolAddress || this.defaultWorkerpool, iexec: this.iexec, dataProtector: this.dataProtector, ipfsNode: this.ipfsNode, ipfsGateway: this.ipfsGateway, - dappAddressOrENS: this.dappAddressOrENS, + dappAddress: this.dappAddress, }); } @@ -156,8 +154,7 @@ export class IExecWeb3mail { await isValidProvider(this.iexec); return sendEmailCampaign({ ...args, - workerpoolAddressOrEns: - args.workerpoolAddressOrEns || this.defaultWorkerpool, + workerpoolAddress: args.workerpoolAddress || this.defaultWorkerpool, dataProtector: this.dataProtector, }); } @@ -189,8 +186,8 @@ export class IExecWeb3mail { const subgraphUrl = this.options?.dataProtectorSubgraph || chainDefaultConfig?.dataProtectorSubgraph; - const dappAddressOrENS = - this.options?.dappAddressOrENS || + const dappAddress = + this.options?.dappAddress || chainDefaultConfig?.dappAddress || (await resolveDappAddressFromCompass( await iexec.config.resolveCompassURL(), @@ -205,7 +202,7 @@ export class IExecWeb3mail { const missing = []; if (!subgraphUrl) missing.push('dataProtectorSubgraph'); - if (!dappAddressOrENS) missing.push('dappAddress'); + if (!dappAddress) missing.push('dappAddress'); if (!dappWhitelistAddress) missing.push('whitelistSmartContract'); if (!ipfsGateway) missing.push('ipfsGateway'); if (!defaultWorkerpool) missing.push('prodWorkerpoolAddress'); @@ -237,7 +234,7 @@ export class IExecWeb3mail { }); return { - dappAddressOrENS, + dappAddress, dappWhitelistAddress: dappWhitelistAddress.toLowerCase(), defaultWorkerpool, graphQLClient, diff --git a/src/web3mail/fetchMyContacts.ts b/src/web3mail/fetchMyContacts.ts index 4c062314..68cc6e3a 100644 --- a/src/web3mail/fetchMyContacts.ts +++ b/src/web3mail/fetchMyContacts.ts @@ -13,7 +13,7 @@ export type FetchMyContacts = typeof fetchMyContacts; export const fetchMyContacts = async ({ graphQLClient = throwIfMissing(), iexec = throwIfMissing(), - dappAddressOrENS = throwIfMissing(), + dappAddress = throwIfMissing(), dappWhitelistAddress = throwIfMissing(), isUserStrict = false, bulkOnly = false, @@ -31,7 +31,7 @@ export const fetchMyContacts = async ({ return fetchUserContacts({ iexec, graphQLClient, - dappAddressOrENS, + dappAddress, dappWhitelistAddress, userAddress, isUserStrict: vIsUserStrict, diff --git a/src/web3mail/fetchUserContacts.ts b/src/web3mail/fetchUserContacts.ts index 5471ba5f..c934fdfe 100644 --- a/src/web3mail/fetchUserContacts.ts +++ b/src/web3mail/fetchUserContacts.ts @@ -6,7 +6,6 @@ import { handleIfProtocolError, WorkflowError } from '../utils/errors.js'; import { autoPaginateRequest } from '../utils/paginate.js'; import { getValidContact } from '../utils/subgraphQuery.js'; import { - addressOrEnsSchema, addressSchema, booleanSchema, throwIfMissing, @@ -22,7 +21,7 @@ import { export const fetchUserContacts = async ({ graphQLClient = throwIfMissing(), iexec = throwIfMissing(), - dappAddressOrENS = throwIfMissing(), + dappAddress = throwIfMissing(), dappWhitelistAddress = throwIfMissing(), userAddress, isUserStrict = false, @@ -32,29 +31,29 @@ export const fetchUserContacts = async ({ DappAddressConsumer & DappWhitelistAddressConsumer & FetchUserContactsParams): Promise => { - const vDappAddressOrENS = addressOrEnsSchema() - .required() - .label('dappAddressOrENS') - .validateSync(dappAddressOrENS); - const vDappWhitelistAddress = addressSchema() - .required() - .label('dappWhitelistAddress') - .validateSync(dappWhitelistAddress); - const vUserAddress = addressOrEnsSchema() - .required() - .label('userAddress') - .validateSync(userAddress); - const vIsUserStrict = booleanSchema() - .label('isUserStrict') - .validateSync(isUserStrict); - const vBulkOnly = booleanSchema().label('bulkOnly').validateSync(bulkOnly); - try { + const address = addressSchema() + .required() + .label('dappAddress') + .validateSync(dappAddress); + const vDappWhitelistAddress = addressSchema() + .required() + .label('dappWhitelistAddress') + .validateSync(dappWhitelistAddress); + const vUserAddress = addressSchema() + .required() + .label('userAddress') + .validateSync(userAddress); + const vIsUserStrict = booleanSchema() + .label('isUserStrict') + .validateSync(isUserStrict); + const vBulkOnly = booleanSchema().label('bulkOnly').validateSync(bulkOnly); + const [dappOrders, whitelistOrders] = await Promise.all([ fetchAllOrdersByApp({ iexec, userAddress: vUserAddress, - appAddress: vDappAddressOrENS, + appAddress: address, isUserStrict: vIsUserStrict, bulkOnly: vBulkOnly, }), @@ -72,8 +71,7 @@ export const fetchUserContacts = async ({ orders.forEach((order) => { if ( - order.order.apprestrict.toLowerCase() === - vDappAddressOrENS.toLowerCase() || + order.order.apprestrict.toLowerCase() === address.toLowerCase() || order.order.apprestrict.toLowerCase() === vDappWhitelistAddress.toLowerCase() ) { diff --git a/src/web3mail/internalTypes.ts b/src/web3mail/internalTypes.ts index a68a98b6..74bdf12c 100644 --- a/src/web3mail/internalTypes.ts +++ b/src/web3mail/internalTypes.ts @@ -1,6 +1,6 @@ import { IExec } from 'iexec'; import { IExecDataProtectorCore } from '@iexec/dataprotector'; -import { AddressOrENS } from './types.js'; +import { Address } from './types.js'; import { GraphQLClient } from 'graphql-request'; export type ProtectedDataQuery = { @@ -13,7 +13,7 @@ export type GraphQLResponse = { }; export type DappAddressConsumer = { - dappAddressOrENS: AddressOrENS; + dappAddress: Address; }; export type IpfsNodeConfigConsumer = { diff --git a/src/web3mail/prepareEmailCampaign.ts b/src/web3mail/prepareEmailCampaign.ts index 6f40d6cd..0f74853a 100644 --- a/src/web3mail/prepareEmailCampaign.ts +++ b/src/web3mail/prepareEmailCampaign.ts @@ -7,7 +7,7 @@ import { import { handleIfProtocolError, WorkflowError } from '../utils/errors.js'; import * as ipfs from '../utils/ipfs-service.js'; import { - addressOrEnsSchema, + addressSchema, contentTypeSchema, emailContentSchema, emailSubjectSchema, @@ -33,8 +33,8 @@ export type PrepareEmailCampaign = typeof prepareEmailCampaign; export const prepareEmailCampaign = async ({ iexec = throwIfMissing(), dataProtector = throwIfMissing(), - workerpoolAddressOrEns, - dappAddressOrENS, + workerpoolAddress, + dappAddress, ipfsNode, ipfsGateway, senderName, @@ -53,9 +53,9 @@ export const prepareEmailCampaign = async ({ DataProtectorConsumer & PrepareEmailCampaignParams): Promise => { try { - const vWorkerpoolAddressOrEns = addressOrEnsSchema() - .label('WorkerpoolAddressOrEns') - .validateSync(workerpoolAddressOrEns); + const vWorkerpoolAddress = addressSchema() + .label('workerpoolAddress') + .validateSync(workerpoolAddress); const vSenderName = senderNameSchema() .label('senderName') @@ -77,10 +77,10 @@ export const prepareEmailCampaign = async ({ const vLabel = labelSchema().label('label').validateSync(label); - const vDappAddressOrENS = addressOrEnsSchema() + const vDappAddress = addressSchema() .required() - .label('dappAddressOrENS') - .validateSync(dappAddressOrENS); + .label('dappAddress') + .validateSync(dappAddress); const vAppMaxPrice = positiveNumberSchema() .label('appMaxPrice') @@ -139,10 +139,10 @@ export const prepareEmailCampaign = async ({ // TODO: end factor this const { bulkRequest: campaignRequest } = await dataProtector.prepareBulkRequest({ - app: vDappAddressOrENS, + app: vDappAddress, appMaxPrice: vAppMaxPrice, workerpoolMaxPrice: vWorkerpoolMaxPrice, - workerpool: vWorkerpoolAddressOrEns, + workerpool: vWorkerpoolAddress, args: vLabel, inputFiles: [], secrets, diff --git a/src/web3mail/sendEmail.ts b/src/web3mail/sendEmail.ts index 7f8262b6..f2e0ec59 100644 --- a/src/web3mail/sendEmail.ts +++ b/src/web3mail/sendEmail.ts @@ -11,7 +11,6 @@ import { generateSecureUniqueId } from '../utils/generateUniqueId.js'; import * as ipfs from '../utils/ipfs-service.js'; import { checkProtectedDataValidity } from '../utils/subgraphQuery.js'; import { - addressOrEnsSchema, addressSchema, contentTypeSchema, emailContentSchema, @@ -37,8 +36,8 @@ export type SendEmail = typeof sendEmail; export const sendEmail = async ({ graphQLClient = throwIfMissing(), iexec = throwIfMissing(), - workerpoolAddressOrEns, - dappAddressOrENS, + workerpoolAddress, + dappAddress, dappWhitelistAddress, ipfsNode, ipfsGateway, @@ -58,7 +57,7 @@ export const sendEmail = async ({ IpfsNodeConfigConsumer & IpfsGatewayConfigConsumer & SendEmailParams): Promise => { - const vDatasetAddress = addressOrEnsSchema() + const vDatasetAddress = addressSchema() .required() .label('protectedData') .validateSync(protectedData); @@ -84,15 +83,15 @@ export const sendEmail = async ({ const vLabel = labelSchema().label('label').validateSync(label); - const vWorkerpoolAddressOrEns = addressOrEnsSchema() + const vWorkerpoolAddress = addressSchema() .required() - .label('WorkerpoolAddressOrEns') - .validateSync(workerpoolAddressOrEns); + .label('workerpoolAddress') + .validateSync(workerpoolAddress); - const vDappAddressOrENS = addressOrEnsSchema() + const vDappAddress = addressSchema() .required() - .label('dappAddressOrENS') - .validateSync(dappAddressOrENS); + .label('dappAddress') + .validateSync(dappAddress); const vDappWhitelistAddress = addressSchema() .required() @@ -129,9 +128,9 @@ export const sendEmail = async ({ // Fetch app order first to determine TEE framework const apporder = await iexec.orderbook .fetchAppOrderbook({ - app: dappAddressOrENS, + app: vDappAddress, minTag: ['tee'], - workerpool: workerpoolAddressOrEns, + workerpool: vWorkerpoolAddress, }) .then((appOrderbook) => { const desiredPriceAppOrderbook = appOrderbook.orders.filter( @@ -152,7 +151,7 @@ export const sendEmail = async ({ iexec.orderbook .fetchDatasetOrderbook({ dataset: vDatasetAddress, - app: dappAddressOrENS, + app: vDappAddress, requester: requesterAddress, }) .then((datasetOrderbook) => { @@ -179,8 +178,8 @@ export const sendEmail = async ({ Promise.all([ // for app iexec.orderbook.fetchWorkerpoolOrderbook({ - workerpool: workerpoolAddressOrEns, - app: vDappAddressOrENS, + workerpool: vWorkerpoolAddress, + app: vDappAddress, dataset: vDatasetAddress, requester: requesterAddress, minTag: workerpoolMinTag, @@ -188,7 +187,7 @@ export const sendEmail = async ({ }), // for app whitelist iexec.orderbook.fetchWorkerpoolOrderbook({ - workerpool: workerpoolAddressOrEns, + workerpool: vWorkerpoolAddress, app: vDappWhitelistAddress, dataset: vDatasetAddress, requester: requesterAddress, @@ -264,14 +263,14 @@ export const sendEmail = async ({ ); const requestorderToSign = await iexec.order.createRequestorder({ - app: vDappAddressOrENS, + app: vDappAddress, category: workerpoolorder.category, dataset: vDatasetAddress, datasetmaxprice: datasetorder.datasetprice, appmaxprice: apporder.appprice, workerpoolmaxprice: workerpoolorder.workerpoolprice, tag: ['tee'], - workerpool: vWorkerpoolAddressOrEns, + workerpool: vWorkerpoolAddress, callback: CALLBACK_WEB3MAIL, params: { iexec_secrets: { diff --git a/src/web3mail/sendEmailCampaign.ts b/src/web3mail/sendEmailCampaign.ts index c3672597..4559e660 100644 --- a/src/web3mail/sendEmailCampaign.ts +++ b/src/web3mail/sendEmailCampaign.ts @@ -2,7 +2,7 @@ import { NULL_ADDRESS } from 'iexec/utils'; import { ValidationError } from 'yup'; import { handleIfProtocolError, WorkflowError } from '../utils/errors.js'; import { - addressOrEnsSchema, + addressSchema, campaignRequestSchema, throwIfMissing, } from '../utils/validators.js'; @@ -17,7 +17,7 @@ export type SendEmailCampaign = typeof sendEmailCampaign; export const sendEmailCampaign = async ({ dataProtector = throwIfMissing(), - workerpoolAddressOrEns = throwIfMissing(), + workerpoolAddress = throwIfMissing(), campaignRequest, }: DataProtectorConsumer & SendEmailCampaignParams): Promise => { @@ -26,18 +26,18 @@ export const sendEmailCampaign = async ({ .label('campaignRequest') .validateSync(campaignRequest) as CampaignRequest; - const vWorkerpoolAddressOrEns = addressOrEnsSchema() + const vWorkerpoolAddress = addressSchema() .required() - .label('workerpoolAddressOrEns') - .validateSync(workerpoolAddressOrEns); + .label('workerpoolAddress') + .validateSync(workerpoolAddress); if ( vCampaignRequest.workerpool !== NULL_ADDRESS && vCampaignRequest.workerpool.toLowerCase() !== - vWorkerpoolAddressOrEns.toLowerCase() + vWorkerpoolAddress.toLowerCase() ) { throw new ValidationError( - "workerpoolAddressOrEns doesn't match campaignRequest workerpool" + "workerpoolAddress doesn't match campaignRequest workerpool" ); } @@ -45,7 +45,7 @@ export const sendEmailCampaign = async ({ // Process the prepared bulk request const processBulkRequestResponse = await dataProtector.processBulkRequest({ bulkRequest: vCampaignRequest, - workerpool: vWorkerpoolAddressOrEns, + workerpool: vWorkerpoolAddress, waitForResult: false, }); diff --git a/src/web3mail/types.ts b/src/web3mail/types.ts index bbaf1260..4b8d0fda 100644 --- a/src/web3mail/types.ts +++ b/src/web3mail/types.ts @@ -1,12 +1,8 @@ -import { EnhancedWallet } from 'iexec'; +import type { AbstractSigner } from 'ethers'; import { IExecConfigOptions } from 'iexec/IExecConfig'; import type { BulkRequest } from '@iexec/dataprotector'; -export type Web3SignerProvider = EnhancedWallet; - -export type ENS = string; - -export type AddressOrENS = Address | ENS; +export type Web3SignerProvider = AbstractSigner; export type Address = string; @@ -59,7 +55,7 @@ export type SendEmailParams = { contentType?: string; senderName?: string; label?: string; - workerpoolAddressOrEns?: AddressOrENS; + workerpoolAddress?: Address; dataMaxPrice?: number; appMaxPrice?: number; workerpoolMaxPrice?: number; @@ -99,10 +95,10 @@ export type SendEmailResponse = { */ export type Web3MailConfigOptions = { /** - * The Ethereum contract address or ENS (Ethereum Name Service) for the email sender dapp. + * Ethereum contract address for the email sender dapp. * If not provided, the default web3mail address will be used. */ - dappAddressOrENS?: AddressOrENS; + dappAddress?: Address; /** * The Ethereum contract address for the whitelist. @@ -155,7 +151,7 @@ export type PrepareEmailCampaignParams = { emailContent: string; contentType?: string; label?: string; - workerpoolAddressOrEns?: AddressOrENS; + workerpoolAddress?: Address; dataMaxPrice?: number; appMaxPrice?: number; workerpoolMaxPrice?: number; @@ -176,9 +172,9 @@ export type SendEmailCampaignParams = { */ campaignRequest: CampaignRequest; /** - * Workerpool address or ENS to use for processing + * Workerpool contract address used for processing */ - workerpoolAddressOrEns?: AddressOrENS; + workerpoolAddress?: string; }; export type SendEmailCampaignResponse = { diff --git a/tests/e2e/constructor.test.ts b/tests/e2e/constructor.test.ts index 733e1982..e2e585b6 100644 --- a/tests/e2e/constructor.test.ts +++ b/tests/e2e/constructor.test.ts @@ -79,7 +79,7 @@ describe('IExecWeb3mail()', () => { const wallet = Wallet.createRandom(); const customSubgraphUrl = 'https://example.com/custom-subgraph'; const customIpfsGateway = 'https://example.com/ipfs_gateway'; - const customDapp = 'web3mailstg.apps.iexec.eth'; + const customDapp = '0x1111111111111111111111111111111111111111'; const customIpfsNode = 'https://example.com/node'; const smsURL = 'https://custom-sms-url.com'; const iexecGatewayURL = 'https://custom-market-api-url.com'; @@ -95,7 +95,7 @@ describe('IExecWeb3mail()', () => { ipfsNode: customIpfsNode, ipfsGateway: customIpfsGateway, dataProtectorSubgraph: customSubgraphUrl, - dappAddressOrENS: customDapp, + dappAddress: customDapp, dappWhitelistAddress: customDappWhitelistAddress, } ); @@ -103,20 +103,18 @@ describe('IExecWeb3mail()', () => { const graphQLClient = web3mail['graphQLClient']; const ipfsNode = web3mail['ipfsNode']; const ipfsGateway = web3mail['ipfsGateway']; - const dappAddressOrENS = web3mail['dappAddressOrENS']; + const dappAddress = web3mail['dappAddress']; const iexec = web3mail['iexec']; const whitelistAddress = web3mail['dappWhitelistAddress']; expect(graphQLClient['url']).toBe(customSubgraphUrl); expect(ipfsNode).toStrictEqual(customIpfsNode); expect(ipfsGateway).toStrictEqual(customIpfsGateway); - expect(dappAddressOrENS).toStrictEqual(customDapp); + expect(dappAddress).toStrictEqual(customDapp); expect(whitelistAddress).toStrictEqual( customDappWhitelistAddress.toLowerCase() ); - expect(await iexec.config.resolveSmsURL({ teeFramework: 'tdx' })).toBe( - smsURL - ); + expect(await iexec.config.resolveSmsURL()).toBe(smsURL); expect(await iexec.config.resolveIexecGatewayURL()).toBe(iexecGatewayURL); }); @@ -189,7 +187,7 @@ describe('IExecWeb3mail()', () => { arbitrumSepoliaConfig!.ipfsGateway ); expect(web3mail['ipfsNode']).toBe(arbitrumSepoliaConfig!.ipfsUploadUrl); - expect(web3mail['dappAddressOrENS']).toMatch(/^0x[a-fA-F0-9]{40}$/); // resolved from Compass + expect(web3mail['dappAddress']).toMatch(/^0x[a-fA-F0-9]{40}$/); // resolved from Compass expect(web3mail['dappWhitelistAddress']).toBe( arbitrumSepoliaConfig!.whitelistSmartContract.toLowerCase() ); @@ -203,17 +201,17 @@ describe('IExecWeb3mail()', () => { it('should allow custom configuration override for Arbitrum Sepolia', async () => { const customIpfsGateway = 'https://custom-arbitrum-ipfs.com'; - const customDappAddress = 'custom.arbitrum.app.eth'; + const customDappAddress = '0x2222222222222222222222222222222222222222'; const web3mail = new IExecWeb3mail(experimentalNetworkSigner, { allowExperimentalNetworks: true, ipfsGateway: customIpfsGateway, - dappAddressOrENS: customDappAddress, + dappAddress: customDappAddress, }); await web3mail.init(); expect(web3mail['ipfsGateway']).toBe(customIpfsGateway); - expect(web3mail['dappAddressOrENS']).toBe(customDappAddress); + expect(web3mail['dappAddress']).toBe(customDappAddress); const arbitrumSepoliaConfig = getChainDefaultConfig(421614, { allowExperimentalNetworks: true, @@ -250,9 +248,9 @@ describe('IExecWeb3mail()', () => { ); await web3mail.init(); - const dappAddressOrENS = web3mail['dappAddressOrENS']; - expect(typeof dappAddressOrENS).toBe('string'); - expect(dappAddressOrENS).toMatch(/^0x[a-fA-F0-9]{40}$/); + const dappAddress = web3mail['dappAddress']; + expect(typeof dappAddress).toBe('string'); + expect(dappAddress).toMatch(/^0x[a-fA-F0-9]{40}$/); }); }); }); diff --git a/tests/e2e/fetchUserContacts.test.ts b/tests/e2e/fetchUserContacts.test.ts index 59f3342e..ab2fcd73 100644 --- a/tests/e2e/fetchUserContacts.test.ts +++ b/tests/e2e/fetchUserContacts.test.ts @@ -54,7 +54,7 @@ describe('web3mail.fetchMyContacts()', () => { await web3mail.init(); // eslint-disable-next-line @typescript-eslint/dot-notation - const authorizedApp = web3mail['dappAddressOrENS']; + const authorizedApp = web3mail['dappAddress']; await dataProtector.grantAccess({ authorizedApp: authorizedApp, @@ -223,7 +223,7 @@ describe('web3mail.fetchMyContacts()', () => { await dataProtector.grantAccess({ authorizedApp: dappAddress, protectedData: protectedData1.address, - authorizedUser: ethProvider.address, + authorizedUser: await ethProvider.getAddress(), }); const options = { diff --git a/tests/e2e/sendEmail.test.ts b/tests/e2e/sendEmail.test.ts index 6af2c458..cafada01 100644 --- a/tests/e2e/sendEmail.test.ts +++ b/tests/e2e/sendEmail.test.ts @@ -135,7 +135,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, - workerpoolAddressOrEns: paidWorkerpoolAddress, + workerpoolAddress: paidWorkerpoolAddress, }) .catch((e) => (error = e)); expect(error).toBeDefined(); @@ -157,7 +157,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, - workerpoolAddressOrEns: paidWorkerpoolAddress, + workerpoolAddress: paidWorkerpoolAddress, workerpoolMaxPrice: prodWorkerpoolPublicPrice, }) .catch((e) => (error = e)); @@ -182,7 +182,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, - workerpoolAddressOrEns: paidWorkerpoolAddress, + workerpoolAddress: paidWorkerpoolAddress, workerpoolMaxPrice: prodWorkerpoolPublicPrice, }); expect(sendEmailResponse).toStrictEqual({ @@ -203,7 +203,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: invalidProtectedData.address, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }) ).rejects.toThrow( new Error( @@ -229,7 +229,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: protectedData.address, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }) ).rejects.toThrow( new WorkflowError({ @@ -264,7 +264,7 @@ describe('web3mail.sendEmail()', () => { try { await invalidWeb3mail.sendEmail({ protectedData: validProtectedData.address, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, emailSubject: 'My email subject', emailContent: 'My email content', }); @@ -289,7 +289,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); expect(sendEmailResponse).toStrictEqual({ taskId: expect.any(String), @@ -322,7 +322,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: protectedDataForWhitelist.address, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); expect(sendEmailResponse).toStrictEqual({ taskId: expect.any(String), @@ -341,7 +341,7 @@ describe('web3mail.sendEmail()', () => { '

Test html

test paragraph

', protectedData: validProtectedData.address, contentType: 'text/html', - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); expect(sendEmailResponse).toStrictEqual({ taskId: expect.any(String), @@ -359,7 +359,7 @@ describe('web3mail.sendEmail()', () => { emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, senderName: 'Product Team', - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); expect(sendEmailResponse).toStrictEqual({ taskId: expect.any(String), @@ -381,7 +381,7 @@ describe('web3mail.sendEmail()', () => { emailContent: LARGE_CONTENT, protectedData: validProtectedData.address, senderName: 'Product Team', - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); expect(sendEmailResponse).toStrictEqual({ taskId: expect.any(String), @@ -398,7 +398,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, label: 'ID1234678', }); expect(sendEmailResponse).toStrictEqual({ diff --git a/tests/e2e/sendEmailCampaign.test.ts b/tests/e2e/sendEmailCampaign.test.ts index 0042fbae..3469e74d 100644 --- a/tests/e2e/sendEmailCampaign.test.ts +++ b/tests/e2e/sendEmailCampaign.test.ts @@ -151,7 +151,7 @@ describe('web3mail.sendEmailCampaign()', () => { await web3mail .sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: workerpoolToUse, + workerpoolAddress: workerpoolToUse, }) .catch((e) => (error = e)); @@ -216,13 +216,13 @@ describe('web3mail.sendEmailCampaign()', () => { grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, workerpoolMaxPrice: prodWorkerpoolPublicPrice, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); // Send campaign const result = await web3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); let tasks = result.tasks; @@ -267,7 +267,7 @@ describe('web3mail.sendEmailCampaign()', () => { await web3mail .sendEmailCampaign({ campaignRequest: undefined as any, // Testing missing campaignRequest - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }) .catch((e) => (error = e)); @@ -283,7 +283,7 @@ describe('web3mail.sendEmailCampaign()', () => { ); it( - 'should throw an error if workerpoolAddressOrEns is invalid', + 'should throw an error if workerpoolAddress is invalid', async () => { // Prepare campaign first const contacts: Contact[] = await web3mail.fetchMyContacts({ @@ -296,24 +296,23 @@ describe('web3mail.sendEmailCampaign()', () => { emailContent: 'Bulk test message', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); let error: Error; await web3mail .sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: 'invalid-address', + workerpoolAddress: 'invalid-address', }) .catch((e) => (error = e)); expect(error).toBeDefined(); - // Invalid address throws ValidationError from addressOrEnsSchema validation + // Invalid address throws ValidationError from addressSchema validation const isValidationError = error instanceof ValidationError; expect(isValidationError).toBe(true); expect( - error.message === - 'workerpoolAddressOrEns should be an ethereum address or a ENS name' + error.message === 'workerpoolAddress should be an ethereum address' ).toBe(true); }, 2 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_WEB2_SERVICES_TIME @@ -352,12 +351,12 @@ describe('web3mail.sendEmailCampaign()', () => { emailContent: 'Bulk test message', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); await invalidWeb3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); } catch (err) { error = err as Web3mailWorkflowError; @@ -397,7 +396,7 @@ describe('web3mail.sendEmailCampaign()', () => { senderName: 'Integration Test', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, workerpoolMaxPrice: prodWorkerpoolPublicPrice, }); // Verify campaign request was created @@ -407,7 +406,7 @@ describe('web3mail.sendEmailCampaign()', () => { // Send the campaign const result = await web3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); let tasks = result.tasks; @@ -464,13 +463,13 @@ describe('web3mail.sendEmailCampaign()', () => { emailContent: 'Bulk test message', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 2, // Process 2 emails per task - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, workerpoolMaxPrice: prodWorkerpoolPublicPrice, }); // Send campaign const result = await web3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: prodWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); let tasks = result.tasks; diff --git a/tests/unit/fetchMyContacts.test.ts b/tests/unit/fetchMyContacts.test.ts index 4d2fc77a..a058fa00 100644 --- a/tests/unit/fetchMyContacts.test.ts +++ b/tests/unit/fetchMyContacts.test.ts @@ -80,7 +80,7 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: mockDappAddress, + dappAddress: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, }); const userAddress = (await iexec.wallet.getAddress()).toLowerCase(); @@ -141,7 +141,7 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: mockDappAddress, + dappAddress: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, isUserStrict: true, }); @@ -216,7 +216,7 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: mockDappAddress, + dappAddress: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, }); @@ -266,7 +266,7 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: mockDappAddress, + dappAddress: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, bulkOnly: false, }); @@ -330,7 +330,7 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: mockDappAddress, + dappAddress: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, bulkOnly: true, }); @@ -394,7 +394,7 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: mockDappAddress, + dappAddress: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, isUserStrict: true, bulkOnly: true, diff --git a/tests/unit/sendEmail.test.ts b/tests/unit/sendEmail.test.ts index 76ff85d1..27a99031 100644 --- a/tests/unit/sendEmail.test.ts +++ b/tests/unit/sendEmail.test.ts @@ -184,8 +184,8 @@ describe('sendEmail', () => { iexec, ipfsGateway: defaultConfig!.ipfsGateway, ipfsNode: defaultConfig!.ipfsUploadUrl, - workerpoolAddressOrEns: defaultConfig!.prodWorkerpoolAddress, - dappAddressOrENS: mockDappAddress, + workerpoolAddress: defaultConfig!.prodWorkerpoolAddress, + dappAddress: mockDappAddress, dappWhitelistAddress: defaultConfig!.whitelistSmartContract.toLowerCase(), emailSubject: 'e2e mail object for test', diff --git a/tests/unit/utils/validators.test.ts b/tests/unit/utils/validators.test.ts index b3dd9f2f..8d524c17 100644 --- a/tests/unit/utils/validators.test.ts +++ b/tests/unit/utils/validators.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from '@jest/globals'; import { ValidationError } from 'yup'; import { - addressOrEnsSchema, + addressSchema, emailSubjectSchema, emailContentSchema, senderNameSchema, @@ -12,50 +12,41 @@ import { getRandomAddress, getRequiredFieldMessage } from '../../test-utils.js'; const CANNOT_BE_NULL_ERROR = new ValidationError('this cannot be null'); const IS_REQUIRED_ERROR = new ValidationError(getRequiredFieldMessage()); -describe('addressOrEnsSchema()', () => { +describe('addressSchema()', () => { describe('validateSync()', () => { const address = getRandomAddress(); const EXPECTED_ERROR = new ValidationError( - 'this should be an ethereum address or a ENS name' + 'this should be an ethereum address' ); it('transforms to lowercase', () => { - const res = addressOrEnsSchema().validateSync(address); + const res = addressSchema().validateSync(address); expect(res).toBe(address.toLowerCase()); }); it('accepts undefined (is not required by default)', () => { - const res = addressOrEnsSchema().validateSync(undefined); + const res = addressSchema().validateSync(undefined); expect(res).toBeUndefined(); }); it('accepts case insensitive ethereum address', () => { - expect(addressOrEnsSchema().validateSync(address)).toBeDefined(); - expect( - addressOrEnsSchema().validateSync(address.toUpperCase()) - ).toBeDefined(); - expect( - addressOrEnsSchema().validateSync(address.toLowerCase()) - ).toBeDefined(); + expect(addressSchema().validateSync(address)).toBeDefined(); + expect(addressSchema().validateSync(address.toUpperCase())).toBeDefined(); + expect(addressSchema().validateSync(address.toLowerCase())).toBeDefined(); }); - it('accepts string ending with ".eth"', () => { - expect(addressOrEnsSchema().validateSync('FOO.eth')).toBe('foo.eth'); + it('does not accept string ending with ".eth"', () => { + expect(() => addressSchema().validateSync('FOO.eth')).toThrow( + EXPECTED_ERROR + ); }); it('does not accept null', () => { - expect(() => addressOrEnsSchema().validateSync(null)).toThrow( + expect(() => addressSchema().validateSync(null)).toThrow( CANNOT_BE_NULL_ERROR ); }); it('does not accept empty string', () => { - expect(() => addressOrEnsSchema().validateSync('')).toThrow( - EXPECTED_ERROR - ); + expect(() => addressSchema().validateSync('')).toThrow(EXPECTED_ERROR); }); it('does not accept non address string', () => { - expect(() => addressOrEnsSchema().validateSync('test')).toThrow( - EXPECTED_ERROR - ); - }); - it('does not accept ENS name with label < 3 char', () => { - expect(() => addressOrEnsSchema().validateSync('ab.eth')).toThrow( + expect(() => addressSchema().validateSync('test')).toThrow( EXPECTED_ERROR ); }); @@ -64,7 +55,7 @@ describe('addressOrEnsSchema()', () => { describe('validateSync()', () => { it('does not accept undefined', () => { expect(() => - addressOrEnsSchema().required().validateSync(undefined) + addressSchema().required().validateSync(undefined) ).toThrow(IS_REQUIRED_ERROR); }); }); From 1290fead3cc25b5bc5a442b25125d6e3b590ac03 Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Wed, 29 Apr 2026 15:00:07 +0200 Subject: [PATCH 09/13] chore(sdk): bump dataprotector SDK --- package-lock.json | 67 ++++------------------------------------------- package.json | 2 +- 2 files changed, 6 insertions(+), 63 deletions(-) diff --git a/package-lock.json b/package-lock.json index cc99be79..cbfaf64e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.9.0", "license": "Apache-2.0", "dependencies": { - "@iexec/dataprotector": "^2.0.0-beta.24", + "@iexec/dataprotector": "^2.0.0-beta.27", "buffer": "^6.0.3", "ethers": "^6.15.0", "graphql-request": "^6.1.0", @@ -800,9 +800,9 @@ "license": "BSD-3-Clause" }, "node_modules/@iexec/dataprotector": { - "version": "2.0.0-beta.25", - "resolved": "https://registry.npmjs.org/@iexec/dataprotector/-/dataprotector-2.0.0-beta.25.tgz", - "integrity": "sha512-8+sCDCteegOv7PWnhL8O+DIP6DtgrAp2kSh0jihMJ/7MhRg9unbOdq9T//DY9sN5/zsU3SsTQv//x0hrXqtkDA==", + "version": "2.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@iexec/dataprotector/-/dataprotector-2.0.0-beta.27.tgz", + "integrity": "sha512-t5+e/XBCq7L7ztMkRb2I5WYqo6pWuXKqMeilQb+SGPPgk7rFTIMhex6d/MJl7peTTAvWKeLU+WNVPY5aXjc+BA==", "license": "Apache-2.0", "dependencies": { "@ethersproject/bytes": "^5.7.0", @@ -814,7 +814,7 @@ "debug": "^4.3.4", "ethers": "^6.13.2", "graphql-request": "^6.0.0", - "iexec": "^8.23.0", + "iexec": "^9.0.0", "jszip": "^3.7.1", "kubo-rpc-client": "^5.4.1", "magic-bytes.js": "^1.0.15", @@ -822,63 +822,6 @@ "yup": "^1.0.2" } }, - "node_modules/@iexec/dataprotector/node_modules/iexec": { - "version": "8.24.0", - "resolved": "https://registry.npmjs.org/iexec/-/iexec-8.24.0.tgz", - "integrity": "sha512-XMi+kZlRHPB5prubA7PQvhEmKxENN/5P0+gfe96eKKUWZSb3qllzi14btRE/MEmUXwsQok9kpIOq9IajUY8VQQ==", - "license": "Apache-2.0", - "dependencies": { - "@multiformats/multiaddr": "^13.0.1", - "@types/bn.js": "^5.2.0", - "bn.js": "^5.2.2", - "buffer": "^6.0.3", - "commander": "^13.1.0", - "debug": "^4.4.3", - "ethers": "^6.13.5", - "fs-extra": "^11.3.0", - "graphql-request": "^7.3.5", - "inquirer": "^13.1.0", - "is-docker": "^3.0.0", - "jszip": "^3.10.1", - "kubo-rpc-client": "^5.3.0", - "multiformats": "^13.4.2", - "node-forge": "^1.3.2", - "ora": "^9.0.0", - "prettyjson": "^1.2.5", - "query-string": "^9.1.1", - "rlc-faucet-contract": "^1.0.10", - "semver": "^7.7.3", - "update-check": "^1.5.4", - "yup": "^1.6.1" - }, - "bin": { - "iexec": "dist/esm/cli/cmd/iexec.js" - } - }, - "node_modules/@iexec/dataprotector/node_modules/iexec/node_modules/@multiformats/multiaddr": { - "version": "13.0.1", - "resolved": "https://registry.npmjs.org/@multiformats/multiaddr/-/multiaddr-13.0.1.tgz", - "integrity": "sha512-XToN915cnfr6Lr9EdGWakGJbPT0ghpg/850HvdC+zFX8XvpLZElwa8synCiwa8TuvKNnny6m8j8NVBNCxhIO3g==", - "license": "Apache-2.0 OR MIT", - "dependencies": { - "@chainsafe/is-ip": "^2.0.1", - "multiformats": "^13.0.0", - "uint8-varint": "^2.0.1", - "uint8arrays": "^5.0.0" - } - }, - "node_modules/@iexec/dataprotector/node_modules/iexec/node_modules/graphql-request": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/graphql-request/-/graphql-request-7.4.0.tgz", - "integrity": "sha512-xfr+zFb/QYbs4l4ty0dltqiXIp07U6sl+tOKAb0t50/EnQek6CVVBLjETXi+FghElytvgaAWtIOt3EV7zLzIAQ==", - "license": "MIT", - "dependencies": { - "@graphql-typed-document-node/core": "^3.2.0" - }, - "peerDependencies": { - "graphql": "14 - 16" - } - }, "node_modules/@inquirer/ansi": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-2.0.3.tgz", diff --git a/package.json b/package.json index 21a95b46..b66a24c3 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ }, "homepage": "https://github.com/iExecBlockchainComputing/web3mail-sdk#readme", "dependencies": { - "@iexec/dataprotector": "^2.0.0-beta.24", + "@iexec/dataprotector": "^2.0.0-beta.27", "buffer": "^6.0.3", "ethers": "^6.15.0", "graphql-request": "^6.1.0", From db3ae0f9847b72105fe15729d1698ed6ec49b0cf Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:24:31 +0200 Subject: [PATCH 10/13] fix!: drop Bellecour/SGX defaults, require explicit chain and provider - getWeb3Provider(privateKey, host, options); remove implicit Arbitrum Sepolia host - IExecWeb3mail: required ethProvider; chain id without DEFAULT_CHAIN_ID fallback - Remove ENS deployment helpers (configureEnsName, resolveName); getIExec requires host - Tests: TEST_CHAIN test timings/subgraph; drop ens.resolveName mocks; dapp bulk log --- README.md | 14 +++--- dapp/src/executeTask.js | 1 + demo/node-ts/index.ts | 5 ++- demo/node/index.js | 5 ++- deployment-dapp/package.json | 1 - deployment-dapp/src/configureEnsNameScript.ts | 22 --------- .../src/singleFunction/configureEnsName.ts | 11 ----- .../src/singleFunction/resolveName.ts | 8 ---- deployment-dapp/src/utils/utils.ts | 6 +-- src/config/config.ts | 26 ++++++++++- src/utils/getChainId.ts | 13 +++++- src/utils/getWeb3Provider.ts | 20 ++++++--- src/web3mail/IExecWeb3mail.ts | 4 +- tests/e2e/constructor.test.ts | 18 +++----- tests/e2e/fetchMyContacts.test.ts | 4 +- tests/e2e/fetchUserContacts.test.ts | 11 ++--- tests/e2e/prepareEmailCampaign.test.ts | 3 +- tests/e2e/sendEmail.test.ts | 8 +--- tests/e2e/sendEmailCampaign.test.ts | 3 +- tests/test-utils.ts | 45 ++++++++++--------- tests/unit/constructor.test.ts | 7 +-- tests/unit/fetchMyContacts.test.ts | 37 +-------------- tests/unit/sendEmail.test.ts | 7 +-- tests/utils/mockAllForSendEmail.ts | 5 --- 24 files changed, 114 insertions(+), 170 deletions(-) delete mode 100644 deployment-dapp/src/configureEnsNameScript.ts delete mode 100644 deployment-dapp/src/singleFunction/configureEnsName.ts delete mode 100644 deployment-dapp/src/singleFunction/resolveName.ts diff --git a/README.md b/README.md index f0f838e8..be0e3a35 100644 --- a/README.md +++ b/README.md @@ -41,21 +41,21 @@ yarn add @iexec/web3mail ### Browser ```ts -import { IExecWeb3mail } from "@iexec/web3mail"; +import { IExecWeb3mail } from '@iexec/web3mail'; -const web3Provider = window.ethereum; -const web3mail = new IExecWeb3mail(web3Provider); +const web3Provider = window.ethereum; +const web3mail = new IExecWeb3mail(web3Provider); ``` ### NodeJS ```ts -import { IExecWeb3mail, getWeb3Provider } from "@iexec/web3mail"; +import { IExecWeb3mail, getWeb3Provider } from '@iexec/web3mail'; -const { PRIVATE_KEY } = process.env; +const { PRIVATE_KEY } = process.env; -const web3Provider = getWeb3Provider(PRIVATE_KEY); -const web3mail = new IExecWeb3mail(web3Provider); +const web3Provider = getWeb3Provider(PRIVATE_KEY, 421614); +const web3mail = new IExecWeb3mail(web3Provider); ``` ## Documentation diff --git a/dapp/src/executeTask.js b/dapp/src/executeTask.js index c49bab4d..f1029e0a 100644 --- a/dapp/src/executeTask.js +++ b/dapp/src/executeTask.js @@ -100,6 +100,7 @@ async function processProtectedData({ result.success = false; result.error = e.message; } + console.log(`Protected data ${index} processed:`, result); return result; } diff --git a/demo/node-ts/index.ts b/demo/node-ts/index.ts index b96fe592..89e4b586 100644 --- a/demo/node-ts/index.ts +++ b/demo/node-ts/index.ts @@ -2,7 +2,10 @@ import { IExecWeb3mail, getWeb3Provider } from '@iexec/web3mail'; import { Wallet } from 'ethers'; const test = async () => { - const ethProvider = getWeb3Provider(Wallet.createRandom().privateKey); + const ethProvider = getWeb3Provider( + Wallet.createRandom().privateKey, + 421614 + ); const web3mail = new IExecWeb3mail(ethProvider); diff --git a/demo/node/index.js b/demo/node/index.js index b96fe592..89e4b586 100644 --- a/demo/node/index.js +++ b/demo/node/index.js @@ -2,7 +2,10 @@ import { IExecWeb3mail, getWeb3Provider } from '@iexec/web3mail'; import { Wallet } from 'ethers'; const test = async () => { - const ethProvider = getWeb3Provider(Wallet.createRandom().privateKey); + const ethProvider = getWeb3Provider( + Wallet.createRandom().privateKey, + 421614 + ); const web3mail = new IExecWeb3mail(ethProvider); diff --git a/deployment-dapp/package.json b/deployment-dapp/package.json index e978f7da..338c522b 100644 --- a/deployment-dapp/package.json +++ b/deployment-dapp/package.json @@ -9,7 +9,6 @@ "push-dapp-secret": "tsx ./src/pushSecretScript.ts", "publish-sell-order": "tsx ./src/publishSellOrderScript.ts", "revoke-sell-order": "tsx ./src/revokeSellOrderScript.ts", - "configure-ens": "tsx ./src/configureEnsNameScript.ts", "lint": "eslint .", "format": "prettier --write \"src/**/*.ts\"", "check-format": "prettier --check \"src/**/*.ts\"" diff --git a/deployment-dapp/src/configureEnsNameScript.ts b/deployment-dapp/src/configureEnsNameScript.ts deleted file mode 100644 index ff6217dd..00000000 --- a/deployment-dapp/src/configureEnsNameScript.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { configureEnsName } from './singleFunction/configureEnsName.js'; -import { getIExec, loadAppAddress } from './utils/utils.js'; - -const main = async () => { - const { RPC_URL, WALLET_PRIVATE_KEY, DAPP_ENS_NAME } = process.env; - - const appAddress = await loadAppAddress(); - - if (!WALLET_PRIVATE_KEY) - throw Error(`Failed to get privateKey from environment variables`); - - if (!DAPP_ENS_NAME) - throw Error(`Failed to get DAPP_ENS_NAME from environment variables`); - - const iexec = getIExec(WALLET_PRIVATE_KEY, RPC_URL); - await configureEnsName(iexec, appAddress, DAPP_ENS_NAME); -}; - -main().catch((e) => { - console.log(e); - process.exit(1); -}); diff --git a/deployment-dapp/src/singleFunction/configureEnsName.ts b/deployment-dapp/src/singleFunction/configureEnsName.ts deleted file mode 100644 index ffd748bf..00000000 --- a/deployment-dapp/src/singleFunction/configureEnsName.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { ENS, IExec } from 'iexec'; - -export const configureEnsName = async ( - iexec: IExec, - appAddress: string, - name: ENS -): Promise => { - console.log(`Configuring ENS ${name} for app ${appAddress}`); - const result = await iexec.ens.configureResolution(name, appAddress); - console.log(`Configured:\n${JSON.stringify(result, undefined, 2)}`); -}; diff --git a/deployment-dapp/src/singleFunction/resolveName.ts b/deployment-dapp/src/singleFunction/resolveName.ts deleted file mode 100644 index 07404a0a..00000000 --- a/deployment-dapp/src/singleFunction/resolveName.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { ENS, IExec } from 'iexec'; - -export const resolveName = async ( - iexec: IExec, - name: ENS -): Promise => { - return iexec.ens.resolveName(name); -}; diff --git a/deployment-dapp/src/utils/utils.ts b/deployment-dapp/src/utils/utils.ts index dbc14482..14b84bc8 100644 --- a/deployment-dapp/src/utils/utils.ts +++ b/deployment-dapp/src/utils/utils.ts @@ -1,12 +1,8 @@ import fs from 'fs/promises'; import { IExec, utils } from 'iexec'; -export const getIExec = ( - privateKey: string, - host: string = 'arbitrum-sepolia-testnet' -): IExec => { +export const getIExec = (privateKey: string, host: string): IExec => { const ethProvider = utils.getSignerFromPrivateKey(host, privateKey, { - providers: {}, allowExperimentalNetworks: true, }); return new IExec( diff --git a/src/config/config.ts b/src/config/config.ts index 7cc6cde6..a87e73e4 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -5,8 +5,6 @@ export const DEFAULT_CONTENT_TYPE = 'text/plain'; export const ANY_DATASET_ADDRESS = 'any'; export const CALLBACK_WEB3MAIL = '0x5f936db7ad6d29371808e42a87015595d90509ba'; -export const DEFAULT_CHAIN_ID = 421614; - interface ChainConfig { name: string; dappAddress?: string; @@ -57,3 +55,27 @@ export const getChainDefaultConfig = ( return config; }; + +/** + * When `ethProvider` is a string, it may be an RPC URL, a decimal chain id, or an + * iExec chain host name (see each chain's `name` in CHAIN_CONFIG). RPC URLs are not + * resolved here (returns undefined); JsonRpcProvider handles those. + */ +export function tryResolveChainIdFromProviderString( + hint: string +): number | undefined { + const trimmed = hint.trim(); + if (/^https?:\/\//i.test(trimmed)) { + return undefined; + } + if (/^\d+$/.test(trimmed)) { + return Number(trimmed); + } + const lower = trimmed.toLowerCase(); + for (const [id, cfg] of Object.entries(CHAIN_CONFIG)) { + if (cfg.name.toLowerCase() === lower) { + return Number(id); + } + } + return undefined; +} diff --git a/src/utils/getChainId.ts b/src/utils/getChainId.ts index 02338c18..0f7529e2 100644 --- a/src/utils/getChainId.ts +++ b/src/utils/getChainId.ts @@ -5,7 +5,7 @@ import { AbstractSigner, Eip1193Provider, } from 'ethers'; -import { DEFAULT_CHAIN_ID } from '../config/config.js'; +import { tryResolveChainIdFromProviderString } from '../config/config.js'; type EthersCompatibleProvider = | string @@ -18,6 +18,10 @@ export async function getChainIdFromProvider( ): Promise { try { if (typeof ethProvider === 'string') { + const resolved = tryResolveChainIdFromProviderString(ethProvider); + if (resolved !== undefined) { + return resolved; + } const provider = new JsonRpcProvider(ethProvider); const network = await provider.getNetwork(); return Number(network.chainId); @@ -38,6 +42,11 @@ export async function getChainIdFromProvider( } } catch (e) { console.warn('Failed to detect chainId:', e); + throw new Error( + `Failed to detect chainId from ethProvider: ${ + e instanceof Error ? e.message : String(e) + }` + ); } - return DEFAULT_CHAIN_ID; + throw new Error('Unsupported ethProvider for chain ID detection'); } diff --git a/src/utils/getWeb3Provider.ts b/src/utils/getWeb3Provider.ts index 7c7abddc..25ab55d9 100644 --- a/src/utils/getWeb3Provider.ts +++ b/src/utils/getWeb3Provider.ts @@ -1,14 +1,20 @@ import { getSignerFromPrivateKey } from 'iexec/utils'; import { Web3SignerProvider } from '../web3mail/types.js'; +/** + * Build an ethers signer for Node.js from a private key and an explicit chain. + * + * `host` must be provided (chain id or iExec chain host string). There is no default + * chain: omitting it used to fall back to Arbitrum Sepolia and could migrate users + * silently when defaults change. + * + * @see https://github.com/iExecBlockchainComputing/dataprotector-sdk/blob/dataprotector-v2.0.0-beta.27/packages/sdk/src/utils/getWeb3Provider.ts + */ export const getWeb3Provider = ( privateKey: string, - options: { allowExperimentalNetworks?: boolean; host?: number | string } = {} -): Web3SignerProvider => { - const chainHost = options?.host - ? `${options.host}` - : 'arbitrum-sepolia-testnet'; - return getSignerFromPrivateKey(chainHost, privateKey, { + host: number | string, + options: { allowExperimentalNetworks?: boolean } = {} +): Web3SignerProvider => + getSignerFromPrivateKey(`${host}`, privateKey, { allowExperimentalNetworks: options?.allowExperimentalNetworks, }); -}; diff --git a/src/web3mail/IExecWeb3mail.ts b/src/web3mail/IExecWeb3mail.ts index 3d562320..2b09cae3 100644 --- a/src/web3mail/IExecWeb3mail.ts +++ b/src/web3mail/IExecWeb3mail.ts @@ -68,10 +68,10 @@ export class IExecWeb3mail { private options: Web3MailConfigOptions; constructor( - ethProvider?: EthersCompatibleProvider, + ethProvider: EthersCompatibleProvider, options?: Web3MailConfigOptions ) { - this.ethProvider = ethProvider || 'arbitrum-sepolia-testnet'; + this.ethProvider = ethProvider; this.options = options || {}; } diff --git a/tests/e2e/constructor.test.ts b/tests/e2e/constructor.test.ts index e2e585b6..17718031 100644 --- a/tests/e2e/constructor.test.ts +++ b/tests/e2e/constructor.test.ts @@ -8,10 +8,7 @@ import { getTestWeb3SignerProvider, MAX_EXPECTED_WEB2_SERVICES_TIME, } from '../test-utils.js'; -import { - DEFAULT_CHAIN_ID, - getChainDefaultConfig, -} from '../../src/config/config.js'; +import { getChainDefaultConfig } from '../../src/config/config.js'; describe('IExecWeb3mail()', () => { it('instantiates with a valid ethProvider', async () => { @@ -30,7 +27,7 @@ describe('IExecWeb3mail()', () => { ); await web3mail.init(); const ipfsGateway = web3mail['ipfsGateway']; - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); + const defaultConfig = getChainDefaultConfig(421614); expect(defaultConfig).not.toBeNull(); expect(ipfsGateway).toStrictEqual(defaultConfig!.ipfsGateway); }); @@ -56,7 +53,7 @@ describe('IExecWeb3mail()', () => { ); await web3mail.init(); const graphQLClient = web3mail['graphQLClient']; - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); + const defaultConfig = getChainDefaultConfig(421614); expect(defaultConfig).not.toBeNull(); expect(graphQLClient['url']).toBe(defaultConfig!.dataProtectorSubgraph); }); @@ -145,10 +142,8 @@ describe('IExecWeb3mail()', () => { describe('When instantiating SDK with an experimental network', () => { const experimentalNetworkSigner = getWeb3Provider( Wallet.createRandom().privateKey, - { - host: 421614, - allowExperimentalNetworks: true, - } + 421614, + { allowExperimentalNetworks: true } ); describe('Without allowExperimentalNetworks', () => { @@ -240,8 +235,7 @@ describe('IExecWeb3mail()', () => { expect(chainConfig.dappAddress).toBeUndefined(); // ENS not supported on this network const web3mail = new IExecWeb3mail( - getWeb3Provider(Wallet.createRandom().privateKey, { - host: chainId, + getWeb3Provider(Wallet.createRandom().privateKey, chainId, { allowExperimentalNetworks: true, }), { allowExperimentalNetworks: true } diff --git a/tests/e2e/fetchMyContacts.test.ts b/tests/e2e/fetchMyContacts.test.ts index 0ca25daf..9c5eb463 100644 --- a/tests/e2e/fetchMyContacts.test.ts +++ b/tests/e2e/fetchMyContacts.test.ts @@ -8,8 +8,8 @@ import { NULL_ADDRESS } from 'iexec/utils'; import { IExecWeb3mail } from '../../src/index.js'; import { MAX_EXPECTED_BLOCKTIME, - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME, MAX_EXPECTED_WEB2_SERVICES_TIME, + TEST_CHAIN, deployRandomDataset, getTestConfig, getTestDappAddress, @@ -191,7 +191,7 @@ describe('web3mail.fetchMyContacts()', () => { expect(noBulkContact).toBeUndefined(); }, MAX_EXPECTED_BLOCKTIME + - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME + + TEST_CHAIN.maxExpectedSubgraphIndexingTime + MAX_EXPECTED_WEB2_SERVICES_TIME ); diff --git a/tests/e2e/fetchUserContacts.test.ts b/tests/e2e/fetchUserContacts.test.ts index ab2fcd73..e1f797ba 100644 --- a/tests/e2e/fetchUserContacts.test.ts +++ b/tests/e2e/fetchUserContacts.test.ts @@ -4,15 +4,12 @@ import { } from '@iexec/dataprotector'; import { beforeAll, describe, expect, it } from '@jest/globals'; import { HDNodeWallet, Wallet } from 'ethers'; -import { - DEFAULT_CHAIN_ID, - getChainDefaultConfig, -} from '../../src/config/config.js'; +import { getChainDefaultConfig } from '../../src/config/config.js'; import { IExecWeb3mail, WorkflowError } from '../../src/index.js'; import { MAX_EXPECTED_BLOCKTIME, - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME, MAX_EXPECTED_WEB2_SERVICES_TIME, + TEST_CHAIN, getTestConfig, getTestDappAddress, setBalance, @@ -97,7 +94,7 @@ describe('web3mail.fetchMyContacts()', () => { async () => { const userWithAccess = Wallet.createRandom().address; const authorizedWhitelist = - getChainDefaultConfig(DEFAULT_CHAIN_ID)!.whitelistSmartContract; + getChainDefaultConfig(421614)!.whitelistSmartContract; await dataProtector.grantAccess({ authorizedApp: dappAddress, @@ -308,7 +305,7 @@ describe('web3mail.fetchMyContacts()', () => { expect(noBulkContact).toBeUndefined(); }, MAX_EXPECTED_BLOCKTIME + - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME + + TEST_CHAIN.maxExpectedSubgraphIndexingTime + MAX_EXPECTED_WEB2_SERVICES_TIME ); diff --git a/tests/e2e/prepareEmailCampaign.test.ts b/tests/e2e/prepareEmailCampaign.test.ts index 3680e457..16305228 100644 --- a/tests/e2e/prepareEmailCampaign.test.ts +++ b/tests/e2e/prepareEmailCampaign.test.ts @@ -8,7 +8,6 @@ import { Contact, IExecWeb3mail } from '../../src/index.js'; import { MAX_EXPECTED_BLOCKTIME, MAX_EXPECTED_WEB2_SERVICES_TIME, - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME, TEST_CHAIN, createAndPublishAppOrders, getRandomWallet, @@ -95,7 +94,7 @@ describe('web3mail.prepareEmailCampaign()', () => { await waitSubgraphIndexing(); web3mail = new IExecWeb3mail(...getTestConfig(consumerWallet.privateKey)); - }, 3 * MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_SUBGRAPH_INDEXING_TIME + 10_000); + }, 3 * MAX_EXPECTED_BLOCKTIME + TEST_CHAIN.maxExpectedSubgraphIndexingTime + 10_000); it( 'should prepare an email campaignRequest', diff --git a/tests/e2e/sendEmail.test.ts b/tests/e2e/sendEmail.test.ts index cafada01..bc174931 100644 --- a/tests/e2e/sendEmail.test.ts +++ b/tests/e2e/sendEmail.test.ts @@ -23,10 +23,7 @@ import { } from '../test-utils.js'; import { IExec } from 'iexec'; import { NULL_ADDRESS } from 'iexec/utils'; -import { - DEFAULT_CHAIN_ID, - getChainDefaultConfig, -} from '../../src/config/config.js'; +import { getChainDefaultConfig } from '../../src/config/config.js'; describe('web3mail.sendEmail()', () => { let consumerWallet: HDNodeWallet; @@ -311,8 +308,7 @@ describe('web3mail.sendEmail()', () => { //grant access to whitelist await dataProtector.grantAccess({ - authorizedApp: - getChainDefaultConfig(DEFAULT_CHAIN_ID)?.whitelistSmartContract, //whitelist address + authorizedApp: getChainDefaultConfig(421614)?.whitelistSmartContract, //whitelist address protectedData: protectedDataForWhitelist.address, authorizedUser: consumerWallet.address, // consumer wallet numberOfAccess: 1000, diff --git a/tests/e2e/sendEmailCampaign.test.ts b/tests/e2e/sendEmailCampaign.test.ts index 3469e74d..02812df2 100644 --- a/tests/e2e/sendEmailCampaign.test.ts +++ b/tests/e2e/sendEmailCampaign.test.ts @@ -12,7 +12,6 @@ import { } from '../../src/index.js'; import { MAX_EXPECTED_BLOCKTIME, - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME, MAX_EXPECTED_WEB2_SERVICES_TIME, TEST_CHAIN, createAndPublishAppOrders, @@ -122,7 +121,7 @@ describe('web3mail.sendEmailCampaign()', () => { await waitSubgraphIndexing(); web3mail = new IExecWeb3mail(...getTestConfig(consumerWallet.privateKey)); - }, MAX_EXPECTED_BLOCKTIME + MAX_EXPECTED_SUBGRAPH_INDEXING_TIME); + }, MAX_EXPECTED_BLOCKTIME + TEST_CHAIN.maxExpectedSubgraphIndexingTime); describe('when using the default (not free) prod workerpool', () => { it( diff --git a/tests/test-utils.ts b/tests/test-utils.ts index 7be3db3f..b0581d26 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -16,12 +16,6 @@ import { import { IExec, utils } from 'iexec'; import { randomInt } from 'crypto'; -export const TEST_COMPASS_URL = 'http://127.0.0.1:8069'; - -const TEST_POOL_SIGNER = new Wallet( - '0x6a12f56d7686e85ab0f46eb3c19cb0c75bfabf8fb04e595654fc93ad652fa7bc' -); - export const TEST_CHAIN = { name: 'arbitrum-sepolia' as const, isNative: false, @@ -35,9 +29,11 @@ export const TEST_CHAIN = { smsURL: 'http://127.0.0.1:13350', resultProxyURL: 'http://127.0.0.1:13250', iexecGatewayURL: 'http://127.0.0.1:3050', - compassUrl: TEST_COMPASS_URL, + compassUrl: 'http://127.0.0.1:8069', prodWorkerpool: '0x2956f0cb779904795a5f30d3b3ea88b714c3123f', - prodWorkerpoolOwnerWallet: TEST_POOL_SIGNER, + prodWorkerpoolOwnerWallet: new Wallet( + '0x6a12f56d7686e85ab0f46eb3c19cb0c75bfabf8fb04e595654fc93ad652fa7bc' + ), appOwnerWallet: new Wallet( '0xa911b93e50f57c156da0b8bff2277d241bcdb9345221a3e246a99c6e7cedcde5' ), @@ -46,6 +42,20 @@ export const TEST_CHAIN = { }), hubAddress: '0xB2157BF2fAb286b2A4170E3491Ac39770111Da3E', defaultInitBalance: 1n * 10n ** 18n, + subgraphUrl: 'http://127.0.0.1:8000/subgraphs/name/DataProtector-v2', + /** + * [rlc-multichain](https://github.com/iExecBlockchainComputing/rlc-multichain/tree/v0.1.0) is an openzeppelin ERC20Upgradeable contract + * + * ERC20Upgradeable contract use a specific storage slot, which is: + * ``` + * // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff)) + * bytes32 private constant ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00; + * ``` + * sources: https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/v5.3.0/contracts/token/ERC20/ERC20Upgradeable.sol#L43-L44 + */ + erc20BalanceSlot: + '0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00' as const, + maxExpectedSubgraphIndexingTime: 10_000, }; const HUB_TOKEN_ABI = [ @@ -58,9 +68,6 @@ const HUB_TOKEN_ABI = [ }, ] as const; -const ERC20_BALANCE_SLOT = - '0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00'; - const anvilSetStorageAt = async ( contract: string, slot: string, @@ -91,7 +98,7 @@ const anvilSetNRlcTokenBalance = async ( const balanceSlot = keccak256( AbiCoder.defaultAbiCoder().encode( ['address', 'uint256'], - [address, ERC20_BALANCE_SLOT] + [address, TEST_CHAIN.erc20BalanceSlot] ) ); await anvilSetStorageAt( @@ -103,11 +110,6 @@ const anvilSetNRlcTokenBalance = async ( export const sleep = (ms: number) => new Promise((res) => setTimeout(res, ms)); -export const MAX_EXPECTED_SUBGRAPH_INDEXING_TIME = 10_000; - -const DATAPROTECTOR_SUBGRAPH_URL = - 'http://127.0.0.1:8000/subgraphs/name/DataProtector-v2'; - export const waitSubgraphIndexing = async ( timeoutMs = 60_000 ): Promise => { @@ -117,7 +119,7 @@ export const waitSubgraphIndexing = async ( const deadline = Date.now() + timeoutMs; while (Date.now() < deadline) { try { - const res = await fetch(DATAPROTECTOR_SUBGRAPH_URL, { + const res = await fetch(TEST_CHAIN.subgraphUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: '{ _meta { block { number } } }' }), @@ -171,9 +173,8 @@ export const getTestConfig = ( iexecOptions: getTestIExecOption(), ipfsGateway: 'http://127.0.0.1:8080', ipfsNode: 'http://127.0.0.1:5001', - dataProtectorSubgraph: - 'http://127.0.0.1:8000/subgraphs/name/DataProtector-v2', - compassUrl: TEST_COMPASS_URL, + dataProtectorSubgraph: TEST_CHAIN.subgraphUrl, + compassUrl: TEST_CHAIN.compassUrl, }; return [ethProvider, options]; }; @@ -181,7 +182,7 @@ export const getTestConfig = ( export const getTestDappAddress = async (): Promise => { const chainId = Number(TEST_CHAIN.chainId); const res = await fetch( - `${TEST_COMPASS_URL}/${chainId}/iapps/web3mail-tdx` + `${TEST_CHAIN.compassUrl}/${chainId}/iapps/web3mail-tdx` ).then((r) => r.json()); if (!res?.address) { throw new Error(`Could not resolve dapp address from test compass`); diff --git a/tests/unit/constructor.test.ts b/tests/unit/constructor.test.ts index 33dd34b8..20225edd 100644 --- a/tests/unit/constructor.test.ts +++ b/tests/unit/constructor.test.ts @@ -3,10 +3,11 @@ import { IExecWeb3mail } from '../../src/index.js'; describe('When instantiating SDK without a signer', () => { describe('When calling a write method', () => { it('should throw an error for unauthorized method', async () => { - // --- GIVEN - const web3mail = new IExecWeb3mail(); + // Host string gives chain config but iExec has no wallet signer + const web3mail = new IExecWeb3mail('421614', { + allowExperimentalNetworks: true, + }); - // --- WHEN/THEN await expect(web3mail.fetchMyContacts()).rejects.toThrow( 'Unauthorized method. Please log in with your wallet, you must set a valid provider with a signer.' ); diff --git a/tests/unit/fetchMyContacts.test.ts b/tests/unit/fetchMyContacts.test.ts index a058fa00..6ee66871 100644 --- a/tests/unit/fetchMyContacts.test.ts +++ b/tests/unit/fetchMyContacts.test.ts @@ -1,9 +1,6 @@ import { describe, expect, it, jest } from '@jest/globals'; import { Address } from 'iexec'; -import { - DEFAULT_CHAIN_ID, - getChainDefaultConfig, -} from '../../src/config/config.js'; +import { getChainDefaultConfig } from '../../src/config/config.js'; import { type FetchMyContacts } from '../../src/web3mail/fetchMyContacts.js'; import { getRandomAddress } from '../test-utils.js'; @@ -14,7 +11,7 @@ jest.unstable_mockModule('../../src/utils/subgraphQuery.js', () => ({ describe('fetchMyContacts', () => { let testedModule: any; let fetchMyContacts: FetchMyContacts; - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); + const defaultConfig = getChainDefaultConfig(421614); const mockDappAddress = getRandomAddress().toLowerCase(); beforeAll(async () => { @@ -65,11 +62,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -126,11 +118,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -200,11 +187,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -251,11 +233,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -315,11 +292,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -379,11 +351,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, diff --git a/tests/unit/sendEmail.test.ts b/tests/unit/sendEmail.test.ts index 27a99031..0e521331 100644 --- a/tests/unit/sendEmail.test.ts +++ b/tests/unit/sendEmail.test.ts @@ -3,10 +3,7 @@ import { ValidationError } from 'yup'; import { type SendEmail } from '../../src/web3mail/sendEmail.js'; import { getRandomAddress } from '../test-utils.js'; import { mockAllForSendEmail } from '../utils/mockAllForSendEmail.js'; -import { - DEFAULT_CHAIN_ID, - getChainDefaultConfig, -} from '../../src/config/config.js'; +import { getChainDefaultConfig } from '../../src/config/config.js'; jest.unstable_mockModule('../../src/utils/subgraphQuery.js', () => ({ checkProtectedDataValidity: jest.fn(), @@ -173,7 +170,7 @@ describe('sendEmail', () => { const userAddress = await iexec.wallet.getAddress(); - const defaultConfig = getChainDefaultConfig(DEFAULT_CHAIN_ID); + const defaultConfig = getChainDefaultConfig(421614); expect(defaultConfig).not.toBeNull(); // --- WHEN diff --git a/tests/utils/mockAllForSendEmail.ts b/tests/utils/mockAllForSendEmail.ts index 7fdcab0f..566ed03c 100644 --- a/tests/utils/mockAllForSendEmail.ts +++ b/tests/utils/mockAllForSendEmail.ts @@ -11,11 +11,6 @@ export function mockAllForSendEmail() { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: jest .fn<() => Promise<{ orders: any[] }>>() From b02a49bf0f379b9cbefdb7a0e3b02f7177b133d0 Mon Sep 17 00:00:00 2001 From: paypes <43441600+abbesBenayache@users.noreply.github.com> Date: Thu, 30 Apr 2026 08:24:31 +0200 Subject: [PATCH 11/13] fix!: drop Bellecour/SGX defaults, require explicit chain and provider - getWeb3Provider(privateKey, host, options); remove implicit Arbitrum Sepolia host - IExecWeb3mail: required ethProvider; chain id without DEFAULT_CHAIN_ID fallback - Remove ENS deployment helpers (configureEnsName, resolveName); getIExec requires host - Tests: TEST_CHAIN test timings/subgraph; drop ens.resolveName mocks; dapp bulk log --- tests/docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index fd8e1994..54614390 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -246,8 +246,7 @@ services: postgres_pass: password postgres_db: graphnode-db ipfs: ipfs:5001 - # Alias arbitrum-sepolia-fork as "bellecour" so the subgraph template (hardcoded network name) works - ethereum: 'bellecour:http://arbitrum-sepolia-fork:8545' + ethereum: 'arbitrum-sepolia:http://arbitrum-sepolia-fork:8545' GRAPH_ETHEREUM_GENESIS_BLOCK_NUMBER: $ARBITRUM_SEPOLIA_FORK_BLOCK depends_on: arbitrum-sepolia-fork: From 3ef4e91bc3e170785e4e38138a1ab29f2e35901e Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:07:01 +0200 Subject: [PATCH 12/13] test: fix subgraph deployer --- tests/docker-compose.yml | 10 ++++------ tests/test-utils.ts | 3 ++- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 54614390..3ae00c53 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -247,7 +247,7 @@ services: postgres_db: graphnode-db ipfs: ipfs:5001 ethereum: 'arbitrum-sepolia:http://arbitrum-sepolia-fork:8545' - GRAPH_ETHEREUM_GENESIS_BLOCK_NUMBER: $ARBITRUM_SEPOLIA_FORK_BLOCK + GRAPH_ETHEREUM_GENESIS_BLOCK_NUMBER: $ARBITRUM_SEPOLIA_INDEX_BLOCK depends_on: arbitrum-sepolia-fork: condition: service_healthy @@ -264,7 +264,7 @@ services: dataprotector-subgraph-deployer: platform: linux/amd64 - image: iexechub/dataprotector-subgraph-deployer:3.0.0 + image: iexechub/dataprotector-subgraph-deployer:3.1.1-main-4df5c2d restart: 'no' depends_on: graphnode: @@ -272,13 +272,11 @@ services: ipfs: condition: service_started environment: - ENV: prod START_BLOCK: $ARBITRUM_SEPOLIA_INDEX_BLOCK - DATAPROTECTOR_ADDRESS: '0x168eAF6C33a77E3caD9db892452f51a5D91df621' - APP_REGISTRY_ADDRESS: '0x9950D94FB074182eE93fF79a50CD698C4983281F' - DATASET_REGISTRY_ADDRESS: '0x07cc4E1Ea30dD02796795876509a3bfC5053128d' + NETWORK_NAME: arbitrum-sepolia GRAPHNODE_URL: http://graphnode:8020 IPFS_URL: http://ipfs:5001 + ENV: prod stack-ready: image: bash diff --git a/tests/test-utils.ts b/tests/test-utils.ts index b0581d26..cb52b527 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -42,7 +42,8 @@ export const TEST_CHAIN = { }), hubAddress: '0xB2157BF2fAb286b2A4170E3491Ac39770111Da3E', defaultInitBalance: 1n * 10n ** 18n, - subgraphUrl: 'http://127.0.0.1:8000/subgraphs/name/DataProtector-v2', + subgraphUrl: + 'http://127.0.0.1:8000/subgraphs/name/arbitrum-sepolia/dataprotector-v2', /** * [rlc-multichain](https://github.com/iExecBlockchainComputing/rlc-multichain/tree/v0.1.0) is an openzeppelin ERC20Upgradeable contract * From 1720c8ade6339bf73f2366fbfad48dedb7422675 Mon Sep 17 00:00:00 2001 From: Pierre Jeanjacquot <26487010+PierreJeanjacquot@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:40:22 +0200 Subject: [PATCH 13/13] chore: cleanup scripts --- tests/scripts/prepare-iexec.js | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 tests/scripts/prepare-iexec.js diff --git a/tests/scripts/prepare-iexec.js b/tests/scripts/prepare-iexec.js deleted file mode 100644 index 30c8b041..00000000 --- a/tests/scripts/prepare-iexec.js +++ /dev/null @@ -1,29 +0,0 @@ -import { readFile, writeFile } from 'fs/promises'; -import { resolve } from 'path'; - -const disableCheckImplementedOnChain = async () => { - const configModulePath = resolve( - 'node_modules/iexec/dist/esm/common/utils/config.js' - ); - - const configModule = await readFile(configModulePath, 'utf8'); - - const OG_CODE_SNIPPET = - 'export const checkImplementedOnChain = (chainId, featureName) => {'; - - const REPLACEMENT_CODE_SNIPPET = - 'export const checkImplementedOnChain = (chainId, featureName) => { return;'; - - if (!configModule.includes(REPLACEMENT_CODE_SNIPPET)) { - console.log('disabling checkImplementedOnChain implementation...'); - const patchedConfigModule = configModule.replace( - OG_CODE_SNIPPET, - REPLACEMENT_CODE_SNIPPET - ); - - await writeFile(configModulePath, patchedConfigModule, 'utf8'); - } -}; - -disableCheckImplementedOnChain(); -