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 fd62aa42..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 @@ -73,34 +71,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 +138,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/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/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/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/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/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-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..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\"" @@ -18,7 +17,7 @@ "author": "", "license": "ISC", "dependencies": { - "iexec": "^8.24.0", + "iexec": "^9.0.0", "typescript": "^5.0.4", "yup": "^1.2.0" }, 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/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/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/deployApp.ts b/deployment-dapp/src/singleFunction/deployApp.ts index 7e9e6aef..82db9899 100644 --- a/deployment-dapp/src/singleFunction/deployApp.ts +++ b/deployment-dapp/src/singleFunction/deployApp.ts @@ -12,46 +12,22 @@ 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; - 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/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 6e8e5331..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 = 'bellecour' -): IExec => { +export const getIExec = (privateKey: string, host: string): IExec => { const ethProvider = utils.getSignerFromPrivateKey(host, privateKey, { - providers: {}, allowExperimentalNetworks: true, }); return new IExec( diff --git a/package-lock.json b/package-lock.json index 477a3d41..cbfaf64e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,11 +9,11 @@ "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", - "iexec": "^8.24.0", + "iexec": "^9.0.0", "kubo-rpc-client": "^5.4.1", "yup": "^1.1.1" }, @@ -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", @@ -5543,9 +5543,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 88394d30..b66a24c3 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", @@ -46,11 +46,11 @@ }, "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", - "iexec": "^8.24.0", + "iexec": "^9.0.0", "kubo-rpc-client": "^5.4.1", "yup": "^1.1.1" }, diff --git a/src/config/config.ts b/src/config/config.ts index 4a8628b7..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 = 134; - interface ChainConfig { name: string; dappAddress?: string; @@ -19,16 +17,6 @@ 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 @@ -67,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 a722978d..25ab55d9 100644 --- a/src/utils/getWeb3Provider.ts +++ b/src/utils/getWeb3Provider.ts @@ -1,13 +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}` : 'bellecour'; - return getSignerFromPrivateKey(chainHost, privateKey, { + host: number | string, + options: { allowExperimentalNetworks?: boolean } = {} +): Web3SignerProvider => + getSignerFromPrivateKey(`${host}`, 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 7c70ba21..2b09cae3 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; @@ -68,17 +68,17 @@ export class IExecWeb3mail { private options: Web3MailConfigOptions; constructor( - ethProvider?: EthersCompatibleProvider, + ethProvider: EthersCompatibleProvider, options?: Web3MailConfigOptions ) { - this.ethProvider = ethProvider || 'bellecour'; + this.ethProvider = ethProvider; this.options = options || {}; } 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 9e7ceb03..c934fdfe 100644 --- a/src/web3mail/fetchUserContacts.ts +++ b/src/web3mail/fetchUserContacts.ts @@ -6,10 +6,8 @@ import { handleIfProtocolError, WorkflowError } from '../utils/errors.js'; import { autoPaginateRequest } from '../utils/paginate.js'; import { getValidContact } from '../utils/subgraphQuery.js'; import { - addressOrEnsSchema, addressSchema, booleanSchema, - isEnsTest, throwIfMissing, } from '../utils/validators.js'; import { Contact, FetchUserContactsParams } from './types.js'; @@ -23,7 +21,7 @@ import { export const fetchUserContacts = async ({ graphQLClient = throwIfMissing(), iexec = throwIfMissing(), - dappAddressOrENS = throwIfMissing(), + dappAddress = throwIfMissing(), dappWhitelistAddress = throwIfMissing(), userAddress, isUserStrict = false, @@ -33,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, }), @@ -70,14 +68,10 @@ export const fetchUserContacts = async ({ const orders = dappOrders.concat(whitelistOrders); const myContacts: Omit[] = []; - let web3DappResolvedAddress = vDappAddressOrENS; - if (isEnsTest(vDappAddressOrENS)) { - web3DappResolvedAddress = await iexec.ens.resolveName(vDappAddressOrENS); - } + orders.forEach((order) => { if ( - order.order.apprestrict.toLowerCase() === - web3DappResolvedAddress.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.models.ts b/src/web3mail/sendEmail.models.ts index be608b71..1a976199 100644 --- a/src/web3mail/sendEmail.models.ts +++ b/src/web3mail/sendEmail.models.ts @@ -1,88 +1,26 @@ -import { Address, BN } from 'iexec'; import { PublishedWorkerpoolorder } from 'iexec/IExecOrderbookModule'; -// import { VoucherInfo } from 'iexec/IExecVoucherModule'; - -// 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) + const [cheapestOrder] = workerpoolOrders + .slice() + .sort( + (order1, order2) => + order1.order.workerpoolprice - order2.order.workerpoolprice ); - 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( - (order1, order2) => - order1.order.workerpoolprice - order2.order.workerpoolprice - ); if ( !cheapestOrder || - cheapestOrder.order.workerpoolprice > - workerpoolMaxPrice + maxVoucherSponsoredAmount + cheapestOrder.order.workerpoolprice > workerpoolMaxPrice ) { return null; } diff --git a/src/web3mail/sendEmail.ts b/src/web3mail/sendEmail.ts index 017dfbc0..f2e0ec59 100644 --- a/src/web3mail/sendEmail.ts +++ b/src/web3mail/sendEmail.ts @@ -11,9 +11,7 @@ import { generateSecureUniqueId } from '../utils/generateUniqueId.js'; import * as ipfs from '../utils/ipfs-service.js'; import { checkProtectedDataValidity } from '../utils/subgraphQuery.js'; import { - addressOrEnsSchema, addressSchema, - booleanSchema, contentTypeSchema, emailContentSchema, emailSubjectSchema, @@ -22,10 +20,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, @@ -41,8 +36,8 @@ export type SendEmail = typeof sendEmail; export const sendEmail = async ({ graphQLClient = throwIfMissing(), iexec = throwIfMissing(), - workerpoolAddressOrEns, - dappAddressOrENS, + workerpoolAddress, + dappAddress, dappWhitelistAddress, ipfsNode, ipfsGateway, @@ -55,7 +50,6 @@ export const sendEmail = async ({ workerpoolMaxPrice = MAX_DESIRED_WORKERPOOL_ORDER_PRICE, senderName, protectedData, - useVoucher = false, }: IExecConsumer & SubgraphConsumer & DappAddressConsumer & @@ -63,7 +57,7 @@ export const sendEmail = async ({ IpfsNodeConfigConsumer & IpfsGatewayConfigConsumer & SendEmailParams): Promise => { - const vDatasetAddress = addressOrEnsSchema() + const vDatasetAddress = addressSchema() .required() .label('protectedData') .validateSync(protectedData); @@ -89,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() @@ -116,10 +110,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,28 +124,13 @@ 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 .fetchAppOrderbook({ - app: dappAddressOrENS, + app: vDappAddress, minTag: ['tee'], - workerpool: workerpoolAddressOrEns, + workerpool: vWorkerpoolAddress, }) .then((appOrderbook) => { const desiredPriceAppOrderbook = appOrderbook.orders.filter( @@ -176,7 +151,7 @@ export const sendEmail = async ({ iexec.orderbook .fetchDatasetOrderbook({ dataset: vDatasetAddress, - app: dappAddressOrENS, + app: vDappAddress, requester: requesterAddress, }) .then((datasetOrderbook) => { @@ -203,21 +178,19 @@ export const sendEmail = async ({ Promise.all([ // for app iexec.orderbook.fetchWorkerpoolOrderbook({ - workerpool: workerpoolAddressOrEns, - app: vDappAddressOrENS, + workerpool: vWorkerpoolAddress, + app: vDappAddress, 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, }), // for app whitelist iexec.orderbook.fetchWorkerpoolOrderbook({ - workerpool: workerpoolAddressOrEns, + workerpool: vWorkerpoolAddress, 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 +202,6 @@ export const sendEmail = async ({ ...workerpoolOrderbookForAppWhitelist.orders, ], workerpoolMaxPrice: vWorkerpoolMaxPrice, - useVoucher: vUseVoucher, - userVoucher, }); if (!desiredPriceWorkerpoolOrder) { @@ -292,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: { @@ -312,15 +283,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/sendEmailCampaign.ts b/src/web3mail/sendEmailCampaign.ts index 15f4b37d..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, }); @@ -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..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,11 +55,10 @@ export type SendEmailParams = { contentType?: string; senderName?: string; label?: string; - workerpoolAddressOrEns?: AddressOrENS; + workerpoolAddress?: Address; dataMaxPrice?: number; appMaxPrice?: number; workerpoolMaxPrice?: number; - useVoucher?: boolean; }; export type FetchMyContactsParams = { @@ -100,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. @@ -156,7 +151,7 @@ export type PrepareEmailCampaignParams = { emailContent: string; contentType?: string; label?: string; - workerpoolAddressOrEns?: AddressOrENS; + workerpoolAddress?: Address; dataMaxPrice?: number; appMaxPrice?: number; workerpoolMaxPrice?: number; @@ -177,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/docker-compose.yml b/tests/docker-compose.yml index fd6faec6..3ae00c53 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -1,27 +1,58 @@ 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 + platform: linux/amd64 + 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,44 +60,53 @@ 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 image: library/mongo:4.2 + platform: linux/amd64 entrypoint: '/bin/bash' command: -c "mongod --bind_ip_all --port 13202" expose: - 13202 + ports: + - 13202:13202 ipfs: restart: unless-stopped image: ipfs/go-ipfs:v0.9.1 + platform: linux/amd64 expose: - 8080 - 5001 @@ -81,8 +121,9 @@ services: start_period: 30s market-mongo: - image: mongo:7.0.6 + image: mongo:6.0.3 restart: unless-stopped + platform: linux/amd64 expose: - 27017 ports: @@ -90,59 +131,85 @@ services: market-redis: image: redis:7.0.7-alpine + platform: linux/amd64 restart: unless-stopped command: redis-server --appendonly yes expose: - 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 + platform: linux/amd64 restart: unless-stopped command: - 'postgres' @@ -162,19 +229,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 +246,10 @@ 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 + ethereum: 'arbitrum-sepolia:http://arbitrum-sepolia-fork:8545' + GRAPH_ETHEREUM_GENESIS_BLOCK_NUMBER: $ARBITRUM_SEPOLIA_INDEX_BLOCK depends_on: - bellecour-fork: + arbitrum-sepolia-fork: condition: service_healthy graphnode-postgres: condition: service_healthy @@ -200,7 +263,8 @@ services: start_period: 30s dataprotector-subgraph-deployer: - image: iexechub/dataprotector-subgraph-deployer:3.0.0 + platform: linux/amd64 + image: iexechub/dataprotector-subgraph-deployer:3.1.1-main-4df5c2d restart: 'no' depends_on: graphnode: @@ -208,47 +272,33 @@ services: ipfs: condition: service_started environment: - ENV: prod - START_BLOCK: $BELLECOUR_FORK_BLOCK + START_BLOCK: $ARBITRUM_SEPOLIA_INDEX_BLOCK + NETWORK_NAME: arbitrum-sepolia 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 - 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 + ENV: prod stack-ready: image: bash + platform: linux/amd64 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..17718031 100644 --- a/tests/e2e/constructor.test.ts +++ b/tests/e2e/constructor.test.ts @@ -4,13 +4,11 @@ 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'; -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 () => { @@ -29,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); }); @@ -55,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); }); @@ -78,7 +76,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'; @@ -94,7 +92,7 @@ describe('IExecWeb3mail()', () => { ipfsNode: customIpfsNode, ipfsGateway: customIpfsGateway, dataProtectorSubgraph: customSubgraphUrl, - dappAddressOrENS: customDapp, + dappAddress: customDapp, dappWhitelistAddress: customDappWhitelistAddress, } ); @@ -102,14 +100,14 @@ 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() ); @@ -121,9 +119,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 @@ -141,22 +139,23 @@ 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, - { - host: 421614, - allowExperimentalNetworks: true, - } + 421614, + { allowExperimentalNetworks: true } ); 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); + + await expect(web3mail.init()).resolves.not.toThrow(); + }, + MAX_EXPECTED_WEB2_SERVICES_TIME + ); }); describe('With allowExperimentalNetworks: true', () => { @@ -183,7 +182,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() ); @@ -197,17 +196,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, @@ -236,17 +235,16 @@ 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 } ); 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/fetchMyContacts.test.ts b/tests/e2e/fetchMyContacts.test.ts index ecbc8579..9c5eb463 100644 --- a/tests/e2e/fetchMyContacts.test.ts +++ b/tests/e2e/fetchMyContacts.test.ts @@ -8,28 +8,29 @@ 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, 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() @@ -203,19 +191,17 @@ describe('web3mail.fetchMyContacts()', () => { expect(noBulkContact).toBeUndefined(); }, MAX_EXPECTED_BLOCKTIME + - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME + + TEST_CHAIN.maxExpectedSubgraphIndexingTime + MAX_EXPECTED_WEB2_SERVICES_TIME ); 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..e1f797ba 100644 --- a/tests/e2e/fetchUserContacts.test.ts +++ b/tests/e2e/fetchUserContacts.test.ts @@ -4,16 +4,15 @@ 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, waitSubgraphIndexing, } from '../test-utils.js'; @@ -23,9 +22,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) ); @@ -49,7 +51,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, @@ -91,13 +93,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(421614)!.whitelistSmartContract; await dataProtector.grantAccess({ - authorizedApp: authorizedApp, + authorizedApp: dappAddress, protectedData: protectedData1.address, authorizedUser: userWithAccess, }); @@ -108,6 +108,8 @@ describe('web3mail.fetchMyContacts()', () => { authorizedUser: userWithAccess, }); + await waitSubgraphIndexing(); + const contacts = await web3mail.fetchUserContacts({ userAddress: userWithAccess, isUserStrict: true, @@ -122,22 +124,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 +154,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,14 +217,10 @@ 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, + authorizedUser: await ethProvider.getAddress(), }); const options = { @@ -270,12 +267,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 +277,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, @@ -311,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 5846d3fc..16305228 100644 --- a/tests/e2e/prepareEmailCampaign.test.ts +++ b/tests/e2e/prepareEmailCampaign.test.ts @@ -4,21 +4,18 @@ 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, MAX_EXPECTED_WEB2_SERVICES_TIME, - MAX_EXPECTED_SUBGRAPH_INDEXING_TIME, TEST_CHAIN, createAndPublishAppOrders, getRandomWallet, getTestConfig, + getTestDappAddress, getTestIExecOption, getTestWeb3SignerProvider, + setBalance, waitSubgraphIndexing, } from '../test-utils.js'; import { IExec } from 'iexec'; @@ -33,23 +30,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 +71,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, @@ -98,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 cdd6d8bc..bc174931 100644 --- a/tests/e2e/sendEmail.test.ts +++ b/tests/e2e/sendEmail.test.ts @@ -2,32 +2,28 @@ 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'; 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; @@ -38,50 +34,34 @@ describe('web3mail.sendEmail()', () => { let invalidProtectedData: ProtectedDataWithSecretProps; let consumerIExecInstance: IExec; let prodWorkerpoolAddress: string; - let learnProdWorkerpoolAddress: string; + let dappAddress: string; const iexecOptions = getTestIExecOption(); - const prodWorkerpoolPublicPrice = 1000; beforeAll(async () => { - // (default) prod workerpool (not free) always available 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 ); + // 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; //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 +77,7 @@ describe('web3mail.sendEmail()', () => { beforeEach(async () => { // use a fresh wallet for calling sendEmail consumerWallet = getRandomWallet(); + await setEthForGas(consumerWallet.address); const consumerEthProvider = getTestWeb3SignerProvider( consumerWallet.privateKey ); @@ -105,7 +86,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, @@ -114,22 +95,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, + workerpoolAddress: 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 @@ -139,19 +148,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, + workerpoolAddress: 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` ) ); @@ -169,6 +179,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: validProtectedData.address, + workerpoolAddress: paidWorkerpoolAddress, workerpoolMaxPrice: prodWorkerpoolPublicPrice, }); expect(sendEmailResponse).toStrictEqual({ @@ -189,7 +200,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: invalidProtectedData.address, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }) ).rejects.toThrow( new Error( @@ -215,7 +226,7 @@ describe('web3mail.sendEmail()', () => { emailSubject: 'e2e mail object for test', emailContent: 'e2e mail content for test', protectedData: protectedData.address, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }) ).rejects.toThrow( new WorkflowError({ @@ -250,7 +261,7 @@ describe('web3mail.sendEmail()', () => { try { await invalidWeb3mail.sendEmail({ protectedData: validProtectedData.address, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, emailSubject: 'My email subject', emailContent: 'My email content', }); @@ -267,394 +278,132 @@ 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 useVoucher:true', () => { + describe('when using the free prod workerpool', () => { 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, + workerpoolAddress: 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(421614)?.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, + workerpoolAddress: 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', + workerpoolAddress: 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', + workerpoolAddress: 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', + workerpoolAddress: 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, + workerpoolAddress: 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..02812df2 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, @@ -16,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, @@ -24,8 +19,11 @@ import { ensureSufficientStake, getRandomWallet, getTestConfig, + getTestDappAddress, getTestIExecOption, getTestWeb3SignerProvider, + setBalance, + sleep, waitSubgraphIndexing, } from '../test-utils.js'; import { IExec } from 'iexec'; @@ -40,11 +38,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 +50,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 +98,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, @@ -139,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( @@ -168,7 +150,7 @@ describe('web3mail.sendEmailCampaign()', () => { await web3mail .sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: workerpoolToUse, + workerpoolAddress: workerpoolToUse, }) .catch((e) => (error = e)); @@ -233,68 +215,43 @@ describe('web3mail.sendEmailCampaign()', () => { grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, workerpoolMaxPrice: prodWorkerpoolPublicPrice, - workerpoolAddressOrEns: prodWorkerpoolAddress, - }); - - // Send campaign - const result = await web3mail.sendEmailCampaign({ - campaignRequest: campaignRequest.campaignRequest, - 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, + workerpoolAddress: prodWorkerpoolAddress, }); // Send campaign const result = await web3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddress: 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) => { + 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 +266,7 @@ describe('web3mail.sendEmailCampaign()', () => { await web3mail .sendEmailCampaign({ campaignRequest: undefined as any, // Testing missing campaignRequest - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }) .catch((e) => (error = e)); @@ -325,7 +282,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({ @@ -338,24 +295,23 @@ describe('web3mail.sendEmailCampaign()', () => { emailContent: 'Bulk test message', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + 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 @@ -394,12 +350,12 @@ describe('web3mail.sendEmailCampaign()', () => { emailContent: 'Bulk test message', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); await invalidWeb3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, }); } catch (err) { error = err as Web3mailWorkflowError; @@ -419,6 +375,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 +395,9 @@ describe('web3mail.sendEmailCampaign()', () => { senderName: 'Integration Test', grantedAccesses: bulkOrders, maxProtectedDataPerTask: 3, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddress: prodWorkerpoolAddress, + workerpoolMaxPrice: prodWorkerpoolPublicPrice, }); - // Verify campaign request was created expect(campaignRequest).toBeDefined(); expect(campaignRequest.campaignRequest).toBeDefined(); @@ -444,15 +405,38 @@ describe('web3mail.sendEmailCampaign()', () => { // Send the campaign const result = await web3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddress: 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); + 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 +444,11 @@ describe('web3mail.sendEmailCampaign()', () => { it( 'should handle multiple protected data per task correctly', async () => { + await ensureSufficientStake( + consumerIExecInstance, + prodWorkerpoolPublicPrice * 2 + ); + // Fetch contacts const contacts: Contact[] = await web3mail.fetchMyContacts({ bulkOnly: true, @@ -467,29 +456,50 @@ 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, + workerpoolAddress: prodWorkerpoolAddress, + workerpoolMaxPrice: prodWorkerpoolPublicPrice, }); - // Send campaign const result = await web3mail.sendEmailCampaign({ campaignRequest: campaignRequest.campaignRequest, - workerpoolAddressOrEns: learnProdWorkerpoolAddress, + workerpoolAddress: 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); + 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).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..7d862cdc --- /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": "0x2956f0cb779904795a5f30d3b3ea88b714c3123f" + }, + { + "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..46f0c7ce --- /dev/null +++ b/tests/scripts/prepare-forks-for-tests.js @@ -0,0 +1,225 @@ +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.prodWorkerpoolOwner +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}`); + }; + +/** + * 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 = + (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 + ); +}; + +main().catch((err) => { + console.error(err); + process.exit(1); +}); 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(); - diff --git a/tests/scripts/prepare-test-env.js b/tests/scripts/prepare-test-env.js index 7cdb445b..a77014c9 100644 --- a/tests/scripts/prepare-test-env.js +++ b/tests/scripts/prepare-test-env.js @@ -1,33 +1,53 @@ 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} +# 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) { + 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..cb52b527 100644 --- a/tests/test-utils.ts +++ b/tests/test-utils.ts @@ -1,51 +1,142 @@ // 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, } from '../src/web3mail/types.js'; import { IExec, utils } from 'iexec'; import { randomInt } from 'crypto'; -import { getSignerFromPrivateKey } from 'iexec/utils'; 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', - 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', + 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: 'http://127.0.0.1:8069', + prodWorkerpool: '0x2956f0cb779904795a5f30d3b3ea88b714c3123f', prodWorkerpoolOwnerWallet: new Wallet( '0x6a12f56d7686e85ab0f46eb3c19cb0c75bfabf8fb04e595654fc93ad652fa7bc' ), 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, + 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 + * + * 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 = [ + { + inputs: [], + name: 'token', + outputs: [{ internalType: 'address', name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, +] as const; + +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, TEST_CHAIN.erc20BalanceSlot] + ) + ); + 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 waitSubgraphIndexing = async ( + timeoutMs = 60_000 +): Promise => { + const provider = new JsonRpcProvider(TEST_CHAIN.rpcURL); + const targetBlock = await provider.getBlockNumber(); -export const waitSubgraphIndexing = () => - sleep(MAX_EXPECTED_SUBGRAPH_INDEXING_TIME); + const deadline = Date.now() + timeoutMs; + while (Date.now() < deadline) { + try { + const res = await fetch(TEST_CHAIN.subgraphUrl, { + 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`; @@ -60,11 +151,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 @@ -75,8 +162,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, }); @@ -89,12 +174,23 @@ 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', + dataProtectorSubgraph: TEST_CHAIN.subgraphUrl, + compassUrl: TEST_CHAIN.compassUrl, }; return [ethProvider, options]; }; +export const getTestDappAddress = async (): Promise => { + const chainId = Number(TEST_CHAIN.chainId); + const res = await fetch( + `${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`); + } + return res.address as string; +}; + export const getEventFromLogs = (eventName, logs, { strict = true }) => { const eventFound = logs.find((log) => log.eventName === eventName); if (!eventFound) { @@ -134,7 +230,7 @@ export const createAndPublishAppOrders = async ( await resourceProvider.order .createApporder({ app: appAddress, - tag: ['tee', 'scone'], + tag: ['tee', 'tdx'], volume: 100, appprice: 0, }) @@ -164,74 +260,22 @@ 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 createVoucherType = async ({ - description = 'test', - duration = 1000, -} = {}) => { - 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 setEthForGas = async ( + address: string, + wei: bigint = TEST_CHAIN.defaultInitBalance +) => { + if (!TEST_CHAIN.isNative) { + await setBalance(address, wei); + } }; export const ensureSufficientStake = async ( @@ -240,6 +284,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 +358,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,191 +367,9 @@ export const createAndPublishWorkerpoolOrder = async ( requesterrestrict, volume, workerpoolprice, - tag: ['tee', 'scone'], + tag: ['tee', 'tdx'], }); await iexec.order .signWorkerpoolorder(workerpoolorder) .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 } - ); - - // ensure RLC balance - await setNRlcBalance(await iexec.wallet.getAddress(), value); - - // deposit RLC to voucherHub - const contractClient = await iexec.config.resolveContractsClient(); - const iexecContract = contractClient.getIExecContract(); - - try { - await iexecContract.depositFor(TEST_CHAIN.voucherHubAddress, { - value: BigInt(value) * 10n ** 9n, - gasPrice: 0, - }); - } 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); - - 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/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 5fc38602..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,8 @@ 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 () => { // import tested module after all mocked modules @@ -64,11 +62,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -79,13 +72,13 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddress: 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, @@ -125,11 +118,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -140,14 +128,14 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddress: 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, @@ -199,11 +187,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -215,7 +198,7 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddress: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, }); @@ -250,11 +233,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -265,14 +243,14 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddress: 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, @@ -314,11 +292,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -329,14 +302,14 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddress: 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, @@ -378,11 +351,6 @@ describe('fetchMyContacts', () => { .fn<() => Promise
>() .mockResolvedValue(getRandomAddress()), }, - ens: { - resolveName: jest - .fn<() => Promise
>() - .mockResolvedValue(getRandomAddress()), - }, orderbook: { fetchDatasetOrderbook: mockFetchDatasetOrderbook, }, @@ -393,7 +361,7 @@ describe('fetchMyContacts', () => { iexec: iexec, // @ts-expect-error No need for graphQLClient here graphQLClient: {}, - dappAddressOrENS: defaultConfig.dappAddress, + dappAddress: mockDappAddress, dappWhitelistAddress: defaultConfig.whitelistSmartContract, isUserStrict: true, bulkOnly: true, @@ -402,7 +370,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.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 430f00dc..0e521331 100644 --- a/tests/unit/sendEmail.test.ts +++ b/tests/unit/sendEmail.test.ts @@ -1,12 +1,9 @@ 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, - getChainDefaultConfig, -} from '../../src/config/config.js'; +import { getChainDefaultConfig } from '../../src/config/config.js'; jest.unstable_mockModule('../../src/utils/subgraphQuery.js', () => ({ checkProtectedDataValidity: jest.fn(), @@ -168,54 +165,51 @@ 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(421614); + 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, + workerpoolAddress: defaultConfig!.prodWorkerpoolAddress, + dappAddress: 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/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); }); }); diff --git a/tests/utils/mockAllForSendEmail.ts b/tests/utils/mockAllForSendEmail.ts index b268e76a..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[] }>>() @@ -25,7 +20,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({