diff --git a/.dockerignore b/.dockerignore index 5961d58c..18eb0a1b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -26,6 +26,7 @@ packages/contracts/test/build_integration/*.json packages/contracts/test/build_integration/*.zkey packages/contracts/test/build_integration/*.wasm packages/contracts/test/build_integration/*.txt +packages/contracts/test/EmailAccountRecoveryZkSync # NFT Relayer packages/nft_relayer/sendgrid.env diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..8b688cb0 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,28 @@ +## Description + + + + +## Type of change + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +## How Has This Been Tested? + + +- [ ] Test A +- [ ] Test B + +## Checklist: + +- [ ] My code follows the style guidelines of this project +- [ ] I have performed a self-review of my own code +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] My changes generate no new warnings +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules \ No newline at end of file diff --git a/.github/workflows/build-fmt.yml b/.github/workflows/build-fmt.yml new file mode 100644 index 00000000..0cb1b536 --- /dev/null +++ b/.github/workflows/build-fmt.yml @@ -0,0 +1,64 @@ +name: Build and Format + +on: [push] + +jobs: + build-and-format: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - run: rustup show + + - name: Install rustfmt and clippy + run: | + rustup component add rustfmt + rustup component add clippy + + - uses: Swatinem/rust-cache@v2 + + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: "yarn" + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly-0079a1146b79a4aeda58b0258215bedb1f92700b + + - name: Build contracts + working-directory: packages/contracts + run: yarn build + + - name: Free Disk Space (Ubuntu) + uses: jlumbroso/free-disk-space@main + with: + # this might remove tools that are actually needed, + # if set to "true" but frees about 6 GB + tool-cache: false + + # all of these default to true, but feel free to set to + # "false" if necessary for your workflow + android: true + dotnet: true + haskell: true + large-packages: true + docker-images: true + swap-storage: true + + - name: Build and check for warnings + env: + RUSTFLAGS: "-D warnings" + run: cargo build --release + + - name: Check formatting + run: cargo fmt -- --check + + - name: Run clippy + run: cargo clippy -- -D warnings diff --git a/.github/workflows/build-img.yml b/.github/workflows/build-img.yml new file mode 100644 index 00000000..2e578f91 --- /dev/null +++ b/.github/workflows/build-img.yml @@ -0,0 +1,51 @@ +name: Build and Push Docker Image + +on: + push: + branches: + - refactor + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build-and-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=sha,prefix= + type=raw,value=latest + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: ./Full.Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/build-test-fmt.yml b/.github/workflows/build-test-fmt.yml deleted file mode 100644 index 17f94353..00000000 --- a/.github/workflows/build-test-fmt.yml +++ /dev/null @@ -1,55 +0,0 @@ -name: Build-Test-Fmt - -on: - [push] - -jobs: - build-test-fmt: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v3 - - - run: rustup show - - - uses: Swatinem/rust-cache@v2 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: "yarn" - - - name: Install dependencies - run: yarn install --frozen-lockfile - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly-0079a1146b79a4aeda58b0258215bedb1f92700b - - - name: Run tests - working-directory: packages/contracts - run: yarn build - - - name: Free Disk Space (Ubuntu) - uses: jlumbroso/free-disk-space@main - with: - # this might remove tools that are actually needed, - # if set to "true" but frees about 6 GB - tool-cache: false - - # all of these default to true, but feel free to set to - # "false" if necessary for your workflow - android: true - dotnet: true - haskell: true - large-packages: true - docker-images: true - swap-storage: true - - - name: Build - run: cargo build --release - - - name: Test - run: cargo test --release diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml index b222e2f7..eff33c29 100644 --- a/.github/workflows/unit-tests.yml +++ b/.github/workflows/unit-tests.yml @@ -1,4 +1,4 @@ -name: unit-tests +name: Unit Tests on: [push] @@ -47,7 +47,7 @@ jobs: uses: actions/setup-node@v3 with: node-version: 18 - + - name: Install yarn run: npm install -g yarn @@ -62,3 +62,33 @@ jobs: - name: Run tests working-directory: packages/contracts run: yarn test + + relayer: + name: relayer + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Node.js + uses: actions/setup-node@v3 + with: + node-version: 18 + + - name: Install yarn + run: npm install -g yarn + + - name: Install dependencies + run: yarn install --frozen-lockfile + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1.2.0 + with: + version: nightly-0079a1146b79a4aeda58b0258215bedb1f92700b + + - name: Build contracts + working-directory: packages/contracts + run: yarn build + + - name: Run tests + working-directory: packages/relayer + run: cargo test diff --git a/.gitignore b/.gitignore index 0ee936ed..dd8c2c66 100644 --- a/.gitignore +++ b/.gitignore @@ -57,6 +57,10 @@ sql_database.db .sqlx .ic.pem +# ABIs +packages/relayer/src/abis/* +!packages/realyer/src/abis/mod.rs + # Prover packages/prover/build/* packages/prover/params/*.zkey @@ -80,6 +84,9 @@ book # Vs code settings .vscode +# Editor settings +.idea + # For zksync zkout .cache diff --git a/Cargo.lock b/Cargo.lock index ef6274cd..18223391 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4121,6 +4121,7 @@ dependencies = [ "regex", "relayer-utils", "reqwest 0.11.27", + "rustc-hex", "serde", "serde_json", "sled", @@ -4129,6 +4130,7 @@ dependencies = [ "slog-json", "slog-term", "sqlx", + "thiserror", "tiny_http", "tokio", "tower-http", @@ -4139,7 +4141,7 @@ dependencies = [ [[package]] name = "relayer-utils" version = "0.3.7" -source = "git+https://github.com/zkemail/relayer-utils.git#c9371a327fecb422f5e0f015ebca9199a06b658f" +source = "git+https://github.com/zkemail/relayer-utils.git?rev=6fb85e6#6fb85e6b867165a5deb2fe877bc81827b6bf001e" dependencies = [ "anyhow", "base64 0.21.7", @@ -4151,6 +4153,7 @@ dependencies = [ "hmac-sha256", "itertools 0.10.5", "lazy_static", + "mailparse", "neon", "num-bigint", "num-traits", @@ -4158,6 +4161,7 @@ dependencies = [ "poseidon-rs", "rand_core", "regex", + "reqwest 0.11.27", "rsa", "serde", "serde_json", @@ -6553,7 +6557,7 @@ dependencies = [ [[package]] name = "zk-regex-apis" version = "2.1.1" -source = "git+https://github.com/zkemail/zk-regex.git#3b626316b07081378ffdca0d36ed2bec6df5b55a" +source = "git+https://github.com/zkemail/zk-regex.git#531575345558ba938675d725bd54df45c866ef74" dependencies = [ "fancy-regex", "itertools 0.13.0", diff --git a/Cargo.toml b/Cargo.toml index 9fd16a34..fc094365 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] members = ["packages/relayer"] -exclude = ["node_modules/*"] +exclude = ["node_modules/*", "packages/relayer/src/abis"] resolver = "2" diff --git a/Full.Dockerfile b/Full.Dockerfile new file mode 100644 index 00000000..bb07d4e7 --- /dev/null +++ b/Full.Dockerfile @@ -0,0 +1,66 @@ +# Use the latest official Rust image as the base +FROM rust:latest + +# Use bash as the shell +SHELL ["/bin/bash", "-c"] + +# Install NVM, Node.js, and Yarn +RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash \ + && . $HOME/.nvm/nvm.sh \ + && nvm install 18 \ + && nvm alias default 18 \ + && nvm use default \ + && npm install -g yarn + +# Set the working directory +WORKDIR /relayer + +# Pre-configure Git to avoid common issues and increase clone verbosity +RUN git config --global advice.detachedHead false \ + && git config --global core.compression 0 \ + && git config --global protocol.version 2 \ + && git config --global http.postBuffer 1048576000 \ + && git config --global fetch.verbose true + +# Copy project files +COPY . . + +# Remove the packages/relayer directory +RUN rm -rf packages/relayer + +# Install Yarn dependencies with retry mechanism +RUN . $HOME/.nvm/nvm.sh && nvm use default && yarn || \ + (sleep 5 && yarn) || \ + (sleep 10 && yarn) + +# Install Foundry +RUN curl -L https://foundry.paradigm.xyz | bash \ + && source $HOME/.bashrc \ + && foundryup + +# Verify Foundry installation +RUN source $HOME/.bashrc && forge --version + +# Set the working directory for contracts +WORKDIR /relayer/packages/contracts + +# Install Yarn dependencies for contracts +RUN source $HOME/.nvm/nvm.sh && nvm use default && yarn + +# Build the contracts using Foundry +RUN source $HOME/.bashrc && forge build + +# Copy the project files +COPY packages/relayer /relayer/packages/relayer + +# Set the working directory for the Rust project +WORKDIR /relayer/packages/relayer + +# Build the Rust project with caching +RUN cargo build + +# Expose port +EXPOSE 4500 + +# Set the default command +CMD ["cargo", "run"] diff --git a/Relayer.Dockerfile b/Relayer.Dockerfile index fd362f3b..1dafb15e 100644 --- a/Relayer.Dockerfile +++ b/Relayer.Dockerfile @@ -1,5 +1,5 @@ # Use the base image -FROM bisht13/ar-relayer-base:latest +FROM bisht13/relayer-base # Copy the project files COPY packages/relayer /relayer/packages/relayer @@ -14,4 +14,4 @@ RUN cargo build EXPOSE 4500 # Set the default command -CMD ["cargo", "run"] \ No newline at end of file +CMD ["cargo", "run"] diff --git a/kubernetes/cloudbuild-base.yml b/kubernetes/cloudbuild-base.yml new file mode 100644 index 00000000..0bd58620 --- /dev/null +++ b/kubernetes/cloudbuild-base.yml @@ -0,0 +1,19 @@ +steps: + # Build the base container image + - name: 'gcr.io/cloud-builders/docker' + args: + [ + 'build', + '-t', + 'us-central1-docker.pkg.dev/zkairdrop/ether-email-auth/relayer-base:v1', + '-f', + 'Base.Dockerfile', + '.', + ] + # Push the base container image to Artifact Registry + - name: 'gcr.io/cloud-builders/docker' + args: + [ + 'push', + 'us-central1-docker.pkg.dev/zkairdrop/ether-email-auth/relayer-base:v1', + ] diff --git a/kubernetes/cloudbuild-relayer.yml b/kubernetes/cloudbuild-relayer.yml new file mode 100644 index 00000000..baab474d --- /dev/null +++ b/kubernetes/cloudbuild-relayer.yml @@ -0,0 +1,21 @@ +options: + machineType: 'N1_HIGHCPU_32' +steps: + # Build the base container image + - name: 'gcr.io/cloud-builders/docker' + args: + [ + 'build', + '-t', + 'us-central1-docker.pkg.dev/zkairdrop/ether-email-auth/relayer:v2', + '-f', + 'Relayer.Dockerfile', + '.', + ] + # Push the base container image to Artifact Registry + - name: 'gcr.io/cloud-builders/docker' + args: + [ + 'push', + 'us-central1-docker.pkg.dev/zkairdrop/ether-email-auth/relayer:v2', + ] diff --git a/kubernetes/relayer.staging.yml b/kubernetes/relayer.staging.yml new file mode 100644 index 00000000..cf985be4 --- /dev/null +++ b/kubernetes/relayer.staging.yml @@ -0,0 +1,163 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: relayer-config-email-auth + namespace: ar-base-sepolia-staging + labels: + app: relayer +data: + EMAIL_ACCOUNT_RECOVERY_VERSION_ID: "" + CHAIN_RPC_PROVIDER: "" + CHAIN_RPC_EXPLORER: "" + CHAIN_ID: "" + WEB_SERVER_ADDRESS: "" + REGEX_JSON_DIR_PATH: "" + EMAIL_TEMPLATES_PATH: "" + CANISTER_ID: "" + IC_REPLICA_URL: "" + JSON_LOGGER: "" + PEM_PATH: "" + SMTP_SERVER: "" + +--- +apiVersion: v1 +kind: Secret +metadata: + name: relayer-secret-email-auth + namespace: ar-base-sepolia-staging + labels: + app: relayer +type: Opaque +data: + PRIVATE_KEY: + DATABASE_URL: + PROVER_ADDRESS: + ICPEM: + +--- +apiVersion: v1 +kind: Secret +metadata: + name: relayer-smtp-secret + namespace: ar-base-sepolia-staging + labels: + app: relayer +type: Opaque +data: + SMTP_LOGIN_ID: + SMTP_LOGIN_PASSWORD: + SMTP_DOMAIN_NAME: + SERVER_HOST: + SERVER_PORT: + JSON_LOGGER: + +--- +apiVersion: v1 +kind: Secret +metadata: + name: relayer-imap-secret + namespace: ar-base-sepolia-staging + labels: + app: relayer +type: Opaque +data: + RELAYER_ENDPOINT: + IMAP_LOGIN_ID: + IMAP_LOGIN_PASSWORD: + IMAP_PORT: + IMAP_DOMAIN_NAME: + SERVER_HOST: + AUTH_TYPE: + JSON_LOGGER: + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: relayer-email-auth + namespace: ar-base-sepolia-staging + labels: + app: relayer +spec: + selector: + matchLabels: + app: relayer + template: + metadata: + labels: + app: relayer + spec: + containers: + - name: relayer-container + image: us-central1-docker.pkg.dev/zkairdrop/ether-email-auth/relayer:v2 + ports: + - containerPort: 4500 + envFrom: + - configMapRef: + name: relayer-config-email-auth + - secretRef: + name: relayer-secret-email-auth + livenessProbe: + httpGet: + path: /api/echo + port: 4500 + initialDelaySeconds: 60 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /api/echo + port: 4500 + initialDelaySeconds: 60 + periodSeconds: 30 + volumeMounts: + - name: pem-volume + mountPath: "/relayer/packages/relayer/.ic.pem" + subPath: ".ic.pem" + - name: smtp-container + image: bisht13/relayer-smtp-new:latest + ports: + - containerPort: 8080 + envFrom: + - secretRef: + name: relayer-smtp-secret + - name: imap-container + image: bisht13/relayer-imap-new:latest + envFrom: + - secretRef: + name: relayer-imap-secret + volumes: + - name: pem-volume + secret: + secretName: relayer-secret-email-auth + items: + - key: ICPEM + path: ".ic.pem" +--- +apiVersion: v1 +kind: Service +metadata: + name: relayer-svc-email-auth + namespace: ar-base-sepolia-staging +spec: + selector: + app: relayer + ports: + - protocol: TCP + port: 443 + targetPort: 4500 + type: ClusterIP + +--- +apiVersion: v1 +kind: Service +metadata: + name: relayer-smtp-svc + namespace: ar-base-sepolia-staging +spec: + selector: + app: relayer + ports: + - protocol: TCP + port: 443 + targetPort: 8080 + type: ClusterIP diff --git a/kubernetes/relayer.yml b/kubernetes/relayer.yml index e815f95b..d4edc713 100644 --- a/kubernetes/relayer.yml +++ b/kubernetes/relayer.yml @@ -11,7 +11,7 @@ data: CHAIN_RPC_EXPLORER: "" CHAIN_ID: "" WEB_SERVER_ADDRESS: "" - CIRCUITS_DIR_PATH: "" + REGEX_JSON_DIR_PATH: "" EMAIL_TEMPLATES_PATH: "" CANISTER_ID: "" IC_REPLICA_URL: "" @@ -89,7 +89,7 @@ spec: spec: containers: - name: relayer-container - image: bisht13/ar-relayer-base-1:latest + image: bisht13/ar-relayer-base-3:latest ports: - containerPort: 4500 envFrom: @@ -114,7 +114,7 @@ spec: mountPath: "/relayer/packages/relayer/.ic.pem" subPath: ".ic.pem" - name: smtp-container - image: bisht13/relayer-smtp-new:latest + image: bisht13/relayer-smtp:latest ports: - containerPort: 8080 envFrom: @@ -133,6 +133,30 @@ spec: - key: ICPEM path: ".ic.pem" +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: relayer-imap + namespace: ar-base-sepolia + labels: + app: relayer +spec: + selector: + matchLabels: + app: relayer-imap + template: + metadata: + labels: + app: relayer-imap + spec: + containers: + - name: imap-container + image: bisht13/relayer-imap-new:latest + envFrom: + - secretRef: + name: relayer-imap-secret + --- apiVersion: v1 kind: Service diff --git a/libs/rapidsnark.Dockerfile b/libs/rapidsnark.Dockerfile deleted file mode 100644 index b1c3f656..00000000 --- a/libs/rapidsnark.Dockerfile +++ /dev/null @@ -1,22 +0,0 @@ - -FROM ubuntu:20.04 - -ARG DEBIAN_FRONTEND=noninteractive - -# Install Node.js, Yarn and required dependencies -RUN apt-get update \ - && apt-get install -y curl git gnupg build-essential cmake libgmp-dev libsodium-dev nasm \ - && curl --silent --location https://deb.nodesource.com/setup_12.x | bash - \ - && apt-get install -y nodejs - -RUN git clone https://github.com/iden3/rapidsnark.git /rapidsnark -WORKDIR /rapidsnark -RUN npm install -RUN git submodule init -RUN git submodule update -RUN npx task createFieldSources -RUN npx task buildPistache -RUN apt-get install -y -RUN npx task buildProver - -ENTRYPOINT ["/rapidsnark/build/prover"] diff --git a/packages/circuits/input.json b/packages/circuits/input.json deleted file mode 100644 index 7aec4db2..00000000 --- a/packages/circuits/input.json +++ /dev/null @@ -1,2136 +0,0 @@ -{ - "padded_header": [ - 115, - 117, - 98, - 106, - 101, - 99, - 116, - 58, - 69, - 109, - 97, - 105, - 108, - 32, - 65, - 99, - 99, - 111, - 117, - 110, - 116, - 32, - 82, - 101, - 99, - 111, - 118, - 101, - 114, - 121, - 32, - 84, - 101, - 115, - 116, - 49, - 13, - 10, - 116, - 111, - 58, - 115, - 117, - 101, - 103, - 97, - 109, - 105, - 115, - 111, - 114, - 97, - 64, - 103, - 109, - 97, - 105, - 108, - 46, - 99, - 111, - 109, - 13, - 10, - 102, - 114, - 111, - 109, - 58, - 101, - 109, - 97, - 105, - 119, - 97, - 108, - 108, - 101, - 116, - 46, - 97, - 108, - 105, - 99, - 101, - 64, - 103, - 109, - 97, - 105, - 108, - 46, - 99, - 111, - 109, - 13, - 10, - 109, - 105, - 109, - 101, - 45, - 118, - 101, - 114, - 115, - 105, - 111, - 110, - 58, - 49, - 46, - 48, - 13, - 10, - 100, - 97, - 116, - 101, - 58, - 70, - 114, - 105, - 44, - 32, - 48, - 54, - 32, - 83, - 101, - 112, - 32, - 50, - 48, - 50, - 52, - 32, - 48, - 53, - 58, - 53, - 55, - 58, - 52, - 52, - 32, - 45, - 48, - 55, - 48, - 48, - 32, - 40, - 80, - 68, - 84, - 41, - 13, - 10, - 109, - 101, - 115, - 115, - 97, - 103, - 101, - 45, - 105, - 100, - 58, - 60, - 54, - 54, - 100, - 97, - 102, - 99, - 52, - 56, - 46, - 49, - 55, - 48, - 97, - 48, - 50, - 50, - 48, - 46, - 51, - 51, - 99, - 51, - 100, - 48, - 46, - 102, - 99, - 98, - 48, - 64, - 109, - 120, - 46, - 103, - 111, - 111, - 103, - 108, - 101, - 46, - 99, - 111, - 109, - 62, - 13, - 10, - 100, - 107, - 105, - 109, - 45, - 115, - 105, - 103, - 110, - 97, - 116, - 117, - 114, - 101, - 58, - 118, - 61, - 49, - 59, - 32, - 97, - 61, - 114, - 115, - 97, - 45, - 115, - 104, - 97, - 50, - 53, - 54, - 59, - 32, - 99, - 61, - 114, - 101, - 108, - 97, - 120, - 101, - 100, - 47, - 114, - 101, - 108, - 97, - 120, - 101, - 100, - 59, - 32, - 100, - 61, - 103, - 109, - 97, - 105, - 108, - 46, - 99, - 111, - 109, - 59, - 32, - 115, - 61, - 50, - 48, - 50, - 51, - 48, - 54, - 48, - 49, - 59, - 32, - 116, - 61, - 49, - 55, - 50, - 53, - 54, - 50, - 55, - 52, - 54, - 53, - 59, - 32, - 120, - 61, - 49, - 55, - 50, - 54, - 50, - 51, - 50, - 50, - 54, - 53, - 59, - 32, - 100, - 97, - 114, - 97, - 61, - 103, - 111, - 111, - 103, - 108, - 101, - 46, - 99, - 111, - 109, - 59, - 32, - 104, - 61, - 115, - 117, - 98, - 106, - 101, - 99, - 116, - 58, - 116, - 111, - 58, - 102, - 114, - 111, - 109, - 58, - 109, - 105, - 109, - 101, - 45, - 118, - 101, - 114, - 115, - 105, - 111, - 110, - 58, - 100, - 97, - 116, - 101, - 58, - 109, - 101, - 115, - 115, - 97, - 103, - 101, - 45, - 105, - 100, - 58, - 102, - 114, - 111, - 109, - 58, - 116, - 111, - 58, - 99, - 99, - 58, - 115, - 117, - 98, - 106, - 101, - 99, - 116, - 32, - 58, - 100, - 97, - 116, - 101, - 58, - 109, - 101, - 115, - 115, - 97, - 103, - 101, - 45, - 105, - 100, - 58, - 114, - 101, - 112, - 108, - 121, - 45, - 116, - 111, - 59, - 32, - 98, - 104, - 61, - 50, - 110, - 99, - 84, - 75, - 79, - 68, - 78, - 43, - 108, - 98, - 48, - 68, - 57, - 43, - 90, - 97, - 67, - 85, - 104, - 57, - 118, - 84, - 106, - 111, - 109, - 72, - 78, - 80, - 110, - 73, - 54, - 57, - 109, - 107, - 55, - 119, - 101, - 87, - 115, - 54, - 65, - 56, - 61, - 59, - 32, - 98, - 61, - 128, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 15, - 32, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ], - "padded_body": [ - 45, - 45, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 54, - 48, - 53, - 48, - 48, - 49, - 54, - 56, - 53, - 53, - 54, - 56, - 48, - 56, - 51, - 55, - 50, - 54, - 57, - 61, - 61, - 13, - 10, - 67, - 111, - 110, - 116, - 101, - 110, - 116, - 45, - 84, - 121, - 112, - 101, - 58, - 32, - 116, - 101, - 120, - 116, - 47, - 104, - 116, - 109, - 108, - 59, - 32, - 99, - 104, - 97, - 114, - 115, - 101, - 116, - 61, - 34, - 117, - 116, - 102, - 45, - 56, - 34, - 13, - 10, - 77, - 73, - 77, - 69, - 45, - 86, - 101, - 114, - 115, - 105, - 111, - 110, - 58, - 32, - 49, - 46, - 48, - 13, - 10, - 67, - 111, - 110, - 116, - 101, - 110, - 116, - 45, - 84, - 114, - 97, - 110, - 115, - 102, - 101, - 114, - 45, - 69, - 110, - 99, - 111, - 100, - 105, - 110, - 103, - 58, - 32, - 113, - 117, - 111, - 116, - 101, - 100, - 45, - 112, - 114, - 105, - 110, - 116, - 97, - 98, - 108, - 101, - 13, - 10, - 67, - 111, - 110, - 116, - 101, - 110, - 116, - 45, - 84, - 121, - 112, - 101, - 58, - 32, - 116, - 101, - 120, - 116, - 47, - 104, - 116, - 109, - 108, - 59, - 32, - 99, - 104, - 97, - 114, - 115, - 101, - 116, - 61, - 117, - 116, - 102, - 45, - 56, - 13, - 10, - 13, - 10, - 13, - 10, - 32, - 60, - 104, - 116, - 109, - 108, - 62, - 13, - 10, - 32, - 60, - 98, - 111, - 100, - 121, - 62, - 13, - 10, - 32, - 60, - 104, - 49, - 62, - 72, - 101, - 108, - 108, - 111, - 33, - 60, - 47, - 104, - 49, - 62, - 13, - 10, - 32, - 60, - 112, - 62, - 84, - 104, - 105, - 115, - 32, - 105, - 115, - 32, - 97, - 32, - 116, - 101, - 115, - 116, - 32, - 101, - 109, - 97, - 105, - 108, - 32, - 119, - 105, - 116, - 104, - 32, - 97, - 32, - 98, - 97, - 115, - 105, - 99, - 32, - 72, - 84, - 77, - 76, - 32, - 98, - 111, - 100, - 121, - 46, - 60, - 47, - 112, - 62, - 13, - 10, - 32, - 60, - 100, - 105, - 118, - 32, - 105, - 100, - 61, - 51, - 68, - 34, - 122, - 107, - 101, - 109, - 97, - 105, - 108, - 34, - 62, - 65, - 99, - 99, - 101, - 112, - 116, - 32, - 103, - 117, - 97, - 114, - 100, - 105, - 97, - 110, - 32, - 114, - 101, - 113, - 117, - 101, - 115, - 116, - 32, - 102, - 111, - 114, - 32, - 48, - 120, - 48, - 67, - 48, - 54, - 54, - 56, - 56, - 101, - 54, - 49, - 67, - 48, - 54, - 52, - 54, - 54, - 69, - 61, - 13, - 10, - 50, - 97, - 53, - 67, - 54, - 102, - 69, - 52, - 69, - 49, - 53, - 99, - 51, - 53, - 57, - 50, - 54, - 48, - 97, - 51, - 51, - 102, - 51, - 32, - 67, - 111, - 100, - 101, - 32, - 49, - 49, - 54, - 50, - 101, - 98, - 102, - 102, - 52, - 48, - 57, - 49, - 56, - 97, - 102, - 101, - 53, - 51, - 48, - 53, - 101, - 54, - 56, - 51, - 57, - 54, - 102, - 48, - 50, - 56, - 51, - 101, - 98, - 54, - 55, - 53, - 57, - 48, - 49, - 100, - 48, - 51, - 56, - 55, - 102, - 57, - 61, - 13, - 10, - 55, - 100, - 50, - 49, - 57, - 50, - 56, - 100, - 52, - 50, - 51, - 97, - 97, - 97, - 48, - 98, - 53, - 52, - 60, - 47, - 100, - 105, - 118, - 62, - 61, - 50, - 48, - 13, - 10, - 32, - 60, - 47, - 98, - 111, - 100, - 121, - 62, - 13, - 10, - 32, - 60, - 47, - 104, - 116, - 109, - 108, - 62, - 13, - 10, - 32, - 61, - 50, - 48, - 13, - 10, - 45, - 45, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 61, - 54, - 48, - 53, - 48, - 48, - 49, - 54, - 56, - 53, - 53, - 54, - 56, - 48, - 56, - 51, - 55, - 50, - 54, - 57, - 61, - 61, - 45, - 45, - 13, - 10, - 128, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 16, - 112, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ], - "body_hash_idx": 436, - "public_key": [ - "2107195391459410975264579855291297887", - "2562632063603354817278035230349645235", - "1868388447387859563289339873373526818", - "2159353473203648408714805618210333973", - "351789365378952303483249084740952389", - "659717315519250910761248850885776286", - "1321773785542335225811636767147612036", - "258646249156909342262859240016844424", - "644872192691135519287736182201377504", - "174898460680981733302111356557122107", - "1068744134187917319695255728151595132", - "1870792114609696396265442109963534232", - "8288818605536063568933922407756344", - "1446710439657393605686016190803199177", - "2256068140678002554491951090436701670", - "518946826903468667178458656376730744", - "3222036726675473160989497427257757" - ], - "signature": [ - "170161271844255892981997056109468295", - "2042410320678089637820651285407478756", - "2235307951907446725362990721960277744", - "2558650872283482274023232178928077002", - "1125115414447411231828809260942904609", - "2396701783176084287341878147443533109", - "2128856280301536390906877240389145121", - "2428098792522595894701475799989919597", - "1647552530172515032677576620955308208", - "2527537180972491287857094609185809092", - "2132950398601810533565118801188358141", - "160538878880934704009688085302112521", - "898519688023416454463467167922781011", - "258221678414411925992593903157944861", - "1423180960707426692015227448675294049", - "1156922850624474726553341869246641892", - "1773570027362757618569452574560009" - ], - "padded_header_len": 512, - "padded_body_len": 576, - "precomputed_sha": [ - 106, - 9, - 230, - 103, - 187, - 103, - 174, - 133, - 60, - 110, - 243, - 114, - 165, - 79, - 245, - 58, - 81, - 14, - 82, - 127, - 155, - 5, - 104, - 140, - 31, - 131, - 217, - 171, - 91, - 224, - 205, - 25 - ], - "account_code": "0x1162ebff40918afe5305e68396f0283eb675901d0387f97d21928d423aaa0b54", - "from_addr_idx": 69, - "domain_idx": 17, - "timestamp_idx": 297, - "code_idx": 380, - "command_idx": 0, - "padded_cleaned_body": null -} \ No newline at end of file diff --git a/packages/contracts/.env.sample b/packages/contracts/.env.example similarity index 91% rename from packages/contracts/.env.sample rename to packages/contracts/.env.example index 658d64de..f7c8917b 100644 --- a/packages/contracts/.env.sample +++ b/packages/contracts/.env.example @@ -10,7 +10,7 @@ PRIVATE_KEY= CHAIN_ID=84532 RPC_URL="https://sepolia.base.org" -SIGNER=0x69bec2dd161d6bbcc91ec32aa44d9333ebc864c0 # Signer for the dkim oracle on IC +SIGNER=0x69bec2dd161d6bbcc91ec32aa44d9333ebc864c0 # Signer for the dkim oracle on IC (Don't change this) ETHERSCAN_API_KEY= # CHAIN_NAME="base_sepolia" diff --git a/packages/prover/circom_proofgen.sh b/packages/prover/circom_proofgen.sh index 738f2e2c..18ac8162 100755 --- a/packages/prover/circom_proofgen.sh +++ b/packages/prover/circom_proofgen.sh @@ -21,21 +21,9 @@ public_path="${buildDir}/rapidsnark_public_${circuitName}_${nonce}.json" cd "${SCRIPT_DIR}" echo "entered zk email path: ${SCRIPT_DIR}" -echo "NODE_OPTIONS='--max-old-space-size=644000' snarkjs wc "${paramsDir}/${circuitName}.wasm" "${input_path}" "${witness_path}"" -NODE_OPTIONS='--max-old-space-size=644000' snarkjs wc "${paramsDir}/${circuitName}.wasm" "${input_path}" "${witness_path}" | tee /dev/stderr +${paramsDir}/${circuitName}_cpp/${circuitName} "${input_path}" "${witness_path}" | tee /dev/stderr status_jswitgen=$? -echo "✓ Finished witness generation with js! ${status_jswitgen}" - -# TODO: Get C-based witness gen to work -# echo "/${build_dir}/${CIRCUIT_NAME}_cpp/${CIRCUIT_NAME} ${input_wallet_path} ${witness_path}" -# "/${build_dir}/${CIRCUIT_NAME}_cpp/${CIRCUIT_NAME}" "${input_wallet_path}" "${witness_path}" -# status_c_wit=$? - -# echo "Finished C witness gen! Status: ${status_c_wit}" -# if [ $status_c_wit -ne 0 ]; then -# echo "C based witness gen failed with status (might be on machine specs diff than compilation): ${status_c_wit}" -# exit 1 -# fi +echo "✓ Finished witness generation with cpp! ${status_jswitgen}" if [ $isLocal = 1 ]; then # DEFAULT SNARKJS PROVER (SLOW) @@ -43,14 +31,14 @@ if [ $isLocal = 1 ]; then status_prover=$? echo "✓ Finished slow proofgen! Status: ${status_prover}" else - # RAPIDSNARK PROVER (10x FASTER) - echo "ldd ${SCRIPT_DIR}/rapidsnark/build/prover" - ldd "${SCRIPT_DIR}/rapidsnark/build/prover" + # RAPIDSNARK PROVER GPU + echo "ldd ${SCRIPT_DIR}/rapidsnark/package/bin/prover_cuda" + ldd "${SCRIPT_DIR}/rapidsnark/package/bin/prover_cuda" status_lld=$? echo "✓ lld prover dependencies present! ${status_lld}" - echo "${SCRIPT_DIR}/rapidsnark/build/prover ${paramsDir}/${circuitName}.zkey ${witness_path} ${proof_path} ${public_path}" - "${SCRIPT_DIR}/rapidsnark/build/prover" "${paramsDir}/${circuitName}.zkey" "${witness_path}" "${proof_path}" "${public_path}" | tee /dev/stderr + echo "${SCRIPT_DIR}/rapidsnark/package/bin/prover_cuda ${paramsDir}/${circuitName}.zkey ${witness_path} ${proof_path} ${public_path}" + "${SCRIPT_DIR}/rapidsnark/package/bin/prover_cuda" "${paramsDir}/${circuitName}.zkey" "${witness_path}" "${proof_path}" "${public_path}" | tee /dev/stderr status_prover=$? echo "✓ Finished rapid proofgen! Status: ${status_prover}" fi diff --git a/packages/prover/local_setup.sh b/packages/prover/local_setup.sh index 25f2360a..13381806 100755 --- a/packages/prover/local_setup.sh +++ b/packages/prover/local_setup.sh @@ -15,4 +15,4 @@ curl https://storage.googleapis.com/circom-ether-email-auth/v0.1.1-preview/email # curl https://email-wallet-trusted-setup-ceremony-pse-p0tion-production.s3.eu-central-1.amazonaws.com/circuits/emailwallet-account-transport/contributions/emailwallet-account-transport_00005.zkey --output /root/params/account_transport.zkey # curl https://email-wallet-trusted-setup-ceremony-pse-p0tion-production.s3.eu-central-1.amazonaws.com/circuits/emailwallet-claim/contributions/emailwallet-claim_00006.zkey --output /root/params/claim.zkey # curl https://email-wallet-trusted-setup-ceremony-pse-p0tion-production.s3.eu-central-1.amazonaws.com/circuits/emailwallet-email-sender/contributions/emailwallet-email-sender_00006.zkey --output /root/params/email_sender.zkey -chmod +x circom_proofgen.sh +chmod +x circom_proofgen.sh \ No newline at end of file diff --git a/packages/relayer/.env.example b/packages/relayer/.env.example index 0f696907..5a0c88a9 100644 --- a/packages/relayer/.env.example +++ b/packages/relayer/.env.example @@ -1,5 +1,5 @@ -EMAIL_ACCOUNT_RECOVERY_VERSION_ID= # Version ID of the email account recovery. -PRIVATE_KEY= # Private key for Relayer's account. +EMAIL_ACCOUNT_RECOVERY_VERSION_ID=1 # Version ID of the email account recovery. +PRIVATE_KEY="" # Private key for Relayer's account. CHAIN_RPC_PROVIDER=http://127.0.0.1:8545 CHAIN_RPC_EXPLORER= CHAIN_ID=11155111 # Chain ID of the testnet. @@ -11,11 +11,11 @@ PROVER_ADDRESS="https://zkemail--email-auth-prover-v1-3-0-flask-app.modal.run" DATABASE_URL= "postgres://test@localhost/emailauth_test" RELAYER_EMAIL_ADDR= WEB_SERVER_ADDRESS="127.0.0.1:4500" -CIRCUITS_DIR_PATH= #Path to email-wallet/packages/circuits EMAIL_TEMPLATES_PATH= #Path to email templates, e.g. ./packages/relayer/eml_templates/ +REGEX_JSON_DIR_PATH= CANISTER_ID="q7eci-dyaaa-aaaak-qdbia-cai" PEM_PATH="./.ic.pem" IC_REPLICA_URL="https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=q7eci-dyaaa-aaaak-qdbia-cai" -JSON_LOGGER=false \ No newline at end of file +JSON_LOGGER=false diff --git a/packages/relayer/CODING_GUIDELINES.md b/packages/relayer/CODING_GUIDELINES.md new file mode 100644 index 00000000..93043ca2 --- /dev/null +++ b/packages/relayer/CODING_GUIDELINES.md @@ -0,0 +1,98 @@ +# Coding Guidelines for Relayer + +This document outlines the coding guidelines for contributing to the Relayer. Following these guidelines will help maintain a consistent and high-quality codebase. + +## 1. Code Formatting + +- **Tool**: Use `rustfmt` to automatically format your code. Ensure that all code is formatted before committing. Run `cargo fmt` to format your code according to the project's style guidelines. +- **Indentation**: Use 4 spaces per indentation level. Do not use tabs. +- **Line Length**: Aim to keep lines under 100 characters, but it's not a strict rule. Use your judgment to ensure readability. +- **Imports**: Group imports into four sections: `extern crate`, `use`, `use crate`, and `use super`. + - Example: + ```rust + extern crate serde; + + use std::collections::HashMap; + use std::io::{self, Read}; + + use crate::utils::config; + + use super::super::common::logger; + ``` +- **Braces**: Use the Allman style for braces, where the opening brace is on the same line as the function signature. + - Example: + ```rust + fn main() { + // function body + } + ``` +- **Comments**: Use `//` for single-line comments and `/* ... */` for multi-line comments. +- **Whitespace**: Use a single space after commas and colons, and no space before commas and colons. + - Example: + ```rust + let numbers = vec![1, 2, 3]; + let user = User { name: "Alice", age: 30 }; + ``` +- **Function Length**: Aim to keep functions short and focused. If a function is too long, consider breaking it up into smaller functions. +- **Code Duplication**: Avoid duplicating code. If you find yourself copying and pasting code, consider refactoring it into a shared function or module. +- **No warnings**: Ensure that your code compiles without warnings. Fix any warnings before committing. + +## 2. Code Linting + +- **Tool**: Use `cargo clippy` to lint your code and catch common mistakes and improve your Rust code. Run `cargo clippy` before committing your code to ensure it adheres to Rust's best practices and the project's specific requirements. +- **Handling Lints**: Address all warnings and errors reported by `clippy`. If you must ignore a lint, use `#[allow(clippy::lint_name)]` and provide a comment explaining why. + +## 3. Naming Conventions + +- **Variables and Functions**: Use `snake_case`. + - Example: `let user_name = "Alice";` +- **Structs and Enums**: Use `PascalCase`. + - Example: `struct UserAccount { ... }` +- **Constants**: Use `UPPER_SNAKE_CASE`. + - Example: `const MAX_USERS: u32 = 100;` +- **Module Names**: Use `snake_case`. + - Example: `mod user_account;` + +## 4. Documentation + +- **Public Items**: All public functions, structs, and modules must have documentation comments using `///`. + - Example: + ```rust + /// Creates a new user account. + /// + /// # Arguments + /// + /// * `name` - The name of the user. + /// + /// # Returns + /// + /// A `UserAccount` struct. + pub fn create_user_account(name: &str) -> UserAccount { + // function body + } + ``` +- **Private Items**: Document private items where the intent or functionality is not immediately clear. +- **Module Documentation**: Each module should have a comment at the top explaining its purpose. + - Example: + ```rust + //! This module contains utility functions for user management. + + // module contents + ``` + +## 5. Error Handling + +- **Use of `Result` and `Option`**: + - Use `Result` for operations that can fail and `Option` for values that may or may not be present. + - Example: + ```rust + fn find_user(id: u32) -> Option { + // function body + } + + fn open_file(path: &str) -> Result { + // function body + } + ``` +- **Custom Error Types**: When appropriate, define custom error types using `enum` and implement the `anyhow::Error` trait. +- **Error Propagation**: Propagate errors using `?` where possible to simplify error handling. \ No newline at end of file diff --git a/packages/relayer/CONTRIBUTING.md b/packages/relayer/CONTRIBUTING.md new file mode 100644 index 00000000..b249c89d --- /dev/null +++ b/packages/relayer/CONTRIBUTING.md @@ -0,0 +1,116 @@ +# Contributing to Relayer + +Thank you for considering contributing to our project! We welcome contributions of all kinds, including code, documentation, bug reports, feature requests, and more. This document outlines the process for contributing to this project. + +## Table of Contents +- [Contributing to Relayer](#contributing-to-relayer) + - [Table of Contents](#table-of-contents) + - [1. Code of Conduct](#1-code-of-conduct) + - [2. Getting Started](#2-getting-started) + - [3. Coding Guidelines](#3-coding-guidelines) + - [4. Testing](#4-testing) + - [5. Commit Messages](#5-commit-messages) + - [6. Pull Request Process](#6-pull-request-process) + - [7. Contact](#7-contact) + +## 1. Code of Conduct +We are committed to providing a welcoming and inspiring community for all and expect our Code of Conduct to be honored. Anyone who violates this code of conduct may be banned from the community. + +Our community strives to: + +- **Be friendly and patient.** +- **Be welcoming**: We strive to be a community that welcomes and supports people of all backgrounds and identities. +- **Be considerate**: Your work will be used by other people, and you in turn will depend on the work of others. +- **Be respectful**: Not all of us will agree all the time, but disagreement is no excuse for poor behavior and poor manners. +- **Be careful in the words that you choose**: We are a community of professionals, and we conduct ourselves professionally. +- **Be kind to others**: Do not insult or put down other participants. Harassment and other exclusionary behavior aren't acceptable. + +This includes, but is not limited to: + +- Violent threats or language directed against another person. +- Discriminatory jokes and language. +- Posting sexually explicit or violent material. +- Posting (or threatening to post) other people's personally identifying information ("doxing"). +- Personal insults, especially those using racist or sexist terms. +- Unwelcome sexual attention. +- Advocating for, or encouraging, any of the above behavior. +- Repeated harassment of others. In general, if someone asks you to stop, then stop. + +Moderation + +These are the policies for upholding our community’s standards of conduct. If you feel that a thread needs moderation, please contact the community team at [paradox@pse.dev](mailto:paradox@pse.dev). + +1. **Remarks that violate the Relayer Utils standards of conduct, including hateful, hurtful, oppressive, or exclusionary remarks, are not allowed.** (Cursing is allowed, but never targeting another user, and never in a hateful manner.) +2. **Remarks that moderators find inappropriate, whether listed in the code of conduct or not, are also not allowed.** +3. **Moderators will first respond to such remarks with a warning.** +4. **If the warning is unheeded, the user will be “kicked,” i.e., temporarily banned from the community.** +5. **If the user comes back and continues to make trouble, they will be banned permanently from the community.** +6. **Moderators may choose at their discretion to un-ban the user if it was a first offense and they offer the offended party a genuine apology.** +7. **If a moderator bans someone and you think it was unjustified, please take it up with that moderator, or with a different moderator, in a private discussion.** + +**Please try to emulate these behaviors, especially when debating the merits of different options.** + +Thank you for helping make this a welcoming, friendly community for all. + +This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) + + +## 2. Getting Started +To start contributing, follow these steps: + +1. Fork the repository. +2. Clone your fork to your local machine: + ```bash + git clone https://github.com/zkemail/relayer-utils.git + ``` +3. Create a new branch for your feature or bugfix: + ```bash + git checkout -b feat/your-feature-name + ``` +4. Install the necessary dependencies: + ```bash + cargo build + ``` +5. Make your changes. + +## 3. Coding Guidelines + +Please follow the coding guidelines in [CODING_GUIDELINES.md](CODING_GUIDELINES.md) when contributing to this project. + +## 4. Testing + +Please write tests for your contributions. We aim for high test coverage. + + • Unit Tests: Place unit tests in the same file as the code they are testing. + • Integration Tests: Place integration tests in the tests/ directory. + +Run all tests before submitting your code with cargo test. + +Run all tests before submitting your code with cargo test. + +## 5. Commit Messages + +Use conventional commit messages for your commits. This helps us automatically generate the changelog and follow semantic versioning. + + • Format: `: ` + • Example: `feat: add new feature` + +For more information, see [Conventional Commits](https://www.conventionalcommits.org/). + +## 6. Pull Request Process + + 1. Ensure your branch is up-to-date with the main branch: + • git fetch origin + • git checkout main + • git merge origin/main + 2. Push your branch to your fork: + • git push origin feature/your-feature-name + 3. Open a pull request from your branch to the main branch of the original repository. + 4. Ensure that your pull request passes all checks (e.g., CI tests). + 5. A reviewer will review your pull request. Be prepared to make adjustments based on feedback. + +## 7. Contact + +If you have any questions or need further assistance, feel free to open an issue or contact us at [paradox@pse.dev](mailto:paradox@pse.dev). + +Thank you for your contributions! \ No newline at end of file diff --git a/packages/relayer/Cargo.toml b/packages/relayer/Cargo.toml index 7fac6c4e..ef4a199d 100644 --- a/packages/relayer/Cargo.toml +++ b/packages/relayer/Cargo.toml @@ -24,7 +24,9 @@ serde_json = "1.0.68" tiny_http = "0.12.0" lettre = { version = "0.10.4", features = ["tokio1", "tokio1-native-tls"] } ethers = { version = "2.0.10", features = ["abigen"] } -relayer-utils = { version = "0.3.7", git = "https://github.com/zkemail/relayer-utils.git" } +# relayer-utils = { version = "0.3.7", git = "https://github.com/zkemail/relayer-utils.git" } +relayer-utils = { rev = "6fb85e6", git = "https://github.com/zkemail/relayer-utils.git" } +# relayer-utils = { path="../../../relayer-utils" } futures = "0.3.28" sqlx = { version = "=0.7.3", features = ["postgres", "runtime-tokio"] } regex = "1.10.2" @@ -54,6 +56,8 @@ http = "1.1.0" ic-agent = { version = "0.37.1", features = ["pem", "reqwest"] } ic-utils = "0.37.0" candid = "0.10.10" +thiserror = "1.0.63" +rustc-hex = "2.1.0" [build-dependencies] ethers = "2.0.10" diff --git a/packages/relayer/README.md b/packages/relayer/README.md index 5d859785..4689b36b 100644 --- a/packages/relayer/README.md +++ b/packages/relayer/README.md @@ -9,12 +9,12 @@ You can run the relayer either on your local environments or cloud instances (we 3. If you have not deployed common contracts, build contract artifacts and deploy required contracts. 1. `cd packages/contracts` and run `forge build`. 2. Set the env file in `packages/contracts/.env`, an example env file is as follows, - + ```jsx LOCALHOST_RPC_URL=http://127.0.0.1:8545 SEPOLIA_RPC_URL=https://sepolia.base.org MAINNET_RPC_URL=https://mainnet.base.org - + PRIVATE_KEY="" CHAIN_ID=84532 RPC_URL="https://sepolia.base.org" @@ -38,7 +38,7 @@ You can run the relayer either on your local environments or cloud instances (we CHAIN_RPC_PROVIDER=https://sepolia.base.org CHAIN_RPC_EXPLORER=https://sepolia.basescan.org CHAIN_ID=84532 # Chain ID of the testnet. - + # IMAP + SMTP (Settings will be provided by your email provider) IMAP_DOMAIN_NAME=imap.gmail.com IMAP_PORT=993 @@ -46,20 +46,28 @@ You can run the relayer either on your local environments or cloud instances (we SMTP_DOMAIN_NAME=smtp.gmail.com LOGIN_ID= # IMAP login id - usually your email address. LOGIN_PASSWORD="" # IMAP password - usually your email password. - + PROVER_ADDRESS="http://localhost:8080" # Address of the prover. - + DATABASE_URL= "postgres://new_user:my_secure_password@localhost/my_new_database" WEB_SERVER_ADDRESS="127.0.0.1:4500" - CIRCUITS_DIR_PATH= # Absolute path to packages/circuits EMAIL_TEMPLATES_PATH= # Absolute path to packages/relayer/eml_templates - + CANISTER_ID="q7eci-dyaaa-aaaak-qdbia-cai" PEM_PATH="./.ic.pem" IC_REPLICA_URL="https://a4gq6-oaaaa-aaaab-qaa4q-cai.raw.icp0.io/?id=q7eci-dyaaa-aaaak-qdbia-cai" - + JSON_LOGGER=false ``` + 3. Generate the `.ic.pem` file and password. + - Create the `.ic.pem` file using OpenSSL: + ```sh + openssl genpkey -algorithm RSA -out .ic.pem -aes-256-cbc -pass pass:your_password + ``` + - If you need a password, you can generate a random password using: + ```sh + openssl rand -base64 32 + ``` 7. You should have your entire setup up and running! NOTE: You need to turn on IMAP on the email id you’d be using for the relayer. @@ -76,11 +84,11 @@ Note that from June 2024, IMAP will be enabled by default. ##### Enable two-factor authentication for your Google account: -Refer to the following help link. +Refer to the following help link. [Google 2FA Setup](https://support.google.com/accounts/answer/185839?hl=en&co=GENIE.Platform%3DDesktop) -##### Create an app password: +##### Create an app password: Refer to the following help link. If you do not see the 'App passwords' option, try searching for 'app pass' in the search box to select it. @@ -97,7 +105,7 @@ Refer to the following help link. If you do not see the 'App passwords' option, 4. (Optional) Delete `db.yml` , `ingress.yml` and `relayer.yml` if applied already 5. (Optional) Build the Relayer’s Docker image and publish it. 6. Set the config in the respective manifests (Here, you can set the image of the relayer in `relayer.yml` , latest image already present in the config.) -7. Apply `db.yml` +7. Apply `db.yml` 8. Apply `relayer.yml` , ssh into the pod and run `nohup cargo run &` , this step should be done under a min to pass the liveness check. 9. Apply `ingress.yml` @@ -143,9 +151,9 @@ It exposes the following REST APIs. 5. If the contract of `account_eth_addr` is not deployed, return a 400 response. 4. If a record with `account_code` exists in the `credentials` table, return a 400 response. 6. Randomly generate a `request_id`. If a record with `request_id` exists in the `requests` table, regenerate a new `request_id`. - 7. If a record with `account_eth_addr`, `guardian_email_addr` and `is_set=true` exists in the `credentials` table, + 7. If a record with `account_eth_addr`, `guardian_email_addr` and `is_set=true` exists in the `credentials` table, 1. Insert `(request_id, account_eth_addr, controller_eth_addr, guardian_email_addr, false, template_idx, false)` into the `requests` table. - 2. Send `guardian_email_addr` an error email to say that `account_eth_addr` tries to set you to a guardian, which is rejected since you are already its guardian. + 2. Send `guardian_email_addr` an error email to say that `account_eth_addr` tries to set you to a guardian, which is rejected since you are already its guardian. 3. Return a 200 response along with `request_id` and `subject_params` **to prevent a malicious client user from learning if the pair of the `account_eth_addr` and the `guardian_email_addr` is already set or not.** 8. Insert `(account_code, account_eth_addr, controller_eth_addr, guardian_email_addr, false)` into the `credentials` table. 9. Insert `(request_id, account_eth_addr, controller_eth_addr, guardian_email_addr, false, template_idx)` into the `requests` table. @@ -158,12 +166,12 @@ It exposes the following REST APIs. - `POST recoveryRequest` 1. Receive `controller_eth_addr`, `guardian_email_addr`, `template_idx`, and `subject`. - 2. Let `subject_template` be the `template_idx`-th template in `recoverySubjectTemplates()` of `wallet_eth_addr`. + 2. Let `subject_template` be the `template_idx`-th template in `recoverySubjectTemplates()` of `account_eth_addr`. 3. If the `subject` does not match with `subject_template` return a 400 response. Let `subject_params` be the parsed values. 4. Extract `account_eth_addr` from the given `subject` by following `subject_template`. 5. If the contract of `account_eth_addr` is not deployed, return a 400 response. 6. Randomly generate a `request_id`. If a record with `request_id` exists in the `requests` table, regenerate a new `request_id`. - 7. If a record with `account_eth_addr`, `guardian_email_addr`, and `is_set=true` exists in the `credentials` table, + 7. If a record with `account_eth_addr`, `guardian_email_addr`, and `is_set=true` exists in the `credentials` table, 1. Insert `(request_id, account_eth_addr, controller_eth_addr, guardian_email_addr, true, template_idx, false)` into the `requests` table. 2. Send an email as follows. - To: `guardian_email_addr` @@ -171,14 +179,14 @@ It exposes the following REST APIs. - Reply-to: `relayer_email_addr_before_domain ~~+ "+code" + hex(account_code)~~ + "@" + relayer_email_addr_domain`. - Body: Any message, but it MUST contain `"#" + digit(request_id)`. 3. Return a 200 response along with `request_id` and `subject_params`. - 7. If a record with `account_eth_addr`, `guardian_email_addr`, and `is_set=false` exists in the `credentials` table, - 1. Insert `(request_id, wallet_eth_addr, guardian_email_addr, true, template_idx, false)` into the `requests` table. + 7. If a record with `account_eth_addr`, `guardian_email_addr`, and `is_set=false` exists in the `credentials` table, + 1. Insert `(request_id, account_eth_addr, guardian_email_addr, true, template_idx, false)` into the `requests` table. 2. Send an email as follows. - To: `guardian_email_addr` - Subject: A message to say that `account_eth_addr` requests your account recovery, but you have not approved being its guardian. 3. Return a 200 response along with `request_id` and `subject_params`. - 8. If a record with `wallet_eth_addr`, `guardian_email_addr` does not exist in the `credentials` table, - 1. Insert `(request_id, wallet_eth_addr, guardian_email_addr, true, template_idx, false)` into the `requests` table. + 8. If a record with `account_eth_addr`, `guardian_email_addr` does not exist in the `credentials` table, + 1. Insert `(request_id, account_eth_addr, guardian_email_addr, true, template_idx, false)` into the `requests` table. 2. Send an email as follows. - To: `guardian_email_addr` - Subject: if the domain of `guardian_email_addr` signs the To field, `subject`. Otherwise, `subject + " Code "`. diff --git a/packages/relayer/eml_templates/acceptance_request.html b/packages/relayer/eml_templates/acceptance_request.html index da2e4356..853efc6f 100644 --- a/packages/relayer/eml_templates/acceptance_request.html +++ b/packages/relayer/eml_templates/acceptance_request.html @@ -1,417 +1,255 @@ + - Email Auth + - Set Your Guardian Email - - + + + + + -  
{{command}}
diff --git a/packages/relayer/eml_templates/acceptance_success.html b/packages/relayer/eml_templates/acceptance_success.html index 4e45a5b4..45908fee 100644 --- a/packages/relayer/eml_templates/acceptance_success.html +++ b/packages/relayer/eml_templates/acceptance_success.html @@ -1,414 +1,250 @@ + - Email Auth + - Guardian Email Set! - - + + + + + -   diff --git a/packages/relayer/eml_templates/acknowledgement.html b/packages/relayer/eml_templates/acknowledgement.html index 713ce805..6c720aa7 100644 --- a/packages/relayer/eml_templates/acknowledgement.html +++ b/packages/relayer/eml_templates/acknowledgement.html @@ -1,412 +1,249 @@ + - Email Wallet + - Email Wallet Acknowledgement - - + + + + + -   diff --git a/packages/relayer/eml_templates/credential_not_present.html b/packages/relayer/eml_templates/credential_not_present.html index d823a014..dde84e8d 100644 --- a/packages/relayer/eml_templates/credential_not_present.html +++ b/packages/relayer/eml_templates/credential_not_present.html @@ -1,417 +1,254 @@ + - Email Auth + - Credential Not Present - - + + + + + -   diff --git a/packages/relayer/eml_templates/error.html b/packages/relayer/eml_templates/error.html index aef12c26..52893dda 100644 --- a/packages/relayer/eml_templates/error.html +++ b/packages/relayer/eml_templates/error.html @@ -1,414 +1,250 @@ + - Email Auth + - Error - - + + + + + -   diff --git a/packages/relayer/eml_templates/guardian_already_exists.html b/packages/relayer/eml_templates/guardian_already_exists.html index 8d52d462..00a0192b 100644 --- a/packages/relayer/eml_templates/guardian_already_exists.html +++ b/packages/relayer/eml_templates/guardian_already_exists.html @@ -1,414 +1,251 @@ + - Email Auth + - Guardian Already Set - - + + + + + -   diff --git a/packages/relayer/eml_templates/guardian_not_set.html b/packages/relayer/eml_templates/guardian_not_set.html index 853bb3d6..4ad15982 100644 --- a/packages/relayer/eml_templates/guardian_not_set.html +++ b/packages/relayer/eml_templates/guardian_not_set.html @@ -1,412 +1,249 @@ + - Email Auth + - Guardian Not Set - - + + + + + -   diff --git a/packages/relayer/eml_templates/recovery_request.html b/packages/relayer/eml_templates/recovery_request.html index 0081a8a6..d09987f4 100644 --- a/packages/relayer/eml_templates/recovery_request.html +++ b/packages/relayer/eml_templates/recovery_request.html @@ -1,417 +1,254 @@ + - Email Auth + - Recovery Request - - + + + + + -  
{{command}}
diff --git a/packages/relayer/eml_templates/recovery_success.html b/packages/relayer/eml_templates/recovery_success.html index d067c2f6..a9416ca4 100644 --- a/packages/relayer/eml_templates/recovery_success.html +++ b/packages/relayer/eml_templates/recovery_success.html @@ -1,414 +1,250 @@ + - Email Auth + - Recovery Successful - - + + + + + -   diff --git a/packages/relayer/src/abis/email_account_recovery.rs b/packages/relayer/src/abis/email_account_recovery.rs deleted file mode 100644 index 72cf8eb6..00000000 --- a/packages/relayer/src/abis/email_account_recovery.rs +++ /dev/null @@ -1,1588 +0,0 @@ -pub use email_account_recovery::*; -/// This module was auto-generated with ethers-rs Abigen. -/// More information at: -#[allow( - clippy::enum_variant_names, - clippy::too_many_arguments, - clippy::upper_case_acronyms, - clippy::type_complexity, - dead_code, - non_camel_case_types, -)] -pub mod email_account_recovery { - #[allow(deprecated)] - fn __abi() -> ::ethers::core::abi::Abi { - ::ethers::core::abi::ethabi::Contract { - constructor: ::core::option::Option::None, - functions: ::core::convert::From::from([ - ( - ::std::borrow::ToOwned::to_owned("acceptanceCommandTemplates"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "acceptanceCommandTemplates", - ), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers::core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers::core::abi::ethabi::ParamType::String, - ), - ), - ), - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string[][]"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("completeRecovery"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("completeRecovery"), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("account"), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("completeCalldata"), - kind: ::ethers::core::abi::ethabi::ParamType::Bytes, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("computeAcceptanceTemplateId"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "computeAcceptanceTemplateId", - ), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("templateIdx"), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::Pure, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("computeEmailAuthAddress"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "computeEmailAuthAddress", - ), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("recoveredAccount"), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("accountSalt"), - kind: ::ethers::core::abi::ethabi::ParamType::FixedBytes( - 32usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes32"), - ), - }, - ], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("computeRecoveryTemplateId"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "computeRecoveryTemplateId", - ), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("templateIdx"), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::Pure, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("dkim"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("dkim"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("dkimAddr"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("dkimAddr"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("emailAuthImplementation"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "emailAuthImplementation", - ), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("emailAuthImplementationAddr"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "emailAuthImplementationAddr", - ), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned( - "extractRecoveredAccountFromAcceptanceCommand", - ), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "extractRecoveredAccountFromAcceptanceCommand", - ), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("commandParams"), - kind: ::ethers::core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers::core::abi::ethabi::ParamType::Bytes, - ), - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes[]"), - ), - }, - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("templateIdx"), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned( - "extractRecoveredAccountFromRecoveryCommand", - ), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "extractRecoveredAccountFromRecoveryCommand", - ), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("commandParams"), - kind: ::ethers::core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers::core::abi::ethabi::ParamType::Bytes, - ), - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bytes[]"), - ), - }, - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("templateIdx"), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("handleAcceptance"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("handleAcceptance"), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("emailAuthMsg"), - kind: ::ethers::core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers::core::abi::ethabi::ParamType::Uint(256usize), - ::ethers::core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers::core::abi::ethabi::ParamType::Bytes, - ), - ), - ::ethers::core::abi::ethabi::ParamType::Uint(256usize), - ::ethers::core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers::core::abi::ethabi::ParamType::String, - ::ethers::core::abi::ethabi::ParamType::FixedBytes(32usize), - ::ethers::core::abi::ethabi::ParamType::Uint(256usize), - ::ethers::core::abi::ethabi::ParamType::String, - ::ethers::core::abi::ethabi::ParamType::FixedBytes(32usize), - ::ethers::core::abi::ethabi::ParamType::FixedBytes(32usize), - ::ethers::core::abi::ethabi::ParamType::Bool, - ::ethers::core::abi::ethabi::ParamType::Bytes, - ], - ), - ], - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("struct EmailAuthMsg"), - ), - }, - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("templateIdx"), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("handleRecovery"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("handleRecovery"), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("emailAuthMsg"), - kind: ::ethers::core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers::core::abi::ethabi::ParamType::Uint(256usize), - ::ethers::core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers::core::abi::ethabi::ParamType::Bytes, - ), - ), - ::ethers::core::abi::ethabi::ParamType::Uint(256usize), - ::ethers::core::abi::ethabi::ParamType::Tuple( - ::std::vec![ - ::ethers::core::abi::ethabi::ParamType::String, - ::ethers::core::abi::ethabi::ParamType::FixedBytes(32usize), - ::ethers::core::abi::ethabi::ParamType::Uint(256usize), - ::ethers::core::abi::ethabi::ParamType::String, - ::ethers::core::abi::ethabi::ParamType::FixedBytes(32usize), - ::ethers::core::abi::ethabi::ParamType::FixedBytes(32usize), - ::ethers::core::abi::ethabi::ParamType::Bool, - ::ethers::core::abi::ethabi::ParamType::Bytes, - ], - ), - ], - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("struct EmailAuthMsg"), - ), - }, - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("templateIdx"), - kind: ::ethers::core::abi::ethabi::ParamType::Uint( - 256usize, - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("uint256"), - ), - }, - ], - outputs: ::std::vec![], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::NonPayable, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("isActivated"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("isActivated"), - inputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::borrow::ToOwned::to_owned("recoveredAccount"), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Bool, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("bool"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("recoveryCommandTemplates"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned( - "recoveryCommandTemplates", - ), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers::core::abi::ethabi::ParamType::Array( - ::std::boxed::Box::new( - ::ethers::core::abi::ethabi::ParamType::String, - ), - ), - ), - ), - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("string[][]"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("verifier"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("verifier"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ( - ::std::borrow::ToOwned::to_owned("verifierAddr"), - ::std::vec![ - ::ethers::core::abi::ethabi::Function { - name: ::std::borrow::ToOwned::to_owned("verifierAddr"), - inputs: ::std::vec![], - outputs: ::std::vec![ - ::ethers::core::abi::ethabi::Param { - name: ::std::string::String::new(), - kind: ::ethers::core::abi::ethabi::ParamType::Address, - internal_type: ::core::option::Option::Some( - ::std::borrow::ToOwned::to_owned("address"), - ), - }, - ], - constant: ::core::option::Option::None, - state_mutability: ::ethers::core::abi::ethabi::StateMutability::View, - }, - ], - ), - ]), - events: ::std::collections::BTreeMap::new(), - errors: ::std::collections::BTreeMap::new(), - receive: false, - fallback: false, - } - } - ///The parsed JSON ABI of the contract. - pub static EMAILACCOUNTRECOVERY_ABI: ::ethers::contract::Lazy< - ::ethers::core::abi::Abi, - > = ::ethers::contract::Lazy::new(__abi); - pub struct EmailAccountRecovery(::ethers::contract::Contract); - impl ::core::clone::Clone for EmailAccountRecovery { - fn clone(&self) -> Self { - Self(::core::clone::Clone::clone(&self.0)) - } - } - impl ::core::ops::Deref for EmailAccountRecovery { - type Target = ::ethers::contract::Contract; - fn deref(&self) -> &Self::Target { - &self.0 - } - } - impl ::core::ops::DerefMut for EmailAccountRecovery { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } - } - impl ::core::fmt::Debug for EmailAccountRecovery { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - f.debug_tuple(::core::stringify!(EmailAccountRecovery)) - .field(&self.address()) - .finish() - } - } - impl EmailAccountRecovery { - /// Creates a new contract instance with the specified `ethers` client at - /// `address`. The contract derefs to a `ethers::Contract` object. - pub fn new>( - address: T, - client: ::std::sync::Arc, - ) -> Self { - Self( - ::ethers::contract::Contract::new( - address.into(), - EMAILACCOUNTRECOVERY_ABI.clone(), - client, - ), - ) - } - ///Calls the contract's `acceptanceCommandTemplates` (0x222f6cb5) function - pub fn acceptance_command_templates( - &self, - ) -> ::ethers::contract::builders::ContractCall< - M, - ::std::vec::Vec<::std::vec::Vec<::std::string::String>>, - > { - self.0 - .method_hash([34, 47, 108, 181], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `completeRecovery` (0xc18d09cf) function - pub fn complete_recovery( - &self, - account: ::ethers::core::types::Address, - complete_calldata: ::ethers::core::types::Bytes, - ) -> ::ethers::contract::builders::ContractCall { - self.0 - .method_hash([193, 141, 9, 207], (account, complete_calldata)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `computeAcceptanceTemplateId` (0x32ccc2f2) function - pub fn compute_acceptance_template_id( - &self, - template_idx: ::ethers::core::types::U256, - ) -> ::ethers::contract::builders::ContractCall { - self.0 - .method_hash([50, 204, 194, 242], template_idx) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `computeEmailAuthAddress` (0x3a8eab14) function - pub fn compute_email_auth_address( - &self, - recovered_account: ::ethers::core::types::Address, - account_salt: [u8; 32], - ) -> ::ethers::contract::builders::ContractCall< - M, - ::ethers::core::types::Address, - > { - self.0 - .method_hash([58, 142, 171, 20], (recovered_account, account_salt)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `computeRecoveryTemplateId` (0x6da99515) function - pub fn compute_recovery_template_id( - &self, - template_idx: ::ethers::core::types::U256, - ) -> ::ethers::contract::builders::ContractCall { - self.0 - .method_hash([109, 169, 149, 21], template_idx) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `dkim` (0x400ad5ce) function - pub fn dkim( - &self, - ) -> ::ethers::contract::builders::ContractCall< - M, - ::ethers::core::types::Address, - > { - self.0 - .method_hash([64, 10, 213, 206], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `dkimAddr` (0x73357f85) function - pub fn dkim_addr( - &self, - ) -> ::ethers::contract::builders::ContractCall< - M, - ::ethers::core::types::Address, - > { - self.0 - .method_hash([115, 53, 127, 133], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `emailAuthImplementation` (0xb6201692) function - pub fn email_auth_implementation( - &self, - ) -> ::ethers::contract::builders::ContractCall< - M, - ::ethers::core::types::Address, - > { - self.0 - .method_hash([182, 32, 22, 146], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `emailAuthImplementationAddr` (0x1098e02e) function - pub fn email_auth_implementation_addr( - &self, - ) -> ::ethers::contract::builders::ContractCall< - M, - ::ethers::core::types::Address, - > { - self.0 - .method_hash([16, 152, 224, 46], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `extractRecoveredAccountFromAcceptanceCommand` (0x2c4ce129) function - pub fn extract_recovered_account_from_acceptance_command( - &self, - command_params: ::std::vec::Vec<::ethers::core::types::Bytes>, - template_idx: ::ethers::core::types::U256, - ) -> ::ethers::contract::builders::ContractCall< - M, - ::ethers::core::types::Address, - > { - self.0 - .method_hash([44, 76, 225, 41], (command_params, template_idx)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `extractRecoveredAccountFromRecoveryCommand` (0xa5e3ee70) function - pub fn extract_recovered_account_from_recovery_command( - &self, - command_params: ::std::vec::Vec<::ethers::core::types::Bytes>, - template_idx: ::ethers::core::types::U256, - ) -> ::ethers::contract::builders::ContractCall< - M, - ::ethers::core::types::Address, - > { - self.0 - .method_hash([165, 227, 238, 112], (command_params, template_idx)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `handleAcceptance` (0x0481af67) function - pub fn handle_acceptance( - &self, - email_auth_msg: EmailAuthMsg, - template_idx: ::ethers::core::types::U256, - ) -> ::ethers::contract::builders::ContractCall { - self.0 - .method_hash([4, 129, 175, 103], (email_auth_msg, template_idx)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `handleRecovery` (0xb68126fa) function - pub fn handle_recovery( - &self, - email_auth_msg: EmailAuthMsg, - template_idx: ::ethers::core::types::U256, - ) -> ::ethers::contract::builders::ContractCall { - self.0 - .method_hash([182, 129, 38, 250], (email_auth_msg, template_idx)) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `isActivated` (0xc9faa7c5) function - pub fn is_activated( - &self, - recovered_account: ::ethers::core::types::Address, - ) -> ::ethers::contract::builders::ContractCall { - self.0 - .method_hash([201, 250, 167, 197], recovered_account) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `recoveryCommandTemplates` (0x3ef01b8f) function - pub fn recovery_command_templates( - &self, - ) -> ::ethers::contract::builders::ContractCall< - M, - ::std::vec::Vec<::std::vec::Vec<::std::string::String>>, - > { - self.0 - .method_hash([62, 240, 27, 143], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `verifier` (0x2b7ac3f3) function - pub fn verifier( - &self, - ) -> ::ethers::contract::builders::ContractCall< - M, - ::ethers::core::types::Address, - > { - self.0 - .method_hash([43, 122, 195, 243], ()) - .expect("method not found (this should never happen)") - } - ///Calls the contract's `verifierAddr` (0x663ea2e2) function - pub fn verifier_addr( - &self, - ) -> ::ethers::contract::builders::ContractCall< - M, - ::ethers::core::types::Address, - > { - self.0 - .method_hash([102, 62, 162, 226], ()) - .expect("method not found (this should never happen)") - } - } - impl From<::ethers::contract::Contract> - for EmailAccountRecovery { - fn from(contract: ::ethers::contract::Contract) -> Self { - Self::new(contract.address(), contract.client()) - } - } - ///Container type for all input parameters for the `acceptanceCommandTemplates` function with signature `acceptanceCommandTemplates()` and selector `0x222f6cb5` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "acceptanceCommandTemplates", abi = "acceptanceCommandTemplates()")] - pub struct AcceptanceCommandTemplatesCall; - ///Container type for all input parameters for the `completeRecovery` function with signature `completeRecovery(address,bytes)` and selector `0xc18d09cf` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "completeRecovery", abi = "completeRecovery(address,bytes)")] - pub struct CompleteRecoveryCall { - pub account: ::ethers::core::types::Address, - pub complete_calldata: ::ethers::core::types::Bytes, - } - ///Container type for all input parameters for the `computeAcceptanceTemplateId` function with signature `computeAcceptanceTemplateId(uint256)` and selector `0x32ccc2f2` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "computeAcceptanceTemplateId", - abi = "computeAcceptanceTemplateId(uint256)" - )] - pub struct ComputeAcceptanceTemplateIdCall { - pub template_idx: ::ethers::core::types::U256, - } - ///Container type for all input parameters for the `computeEmailAuthAddress` function with signature `computeEmailAuthAddress(address,bytes32)` and selector `0x3a8eab14` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "computeEmailAuthAddress", - abi = "computeEmailAuthAddress(address,bytes32)" - )] - pub struct ComputeEmailAuthAddressCall { - pub recovered_account: ::ethers::core::types::Address, - pub account_salt: [u8; 32], - } - ///Container type for all input parameters for the `computeRecoveryTemplateId` function with signature `computeRecoveryTemplateId(uint256)` and selector `0x6da99515` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "computeRecoveryTemplateId", - abi = "computeRecoveryTemplateId(uint256)" - )] - pub struct ComputeRecoveryTemplateIdCall { - pub template_idx: ::ethers::core::types::U256, - } - ///Container type for all input parameters for the `dkim` function with signature `dkim()` and selector `0x400ad5ce` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "dkim", abi = "dkim()")] - pub struct DkimCall; - ///Container type for all input parameters for the `dkimAddr` function with signature `dkimAddr()` and selector `0x73357f85` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "dkimAddr", abi = "dkimAddr()")] - pub struct DkimAddrCall; - ///Container type for all input parameters for the `emailAuthImplementation` function with signature `emailAuthImplementation()` and selector `0xb6201692` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "emailAuthImplementation", abi = "emailAuthImplementation()")] - pub struct EmailAuthImplementationCall; - ///Container type for all input parameters for the `emailAuthImplementationAddr` function with signature `emailAuthImplementationAddr()` and selector `0x1098e02e` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "emailAuthImplementationAddr", - abi = "emailAuthImplementationAddr()" - )] - pub struct EmailAuthImplementationAddrCall; - ///Container type for all input parameters for the `extractRecoveredAccountFromAcceptanceCommand` function with signature `extractRecoveredAccountFromAcceptanceCommand(bytes[],uint256)` and selector `0x2c4ce129` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "extractRecoveredAccountFromAcceptanceCommand", - abi = "extractRecoveredAccountFromAcceptanceCommand(bytes[],uint256)" - )] - pub struct ExtractRecoveredAccountFromAcceptanceCommandCall { - pub command_params: ::std::vec::Vec<::ethers::core::types::Bytes>, - pub template_idx: ::ethers::core::types::U256, - } - ///Container type for all input parameters for the `extractRecoveredAccountFromRecoveryCommand` function with signature `extractRecoveredAccountFromRecoveryCommand(bytes[],uint256)` and selector `0xa5e3ee70` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "extractRecoveredAccountFromRecoveryCommand", - abi = "extractRecoveredAccountFromRecoveryCommand(bytes[],uint256)" - )] - pub struct ExtractRecoveredAccountFromRecoveryCommandCall { - pub command_params: ::std::vec::Vec<::ethers::core::types::Bytes>, - pub template_idx: ::ethers::core::types::U256, - } - ///Container type for all input parameters for the `handleAcceptance` function with signature `handleAcceptance((uint256,bytes[],uint256,(string,bytes32,uint256,string,bytes32,bytes32,bool,bytes)),uint256)` and selector `0x0481af67` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "handleAcceptance", - abi = "handleAcceptance((uint256,bytes[],uint256,(string,bytes32,uint256,string,bytes32,bytes32,bool,bytes)),uint256)" - )] - pub struct HandleAcceptanceCall { - pub email_auth_msg: EmailAuthMsg, - pub template_idx: ::ethers::core::types::U256, - } - ///Container type for all input parameters for the `handleRecovery` function with signature `handleRecovery((uint256,bytes[],uint256,(string,bytes32,uint256,string,bytes32,bytes32,bool,bytes)),uint256)` and selector `0xb68126fa` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall( - name = "handleRecovery", - abi = "handleRecovery((uint256,bytes[],uint256,(string,bytes32,uint256,string,bytes32,bytes32,bool,bytes)),uint256)" - )] - pub struct HandleRecoveryCall { - pub email_auth_msg: EmailAuthMsg, - pub template_idx: ::ethers::core::types::U256, - } - ///Container type for all input parameters for the `isActivated` function with signature `isActivated(address)` and selector `0xc9faa7c5` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "isActivated", abi = "isActivated(address)")] - pub struct IsActivatedCall { - pub recovered_account: ::ethers::core::types::Address, - } - ///Container type for all input parameters for the `recoveryCommandTemplates` function with signature `recoveryCommandTemplates()` and selector `0x3ef01b8f` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "recoveryCommandTemplates", abi = "recoveryCommandTemplates()")] - pub struct RecoveryCommandTemplatesCall; - ///Container type for all input parameters for the `verifier` function with signature `verifier()` and selector `0x2b7ac3f3` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "verifier", abi = "verifier()")] - pub struct VerifierCall; - ///Container type for all input parameters for the `verifierAddr` function with signature `verifierAddr()` and selector `0x663ea2e2` - #[derive( - Clone, - ::ethers::contract::EthCall, - ::ethers::contract::EthDisplay, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - #[ethcall(name = "verifierAddr", abi = "verifierAddr()")] - pub struct VerifierAddrCall; - ///Container type for all of the contract's call - #[derive(Clone, ::ethers::contract::EthAbiType, Debug, PartialEq, Eq, Hash)] - pub enum EmailAccountRecoveryCalls { - AcceptanceCommandTemplates(AcceptanceCommandTemplatesCall), - CompleteRecovery(CompleteRecoveryCall), - ComputeAcceptanceTemplateId(ComputeAcceptanceTemplateIdCall), - ComputeEmailAuthAddress(ComputeEmailAuthAddressCall), - ComputeRecoveryTemplateId(ComputeRecoveryTemplateIdCall), - Dkim(DkimCall), - DkimAddr(DkimAddrCall), - EmailAuthImplementation(EmailAuthImplementationCall), - EmailAuthImplementationAddr(EmailAuthImplementationAddrCall), - ExtractRecoveredAccountFromAcceptanceCommand( - ExtractRecoveredAccountFromAcceptanceCommandCall, - ), - ExtractRecoveredAccountFromRecoveryCommand( - ExtractRecoveredAccountFromRecoveryCommandCall, - ), - HandleAcceptance(HandleAcceptanceCall), - HandleRecovery(HandleRecoveryCall), - IsActivated(IsActivatedCall), - RecoveryCommandTemplates(RecoveryCommandTemplatesCall), - Verifier(VerifierCall), - VerifierAddr(VerifierAddrCall), - } - impl ::ethers::core::abi::AbiDecode for EmailAccountRecoveryCalls { - fn decode( - data: impl AsRef<[u8]>, - ) -> ::core::result::Result { - let data = data.as_ref(); - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::AcceptanceCommandTemplates(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::CompleteRecovery(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::ComputeAcceptanceTemplateId(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::ComputeEmailAuthAddress(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::ComputeRecoveryTemplateId(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::Dkim(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::DkimAddr(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::EmailAuthImplementation(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::EmailAuthImplementationAddr(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::ExtractRecoveredAccountFromAcceptanceCommand(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::ExtractRecoveredAccountFromRecoveryCommand(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::HandleAcceptance(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::HandleRecovery(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::IsActivated(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::RecoveryCommandTemplates(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::Verifier(decoded)); - } - if let Ok(decoded) = ::decode( - data, - ) { - return Ok(Self::VerifierAddr(decoded)); - } - Err(::ethers::core::abi::Error::InvalidData.into()) - } - } - impl ::ethers::core::abi::AbiEncode for EmailAccountRecoveryCalls { - fn encode(self) -> Vec { - match self { - Self::AcceptanceCommandTemplates(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::CompleteRecovery(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::ComputeAcceptanceTemplateId(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::ComputeEmailAuthAddress(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::ComputeRecoveryTemplateId(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::Dkim(element) => ::ethers::core::abi::AbiEncode::encode(element), - Self::DkimAddr(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::EmailAuthImplementation(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::EmailAuthImplementationAddr(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::ExtractRecoveredAccountFromAcceptanceCommand(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::ExtractRecoveredAccountFromRecoveryCommand(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::HandleAcceptance(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::HandleRecovery(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::IsActivated(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::RecoveryCommandTemplates(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::Verifier(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - Self::VerifierAddr(element) => { - ::ethers::core::abi::AbiEncode::encode(element) - } - } - } - } - impl ::core::fmt::Display for EmailAccountRecoveryCalls { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - Self::AcceptanceCommandTemplates(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::CompleteRecovery(element) => ::core::fmt::Display::fmt(element, f), - Self::ComputeAcceptanceTemplateId(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::ComputeEmailAuthAddress(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::ComputeRecoveryTemplateId(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::Dkim(element) => ::core::fmt::Display::fmt(element, f), - Self::DkimAddr(element) => ::core::fmt::Display::fmt(element, f), - Self::EmailAuthImplementation(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::EmailAuthImplementationAddr(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::ExtractRecoveredAccountFromAcceptanceCommand(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::ExtractRecoveredAccountFromRecoveryCommand(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::HandleAcceptance(element) => ::core::fmt::Display::fmt(element, f), - Self::HandleRecovery(element) => ::core::fmt::Display::fmt(element, f), - Self::IsActivated(element) => ::core::fmt::Display::fmt(element, f), - Self::RecoveryCommandTemplates(element) => { - ::core::fmt::Display::fmt(element, f) - } - Self::Verifier(element) => ::core::fmt::Display::fmt(element, f), - Self::VerifierAddr(element) => ::core::fmt::Display::fmt(element, f), - } - } - } - impl ::core::convert::From - for EmailAccountRecoveryCalls { - fn from(value: AcceptanceCommandTemplatesCall) -> Self { - Self::AcceptanceCommandTemplates(value) - } - } - impl ::core::convert::From for EmailAccountRecoveryCalls { - fn from(value: CompleteRecoveryCall) -> Self { - Self::CompleteRecovery(value) - } - } - impl ::core::convert::From - for EmailAccountRecoveryCalls { - fn from(value: ComputeAcceptanceTemplateIdCall) -> Self { - Self::ComputeAcceptanceTemplateId(value) - } - } - impl ::core::convert::From - for EmailAccountRecoveryCalls { - fn from(value: ComputeEmailAuthAddressCall) -> Self { - Self::ComputeEmailAuthAddress(value) - } - } - impl ::core::convert::From - for EmailAccountRecoveryCalls { - fn from(value: ComputeRecoveryTemplateIdCall) -> Self { - Self::ComputeRecoveryTemplateId(value) - } - } - impl ::core::convert::From for EmailAccountRecoveryCalls { - fn from(value: DkimCall) -> Self { - Self::Dkim(value) - } - } - impl ::core::convert::From for EmailAccountRecoveryCalls { - fn from(value: DkimAddrCall) -> Self { - Self::DkimAddr(value) - } - } - impl ::core::convert::From - for EmailAccountRecoveryCalls { - fn from(value: EmailAuthImplementationCall) -> Self { - Self::EmailAuthImplementation(value) - } - } - impl ::core::convert::From - for EmailAccountRecoveryCalls { - fn from(value: EmailAuthImplementationAddrCall) -> Self { - Self::EmailAuthImplementationAddr(value) - } - } - impl ::core::convert::From - for EmailAccountRecoveryCalls { - fn from(value: ExtractRecoveredAccountFromAcceptanceCommandCall) -> Self { - Self::ExtractRecoveredAccountFromAcceptanceCommand(value) - } - } - impl ::core::convert::From - for EmailAccountRecoveryCalls { - fn from(value: ExtractRecoveredAccountFromRecoveryCommandCall) -> Self { - Self::ExtractRecoveredAccountFromRecoveryCommand(value) - } - } - impl ::core::convert::From for EmailAccountRecoveryCalls { - fn from(value: HandleAcceptanceCall) -> Self { - Self::HandleAcceptance(value) - } - } - impl ::core::convert::From for EmailAccountRecoveryCalls { - fn from(value: HandleRecoveryCall) -> Self { - Self::HandleRecovery(value) - } - } - impl ::core::convert::From for EmailAccountRecoveryCalls { - fn from(value: IsActivatedCall) -> Self { - Self::IsActivated(value) - } - } - impl ::core::convert::From - for EmailAccountRecoveryCalls { - fn from(value: RecoveryCommandTemplatesCall) -> Self { - Self::RecoveryCommandTemplates(value) - } - } - impl ::core::convert::From for EmailAccountRecoveryCalls { - fn from(value: VerifierCall) -> Self { - Self::Verifier(value) - } - } - impl ::core::convert::From for EmailAccountRecoveryCalls { - fn from(value: VerifierAddrCall) -> Self { - Self::VerifierAddr(value) - } - } - ///Container type for all return fields from the `acceptanceCommandTemplates` function with signature `acceptanceCommandTemplates()` and selector `0x222f6cb5` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct AcceptanceCommandTemplatesReturn( - pub ::std::vec::Vec<::std::vec::Vec<::std::string::String>>, - ); - ///Container type for all return fields from the `computeAcceptanceTemplateId` function with signature `computeAcceptanceTemplateId(uint256)` and selector `0x32ccc2f2` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ComputeAcceptanceTemplateIdReturn(pub ::ethers::core::types::U256); - ///Container type for all return fields from the `computeEmailAuthAddress` function with signature `computeEmailAuthAddress(address,bytes32)` and selector `0x3a8eab14` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ComputeEmailAuthAddressReturn(pub ::ethers::core::types::Address); - ///Container type for all return fields from the `computeRecoveryTemplateId` function with signature `computeRecoveryTemplateId(uint256)` and selector `0x6da99515` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ComputeRecoveryTemplateIdReturn(pub ::ethers::core::types::U256); - ///Container type for all return fields from the `dkim` function with signature `dkim()` and selector `0x400ad5ce` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct DkimReturn(pub ::ethers::core::types::Address); - ///Container type for all return fields from the `dkimAddr` function with signature `dkimAddr()` and selector `0x73357f85` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct DkimAddrReturn(pub ::ethers::core::types::Address); - ///Container type for all return fields from the `emailAuthImplementation` function with signature `emailAuthImplementation()` and selector `0xb6201692` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EmailAuthImplementationReturn(pub ::ethers::core::types::Address); - ///Container type for all return fields from the `emailAuthImplementationAddr` function with signature `emailAuthImplementationAddr()` and selector `0x1098e02e` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EmailAuthImplementationAddrReturn(pub ::ethers::core::types::Address); - ///Container type for all return fields from the `extractRecoveredAccountFromAcceptanceCommand` function with signature `extractRecoveredAccountFromAcceptanceCommand(bytes[],uint256)` and selector `0x2c4ce129` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ExtractRecoveredAccountFromAcceptanceCommandReturn( - pub ::ethers::core::types::Address, - ); - ///Container type for all return fields from the `extractRecoveredAccountFromRecoveryCommand` function with signature `extractRecoveredAccountFromRecoveryCommand(bytes[],uint256)` and selector `0xa5e3ee70` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct ExtractRecoveredAccountFromRecoveryCommandReturn( - pub ::ethers::core::types::Address, - ); - ///Container type for all return fields from the `isActivated` function with signature `isActivated(address)` and selector `0xc9faa7c5` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct IsActivatedReturn(pub bool); - ///Container type for all return fields from the `recoveryCommandTemplates` function with signature `recoveryCommandTemplates()` and selector `0x3ef01b8f` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct RecoveryCommandTemplatesReturn( - pub ::std::vec::Vec<::std::vec::Vec<::std::string::String>>, - ); - ///Container type for all return fields from the `verifier` function with signature `verifier()` and selector `0x2b7ac3f3` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct VerifierReturn(pub ::ethers::core::types::Address); - ///Container type for all return fields from the `verifierAddr` function with signature `verifierAddr()` and selector `0x663ea2e2` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct VerifierAddrReturn(pub ::ethers::core::types::Address); - ///`EmailAuthMsg(uint256,bytes[],uint256,(string,bytes32,uint256,string,bytes32,bytes32,bool,bytes))` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EmailAuthMsg { - pub template_id: ::ethers::core::types::U256, - pub command_params: ::std::vec::Vec<::ethers::core::types::Bytes>, - pub skipped_command_prefix: ::ethers::core::types::U256, - pub proof: EmailProof, - } - ///`EmailProof(string,bytes32,uint256,string,bytes32,bytes32,bool,bytes)` - #[derive( - Clone, - ::ethers::contract::EthAbiType, - ::ethers::contract::EthAbiCodec, - Default, - Debug, - PartialEq, - Eq, - Hash - )] - pub struct EmailProof { - pub domain_name: ::std::string::String, - pub public_key_hash: [u8; 32], - pub timestamp: ::ethers::core::types::U256, - pub masked_command: ::std::string::String, - pub email_nullifier: [u8; 32], - pub account_salt: [u8; 32], - pub is_code_exist: bool, - pub proof: ::ethers::core::types::Bytes, - } -} diff --git a/packages/relayer/src/abis/mod.rs b/packages/relayer/src/abis/mod.rs index c3277054..86477a95 100644 --- a/packages/relayer/src/abis/mod.rs +++ b/packages/relayer/src/abis/mod.rs @@ -1,4 +1,8 @@ +#![allow(clippy::all)] + +#[cfg_attr(rustfmt, rustfmt::skip)] pub mod email_account_recovery; +#[cfg_attr(rustfmt, rustfmt::skip)] pub mod email_auth; pub mod forward_dkim_registry; pub mod user_overridable_dkim_registry; diff --git a/packages/relayer/src/chain.rs b/packages/relayer/src/chain.rs index 8278f57d..096f7308 100644 --- a/packages/relayer/src/chain.rs +++ b/packages/relayer/src/chain.rs @@ -4,22 +4,29 @@ use ethers::middleware::Middleware; use ethers::prelude::*; use ethers::signers::Signer; use relayer_utils::converters::u64_to_u8_array_32; -use relayer_utils::LOG; +use relayer_utils::TemplateValue; const CONFIRMATIONS: usize = 1; type SignerM = SignerMiddleware, LocalWallet>; +/// Represents a client for interacting with the blockchain. #[derive(Debug, Clone)] pub struct ChainClient { pub client: Arc, } impl ChainClient { + /// Sets up a new ChainClient. + /// + /// # Returns + /// + /// A `Result` containing the new `ChainClient` if successful, or an error if not. pub async fn setup() -> Result { let wallet: LocalWallet = PRIVATE_KEY.get().unwrap().parse()?; let provider = Provider::::try_from(CHAIN_RPC_PROVIDER.get().unwrap())?; + // Create a new SignerMiddleware with the provider and wallet let client = Arc::new(SignerMiddleware::new( provider, wallet.with_chain_id(*CHAIN_ID.get().unwrap()), @@ -28,6 +35,19 @@ impl ChainClient { Ok(Self { client }) } + /// Sets the DKIM public key hash. + /// + /// # Arguments + /// + /// * `selector` - The selector string. + /// * `domain_name` - The domain name. + /// * `public_key_hash` - The public key hash as a 32-byte array. + /// * `signature` - The signature as Bytes. + /// * `dkim` - The ECDSA Owned DKIM Registry. + /// + /// # Returns + /// + /// A `Result` containing the transaction hash as a String if successful, or an error if not. pub async fn set_dkim_public_key_hash( &self, domain_name: String, @@ -50,22 +70,38 @@ impl ChainClient { signature, ); let tx = call.send().await?; + + // Wait for the transaction to be confirmed let receipt = tx .log() .confirmations(CONFIRMATIONS) .await? .ok_or(anyhow!("No receipt"))?; + + // Format the transaction hash let tx_hash = receipt.transaction_hash; let tx_hash = format!("0x{}", hex::encode(tx_hash.as_bytes())); Ok(tx_hash) } + /// Checks if a DKIM public key hash is valid. + /// + /// # Arguments + /// + /// * `domain_name` - The domain name. + /// * `public_key_hash` - The public key hash as a 32-byte array. + /// * `dkim` - The ECDSA Owned DKIM Registry. + /// + /// # Returns + /// + /// A `Result` containing a boolean indicating if the hash is valid. pub async fn check_if_dkim_public_key_hash_valid( &self, domain_name: ::std::string::String, public_key_hash: [u8; 32], dkim: ForwardDKIMRegistry, ) -> Result { + // Call the contract method to check if the hash is valid let is_valid = dkim .is_dkim_public_key_hash_valid(domain_name, public_key_hash) .call() @@ -73,38 +109,82 @@ impl ChainClient { Ok(is_valid) } - pub async fn get_dkim_from_wallet( + /// Gets the DKIM from a controller. + /// + /// # Arguments + /// + /// * `controller_eth_addr` - The controller Ethereum address as a string. + /// + /// # Returns + /// + /// A `Result` containing the ECDSA Owned DKIM Registry if successful, or an error if not. + pub async fn get_dkim_from_controller( &self, - controller_eth_addr: &String, + controller_eth_addr: &str, ) -> Result, anyhow::Error> { let controller_eth_addr: H160 = controller_eth_addr.parse()?; + + // Create a new EmailAccountRecovery contract instance let contract = EmailAccountRecovery::new(controller_eth_addr, self.client.clone()); + + // Call the dkim method to get the DKIM registry address let dkim = contract.dkim().call().await?; Ok(ForwardDKIMRegistry::new(dkim, self.client.clone())) } + /// Gets the DKIM from an email auth address. + /// + /// # Arguments + /// + /// * `email_auth_addr` - The email auth address as a string. + /// + /// # Returns + /// + /// A `Result` containing the ECDSA Owned DKIM Registry if successful, or an error if not. pub async fn get_dkim_from_email_auth( &self, email_auth_addr: &String, ) -> Result, anyhow::Error> { let email_auth_address: H160 = email_auth_addr.parse()?; + + // Create a new EmailAuth contract instance let contract = EmailAuth::new(email_auth_address, self.client.clone()); + + // Call the dkim_registry_addr method to get the DKIM registry address let dkim = contract.dkim_registry_addr().call().await?; Ok(ForwardDKIMRegistry::new(dkim, self.client.clone())) } + /// Gets the email auth address from a wallet. + /// + /// # Arguments + /// + /// * `controller_eth_addr` - The controller Ethereum address as a string. + /// * `wallet_addr` - The wallet address as a string. + /// * `account_salt` - The account salt as a string. + /// + /// # Returns + /// + /// A `Result` containing the email auth address as H160 if successful, or an error if not. pub async fn get_email_auth_addr_from_wallet( &self, - controller_eth_addr: &String, - wallet_addr: &String, - account_salt: &String, + controller_eth_addr: &str, + wallet_addr: &str, + account_salt: &str, ) -> Result { + // Parse the controller and wallet Ethereum addresses let controller_eth_addr: H160 = controller_eth_addr.parse()?; let wallet_address: H160 = wallet_addr.parse()?; + + // Create a new EmailAccountRecovery contract instance let contract = EmailAccountRecovery::new(controller_eth_addr, self.client.clone()); + + // Decode the account salt let account_salt_bytes = hex::decode(account_salt.trim_start_matches("0x")) .map_err(|e| anyhow!("Failed to decode account_salt: {}", e))?; + + // Compute the email auth address let email_auth_addr = contract .compute_email_auth_address( wallet_address, @@ -116,197 +196,432 @@ impl ChainClient { Ok(email_auth_addr) } - pub async fn is_wallet_deployed(&self, wallet_addr_str: &String) -> bool { - let wallet_addr: H160 = wallet_addr_str.parse().unwrap(); + /// Checks if a wallet is deployed. + /// + /// # Arguments + /// + /// * `wallet_addr_str` - The wallet address as a string. + /// + /// # Returns + /// + /// A `Result` containing a boolean indicating if the wallet is deployed. + pub async fn is_wallet_deployed(&self, wallet_addr_str: &str) -> Result { + // Parse the wallet address + let wallet_addr: H160 = wallet_addr_str.parse().map_err(ChainError::HexError)?; + + // Get the bytecode at the wallet address match self.client.get_code(wallet_addr, None).await { - Ok(code) => !code.is_empty(), + Ok(code) => Ok(!code.is_empty()), Err(e) => { // Log the error or handle it as needed - error!(LOG, "Error querying contract code: {:?}", e); - false + Err(ChainError::signer_middleware_error( + "Failed to check if wallet is deployed", + e, + )) } } } + /// Gets the acceptance command templates. + /// + /// # Arguments + /// + /// * `controller_eth_addr` - The controller Ethereum address as a string. + /// * `template_idx` - The template index. + /// + /// # Returns + /// + /// A `Result` containing a vector of acceptance command templates. pub async fn get_acceptance_command_templates( &self, - controller_eth_addr: &String, + controller_eth_addr: &str, template_idx: u64, - ) -> Result, anyhow::Error> { - let controller_eth_addr: H160 = controller_eth_addr.parse()?; + ) -> Result, ChainError> { + // Parse the controller Ethereum address + let controller_eth_addr: H160 = + controller_eth_addr.parse().map_err(ChainError::HexError)?; + + // Create a new EmailAccountRecovery contract instance let contract = EmailAccountRecovery::new(controller_eth_addr, self.client.clone()); + + // Get the acceptance command templates let templates = contract .acceptance_command_templates() .call() .await - .map_err(|e| anyhow::Error::from(e))?; + .map_err(|e| { + ChainError::contract_error("Failed to get acceptance subject templates", e) + })?; Ok(templates[template_idx as usize].clone()) } + /// Gets the recovery command templates. + /// + /// # Arguments + /// + /// * `controller_eth_addr` - The controller Ethereum address as a string. + /// * `template_idx` - The template index. + /// + /// # Returns + /// + /// A `Result` containing a vector of recovery command templates. pub async fn get_recovery_command_templates( &self, - controller_eth_addr: &String, + controller_eth_addr: &str, template_idx: u64, - ) -> Result, anyhow::Error> { - let controller_eth_addr: H160 = controller_eth_addr.parse()?; + ) -> Result, ChainError> { + // Parse the controller Ethereum address + let controller_eth_addr: H160 = + controller_eth_addr.parse().map_err(ChainError::HexError)?; + + // Create a new EmailAccountRecovery contract instance let contract = EmailAccountRecovery::new(controller_eth_addr, self.client.clone()); + + // Get the recovery command templates let templates = contract .recovery_command_templates() .call() .await - .map_err(|e| anyhow::Error::from(e))?; + .map_err(|e| { + ChainError::contract_error("Failed to get recovery command templates", e) + })?; Ok(templates[template_idx as usize].clone()) } + /// Completes the recovery process. + /// + /// # Arguments + /// + /// * `controller_eth_addr` - The controller Ethereum address as a string. + /// * `account_eth_addr` - The account Ethereum address as a string. + /// * `complete_calldata` - The complete calldata as a string. + /// + /// # Returns + /// + /// A `Result` containing a boolean indicating if the recovery was successful. pub async fn complete_recovery( &self, - controller_eth_addr: &String, - account_eth_addr: &String, - complete_calldata: &String, - ) -> Result { - let controller_eth_addr: H160 = controller_eth_addr.parse()?; + controller_eth_addr: &str, + account_eth_addr: &str, + complete_calldata: &str, + ) -> Result { + // Parse the controller and account Ethereum addresses + let controller_eth_addr: H160 = + controller_eth_addr.parse().map_err(ChainError::HexError)?; + + // Create a new EmailAccountRecovery contract instance let contract = EmailAccountRecovery::new(controller_eth_addr, self.client.clone()); + + // Decode the complete calldata let decoded_calldata = - hex::decode(&complete_calldata.trim_start_matches("0x")).expect("Decoding failed"); - let call = contract.complete_recovery( - account_eth_addr - .parse::() - .expect("Invalid H160 address"), - Bytes::from(decoded_calldata), - ); - let tx = call.send().await?; - // If the transaction is successful, the function will return true and false otherwise. + hex::decode(complete_calldata.trim_start_matches("0x")).expect("Decoding failed"); + + let account_eth_addr = account_eth_addr + .parse::() + .map_err(ChainError::HexError)?; + + // Call the complete_recovery method + let call = contract.complete_recovery(account_eth_addr, Bytes::from(decoded_calldata)); + + let tx = call + .send() + .await + .map_err(|e| ChainError::contract_error("Failed to call complete_recovery", e))?; + + // Wait for the transaction to be confirmed let receipt = tx .log() .confirmations(CONFIRMATIONS) - .await? + .await + .map_err(|e| { + ChainError::provider_error( + "Failed to get receipt after calling complete_recovery", + e, + ) + })? .ok_or(anyhow!("No receipt"))?; + + // Check if the transaction was successful Ok(receipt .status .map(|status| status == U64::from(1)) .unwrap_or(false)) } + /// Handles the acceptance process. + /// + /// # Arguments + /// + /// * `controller_eth_addr` - The controller Ethereum address as a string. + /// * `email_auth_msg` - The email authentication message. + /// * `template_idx` - The template index. + /// + /// # Returns + /// + /// A `Result` containing a boolean indicating if the acceptance was successful. pub async fn handle_acceptance( &self, - controller_eth_addr: &String, + controller_eth_addr: &str, email_auth_msg: EmailAuthMsg, template_idx: u64, - ) -> Result { + ) -> std::result::Result { + // Parse the controller Ethereum address let controller_eth_addr: H160 = controller_eth_addr.parse()?; + + // Create a new EmailAccountRecovery contract instance let contract = EmailAccountRecovery::new(controller_eth_addr, self.client.clone()); + + // Call the handle_acceptance method let call = contract.handle_acceptance(email_auth_msg, template_idx.into()); - let tx = call.send().await?; - // If the transaction is successful, the function will return true and false otherwise. + let tx = call + .send() + .await + .map_err(|e| ChainError::contract_error("Failed to call handle_acceptance", e))?; + + // Wait for the transaction to be confirmed let receipt = tx .log() .confirmations(CONFIRMATIONS) - .await? + .await + .map_err(|e| { + ChainError::provider_error( + "Failed to get receipt after calling handle_acceptance", + e, + ) + })? .ok_or(anyhow!("No receipt"))?; + + // Check if the transaction was successful Ok(receipt .status .map(|status| status == U64::from(1)) .unwrap_or(false)) } + /// Handles the recovery process. + /// + /// # Arguments + /// + /// * `controller_eth_addr` - The controller Ethereum address as a string. + /// * `email_auth_msg` - The email authentication message. + /// * `template_idx` - The template index. + /// + /// # Returns + /// + /// A `Result` containing a boolean indicating if the recovery was successful. pub async fn handle_recovery( &self, - controller_eth_addr: &String, + controller_eth_addr: &str, email_auth_msg: EmailAuthMsg, template_idx: u64, - ) -> Result { + ) -> std::result::Result { + // Parse the controller Ethereum address let controller_eth_addr: H160 = controller_eth_addr.parse()?; + + // Create a new EmailAccountRecovery contract instance let contract = EmailAccountRecovery::new(controller_eth_addr, self.client.clone()); + + // Call the handle_recovery method let call = contract.handle_recovery(email_auth_msg, template_idx.into()); - let tx = call.send().await?; - // If the transaction is successful, the function will return true and false otherwise. + let tx = call + .send() + .await + .map_err(|e| ChainError::contract_error("Failed to call handle_recovery", e))?; + + // Wait for the transaction to be confirmed let receipt = tx .log() .confirmations(CONFIRMATIONS) - .await? + .await + .map_err(|e| { + ChainError::provider_error("Failed to get receipt after calling handle_recovery", e) + })? .ok_or(anyhow!("No receipt"))?; + + // Check if the transaction was successful Ok(receipt .status .map(|status| status == U64::from(1)) .unwrap_or(false)) } - pub async fn get_bytecode(&self, wallet_addr: &String) -> Result { - let wallet_address: H160 = wallet_addr.parse()?; - Ok(self.client.get_code(wallet_address, None).await?) + /// Gets the bytecode of a wallet. + /// + /// # Arguments + /// + /// * `wallet_addr` - The wallet address as a string. + /// + /// # Returns + /// + /// A `Result` containing the bytecode as Bytes. + pub async fn get_bytecode(&self, wallet_addr: &str) -> std::result::Result { + // Parse the wallet address + let wallet_address: H160 = wallet_addr.parse().map_err(ChainError::HexError)?; + + // Get the bytecode at the wallet address + let client_code = self + .client + .get_code(wallet_address, None) + .await + .map_err(|e| ChainError::signer_middleware_error("Failed to get bytecode", e))?; + Ok(client_code) } + /// Gets the storage at a specific slot for a wallet. + /// + /// # Arguments + /// + /// * `wallet_addr` - The wallet address as a string. + /// * `slot` - The storage slot. + /// + /// # Returns + /// + /// A `Result` containing the storage value as H256. pub async fn get_storage_at( &self, - wallet_addr: &String, + wallet_addr: &str, slot: u64, ) -> Result { + // Parse the wallet address let wallet_address: H160 = wallet_addr.parse()?; + + // Get the storage at the specified slot Ok(self .client .get_storage_at(wallet_address, u64_to_u8_array_32(slot).into(), None) .await?) } + /// Gets the recovered account from an acceptance command. + /// + /// # Arguments + /// + /// * `controller_eth_addr` - The controller Ethereum address as a string. + /// * `command_params` - The command parameters. + /// * `template_idx` - The template index. + /// + /// # Returns + /// + /// A `Result` containing the recovered account address as H160. pub async fn get_recovered_account_from_acceptance_command( &self, - controller_eth_addr: &String, + controller_eth_addr: &str, command_params: Vec, template_idx: u64, - ) -> Result { - let controller_eth_addr: H160 = controller_eth_addr.parse()?; + ) -> Result { + // Parse the controller Ethereum address + let controller_eth_addr: H160 = + controller_eth_addr.parse().map_err(ChainError::HexError)?; + + // Create a new EmailAccountRecovery contract instance let contract = EmailAccountRecovery::new(controller_eth_addr, self.client.clone()); + + // Encode the command parameters let command_params_bytes = command_params - .iter() // Change here: use iter() instead of map() directly on Vec + .iter() .map(|s| { - s.abi_encode(None) // Assuming decimal_size is not needed or can be None + s.abi_encode(None) .unwrap_or_else(|_| Bytes::from("Error encoding".as_bytes().to_vec())) - }) // Error handling + }) .collect::>(); + + // Call the extract_recovered_account_from_acceptance_command method let recovered_account = contract .extract_recovered_account_from_acceptance_command( command_params_bytes, template_idx.into(), ) .call() - .await?; + .await + .map_err(|e| { + ChainError::contract_error( + "Failed to get recovered account from acceptance subject", + e, + ) + })?; Ok(recovered_account) } + /// Gets the recovered account from a recovery command. + /// + /// # Arguments + /// + /// * `controller_eth_addr` - The controller Ethereum address as a string. + /// * `command_params` - The command parameters. + /// * `template_idx` - The template index. + /// + /// # Returns + /// + /// A `Result` containing the recovered account address as H160. pub async fn get_recovered_account_from_recovery_command( &self, - controller_eth_addr: &String, + controller_eth_addr: &str, command_params: Vec, template_idx: u64, - ) -> Result { - let controller_eth_addr: H160 = controller_eth_addr.parse()?; + ) -> Result { + // Parse the controller Ethereum address + let controller_eth_addr: H160 = + controller_eth_addr.parse().map_err(ChainError::HexError)?; + + // Create a new EmailAccountRecovery contract instance let contract = EmailAccountRecovery::new(controller_eth_addr, self.client.clone()); + + // Encode the command parameters let command_params_bytes = command_params - .iter() // Change here: use iter() instead of map() directly on Vec + .iter() .map(|s| { - s.abi_encode(None) // Assuming decimal_size is not needed or can be None - .unwrap_or_else(|_| Bytes::from("Error encoding".as_bytes().to_vec())) - }) // Error handling - .collect::>(); + s.abi_encode(None).map_err(|_| { + ChainError::Validation("Error encoding subject parameters".to_string()) + }) + }) + .collect::, ChainError>>()?; + + // Call the extract_recovered_account_from_recovery_command method let recovered_account = contract .extract_recovered_account_from_recovery_command( command_params_bytes, template_idx.into(), ) .call() - .await?; + .await + .map_err(|e| { + ChainError::contract_error( + "Failed to get recovered account from recovery subject", + e, + ) + })?; Ok(recovered_account) } + /// Checks if an account is activated. + /// + /// # Arguments + /// + /// * `controller_eth_addr` - The controller Ethereum address as a string. + /// * `account_eth_addr` - The account Ethereum address as a string. + /// + /// # Returns + /// + /// A `Result` containing a boolean indicating if the account is activated. pub async fn get_is_activated( &self, - controller_eth_addr: &String, - account_eth_addr: &String, - ) -> Result { - let controller_eth_addr: H160 = controller_eth_addr.parse()?; - let account_eth_addr: H160 = account_eth_addr.parse()?; + controller_eth_addr: &str, + account_eth_addr: &str, + ) -> Result { + // Parse the controller and account Ethereum addresses + let controller_eth_addr: H160 = + controller_eth_addr.parse().map_err(ChainError::HexError)?; + let account_eth_addr: H160 = account_eth_addr.parse().map_err(ChainError::HexError)?; + + // Create a new EmailAccountRecovery contract instance let contract = EmailAccountRecovery::new(controller_eth_addr, self.client.clone()); - let is_activated = contract.is_activated(account_eth_addr).call().await?; + + // Call the is_activated method + let is_activated = contract + .is_activated(account_eth_addr) + .call() + .await + .map_err(|e| ChainError::contract_error("Failed to check if is activated", e))?; Ok(is_activated) } } diff --git a/packages/relayer/src/config.rs b/packages/relayer/src/config.rs index bed30ff4..6615cca5 100644 --- a/packages/relayer/src/config.rs +++ b/packages/relayer/src/config.rs @@ -4,13 +4,17 @@ use std::{env, path::PathBuf}; use dotenv::dotenv; +/// Configuration struct for the Relayer service. +/// +/// This struct holds various configuration parameters needed for the Relayer service, +/// including SMTP settings, database path, web server address, and blockchain-related information. #[derive(Clone)] pub struct RelayerConfig { pub smtp_server: String, pub relayer_email_addr: String, pub db_path: String, pub web_server_address: String, - pub circuits_dir_path: PathBuf, + pub regex_json_dir_path: PathBuf, pub prover_address: String, pub chain_rpc_provider: String, pub chain_rpc_explorer: String, @@ -21,15 +25,25 @@ pub struct RelayerConfig { } impl RelayerConfig { + /// Creates a new instance of RelayerConfig. + /// + /// This function loads environment variables using dotenv and populates + /// the RelayerConfig struct with the values. + /// + /// # Returns + /// + /// A new instance of RelayerConfig. pub fn new() -> Self { + // Load environment variables from .env file dotenv().ok(); + // Construct and return the RelayerConfig instance Self { smtp_server: env::var(SMTP_SERVER_KEY).unwrap(), relayer_email_addr: env::var(RELAYER_EMAIL_ADDR_KEY).unwrap(), db_path: env::var(DATABASE_PATH_KEY).unwrap(), web_server_address: env::var(WEB_SERVER_ADDRESS_KEY).unwrap(), - circuits_dir_path: env::var(CIRCUITS_DIR_PATH_KEY).unwrap().into(), + regex_json_dir_path: env::var(REGEX_JSON_DIR_PATH_KEY).unwrap().into(), prover_address: env::var(PROVER_ADDRESS_KEY).unwrap(), chain_rpc_provider: env::var(CHAIN_RPC_PROVIDER_KEY).unwrap(), chain_rpc_explorer: env::var(CHAIN_RPC_EXPLORER_KEY).unwrap(), @@ -45,6 +59,13 @@ impl RelayerConfig { } impl Default for RelayerConfig { + /// Provides a default instance of RelayerConfig. + /// + /// This implementation simply calls the `new()` method to create a default instance. + /// + /// # Returns + /// + /// A default instance of RelayerConfig. fn default() -> Self { Self::new() } diff --git a/packages/relayer/src/core.rs b/packages/relayer/src/core.rs index 9939ab04..a06a6324 100644 --- a/packages/relayer/src/core.rs +++ b/packages/relayer/src/core.rs @@ -1,6 +1,8 @@ #![allow(clippy::upper_case_acronyms)] #![allow(clippy::identity_op)] +use std::fs; + use crate::abis::email_account_recovery::{EmailAuthMsg, EmailProof}; use crate::*; @@ -8,13 +10,25 @@ use ethers::{ abi::{encode, Token}, utils::keccak256, }; -use relayer_utils::{extract_substr_idxes, generate_email_circuit_input, EmailCircuitParams, LOG}; +use relayer_utils::{ + extract_substr_idxes, extract_template_vals_from_command, generate_email_circuit_input, + generate_proof, EmailCircuitParams, LOG, +}; const DOMAIN_FIELDS: usize = 9; const COMMAND_FIELDS: usize = 20; const EMAIL_ADDR_FIELDS: usize = 9; -pub async fn handle_email(email: String) -> Result { +/// Handles an incoming email for authentication or recovery. +/// +/// # Arguments +/// +/// * `email` - The raw email string to be processed. +/// +/// # Returns +/// +/// A `Result` containing an `EmailAuthEvent` on success, or an `EmailError` on failure. +pub async fn handle_email(email: String) -> Result { let parsed_email = ParsedEmail::new_from_raw_email(&email).await?; trace!(LOG, "email: {}", email); let guardian_email_addr = parsed_email.get_from_addr()?; @@ -22,40 +36,53 @@ pub async fn handle_email(email: String) -> Result { trace!(LOG, "From address: {}", guardian_email_addr); let email_body = parsed_email.get_cleaned_body()?; - let request_decomposed_def = - serde_json::from_str(include_str!("./regex_json/request_def.json"))?; + let request_def_path = + PathBuf::from(REGEX_JSON_DIR_PATH.get().unwrap()).join("request_def.json"); + let request_def_contents = fs::read_to_string(&request_def_path).map_err(|e| { + anyhow!( + "Failed to read file {:?}: {}", + request_def_path.display(), + e + ) + })?; + let request_decomposed_def = serde_json::from_str(&request_def_contents) + .map_err(|e| EmailError::Parse(format!("Failed to parse request_def.json: {}", e)))?; let request_idxes = extract_substr_idxes(&email, &request_decomposed_def)?; if request_idxes.is_empty() { - bail!(WRONG_COMMAND_FORMAT); + return Err(EmailError::Body(WRONG_COMMAND_FORMAT.to_string())); } info!(LOG, "Request idxes: {:?}", request_idxes); let request_id = &email[request_idxes[0].0..request_idxes[0].1]; let request_id_u32 = request_id .parse::() - .map_err(|e| anyhow!("Failed to parse request_id to u64: {}", e))?; - let request_record = DB.get_request(request_id_u32).await?; - if request_record.is_none() { - return Ok(EmailAuthEvent::Error { - email_addr: guardian_email_addr, - error: format!("Request {} not found", request_id), - }); - } - let request = request_record.unwrap(); + .map_err(|e| EmailError::Parse(format!("Failed to parse request_id to u64: {}", e)))?; + let request = match DB.get_request(request_id_u32).await? { + Some(req) => req, + None => { + let original_subject = parsed_email.get_subject_all()?; + return Ok(EmailAuthEvent::Error { + email_addr: guardian_email_addr, + error: format!("Request {} not found", request_id), + original_subject, + original_message_id: parsed_email.get_message_id().ok(), + }); + } + }; if request.guardian_email_addr != guardian_email_addr { - return Err(anyhow!( + return Err(EmailError::EmailAddress(format!( "Guardian email address in the request {} is not equal to the one in the email {}", - request.guardian_email_addr, - guardian_email_addr - )); + request.guardian_email_addr, guardian_email_addr + ))); } + let account_code_str = DB .get_account_code_from_wallet_and_email(&request.account_eth_addr, &guardian_email_addr) .await? - .ok_or(anyhow!( + .ok_or(EmailError::NotFound(format!( "The user of the wallet address {} and the email address {} is not registered.", - request.account_eth_addr, - guardian_email_addr - ))?; + request.account_eth_addr, guardian_email_addr + )))?; + check_and_update_dkim( &email, &parsed_email, @@ -63,311 +90,207 @@ pub async fn handle_email(email: String) -> Result { &request.account_eth_addr, request.account_salt.as_deref().unwrap_or_default(), ) - .await?; - - if let Ok(invitation_code) = parsed_email.get_invitation_code(false) { - if !request.is_for_recovery { - trace!(LOG, "Email with account code"); + .await + .map_err(|e| EmailError::Dkim(e.to_string()))?; + + let invitation_code = match parsed_email.get_invitation_code(false) { + Ok(code) => Some(code), + Err(_) => None, + }; + + let params = EmailRequestContext { + request, + email_body, + account_code_str, + email, + parsed_email, + }; + + handle_email_request(params, invitation_code).await +} - if account_code_str != invitation_code { - return Err(anyhow!( +/// Handles the email request based on the presence of an invitation code and whether it's for recovery. +/// +/// # Arguments +/// +/// * `params` - The `EmailRequestContext` containing request details. +/// * `invitation_code` - An optional invitation code. +/// +/// # Returns +/// +/// A `Result` containing an `EmailAuthEvent` or an `EmailError`. +async fn handle_email_request( + params: EmailRequestContext, + invitation_code: Option, +) -> Result { + match (invitation_code, params.request.is_for_recovery) { + (Some(invitation_code), is_for_recovery) if !is_for_recovery => { + if params.account_code_str != invitation_code { + return Err(EmailError::Body(format!( "Stored account code is not equal to one in the email. Stored: {}, Email: {}", - account_code_str, - invitation_code - )); - } - - let command_template = CLIENT - .get_acceptance_command_templates( - &request.controller_eth_addr, - request.template_idx, - ) - .await?; - - let command_params = - match extract_template_vals_from_command(&email_body, command_template) { - Ok(command_params) => command_params, - Err(e) => { - return Ok(EmailAuthEvent::Error { - email_addr: guardian_email_addr, - error: format!("Invalid Command, {}", e), - }); - } - }; - - let command_params_encoded: Vec = command_params - .iter() - .map(|param| param.abi_encode(None).unwrap()) - .collect(); - - let tokens = vec![ - Token::Uint((*EMAIL_ACCOUNT_RECOVERY_VERSION_ID.get().unwrap()).into()), - Token::String("ACCEPTANCE".to_string()), - Token::Uint(request.template_idx.into()), - ]; - - let template_id = keccak256(encode(&tokens)); - - let circuit_input = generate_email_circuit_input( - &email, - &AccountCode::from(hex_to_field(&format!("0x{}", &account_code_str))?), - Some(EmailCircuitParams { - max_header_length: Some(1024), - max_body_length: Some(1024), - sha_precompute_selector: Some(SHA_PRECOMPUTE_SELECTOR.to_string()), - ignore_body_hash_check: Some(false), - }), - ) - .await?; - - let (proof, public_signals) = - generate_proof(&circuit_input, "email_auth", PROVER_ADDRESS.get().unwrap()).await?; - - info!(LOG, "Public signals: {:?}", public_signals); - - let account_salt = u256_to_bytes32(&public_signals[COMMAND_FIELDS + DOMAIN_FIELDS + 3]); - let is_code_exist = public_signals[COMMAND_FIELDS + DOMAIN_FIELDS + 4] == 1u8.into(); - let masked_command = get_masked_command(public_signals.clone(), DOMAIN_FIELDS + 3)?; - - let email_proof = EmailProof { - proof: proof, - domain_name: parsed_email.get_email_domain()?, - public_key_hash: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 0]), - timestamp: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 2]).into(), - masked_command: masked_command, - email_nullifier: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 1]), - account_salt, - is_code_exist, + params.account_code_str, invitation_code + ))); }; - - let email_auth_msg = EmailAuthMsg { - template_id: template_id.into(), - command_params: command_params_encoded, - skipped_command_prefix: U256::zero(), - proof: email_proof.clone(), - }; - - info!(LOG, "Email Auth Msg: {:?}", email_auth_msg); - info!(LOG, "Request: {:?}", request); - - match CLIENT - .handle_acceptance( - &request.controller_eth_addr, - email_auth_msg, - request.template_idx, - ) - .await - { - Ok(true) => { - let creds = Credentials { - account_code: invitation_code, - account_eth_addr: request.account_eth_addr.clone(), - guardian_email_addr: guardian_email_addr.clone(), - is_set: true, - }; - - DB.update_credentials_of_account_code(&creds).await?; - - let updated_request = Request { - account_eth_addr: request.account_eth_addr.clone(), - controller_eth_addr: request.controller_eth_addr.clone(), - guardian_email_addr: guardian_email_addr.clone(), - template_idx: request.template_idx, - is_for_recovery: request.is_for_recovery, - is_processed: true, - request_id: request.request_id, - is_success: Some(true), - email_nullifier: Some(field_to_hex( - &bytes32_to_fr(&email_proof.email_nullifier).unwrap(), - )), - account_salt: Some(bytes32_to_hex(&account_salt)), - }; - - DB.update_request(&updated_request).await?; - - Ok(EmailAuthEvent::AcceptanceSuccess { - account_eth_addr: request.account_eth_addr, - guardian_email_addr, - request_id: request_id_u32, - }) - } - Ok(false) => { - let updated_request = Request { - account_eth_addr: request.account_eth_addr.clone(), - controller_eth_addr: request.controller_eth_addr.clone(), - guardian_email_addr: guardian_email_addr.clone(), - template_idx: request.template_idx, - is_for_recovery: request.is_for_recovery, - is_processed: true, - request_id: request.request_id, - is_success: Some(false), - email_nullifier: Some(field_to_hex( - &bytes32_to_fr(&email_proof.email_nullifier).unwrap(), - )), - account_salt: Some(bytes32_to_hex(&account_salt)), - }; - - DB.update_request(&updated_request).await?; - - Ok(EmailAuthEvent::Error { - email_addr: guardian_email_addr, - error: "Failed to handle acceptance".to_string(), - }) - } - Err(e) => Err(anyhow!("Failed to handle acceptance: {}", e)), - } - } else { - return Ok(EmailAuthEvent::Error { - email_addr: guardian_email_addr, + trace!(LOG, "Email with account code"); + accept(params, invitation_code).await + } + (None, is_for_recovery) if is_for_recovery => recover(params).await, + (Some(_), _) => { + let original_subject = params.parsed_email.get_subject_all()?; + Ok(EmailAuthEvent::Error { + email_addr: params.request.guardian_email_addr, error: "Account code found and for recovery".to_string(), - }); + original_subject, + original_message_id: params.parsed_email.get_message_id().ok(), + }) } + (None, _) => { + let original_subject = params.parsed_email.get_subject_all()?; + Ok(EmailAuthEvent::Error { + email_addr: params.request.guardian_email_addr, + error: "No account code found and not for recovery".to_string(), + original_subject, + original_message_id: params.parsed_email.get_message_id().ok(), + }) + } + } +} + +/// Handles the acceptance of an email authentication request. +/// +/// # Arguments +/// +/// * `params` - The `EmailRequestContext` containing request details. +/// * `invitation_code` - The invitation code from the email. +/// +/// # Returns +/// +/// A `Result` containing an `EmailAuthEvent` or an `EmailError`. +async fn accept( + params: EmailRequestContext, + invitation_code: String, +) -> Result { + let (email_auth_msg, email_proof, account_salt) = get_email_auth_msg(¶ms).await?; + + info!(LOG, "Email Auth Msg: {:?}", email_auth_msg); + info!(LOG, "Request: {:?}", params.request); + + // Handle the acceptance with the client + let is_accepted = CLIENT + .handle_acceptance( + ¶ms.request.controller_eth_addr, + email_auth_msg, + params.request.template_idx, + ) + .await?; + + update_request( + ¶ms, + is_accepted, + email_proof.email_nullifier, + account_salt, + ) + .await?; + + let original_subject = params.parsed_email.get_subject_all()?; + if is_accepted { + let creds = Credentials { + account_code: invitation_code, + account_eth_addr: params.request.account_eth_addr.clone(), + guardian_email_addr: params.request.guardian_email_addr.clone(), + is_set: true, + }; + DB.update_credentials_of_account_code(&creds).await?; + + Ok(EmailAuthEvent::AcceptanceSuccess { + account_eth_addr: params.request.account_eth_addr, + guardian_email_addr: params.request.guardian_email_addr, + request_id: params.request.request_id, + original_subject, + original_message_id: params.parsed_email.get_message_id().ok(), + }) } else { - if request.is_for_recovery { - let command_template = CLIENT - .get_recovery_command_templates(&request.controller_eth_addr, request.template_idx) - .await?; - - let command_params = - match extract_template_vals_from_command(&email_body, command_template) { - Ok(command_params) => command_params, - Err(e) => { - return Ok(EmailAuthEvent::Error { - email_addr: guardian_email_addr, - error: format!("Invalid Command, {}", e), - }); - } - }; - - let command_params_encoded: Vec = command_params - .iter() - .map(|param| param.abi_encode(None).unwrap()) - .collect(); - - let tokens = vec![ - Token::Uint((*EMAIL_ACCOUNT_RECOVERY_VERSION_ID.get().unwrap()).into()), - Token::String("RECOVERY".to_string()), - Token::Uint(request.template_idx.into()), - ]; - - let template_id = keccak256(encode(&tokens)); - - let circuit_input = generate_email_circuit_input( - &email, - &AccountCode::from(hex_to_field(&format!("0x{}", &account_code_str))?), - Some(EmailCircuitParams { - max_header_length: Some(1024), - max_body_length: Some(1024), - sha_precompute_selector: Some(SHA_PRECOMPUTE_SELECTOR.to_string()), - ignore_body_hash_check: Some(false), - }), - ) - .await?; - - let (proof, public_signals) = - generate_proof(&circuit_input, "email_auth", PROVER_ADDRESS.get().unwrap()).await?; - - let account_salt = u256_to_bytes32(&public_signals[COMMAND_FIELDS + DOMAIN_FIELDS + 3]); - let is_code_exist = public_signals[COMMAND_FIELDS + DOMAIN_FIELDS + 4] == 1u8.into(); - let masked_command = get_masked_command(public_signals.clone(), DOMAIN_FIELDS + 3)?; - - let email_proof = EmailProof { - proof: proof, - domain_name: parsed_email.get_email_domain()?, - public_key_hash: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 0]), - timestamp: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 2]).into(), - masked_command: masked_command, - email_nullifier: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 1]), - account_salt, - is_code_exist, - }; + let original_subject = params.parsed_email.get_subject_all()?; + Ok(EmailAuthEvent::Error { + email_addr: params.request.guardian_email_addr, + error: "Failed to handle acceptance".to_string(), + original_subject, + original_message_id: params.parsed_email.get_message_id().ok(), + }) + } +} - let email_auth_msg = EmailAuthMsg { - template_id: template_id.into(), - command_params: command_params_encoded, - skipped_command_prefix: U256::zero(), - proof: email_proof.clone(), - }; +/// Handles the recovery process for an email authentication request. +/// +/// # Arguments +/// +/// * `params` - The `EmailRequestContext` containing request details. +/// +/// # Returns +/// +/// A `Result` containing an `EmailAuthEvent` or an `EmailError`. +async fn recover(params: EmailRequestContext) -> Result { + let (email_auth_msg, email_proof, account_salt) = get_email_auth_msg(¶ms).await?; + + info!(LOG, "Email Auth Msg: {:?}", email_auth_msg); + info!(LOG, "Request: {:?}", params.request); + + // Handle the recovery with the client + let is_success = CLIENT + .handle_recovery( + ¶ms.request.controller_eth_addr, + email_auth_msg, + params.request.template_idx, + ) + .await?; + + update_request( + ¶ms, + is_success, + email_proof.email_nullifier, + account_salt, + ) + .await?; - println!("Email Auth Msg: {:?}", email_auth_msg); - - match CLIENT - .handle_recovery( - &request.controller_eth_addr, - email_auth_msg, - request.template_idx, - ) - .await - { - Ok(true) => { - let updated_request = Request { - account_eth_addr: request.account_eth_addr.clone(), - controller_eth_addr: request.controller_eth_addr.clone(), - guardian_email_addr: guardian_email_addr.clone(), - template_idx: request.template_idx, - is_for_recovery: request.is_for_recovery, - is_processed: true, - request_id: request.request_id, - is_success: Some(true), - email_nullifier: Some(field_to_hex( - &bytes32_to_fr(&email_proof.email_nullifier).unwrap(), - )), - account_salt: Some(bytes32_to_hex(&account_salt)), - }; - - DB.update_request(&updated_request).await?; - - Ok(EmailAuthEvent::RecoverySuccess { - account_eth_addr: request.account_eth_addr, - guardian_email_addr, - request_id: request_id_u32, - }) - } - Ok(false) => { - let updated_request = Request { - account_eth_addr: request.account_eth_addr.clone(), - controller_eth_addr: request.controller_eth_addr.clone(), - guardian_email_addr: guardian_email_addr.clone(), - template_idx: request.template_idx, - is_for_recovery: request.is_for_recovery, - is_processed: true, - request_id: request.request_id, - is_success: Some(false), - email_nullifier: Some(field_to_hex( - &bytes32_to_fr(&email_proof.email_nullifier).unwrap(), - )), - account_salt: Some(bytes32_to_hex(&account_salt)), - }; - - DB.update_request(&updated_request).await?; - - Ok(EmailAuthEvent::Error { - email_addr: guardian_email_addr, - error: "Failed to handle recovery".to_string(), - }) - } - Err(e) => Err(anyhow!("Failed to handle recovery: {}", e)), - } - } else { - return Ok(EmailAuthEvent::Error { - email_addr: guardian_email_addr, - error: "No account code found and not for recovery".to_string(), - }); - } + let original_subject = params.parsed_email.get_subject_all()?; + if is_success { + Ok(EmailAuthEvent::RecoverySuccess { + account_eth_addr: params.request.account_eth_addr, + guardian_email_addr: params.request.guardian_email_addr, + request_id: params.request.request_id, + original_subject, + original_message_id: params.parsed_email.get_message_id().ok(), + }) + } else { + let original_subject = params.parsed_email.get_subject_all()?; + Ok(EmailAuthEvent::Error { + email_addr: params.request.guardian_email_addr, + error: "Failed to handle recovery".to_string(), + original_subject, + original_message_id: params.parsed_email.get_message_id().ok(), + }) } } -pub fn get_masked_command(public_signals: Vec, start_idx: usize) -> Result { +/// Extracts the masked command from public signals. +/// +/// # Arguments +/// +/// * `public_signals` - The vector of public signals. +/// * `start_idx` - The starting index for command extraction. +/// +/// # Returns +/// +/// A `Result` containing the masked command as a `String` or an error. +fn get_masked_command(public_signals: Vec, start_idx: usize) -> Result { // Gather signals from start_idx to start_idx + COMMAND_FIELDS - let mut command_bytes = Vec::new(); - for i in start_idx..start_idx + COMMAND_FIELDS { - let signal = public_signals[i as usize]; - if signal == U256::zero() { - break; - } - let bytes = u256_to_bytes32_little(&signal); - command_bytes.extend_from_slice(&bytes); - } + let command_bytes: Vec = public_signals + .iter() + .skip(start_idx) + .take(COMMAND_FIELDS) + .take_while(|&signal| *signal != U256::zero()) + .flat_map(u256_to_bytes32_little) + .collect(); // Bytes to string, removing null bytes let command = String::from_utf8(command_bytes.into_iter().filter(|&b| b != 0u8).collect()) @@ -375,3 +298,193 @@ pub fn get_masked_command(public_signals: Vec, start_idx: usize) -> Result Ok(command) } + +/// Updates the request status in the database. +/// +/// # Arguments +/// +/// * `params` - The `EmailRequestContext` containing request details. +/// * `is_success` - A boolean indicating whether the request was successful. +/// * `email_nullifier` - The email nullifier as a byte array. +/// * `account_salt` - The account salt as a byte array. +/// +/// # Returns +/// +/// A `Result` indicating success or an `EmailError`. +async fn update_request( + params: &EmailRequestContext, + is_success: bool, + email_nullifier: [u8; 32], + account_salt: [u8; 32], +) -> Result<(), EmailError> { + let updated_request = Request { + account_eth_addr: params.request.account_eth_addr.clone(), + controller_eth_addr: params.request.controller_eth_addr.clone(), + guardian_email_addr: params.request.guardian_email_addr.clone(), + template_idx: params.request.template_idx, + is_for_recovery: params.request.is_for_recovery, + is_processed: true, + request_id: params.request.request_id, + is_success: Some(is_success), + email_nullifier: Some(field_to_hex(&bytes32_to_fr(&email_nullifier)?)), + account_salt: Some(bytes32_to_hex(&account_salt)), + }; + + DB.update_request(&updated_request).await?; + Ok(()) +} + +/// Generates the email proof for authentication. +/// +/// # Arguments +/// +/// * `params` - The `EmailRequestContext` containing request details. +/// +/// # Returns +/// +/// A `Result` containing the `EmailProof` and account salt, or an `EmailError`. +async fn generate_email_proof( + params: &EmailRequestContext, +) -> Result<(EmailProof, [u8; 32]), EmailError> { + let circuit_input = generate_email_circuit_input( + ¶ms.email, + &AccountCode::from(hex_to_field(&format!("0x{}", ¶ms.account_code_str))?), + Some(EmailCircuitParams { + max_header_length: Some(1024), + max_body_length: Some(1024), + sha_precompute_selector: Some(SHA_PRECOMPUTE_SELECTOR.to_string()), + ignore_body_hash_check: Some(false), + }), + ) + .await?; + + let (proof, public_signals) = + generate_proof(&circuit_input, "email_auth", PROVER_ADDRESS.get().unwrap()).await?; + + info!(LOG, "Public signals: {:?}", public_signals); + + let account_salt = u256_to_bytes32(&public_signals[COMMAND_FIELDS + DOMAIN_FIELDS + 3]); + let is_code_exist = public_signals[COMMAND_FIELDS + DOMAIN_FIELDS + 4] == 1u8.into(); + let masked_command = get_masked_command(public_signals.clone(), DOMAIN_FIELDS + 3)?; + + let email_proof = EmailProof { + proof, + domain_name: params.parsed_email.get_email_domain()?, + public_key_hash: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 0]), + timestamp: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 2]).into(), + masked_command, + email_nullifier: u256_to_bytes32(&public_signals[DOMAIN_FIELDS + 1]), + account_salt, + is_code_exist, + }; + + Ok((email_proof, account_salt)) +} + +/// Generates the template ID for the email authentication request. +/// +/// # Arguments +/// +/// * `params` - The `EmailRequestContext` containing request details. +/// +/// # Returns +/// +/// A 32-byte array representing the template ID. +fn get_template_id(params: &EmailRequestContext) -> [u8; 32] { + let action = if params.request.is_for_recovery { + "RECOVERY".to_string() + } else { + "ACCEPTANCE".to_string() + }; + + let tokens = vec![ + Token::Uint((*EMAIL_ACCOUNT_RECOVERY_VERSION_ID.get().unwrap()).into()), + // TODO: Continue here + Token::String(action), + Token::Uint(params.request.template_idx.into()), + ]; + + keccak256(encode(&tokens)) +} + +/// Retrieves and encodes the command parameters for the email authentication request. +/// +/// # Arguments +/// +/// * `params` - The `EmailRequestContext` containing request details. +/// +/// # Returns +/// +/// A `Result` containing a vector of encoded command parameters or an `EmailError`. +async fn get_encoded_command_params( + params: &EmailRequestContext, +) -> Result, EmailError> { + let command_template = if params.request.is_for_recovery { + CLIENT + .get_recovery_command_templates( + ¶ms.request.controller_eth_addr, + params.request.template_idx, + ) + .await + } else { + CLIENT + .get_acceptance_command_templates( + ¶ms.request.controller_eth_addr, + params.request.template_idx, + ) + .await + }?; + + let command_params = extract_template_vals_from_command(¶ms.email_body, command_template) + .map_err(|e| EmailError::Body(format!("Invalid commad: {}", e)))?; + + let command_params_encoded = command_params + .iter() + .map(|param| { + param + .abi_encode(None) + .map_err(|e| EmailError::AbiError(e.to_string())) + }) + .collect::, EmailError>>()?; + + Ok(command_params_encoded) +} + +/// Generates the email authentication message. +/// +/// # Arguments +/// +/// * `params` - The `EmailRequestContext` containing request details. +/// +/// # Returns +/// +/// A `Result` containing the `EmailAuthMsg`, `EmailProof`, and account salt, or an `EmailError`. +async fn get_email_auth_msg( + params: &EmailRequestContext, +) -> Result<(EmailAuthMsg, EmailProof, [u8; 32]), EmailError> { + let command_params_encoded = get_encoded_command_params(params).await?; + let template_id = get_template_id(params); + let (email_proof, account_salt) = generate_email_proof(params).await?; + let email_auth_msg = EmailAuthMsg { + template_id: template_id.into(), + command_params: command_params_encoded, + skipped_command_prefix: U256::zero(), + proof: email_proof.clone(), + }; + Ok((email_auth_msg, email_proof, account_salt)) +} + +/// Represents the context for an email authentication request. +#[derive(Debug, Clone)] +struct EmailRequestContext { + /// The request details. + request: Request, + /// The body of the email. + email_body: String, + /// The account code as a string. + account_code_str: String, + /// The full raw email. + email: String, + /// The parsed email. + parsed_email: ParsedEmail, +} diff --git a/packages/relayer/src/database.rs b/packages/relayer/src/database.rs index acd74822..29729903 100644 --- a/packages/relayer/src/database.rs +++ b/packages/relayer/src/database.rs @@ -3,33 +3,59 @@ use crate::*; use relayer_utils::LOG; use sqlx::{postgres::PgPool, Row}; +/// Represents the credentials for a user account. #[derive(Debug, Clone)] pub struct Credentials { + /// The unique code associated with the account. pub account_code: String, + /// The Ethereum address of the account. pub account_eth_addr: String, + /// The email address of the guardian. pub guardian_email_addr: String, + /// Indicates whether the credentials are set. pub is_set: bool, } +/// Represents a request in the system. #[derive(Debug, Clone)] pub struct Request { + /// The unique identifier for the request. pub request_id: u32, + /// The Ethereum address of the account. pub account_eth_addr: String, + /// The Ethereum address of the controller. pub controller_eth_addr: String, + /// The email address of the guardian. pub guardian_email_addr: String, + /// Indicates whether the request is for recovery. pub is_for_recovery: bool, + /// The index of the template used for the request. pub template_idx: u64, + /// Indicates whether the request has been processed. pub is_processed: bool, + /// Indicates the success status of the request, if available. pub is_success: Option, + /// The nullifier for the email, if available. pub email_nullifier: Option, + /// The salt for the account, if available. pub account_salt: Option, } +/// Represents the database connection and operations. pub struct Database { db: PgPool, } impl Database { + /// Opens a new database connection. + /// + /// # Arguments + /// + /// * `path` - The connection string for the database. + /// + /// # Returns + /// + /// A `Result` containing the `Database` struct if successful, or an error if the connection fails. pub async fn open(path: &str) -> Result { let res = Self { db: PgPool::connect(path) @@ -42,7 +68,13 @@ impl Database { Ok(res) } + /// Sets up the database by creating necessary tables if they don't exist. + /// + /// # Returns + /// + /// A `Result` indicating success or failure of the setup process. pub async fn setup_database(&self) -> Result<()> { + // Create credentials table sqlx::query( "CREATE TABLE IF NOT EXISTS credentials ( account_code TEXT PRIMARY KEY, @@ -54,6 +86,7 @@ impl Database { .execute(&self.db) .await?; + // Create requests table sqlx::query( "CREATE TABLE IF NOT EXISTS requests ( request_id BIGINT PRIMARY KEY, @@ -70,9 +103,57 @@ impl Database { ) .execute(&self.db) .await?; + + // Create expected_replies table + sqlx::query( + "CREATE TABLE IF NOT EXISTS expected_replies ( + message_id VARCHAR(255) PRIMARY KEY, + request_id VARCHAR(255), + has_reply BOOLEAN DEFAULT FALSE, + created_at TIMESTAMP DEFAULT (NOW() AT TIME ZONE 'UTC') + );", + ) + .execute(&self.db) + .await?; Ok(()) } + /// Tests the database connection by attempting to execute a simple query. + /// + /// # Returns + /// + /// A `Result` indicating success or failure of the connection test. + pub(crate) async fn test_db_connection(&self) -> Result<()> { + // Try up to 3 times + for i in 1..4 { + match sqlx::query("SELECT 1").execute(&self.db).await { + Ok(_) => { + info!(LOG, "Connected successfully to database"); + return Ok(()); + } + Err(e) => { + error!( + LOG, + "Failed to initialize connection to the database: {:?}. Retrying...", e + ); + tokio::time::sleep(Duration::from_secs(i * i)).await; + } + } + } + Err(anyhow::anyhow!( + "Failed to initialize database connection after 3 attempts" + )) + } + + /// Retrieves credentials for a given account code. + /// + /// # Arguments + /// + /// * `account_code` - The unique code associated with the account. + /// + /// # Returns + /// + /// A `Result` containing an `Option` if successful, or an error if the query fails. pub(crate) async fn get_credentials(&self, account_code: &str) -> Result> { let row = sqlx::query("SELECT * FROM credentials WHERE account_code = $1") .bind(account_code) @@ -81,6 +162,7 @@ impl Database { match row { Some(row) => { + // Extract values from the row let account_code: String = row.get("account_code"); let account_eth_addr: String = row.get("account_eth_addr"); let guardian_email_addr: String = row.get("guardian_email_addr"); @@ -98,11 +180,21 @@ impl Database { } } + /// Checks if a wallet and email combination is registered in the database. + /// + /// # Arguments + /// + /// * `account_eth_addr` - The Ethereum address of the account. + /// * `email_addr` - The email address to check. + /// + /// # Returns + /// + /// A `Result` containing a boolean indicating if the combination is registered. pub(crate) async fn is_wallet_and_email_registered( &self, account_eth_addr: &str, email_addr: &str, - ) -> bool { + ) -> std::result::Result { let row = sqlx::query( "SELECT * FROM credentials WHERE account_eth_addr = $1 AND guardian_email_addr = $2", ) @@ -110,57 +202,103 @@ impl Database { .bind(email_addr) .fetch_optional(&self.db) .await - .unwrap(); + .map_err(|e| DatabaseError::new("Failed to check if wallet and email are registered", e))?; - match row { - Some(_) => true, - None => false, - } + Ok(row.is_some()) } - pub(crate) async fn update_credentials_of_account_code(&self, row: &Credentials) -> Result<()> { - let res = sqlx::query("UPDATE credentials SET account_eth_addr = $1, guardian_email_addr = $2, is_set = $3 WHERE account_code = $4") + /// Updates the credentials for a given account code. + /// + /// # Arguments + /// + /// * `row` - The `Credentials` struct containing the updated information. + /// + /// # Returns + /// + /// A `Result` indicating success or failure of the update operation. + pub(crate) async fn update_credentials_of_account_code( + &self, + row: &Credentials, + ) -> std::result::Result<(), DatabaseError> { + sqlx::query("UPDATE credentials SET account_eth_addr = $1, guardian_email_addr = $2, is_set = $3 WHERE account_code = $4") .bind(&row.account_eth_addr) .bind(&row.guardian_email_addr) .bind(row.is_set) .bind(&row.account_code) .execute(&self.db) - .await?; + .await + .map_err(|e| { + DatabaseError::new("Failed to update credentials of account code", e) + })?; Ok(()) } + /// Updates the credentials for a given wallet and email combination. + /// + /// # Arguments + /// + /// * `row` - The `Credentials` struct containing the updated information. + /// + /// # Returns + /// + /// A `Result` indicating success or failure of the update operation. pub(crate) async fn update_credentials_of_wallet_and_email( &self, row: &Credentials, - ) -> Result<()> { - let res = sqlx::query("UPDATE credentials SET account_code = $1, is_set = $2 WHERE account_eth_addr = $3 AND guardian_email_addr = $4") + ) -> std::result::Result<(), DatabaseError> { + sqlx::query("UPDATE credentials SET account_code = $1, is_set = $2 WHERE account_eth_addr = $3 AND guardian_email_addr = $4") .bind(&row.account_code) .bind(row.is_set) .bind(&row.account_eth_addr) .bind(&row.guardian_email_addr) .execute(&self.db) - .await?; + .await + .map_err(|e| { + DatabaseError::new("Failed to insert credentials of wallet and email", e) + })?; Ok(()) } + /// Updates the credentials of an inactive guardian. + /// + /// # Arguments + /// + /// * `is_set` - The new value for the `is_set` field. + /// * `account_eth_addr` - The Ethereum address of the account. + /// + /// # Returns + /// + /// A `Result` indicating success or failure of the update operation. pub(crate) async fn update_credentials_of_inactive_guardian( &self, is_set: bool, account_eth_addr: &str, - ) -> Result<()> { - let res = sqlx::query( + ) -> std::result::Result<(), DatabaseError> { + sqlx::query( "UPDATE credentials SET is_set = $1 WHERE account_eth_addr = $2 AND is_set = true", ) .bind(is_set) .bind(account_eth_addr) .execute(&self.db) - .await?; + .await + .map_err(|e| DatabaseError::new("Failed to update credentials of inactive guardian", e))?; Ok(()) } - pub(crate) async fn insert_credentials(&self, row: &Credentials) -> Result<()> { - info!(LOG, "insert row {:?}", row); - let row = sqlx::query( + /// Inserts new credentials into the database. + /// + /// # Arguments + /// + /// * `row` - The `Credentials` struct containing the new information. + /// + /// # Returns + /// + /// A `Result` indicating success or failure of the insert operation. + pub(crate) async fn insert_credentials( + &self, + row: &Credentials, + ) -> std::result::Result<(), DatabaseError> { + sqlx::query( "INSERT INTO credentials (account_code, account_eth_addr, guardian_email_addr, is_set) VALUES ($1, $2, $3, $4) RETURNING *", ) .bind(&row.account_code) @@ -168,33 +306,59 @@ impl Database { .bind(&row.guardian_email_addr) .bind(row.is_set) .fetch_one(&self.db) - .await?; - info!(LOG, "{} row inserted", row.len()); + .await + .map_err(|e| DatabaseError::new("Failed to insert credentials", e))?; + info!(LOG, "Credentials inserted"); Ok(()) } - pub async fn is_guardian_set(&self, account_eth_addr: &str, guardian_email_addr: &str) -> bool { + /// Checks if a guardian is set for a given account and email address. + /// + /// # Arguments + /// + /// * `account_eth_addr` - The Ethereum address of the account. + /// * `guardian_email_addr` - The email address of the guardian. + /// + /// # Returns + /// + /// A `Result` containing a boolean indicating whether the guardian is set. + pub async fn is_guardian_set( + &self, + account_eth_addr: &str, + guardian_email_addr: &str, + ) -> std::result::Result { let row = sqlx::query("SELECT * FROM credentials WHERE account_eth_addr = $1 AND guardian_email_addr = $2 AND is_set = TRUE") .bind(account_eth_addr) .bind(guardian_email_addr) .fetch_optional(&self.db) .await - .unwrap(); + .map_err(|e| DatabaseError::new("Failed to check if guardian is set", e))?; - match row { - Some(_) => true, - None => false, - } + Ok(row.is_some()) } - pub(crate) async fn get_request(&self, request_id: u32) -> Result> { + /// Retrieves a request from the database based on the request ID. + /// + /// # Arguments + /// + /// * `request_id` - The unique identifier of the request. + /// + /// # Returns + /// + /// A `Result` containing an `Option` if successful, or an error if the query fails. + pub(crate) async fn get_request( + &self, + request_id: u32, + ) -> std::result::Result, DatabaseError> { let row = sqlx::query("SELECT * FROM requests WHERE request_id = $1") .bind(request_id as i64) .fetch_optional(&self.db) - .await?; + .await + .map_err(|e| DatabaseError::new("Failed to get request", e))?; match row { Some(row) => { + // Extract values from the row let request_id: i64 = row.get("request_id"); let account_eth_addr: String = row.get("account_eth_addr"); let controller_eth_addr: String = row.get("controller_eth_addr"); @@ -224,8 +388,20 @@ impl Database { } } - pub(crate) async fn update_request(&self, row: &Request) -> Result<()> { - let res = sqlx::query("UPDATE requests SET account_eth_addr = $1, controller_eth_addr = $2, guardian_email_addr = $3, is_for_recovery = $4, template_idx = $5, is_processed = $6, is_success = $7, email_nullifier = $8, account_salt = $9 WHERE request_id = $10") + /// Updates an existing request in the database. + /// + /// # Arguments + /// + /// * `row` - The `Request` struct containing the updated information. + /// + /// # Returns + /// + /// A `Result` indicating success or failure of the update operation. + pub(crate) async fn update_request( + &self, + row: &Request, + ) -> std::result::Result<(), DatabaseError> { + sqlx::query("UPDATE requests SET account_eth_addr = $1, controller_eth_addr = $2, guardian_email_addr = $3, is_for_recovery = $4, template_idx = $5, is_processed = $6, is_success = $7, email_nullifier = $8, account_salt = $9 WHERE request_id = $10") .bind(&row.account_eth_addr) .bind(&row.controller_eth_addr) .bind(&row.guardian_email_addr) @@ -237,22 +413,34 @@ impl Database { .bind(&row.account_salt) .bind(row.request_id as i64) .execute(&self.db) - .await?; + .await + .map_err(|e| DatabaseError::new("Failed to update request", e))?; Ok(()) } + /// Retrieves the account code for a given wallet and email combination. + /// + /// # Arguments + /// + /// * `account_eth_addr` - The Ethereum address of the account. + /// * `email_addr` - The email address associated with the account. + /// + /// # Returns + /// + /// A `Result` containing an `Option` with the account code if found, or an error if the query fails. pub(crate) async fn get_account_code_from_wallet_and_email( &self, account_eth_addr: &str, email_addr: &str, - ) -> Result> { + ) -> std::result::Result, DatabaseError> { let row = sqlx::query( "SELECT * FROM credentials WHERE account_eth_addr = $1 AND guardian_email_addr = $2", ) .bind(account_eth_addr) .bind(email_addr) .fetch_optional(&self.db) - .await?; + .await + .map_err(|e| DatabaseError::new("Failed to get account code from wallet and email", e))?; match row { Some(row) => { @@ -263,21 +451,33 @@ impl Database { } } + /// Retrieves the credentials for a given wallet and email combination. + /// + /// # Arguments + /// + /// * `account_eth_addr` - The Ethereum address of the account. + /// * `email_addr` - The email address associated with the account. + /// + /// # Returns + /// + /// A `Result` containing an `Option` if found, or an error if the query fails. pub(crate) async fn get_credentials_from_wallet_and_email( &self, account_eth_addr: &str, email_addr: &str, - ) -> Result> { + ) -> std::result::Result, DatabaseError> { let row = sqlx::query( "SELECT * FROM credentials WHERE account_eth_addr = $1 AND guardian_email_addr = $2", ) .bind(account_eth_addr) .bind(email_addr) .fetch_optional(&self.db) - .await?; + .await + .map_err(|e| DatabaseError::new("Failed to get credentials from wallet and email", e))?; match row { Some(row) => { + // Extract values from the row let account_code: String = row.get("account_code"); let account_eth_addr: String = row.get("account_eth_addr"); let guardian_email_addr: String = row.get("guardian_email_addr"); @@ -295,9 +495,21 @@ impl Database { } } - pub(crate) async fn insert_request(&self, row: &Request) -> Result<()> { - info!(LOG, "insert row {:?}", row); - let row = sqlx::query( + /// Inserts a new request into the database. + /// + /// # Arguments + /// + /// * `row` - The `Request` struct containing the new request information. + /// + /// # Returns + /// + /// A `Result` indicating success or failure of the insert operation. + pub(crate) async fn insert_request( + &self, + row: &Request, + ) -> std::result::Result<(), DatabaseError> { + let request_id = row.request_id; + sqlx::query( "INSERT INTO requests (request_id, account_eth_addr, controller_eth_addr, guardian_email_addr, is_for_recovery, template_idx, is_processed, is_success, email_nullifier, account_salt) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *", ) .bind(row.request_id as i64) @@ -311,8 +523,63 @@ impl Database { .bind(&row.email_nullifier) .bind(&row.account_salt) .fetch_one(&self.db) - .await?; - info!(LOG, "{} row inserted", row.len()); + .await + .map_err(|e| DatabaseError::new("Failed to insert request", e))?; + info!(LOG, "Request inserted with request_id: {}", request_id); + Ok(()) + } + + /// Adds an expected reply to the database. + /// + /// # Arguments + /// + /// * `message_id` - The unique identifier of the message. + /// * `request_id` - An optional request ID associated with the reply. + /// + /// # Returns + /// + /// A `Result` indicating success or failure of the insert operation. + pub(crate) async fn add_expected_reply( + &self, + message_id: &str, + request_id: Option, + ) -> Result<(), DatabaseError> { + let query = " + INSERT INTO expected_replies (message_id, request_id) + VALUES ($1, $2); + "; + sqlx::query(query) + .bind(message_id) + .bind(request_id) + .execute(&self.db) + .await + .map_err(|e| DatabaseError::new("Failed to insert expected_reply", e))?; Ok(()) } + + /// Checks if the given message_id corresponds to a valid reply. + /// + /// This function updates the `has_reply` field to true if the message_id exists and hasn't been replied to yet. + /// + /// # Arguments + /// + /// * `message_id` - The unique identifier of the message. + /// + /// # Returns + /// + /// A `Result` containing a boolean indicating if the reply is valid (true if the update was successful). + pub(crate) async fn is_valid_reply(&self, message_id: &str) -> Result { + let query = " + UPDATE expected_replies + SET has_reply = true + WHERE message_id = $1 AND has_reply = false + RETURNING *; + "; + let result = sqlx::query(query) + .bind(message_id) + .execute(&self.db) + .await + .map_err(|e| DatabaseError::new("Failed to validate reply", e))?; + Ok(result.rows_affected() > 0) + } } diff --git a/packages/relayer/src/lib.rs b/packages/relayer/src/lib.rs index 5f31b8f9..d0b83379 100644 --- a/packages/relayer/src/lib.rs +++ b/packages/relayer/src/lib.rs @@ -8,7 +8,7 @@ pub mod config; pub mod core; pub mod database; pub mod modules; -pub mod utils; +pub mod strings; pub use abis::*; pub use chain::*; @@ -17,11 +17,11 @@ pub use core::*; pub use database::*; pub use modules::*; use relayer_utils::LOG; -pub use utils::*; +pub use strings::*; -use tokio::sync::Mutex; +use tokio::sync::{Mutex, OnceCell}; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, Result}; use dotenv::dotenv; use ethers::prelude::*; use lazy_static::lazy_static; @@ -32,7 +32,7 @@ use std::path::PathBuf; use std::sync::{Arc, OnceLock}; use tokio::time::Duration; -pub static CIRCUITS_DIR_PATH: OnceLock = OnceLock::new(); +pub static REGEX_JSON_DIR_PATH: OnceLock = OnceLock::new(); pub static WEB_SERVER_ADDRESS: OnceLock = OnceLock::new(); pub static PROVER_ADDRESS: OnceLock = OnceLock::new(); pub static PRIVATE_KEY: OnceLock = OnceLock::new(); @@ -44,17 +44,38 @@ pub static EMAIL_TEMPLATES: OnceLock = OnceLock::new(); pub static RELAYER_EMAIL_ADDRESS: OnceLock = OnceLock::new(); pub static SMTP_SERVER: OnceLock = OnceLock::new(); +static DB_CELL: OnceCell> = OnceCell::const_new(); + +/// Wrapper struct for database access +struct DBWrapper; + +impl DBWrapper { + /// Retrieves the database instance. + /// + /// # Returns + /// + /// A reference to the `Arc`. + /// + /// # Panics + /// + /// Panics if the database is not initialized. + fn get() -> &'static Arc { + DB_CELL.get().expect("Database not initialized") + } +} + +impl std::ops::Deref for DBWrapper { + type Target = Database; + + fn deref(&self) -> &Self::Target { + Self::get() + } +} + +static DB: DBWrapper = DBWrapper; + lazy_static! { - pub static ref DB: Arc = { - dotenv().ok(); - let db = tokio::task::block_in_place(|| { - tokio::runtime::Runtime::new() - .unwrap() - .block_on(Database::open(&env::var(DATABASE_PATH_KEY).unwrap())) - }) - .unwrap(); - Arc::new(db) - }; + /// Shared instance of the `ChainClient`. pub static ref CLIENT: Arc = { dotenv().ok(); let client = tokio::task::block_in_place(|| { @@ -65,13 +86,24 @@ lazy_static! { .unwrap(); Arc::new(client) }; + /// Shared mutex for synchronization. pub static ref SHARED_MUTEX: Arc> = Arc::new(Mutex::new(0)); } +/// Runs the relayer with the given configuration. +/// +/// # Arguments +/// +/// * `config` - The configuration for the relayer. +/// +/// # Returns +/// +/// A `Result` indicating success or failure. pub async fn run(config: RelayerConfig) -> Result<()> { info!(LOG, "Starting relayer"); - CIRCUITS_DIR_PATH.set(config.circuits_dir_path).unwrap(); + // Initialize global configuration + REGEX_JSON_DIR_PATH.set(config.regex_json_dir_path).unwrap(); WEB_SERVER_ADDRESS.set(config.web_server_address).unwrap(); PROVER_ADDRESS.set(config.prover_address).unwrap(); PRIVATE_KEY.set(config.private_key).unwrap(); @@ -87,6 +119,7 @@ pub async fn run(config: RelayerConfig) -> Result<()> { .unwrap(); SMTP_SERVER.set(config.smtp_server).unwrap(); + // Spawn the API server task let api_server_task = tokio::task::spawn(async move { loop { match run_server().await { @@ -96,13 +129,14 @@ pub async fn run(config: RelayerConfig) -> Result<()> { } Err(err) => { error!(LOG, "Error api server: {}", err); - // Optionally, add a delay before restarting + // Add a delay before restarting to prevent rapid restart loops tokio::time::sleep(Duration::from_secs(5)).await; } } } }); + // Wait for the API server task to complete let _ = tokio::join!(api_server_task); Ok(()) diff --git a/packages/relayer/src/modules/dkim.rs b/packages/relayer/src/modules/dkim.rs index bd8fdc86..1a602e75 100644 --- a/packages/relayer/src/modules/dkim.rs +++ b/packages/relayer/src/modules/dkim.rs @@ -1,5 +1,8 @@ +use std::fs; + use anyhow::anyhow; use relayer_utils::extract_substr_idxes; +use relayer_utils::DecomposedRegexConfig; use relayer_utils::LOG; use crate::*; @@ -13,24 +16,47 @@ use ic_utils::canister::*; use serde::Deserialize; +/// Represents a client for interacting with the DKIM Oracle. #[derive(Debug, Clone)] pub struct DkimOracleClient<'a> { + /// The canister used for communication pub canister: Canister<'a>, } +/// Represents a signed DKIM public key. #[derive(Default, CandidType, Deserialize, Debug, Clone)] pub struct SignedDkimPublicKey { + /// The selector for the DKIM key pub selector: String, + /// The domain for the DKIM key pub domain: String, + /// The signature of the DKIM key pub signature: String, + /// The public key pub public_key: String, + /// The hash of the public key pub public_key_hash: String, } impl<'a> DkimOracleClient<'a> { + /// Generates an agent for the DKIM Oracle Client. + /// + /// # Arguments + /// + /// * `pem_path` - The path to the PEM file. + /// * `replica_url` - The URL of the replica. + /// + /// # Returns + /// + /// An `anyhow::Result`. pub fn gen_agent(pem_path: &str, replica_url: &str) -> anyhow::Result { + // Create identity from PEM file let identity = Secp256k1Identity::from_pem_file(pem_path)?; + + // Create transport using the replica URL let transport = ReqwestTransport::create(replica_url)?; + + // Build and return the agent let agent = AgentBuilder::default() .with_identity(identity) .with_transport(transport) @@ -38,24 +64,48 @@ impl<'a> DkimOracleClient<'a> { Ok(agent) } + /// Creates a new DkimOracleClient. + /// + /// # Arguments + /// + /// * `canister_id` - The ID of the canister. + /// * `agent` - The agent to use for communication. + /// + /// # Returns + /// + /// An `anyhow::Result`. pub fn new(canister_id: &str, agent: &'a Agent) -> anyhow::Result { + // Build the canister using the provided ID and agent let canister = CanisterBuilder::new() .with_canister_id(canister_id) - .with_agent(&agent) + .with_agent(agent) .build()?; Ok(Self { canister }) } + /// Requests a signature for a DKIM public key. + /// + /// # Arguments + /// + /// * `selector` - The selector for the DKIM key. + /// * `domain` - The domain for the DKIM key. + /// + /// # Returns + /// + /// An `anyhow::Result`. pub async fn request_signature( &self, selector: &str, domain: &str, ) -> anyhow::Result { + // Build the request to sign the DKIM public key let request = self .canister .update("sign_dkim_public_key") .with_args((selector, domain)) .build::<(Result,)>(); + + // Call the canister and wait for the response let response = request .call_and_wait_one::>() .await? @@ -65,6 +115,19 @@ impl<'a> DkimOracleClient<'a> { } } +/// Checks and updates the DKIM for a given email. +/// +/// # Arguments +/// +/// * `email` - The email address. +/// * `parsed_email` - The parsed email data. +/// * `controller_eth_addr` - The Ethereum address of the controller. +/// * `wallet_addr` - The address of the wallet. +/// * `account_salt` - The salt for the account. +/// +/// # Returns +/// +/// A `Result<()>`. pub async fn check_and_update_dkim( email: &str, parsed_email: &ParsedEmail, @@ -72,31 +135,36 @@ pub async fn check_and_update_dkim( wallet_addr: &str, account_salt: &str, ) -> Result<()> { + // Generate public key hash let mut public_key_n = parsed_email.public_key.clone(); public_key_n.reverse(); let public_key_hash = public_key_hash(&public_key_n)?; info!(LOG, "public_key_hash {:?}", public_key_hash); + + // Get email domain let domain = parsed_email.get_email_domain()?; info!(LOG, "domain {:?}", domain); - if CLIENT.get_bytecode(&wallet_addr.to_string()).await? == Bytes::from(vec![0u8; 20]) { + + // Check if wallet is deployed + if CLIENT.get_bytecode(wallet_addr).await? == Bytes::from_static(&[0u8; 20]) { info!(LOG, "wallet not deployed"); return Ok(()); } + + // Get email auth address from controller let email_auth_addr = CLIENT - .get_email_auth_addr_from_wallet( - &controller_eth_addr.to_string(), - &wallet_addr.to_string(), - &account_salt.to_string(), - ) + .get_email_auth_addr_from_wallet(controller_eth_addr, wallet_addr, account_salt) .await?; let email_auth_addr = format!("0x{:x}", email_auth_addr); - let mut dkim = CLIENT - .get_dkim_from_wallet(&controller_eth_addr.to_string()) - .await?; - if CLIENT.get_bytecode(&email_auth_addr).await? != Bytes::from(vec![]) { + + // Get DKIM from controller or email auth + let mut dkim = CLIENT.get_dkim_from_controller(controller_eth_addr).await?; + if CLIENT.get_bytecode(&email_auth_addr).await? != Bytes::new() { dkim = CLIENT.get_dkim_from_email_auth(&email_auth_addr).await?; } info!(LOG, "dkim {:?}", dkim); + + // Check if DKIM public key hash is valid if CLIENT .check_if_dkim_public_key_hash_valid( domain.clone(), @@ -108,26 +176,46 @@ pub async fn check_and_update_dkim( info!(LOG, "public key registered"); return Ok(()); } - let selector_decomposed_def = - serde_json::from_str(include_str!("../regex_json/selector_def.json")).unwrap(); + + // Get selector + let selector_def_path = + PathBuf::from(env::var(REGEX_JSON_DIR_PATH_KEY).unwrap()).join("selector_def.json"); + let selector_def_contents = fs::read_to_string(&selector_def_path) + .map_err(|e| anyhow!("Failed to read file {}: {}", selector_def_path.display(), e))?; + let selector_decomposed_def: DecomposedRegexConfig = + serde_json::from_str(&selector_def_contents).map_err(|e| { + anyhow!( + "Failed to parse JSON from file {}: {}", + selector_def_path.display(), + e + ) + })?; let selector = { let idxes = extract_substr_idxes(&parsed_email.canonicalized_header, &selector_decomposed_def)?[0]; - let str = parsed_email.canonicalized_header[idxes.0..idxes.1].to_string(); - str + parsed_email.canonicalized_header[idxes.0..idxes.1].to_string() }; + info!(LOG, "selector {}", selector); + + // Generate IC agent and create oracle client let ic_agent = DkimOracleClient::gen_agent( &env::var(PEM_PATH_KEY).unwrap(), &env::var(IC_REPLICA_URL_KEY).unwrap(), )?; let oracle_client = DkimOracleClient::new(&env::var(CANISTER_ID_KEY).unwrap(), &ic_agent)?; + + // Request signature from oracle let oracle_result = oracle_client.request_signature(&selector, &domain).await?; info!(LOG, "DKIM oracle result {:?}", oracle_result); + + // Process oracle response let public_key_hash = hex::decode(&oracle_result.public_key_hash[2..])?; info!(LOG, "public_key_hash from oracle {:?}", public_key_hash); let signature = Bytes::from_hex(&oracle_result.signature[2..])?; info!(LOG, "signature {:?}", signature); + + // Set DKIM public key hash let tx_hash = CLIENT .set_dkim_public_key_hash( domain, diff --git a/packages/relayer/src/modules/mail.rs b/packages/relayer/src/modules/mail.rs index 0f1e3bc2..f44a6688 100644 --- a/packages/relayer/src/modules/mail.rs +++ b/packages/relayer/src/modules/mail.rs @@ -4,6 +4,7 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use tokio::fs::read_to_string; +/// Represents different types of email authentication events. #[derive(Debug, Clone)] pub enum EmailAuthEvent { AcceptanceRequest { @@ -20,6 +21,8 @@ pub enum EmailAuthEvent { Error { email_addr: String, error: String, + original_subject: String, + original_message_id: Option, }, RecoveryRequest { account_eth_addr: String, @@ -31,11 +34,15 @@ pub enum EmailAuthEvent { account_eth_addr: String, guardian_email_addr: String, request_id: u32, + original_subject: String, + original_message_id: Option, }, RecoverySuccess { account_eth_addr: String, guardian_email_addr: String, request_id: u32, + original_subject: String, + original_message_id: Option, }, GuardianNotSet { account_eth_addr: String, @@ -44,17 +51,19 @@ pub enum EmailAuthEvent { GuardianNotRegistered { account_eth_addr: String, guardian_email_addr: String, - subject: String, + command: String, request_id: u32, }, Ack { email_addr: String, - subject: String, + command: String, original_message_id: Option, + original_subject: String, }, NoOp, } +/// Represents an email message to be sent. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EmailMessage { pub to: String, @@ -66,6 +75,7 @@ pub struct EmailMessage { pub body_attachments: Option>, } +/// Represents an attachment in an email message. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EmailAttachment { pub inline_id: String, @@ -73,7 +83,16 @@ pub struct EmailAttachment { pub contents: Vec, } -pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { +/// Handles all possible email events and requests. +/// +/// # Arguments +/// +/// * `event` - The `EmailAuthEvent` to be handled. +/// +/// # Returns +/// +/// A `Result` indicating success or an `EmailError`. +pub async fn handle_email_event(event: EmailAuthEvent) -> Result<(), EmailError> { match event { EmailAuthEvent::AcceptanceRequest { account_eth_addr, @@ -82,8 +101,10 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { command, account_code, } => { + // Prepare the command with the account code let command = format!("{} Code {}", command, account_code); + // Create the plain text body let body_plain = format!( "You have received an guardian request from the wallet address {}. \ {} Code {}. \ @@ -95,6 +116,7 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { let subject = format!("ZK Email Recovery: Acceptance Request"); + // Prepare data for HTML rendering let render_data = serde_json::json!({ "userEmailAddr": guardian_email_addr, "walletAddress": account_eth_addr, @@ -103,6 +125,7 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { }); let body_html = render_html("acceptance_request.html", render_data).await?; + // Create and send the email let email = EmailMessage { to: guardian_email_addr, subject, @@ -113,33 +136,41 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { body_attachments: None, }; - send_email(email).await?; + send_email(email, Some(ExpectsReply::new(request_id))).await?; } - EmailAuthEvent::Error { email_addr, error } => { - let subject = "Error"; + EmailAuthEvent::Error { + email_addr, + error, + original_subject, + original_message_id, + } => { + let subject = format!("Re: {}", original_subject); + let body_plain = format!( "An error occurred while processing your request. \ Error: {}", error ); + // Prepare data for HTML rendering let render_data = serde_json::json!({ "error": error, "userEmailAddr": email_addr, }); let body_html = render_html("error.html", render_data).await?; + // Create and send the email let email = EmailMessage { to: email_addr, - subject: subject.to_string(), - reference: None, - reply_to: None, + subject, + reference: original_message_id.clone(), + reply_to: original_message_id, body_plain, body_html, body_attachments: None, }; - send_email(email).await?; + send_email(email, None).await?; } EmailAuthEvent::GuardianAlreadyExists { account_eth_addr, @@ -152,12 +183,14 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { guardian_email_addr, account_eth_addr ); + // Prepare data for HTML rendering let render_data = serde_json::json!({ "walletAddress": account_eth_addr, "userEmailAddr": guardian_email_addr, }); let body_html = render_html("guardian_already_exists.html", render_data).await?; + // Create and send the email let email = EmailMessage { to: guardian_email_addr, subject: subject.to_string(), @@ -168,7 +201,7 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { body_attachments: None, }; - send_email(email).await?; + send_email(email, None).await?; } EmailAuthEvent::RecoveryRequest { account_eth_addr, @@ -186,6 +219,7 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { let subject = format!("ZK Email Recovery: Recovery Request"); + // Prepare data for HTML rendering let render_data = serde_json::json!({ "userEmailAddr": guardian_email_addr, "walletAddress": account_eth_addr, @@ -194,6 +228,7 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { }); let body_html = render_html("recovery_request.html", render_data).await?; + // Create and send the email let email = EmailMessage { to: guardian_email_addr, subject, @@ -204,20 +239,23 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { body_attachments: None, }; - send_email(email).await?; + send_email(email, Some(ExpectsReply::new(request_id))).await?; } EmailAuthEvent::AcceptanceSuccess { account_eth_addr, guardian_email_addr, request_id, + original_subject, + original_message_id, } => { - let subject = "Acceptance Success"; + let subject = format!("Re: {}", original_subject); let body_plain = format!( "Your guardian request for the wallet address {} has been set. \ Your request ID is #{} is now complete.", account_eth_addr, request_id ); + // Prepare data for HTML rendering let render_data = serde_json::json!({ "walletAddress": account_eth_addr, "userEmailAddr": guardian_email_addr, @@ -225,30 +263,34 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { }); let body_html = render_html("acceptance_success.html", render_data).await?; + // Create and send the email let email = EmailMessage { to: guardian_email_addr, subject: subject.to_string(), - reference: None, - reply_to: None, + reference: original_message_id.clone(), + reply_to: original_message_id, body_plain, body_html, body_attachments: None, }; - send_email(email).await?; + send_email(email, None).await?; } EmailAuthEvent::RecoverySuccess { account_eth_addr, guardian_email_addr, request_id, + original_subject, + original_message_id, } => { - let subject = "Recovery Success"; + let subject = format!("Re: {}", original_subject); let body_plain = format!( "Your recovery request for the wallet address {} is successful. \ Your request ID is #{}.", account_eth_addr, request_id ); + // Prepare data for HTML rendering let render_data = serde_json::json!({ "walletAddress": account_eth_addr, "userEmailAddr": guardian_email_addr, @@ -256,17 +298,18 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { }); let body_html = render_html("recovery_success.html", render_data).await?; + // Create and send the email let email = EmailMessage { to: guardian_email_addr, subject: subject.to_string(), - reference: None, - reply_to: None, + reference: original_message_id.clone(), + reply_to: original_message_id, body_plain, body_html, body_attachments: None, }; - send_email(email).await?; + send_email(email, None).await?; } EmailAuthEvent::GuardianNotSet { account_eth_addr, @@ -275,12 +318,14 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { let subject = "Guardian Not Set"; let body_plain = format!("Guardian not set for wallet address {}", account_eth_addr); + // Prepare data for HTML rendering let render_data = serde_json::json!({ "walletAddress": account_eth_addr, "userEmailAddr": guardian_email_addr, }); let body_html = render_html("guardian_not_set.html", render_data).await?; + // Create and send the email let email = EmailMessage { to: guardian_email_addr, subject: subject.to_string(), @@ -291,31 +336,36 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { body_attachments: None, }; - send_email(email).await?; + send_email(email, None).await?; } EmailAuthEvent::GuardianNotRegistered { account_eth_addr, guardian_email_addr, - subject, + command, request_id, } => { - let subject = format!("{} Code ", subject); + let command = format!("{} Code ", command); let body_plain = format!( "You have received an guardian request from the wallet address {}. \ - Add the guardian's account code in the subject and reply to this email. \ + Reply to this email. \ Your request ID is #{}. \ If you did not initiate this request, please contact us immediately.", account_eth_addr, request_id ); + // Prepare data for HTML rendering let render_data = serde_json::json!({ "userEmailAddr": guardian_email_addr, "walletAddress": account_eth_addr, "requestId": request_id, + "command": command, }); + + let subject = "Guardian Not Registered".to_string(); let body_html = render_html("credential_not_present.html", render_data).await?; + // Create and send the email let email = EmailMessage { to: guardian_email_addr, subject, @@ -326,20 +376,23 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { body_attachments: None, }; - send_email(email).await?; + send_email(email, Some(ExpectsReply::new(request_id))).await?; } EmailAuthEvent::Ack { email_addr, - subject, + command, original_message_id, + original_subject, } => { let body_plain = format!( - "Hi {}!\nYour email with the subject {} is received.", - email_addr, subject + "Hi {}!\nYour email with the command {} is received.", + email_addr, command ); - let render_data = serde_json::json!({"userEmailAddr": email_addr, "request": subject}); + // Prepare data for HTML rendering + let render_data = serde_json::json!({"userEmailAddr": email_addr, "request": command}); let body_html = render_html("acknowledgement.html", render_data).await?; - let subject = format!("Email Wallet Notification. Acknowledgement."); + let subject = format!("Re: {}", original_subject); + // Create and send the email let email = EmailMessage { to: email_addr, subject, @@ -349,7 +402,7 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { reply_to: original_message_id, body_attachments: None, }; - send_email(email).await?; + send_email(email, None).await?; } EmailAuthEvent::NoOp => {} } @@ -357,20 +410,53 @@ pub async fn handle_email_event(event: EmailAuthEvent) -> Result<()> { Ok(()) } -pub async fn render_html(template_name: &str, render_data: Value) -> Result { +/// Renders an HTML template with the given data. +/// +/// # Arguments +/// +/// * `template_name` - The name of the template file. +/// * `render_data` - The data to be used in rendering the template. +/// +/// # Returns +/// +/// A `Result` containing the rendered HTML string or an `EmailError`. +async fn render_html(template_name: &str, render_data: Value) -> Result { + // Construct the full path to the email template let email_template_filename = PathBuf::new() .join(EMAIL_TEMPLATES.get().unwrap()) .join(template_name); - let email_template = read_to_string(&email_template_filename).await?; + // Read the email template file + let email_template = read_to_string(&email_template_filename) + .await + .map_err(|e| { + EmailError::FileNotFound(format!( + "Could not get email template {}: {}", + template_name, e + )) + })?; + + // Create a new Handlebars instance let reg = Handlebars::new(); - Ok(reg.render_template(&email_template, &render_data)?) + // Render the template with the provided data + let template = reg.render_template(&email_template, &render_data)?; + Ok(template) } -pub fn parse_error(error: String) -> Result> { +/// Parses an error string and returns a more user-friendly error message. +/// +/// # Arguments +/// +/// * `error` - The error string to be parsed. +/// +/// # Returns +/// +/// A `Result` containing an `Option` with the parsed error message. +fn parse_error(error: String) -> Result> { let mut error = error; if error.contains("Contract call reverted with data: ") { + // Extract and decode the revert data let revert_data = error .replace("Contract call reverted with data: ", "") .split_at(10) @@ -379,11 +465,12 @@ pub fn parse_error(error: String) -> Result> { let revert_bytes = hex::decode(revert_data) .unwrap() .into_iter() - .filter(|&b| b >= 0x20 && b <= 0x7E) + .filter(|&b| (0x20..=0x7E).contains(&b)) .collect(); error = String::from_utf8(revert_bytes).unwrap().trim().to_string(); } + // Match known error messages and provide user-friendly responses match error.as_str() { "Account is already created" => Ok(Some(error)), "insufficient balance" => Ok(Some("You don't have sufficient balance".to_string())), @@ -391,7 +478,20 @@ pub fn parse_error(error: String) -> Result> { } } -pub async fn send_email(email: EmailMessage) -> Result<()> { +/// Sends an email using the configured SMTP server. +/// +/// # Arguments +/// +/// * `email` - The `EmailMessage` to be sent. +/// * `expects_reply` - An optional `ExpectsReply` struct indicating if a reply is expected. +/// +/// # Returns +/// +/// A `Result` indicating success or an `EmailError`. +async fn send_email( + email: EmailMessage, + expects_reply: Option, +) -> Result<(), EmailError> { let smtp_server = SMTP_SERVER.get().unwrap(); // Send POST request to email server @@ -401,14 +501,85 @@ pub async fn send_email(email: EmailMessage) -> Result<()> { .json(&email) .send() .await - .map_err(|e| anyhow!("Failed to send email: {}", e))?; + .map_err(|e| EmailError::Send(format!("Failed to send email: {}", e)))?; + // Check if the email was sent successfully if !response.status().is_success() { - return Err(anyhow!( + return Err(EmailError::Send(format!( "Failed to send email: {}", response.text().await.unwrap_or_default() - )); + ))); + } + + // Handle expected reply if necessary + if let Some(expects_reply) = expects_reply { + let response_body: EmailResponse = response + .json() + .await + .map_err(|e| EmailError::Parse(format!("Failed to parse response JSON: {}", e)))?; + + let message_id = response_body.message_id; + DB.add_expected_reply(&message_id, expects_reply.request_id) + .await?; } Ok(()) } + +/// Represents the response from the email server after sending an email. +#[derive(Debug, Clone, Serialize, Deserialize)] +struct EmailResponse { + status: String, + message_id: String, +} + +/// Represents an expectation of a reply to an email. +pub struct ExpectsReply { + request_id: Option, +} + +impl ExpectsReply { + /// Creates a new `ExpectsReply` instance with a request ID. + /// + /// # Arguments + /// + /// * `request_id` - The ID of the request expecting a reply. + fn new(request_id: u32) -> Self { + Self { + request_id: Some(request_id.to_string()), + } + } + + /// Creates a new `ExpectsReply` instance without a request ID. + fn new_no_request_id() -> Self { + Self { request_id: None } + } +} + +/// Checks if the email is a reply to a command that expects a reply. +/// Will return false for duplicate replies. +/// Will return true if the email is not a reply. +/// +/// # Arguments +/// +/// * `email` - The `ParsedEmail` to be checked. +/// +/// # Returns +/// +/// A `Result` containing a boolean indicating if the request is valid. +pub async fn check_is_valid_request(email: &ParsedEmail) -> Result { + // Check if the email is a reply by looking for the "In-Reply-To" header + let reply_message_id = match email + .headers + .get_header("In-Reply-To") + .and_then(|v| v.first().cloned()) + { + Some(id) => id, + // Email is not a reply, so it's valid + None => return Ok(true), + }; + + // Check if the reply is valid (not a duplicate) using the database + let is_valid = DB.is_valid_reply(&reply_message_id).await?; + Ok(is_valid) +} diff --git a/packages/relayer/src/modules/mod.rs b/packages/relayer/src/modules/mod.rs index 5ace1b92..eaf1adc5 100644 --- a/packages/relayer/src/modules/mod.rs +++ b/packages/relayer/src/modules/mod.rs @@ -1,3 +1,5 @@ +//! This module contains the dkim, mail and web_server modules. + pub mod dkim; pub mod mail; pub mod web_server; diff --git a/packages/relayer/src/modules/web_server/mod.rs b/packages/relayer/src/modules/web_server/mod.rs index 7dffbb07..3a48beb6 100644 --- a/packages/relayer/src/modules/web_server/mod.rs +++ b/packages/relayer/src/modules/web_server/mod.rs @@ -1,5 +1,9 @@ +//! This module contains the axum web server and its routes and custom errors. + +pub mod relayer_errors; pub mod rest_api; pub mod server; +pub use relayer_errors::*; pub use rest_api::*; pub use server::*; diff --git a/packages/relayer/src/modules/web_server/relayer_errors.rs b/packages/relayer/src/modules/web_server/relayer_errors.rs new file mode 100644 index 00000000..8b49462a --- /dev/null +++ b/packages/relayer/src/modules/web_server/relayer_errors.rs @@ -0,0 +1,223 @@ +use crate::*; +use axum::{ + response::{IntoResponse, Response}, + Json, +}; +use handlebars::RenderError; +use relayer_utils::ExtractSubstrssError; +use reqwest::StatusCode; +use rustc_hex::FromHexError; +use serde_json::json; +use thiserror::Error; + +/// Custom error type for API-related errors +#[derive(Error, Debug)] +pub enum ApiError { + #[error("Database error: {0}")] + Database(#[from] DatabaseError), + #[error("Sqlx error: {0}")] + SqlxError(#[from] sqlx::Error), + #[error("Validation error: {0}")] + Validation(String), + #[error("Chain error: {0}")] + Chain(#[from] ChainError), + // #[error("Not found: {0}")] + // NotFound(String), + #[error("Anyhow error: {0}")] + Anyhow(#[from] anyhow::Error), + #[error("Internal error: {0}")] + Internal(String), + #[error("Error recieving email: {0}")] + Email(#[from] EmailError), +} + +/// Custom error type for email-related errors +#[derive(Error, Debug)] +pub enum EmailError { + #[error("Email body error: {0}")] + Body(String), + #[error("Email address error: {0}")] + EmailAddress(String), + #[error("Parse error: {0}")] + Parse(String), + #[error("DKIM error: {0}")] + Dkim(String), + #[error("ZkRegex error: {0}")] + ZkRegex(#[from] ExtractSubstrssError), + #[error("Database error: {0}")] + Database(#[from] DatabaseError), + #[error("Not found: {0}")] + NotFound(String), + #[error("Circuit error: {0}")] + Circuit(String), + #[error("Chain error: {0}")] + Chain(#[from] ChainError), + #[error("File not found error: {0}")] + FileNotFound(String), + #[error("Render error: {0}")] + Render(#[from] RenderError), + #[error("Failed to send email: {0}")] + Send(String), + #[error("Hex error: {0}")] + HexError(#[from] hex::FromHexError), + #[error("ABI encode error: {0}")] + AbiError(String), + // Currently used with some relayer-utils errors + #[error("Anyhow error: {0}")] + Anyhow(#[from] anyhow::Error), +} + +/// Custom error type for blockchain-related errors +#[derive(Error, Debug)] +pub enum ChainError { + #[error("Contract error: {0}")] + Contract(ContractErrorWrapper), + #[error("Signer middleware error: {0}")] + SignerMiddleware(SignerMiddlewareErrorWrapper), + #[error("Hex error: {0}")] + HexError(#[from] FromHexError), + #[error("Provider error: {0}")] + Provider(ProviderErrorWrapper), + #[error("Anyhow error: {0}")] + Anyhow(#[from] anyhow::Error), + #[error("Validation error: {0}")] + Validation(String), +} + +impl ChainError { + pub fn contract_error(msg: &str, err: ContractError) -> Self { + Self::Contract(ContractErrorWrapper::new(msg.to_string(), err)) + } + + pub fn signer_middleware_error( + msg: &str, + err: signer::SignerMiddlewareError, + ) -> Self { + Self::SignerMiddleware(SignerMiddlewareErrorWrapper::new(msg.to_string(), err)) + } + + pub fn provider_error(msg: &str, err: ethers::providers::ProviderError) -> Self { + Self::Provider(ProviderErrorWrapper::new(msg.to_string(), err)) + } +} + +/// Custom error type for database-related errors +#[derive(Debug, thiserror::Error)] +#[error("{msg}: {source}")] +pub struct DatabaseError { + #[source] + pub source: sqlx::Error, + pub msg: String, +} + +impl DatabaseError { + pub fn new(msg: &str, source: sqlx::Error) -> Self { + Self { + source, + msg: msg.to_string(), + } + } +} + +/// Wrapper for contract-related errors +#[derive(Debug)] +pub struct ContractErrorWrapper { + msg: String, + source: String, +} + +impl std::fmt::Display for ContractErrorWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", self.msg, self.source) + } +} + +impl ContractErrorWrapper { + pub fn new(msg: String, err: ContractError) -> Self { + ContractErrorWrapper { + msg, + source: err.to_string(), + } + } +} + +/// Wrapper for signer middleware-related errors +#[derive(Debug)] +pub struct SignerMiddlewareErrorWrapper { + msg: String, + source: String, +} + +impl std::fmt::Display for SignerMiddlewareErrorWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", self.msg, self.source) + } +} + +impl SignerMiddlewareErrorWrapper { + pub fn new( + msg: String, + err: signer::SignerMiddlewareError, + ) -> Self { + SignerMiddlewareErrorWrapper { + msg, + source: err.to_string(), + } + } +} + +/// Wrapper for provider-related errors +#[derive(Debug)] +pub struct ProviderErrorWrapper { + msg: String, + source: ethers::providers::ProviderError, +} + +impl std::fmt::Display for ProviderErrorWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}: {}", self.msg, self.source) + } +} + +impl ProviderErrorWrapper { + pub fn new(msg: String, err: ethers::providers::ProviderError) -> Self { + ProviderErrorWrapper { msg, source: err } + } +} + +impl ApiError { + pub fn database_error(msg: &str, source: sqlx::Error) -> Self { + Self::Database(DatabaseError::new(msg, source)) + } +} + +impl IntoResponse for ApiError { + fn into_response(self) -> Response { + let (status, error_message) = match self { + ApiError::Database(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + ApiError::Chain(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + ApiError::SqlxError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + ApiError::Validation(e) => (StatusCode::BAD_REQUEST, e.to_string()), + ApiError::Anyhow(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + ApiError::Internal(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + ApiError::Email(e) => match e { + EmailError::Body(e) => (StatusCode::BAD_REQUEST, e.to_string()), + EmailError::EmailAddress(e) => (StatusCode::BAD_REQUEST, e.to_string()), + EmailError::Parse(e) => (StatusCode::BAD_REQUEST, e.to_string()), + EmailError::NotFound(e) => (StatusCode::BAD_REQUEST, e.to_string()), + EmailError::Dkim(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + EmailError::ZkRegex(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + EmailError::Database(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + EmailError::HexError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + EmailError::AbiError(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + EmailError::Circuit(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + EmailError::Chain(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + EmailError::FileNotFound(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + EmailError::Render(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + EmailError::Send(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + EmailError::Anyhow(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()), + }, + }; + (status, Json(json!({ "error": error_message }))).into_response() + } +} diff --git a/packages/relayer/src/modules/web_server/rest_api.rs b/packages/relayer/src/modules/web_server/rest_api.rs index 2812480f..e17988fe 100644 --- a/packages/relayer/src/modules/web_server/rest_api.rs +++ b/packages/relayer/src/modules/web_server/rest_api.rs @@ -1,94 +1,29 @@ use crate::*; use anyhow::Result; -use axum::{body::Body, response::Response}; +use axum::Json; use hex::decode; use rand::Rng; -use relayer_utils::LOG; -use reqwest::StatusCode; +use relayer_utils::{ + calculate_account_salt, extract_template_vals_from_command, TemplateValue, LOG, +}; use serde::{Deserialize, Serialize}; use std::str; -#[derive(Serialize, Deserialize)] -pub struct RequestStatusRequest { - pub request_id: u32, -} - -#[derive(Serialize, Deserialize)] -pub enum RequestStatus { - NotExist = 0, - Pending = 1, - Processed = 2, -} - -#[derive(Serialize, Deserialize)] -pub struct RequestStatusResponse { - pub request_id: u32, - pub status: RequestStatus, - pub is_success: bool, - pub email_nullifier: Option, - pub account_salt: Option, -} - -#[derive(Serialize, Deserialize)] -pub struct AcceptanceRequest { - pub controller_eth_addr: String, - pub guardian_email_addr: String, - pub account_code: String, - pub template_idx: u64, - pub command: String, -} - -#[derive(Serialize, Deserialize)] -pub struct AcceptanceResponse { - pub request_id: u32, - pub command_params: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct RecoveryRequest { - pub controller_eth_addr: String, - pub guardian_email_addr: String, - pub template_idx: u64, - pub command: String, -} - -#[derive(Serialize, Deserialize)] -pub struct RecoveryResponse { - pub request_id: u32, - pub command_params: Vec, -} - -#[derive(Serialize, Deserialize)] -pub struct CompleteRecoveryRequest { - pub account_eth_addr: String, - pub controller_eth_addr: String, - pub complete_calldata: String, -} - -#[derive(Serialize, Deserialize)] -pub struct GetAccountSaltRequest { - pub account_code: String, - pub email_addr: String, -} - -#[derive(Deserialize)] -struct PermittedWallet { - wallet_name: String, - controller_eth_addr: String, - hash_of_bytecode_of_proxy: String, - impl_contract_address: String, - slot_location: String, -} - -#[derive(Serialize, Deserialize)] -pub struct InactiveGuardianRequest { - pub account_eth_addr: String, - pub controller_eth_addr: String, -} - -// Create request status API -pub async fn request_status_api(payload: RequestStatusRequest) -> Result { +/// Retrieves the status of a request. +/// +/// # Arguments +/// +/// * `payload` - A JSON payload containing the request ID. +/// +/// # Returns +/// +/// A `Result` containing a JSON `RequestStatusResponse` or an `ApiError`. +pub async fn request_status_api( + Json(payload): Json, +) -> Result, ApiError> { let row = DB.get_request(payload.request_id).await?; + + // Determine the status based on the retrieved row let status = if let Some(ref row) = row { if row.is_processed { RequestStatus::Processed @@ -98,7 +33,8 @@ pub async fn request_status_api(payload: RequestStatusRequest) -> Result Result Response { +/// Handles an acceptance request for a wallet. +/// +/// # Arguments +/// +/// * `payload` - A JSON payload containing the acceptance request details. +/// +/// # Returns +/// +/// A `Result` containing a JSON `AcceptanceResponse` or an `ApiError`. +pub async fn handle_acceptance_request( + Json(payload): Json, +) -> Result, ApiError> { let command_template = CLIENT .get_acceptance_command_templates(&payload.controller_eth_addr, payload.template_idx) - .await - .unwrap(); + .await?; - let command_params = extract_template_vals(&payload.command, command_template); - - if command_params.is_err() { - return Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::from("Invalid command")) - .unwrap(); - } - - let command_params = command_params.unwrap(); + // Extract and validate command parameters + let command_params = extract_template_vals_from_command(&payload.command, command_template) + .map_err(|_| ApiError::Validation("Invalid command".to_string()))?; + // Recover the account address let account_eth_addr = CLIENT .get_recovered_account_from_acceptance_command( &payload.controller_eth_addr, command_params.clone(), payload.template_idx, ) - .await - .unwrap(); + .await?; let account_eth_addr = format!("0x{:x}", account_eth_addr); - if !CLIENT.is_wallet_deployed(&account_eth_addr).await { - return Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::from("Wallet not deployed")) - .unwrap(); + // Check if the wallet is deployed + if !CLIENT.is_wallet_deployed(&account_eth_addr).await? { + return Err(ApiError::Validation("Wallet not deployed".to_string())); } - // Check if hash of bytecode of proxy contract is equal or not - let bytecode = CLIENT.get_bytecode(&account_eth_addr).await.unwrap(); - let bytecode_hash = format!("0x{}", hex::encode(keccak256(bytecode.as_ref()))); - - // let permitted_wallets: Vec = - // serde_json::from_str(include_str!("../../permitted_wallets.json")).unwrap(); - // let permitted_wallet = permitted_wallets - // .iter() - // .find(|w| w.hash_of_bytecode_of_proxy == bytecode_hash); - - // if let Some(permitted_wallet) = permitted_wallet { - // let slot_location = permitted_wallet.slot_location.parse::().unwrap(); - // let impl_contract_from_proxy = { - // let raw_hex = hex::encode( - // CLIENT - // .get_storage_at(&account_eth_addr, slot_location) - // .await - // .unwrap(), - // ); - // format!("0x{}", &raw_hex[24..]) - // }; - - // if !permitted_wallet - // .impl_contract_address - // .eq_ignore_ascii_case(&impl_contract_from_proxy) - // { - // return Response::builder() - // .status(StatusCode::BAD_REQUEST) - // .body(Body::from( - // "Invalid bytecode, impl contract address mismatch", - // )) - // .unwrap(); - // } - - // if !permitted_wallet - // .controller_eth_addr - // .eq_ignore_ascii_case(&payload.controller_eth_addr) - // { - // return Response::builder() - // .status(StatusCode::BAD_REQUEST) - // .body(Body::from("Invalid controller eth addr")) - // .unwrap(); - // } - // } else { - // return Response::builder() - // .status(StatusCode::BAD_REQUEST) - // .body(Body::from("Wallet not permitted")) - // .unwrap(); - // } - + // Check if the account code is already used if let Ok(Some(creds)) = DB.get_credentials(&payload.account_code).await { - return Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::from("Account code already used")) - .unwrap(); + return Err(ApiError::Validation( + "Account code already used".to_string(), + )); } + // Generate a unique request ID let mut request_id = rand::thread_rng().gen::(); while let Ok(Some(request)) = DB.get_request(request_id).await { request_id = rand::thread_rng().gen::(); @@ -208,60 +96,44 @@ pub async fn handle_acceptance_request(payload: AcceptanceRequest) -> Response Response Response Response { +/// Handles a recovery request for a wallet. +/// +/// # Arguments +/// +/// * `payload` - A JSON payload containing the recovery request details. +/// +/// # Returns +/// +/// A `Result` containing a JSON `RecoveryResponse` or an `ApiError`. +pub async fn handle_recovery_request( + Json(payload): Json, +) -> Result, ApiError> { + // Fetch the command template let command_template = CLIENT .get_recovery_command_templates(&payload.controller_eth_addr, payload.template_idx) - .await - .unwrap(); + .await?; - let command_params = extract_template_vals(&payload.command, command_template); - - if command_params.is_err() { - return Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::from("Invalid command")) - .unwrap(); - } - - let command_params = command_params.unwrap(); + // Extract and validate command parameters + let command_params = extract_template_vals_from_command(&payload.command, command_template) + .map_err(|_| ApiError::Validation("Invalid command".to_string()))?; + // Recover the account address let account_eth_addr = CLIENT .get_recovered_account_from_recovery_command( &payload.controller_eth_addr, command_params.clone(), payload.template_idx, ) - .await - .unwrap(); + .await?; let account_eth_addr = format!("0x{:x}", account_eth_addr); - if !CLIENT.is_wallet_deployed(&account_eth_addr).await { - return Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::from("Wallet not deployed")) - .unwrap(); + // Check if the wallet is deployed + if !CLIENT.is_wallet_deployed(&account_eth_addr).await? { + return Err(ApiError::Validation("Wallet not deployed".to_string())); } - // Check if hash of bytecode of proxy contract is equal or not - let bytecode = CLIENT.get_bytecode(&account_eth_addr).await.unwrap(); - let bytecode_hash = format!("0x{}", hex::encode(keccak256(bytecode.as_ref()))); - - // let permitted_wallets: Vec = - // serde_json::from_str(include_str!("../../permitted_wallets.json")).unwrap(); - // let permitted_wallet = permitted_wallets - // .iter() - // .find(|w| w.hash_of_bytecode_of_proxy == bytecode_hash); - - // if let Some(permitted_wallet) = permitted_wallet { - // let slot_location = permitted_wallet.slot_location.parse::().unwrap(); - // let impl_contract_from_proxy = { - // let raw_hex = hex::encode( - // CLIENT - // .get_storage_at(&account_eth_addr, slot_location) - // .await - // .unwrap(), - // ); - // format!("0x{}", &raw_hex[24..]) - // }; - - // if !permitted_wallet - // .impl_contract_address - // .eq_ignore_ascii_case(&impl_contract_from_proxy) - // { - // return Response::builder() - // .status(StatusCode::BAD_REQUEST) - // .body(Body::from( - // "Invalid bytecode, impl contract address mismatch", - // )) - // .unwrap(); - // } - - // if !permitted_wallet - // .controller_eth_addr - // .eq_ignore_ascii_case(&payload.controller_eth_addr) - // { - // return Response::builder() - // .status(StatusCode::BAD_REQUEST) - // .body(Body::from("Invalid controller eth addr")) - // .unwrap(); - // } - // } else { - // return Response::builder() - // .status(StatusCode::BAD_REQUEST) - // .body(Body::from("Wallet not permitted")) - // .unwrap(); - // } - + // Generate a unique request ID let mut request_id = rand::thread_rng().gen::(); while let Ok(Some(request)) = DB.get_request(request_id).await { request_id = rand::thread_rng().gen::(); } + // Fetch account details and calculate account salt let account = DB .get_credentials_from_wallet_and_email(&account_eth_addr, &payload.guardian_email_addr) - .await; + .await?; - let account_salt = if let Ok(Some(account_details)) = account { + let account_salt = if let Some(account_details) = account { calculate_account_salt(&payload.guardian_email_addr, &account_details.account_code) } else { - return Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::from("Account details not found")) - .unwrap(); + return Err(ApiError::Validation("Wallet not deployed".to_string())); }; + // Handle the case when wallet and email are not registered if !DB .is_wallet_and_email_registered(&account_eth_addr, &payload.guardian_email_addr) - .await + .await? { DB.insert_request(&Request { - request_id: request_id.clone(), + request_id, account_eth_addr: account_eth_addr.clone(), controller_eth_addr: payload.controller_eth_addr.clone(), guardian_email_addr: payload.guardian_email_addr.clone(), @@ -439,49 +240,42 @@ pub async fn handle_recovery_request(payload: RecoveryRequest) -> Response email_nullifier: None, account_salt: Some(account_salt.clone()), }) - .await - .expect("Failed to insert request"); + .await?; handle_email_event(EmailAuthEvent::GuardianNotRegistered { account_eth_addr, guardian_email_addr: payload.guardian_email_addr.clone(), - subject: payload.command.clone(), + command: payload.command.clone(), request_id, }) - .await - .expect("Failed to send GuardianNotRegistered event"); - - return Response::builder() - .status(StatusCode::OK) - .body(Body::from( - serde_json::to_string(&RecoveryResponse { - request_id, - command_params, - }) - .unwrap(), - )) - .unwrap(); + .await?; + + return Ok(Json(RecoveryResponse { + request_id, + command_params, + })); } + // Insert the recovery request + DB.insert_request(&Request { + request_id, + account_eth_addr: account_eth_addr.clone(), + controller_eth_addr: payload.controller_eth_addr.clone(), + guardian_email_addr: payload.guardian_email_addr.clone(), + is_for_recovery: true, + template_idx: payload.template_idx, + is_processed: false, + is_success: None, + email_nullifier: None, + account_salt: Some(account_salt.clone()), + }) + .await?; + + // Handle different scenarios based on guardian status if DB .is_guardian_set(&account_eth_addr, &payload.guardian_email_addr) - .await + .await? { - DB.insert_request(&Request { - request_id: request_id.clone(), - account_eth_addr: account_eth_addr.clone(), - controller_eth_addr: payload.controller_eth_addr.clone(), - guardian_email_addr: payload.guardian_email_addr.clone(), - is_for_recovery: true, - template_idx: payload.template_idx, - is_processed: false, - is_success: None, - email_nullifier: None, - account_salt: Some(account_salt.clone()), - }) - .await - .expect("Failed to insert request"); - handle_email_event(EmailAuthEvent::RecoveryRequest { account_eth_addr, guardian_email_addr: payload.guardian_email_addr.clone(), @@ -489,53 +283,42 @@ pub async fn handle_recovery_request(payload: RecoveryRequest) -> Response command: payload.command.clone(), }) .await + // TODO: Add custom error for handle_email_event .expect("Failed to send Recovery event"); } else { - DB.insert_request(&Request { - request_id: request_id.clone(), - account_eth_addr: account_eth_addr.clone(), - controller_eth_addr: payload.controller_eth_addr.clone(), - guardian_email_addr: payload.guardian_email_addr.clone(), - is_for_recovery: true, - template_idx: payload.template_idx, - is_processed: false, - is_success: None, - email_nullifier: None, - account_salt: Some(account_salt.clone()), - }) - .await - .expect("Failed to insert request"); - handle_email_event(EmailAuthEvent::GuardianNotSet { account_eth_addr, guardian_email_addr: payload.guardian_email_addr.clone(), - // request_id, - // subject: payload.subject.clone(), }) .await + // TODO: Add error handling .expect("Failed to send Recovery event"); } - Response::builder() - .status(StatusCode::OK) - .body(Body::from( - serde_json::to_string(&RecoveryResponse { - request_id, - command_params, - }) - .unwrap(), - )) - .unwrap() + Ok(Json(RecoveryResponse { + request_id, + command_params, + })) } -pub async fn handle_complete_recovery_request(payload: CompleteRecoveryRequest) -> Response { - if !CLIENT.is_wallet_deployed(&payload.account_eth_addr).await { - return Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::from("Wallet not deployed")) - .unwrap(); +/// Handles the completion of a recovery request. +/// +/// # Arguments +/// +/// * `payload` - A JSON payload containing the complete recovery request details. +/// +/// # Returns +/// +/// A `Result` containing a `String` message or an `ApiError`. +pub async fn handle_complete_recovery_request( + Json(payload): Json, +) -> Result { + // Check if the wallet is deployed + if !CLIENT.is_wallet_deployed(&payload.account_eth_addr).await? { + return Err(ApiError::Validation("Wallet not deployed".to_string())); } + // Attempt to complete the recovery match CLIENT .complete_recovery( &payload.controller_eth_addr, @@ -544,14 +327,8 @@ pub async fn handle_complete_recovery_request(payload: CompleteRecoveryRequest) ) .await { - Ok(true) => Response::builder() - .status(StatusCode::OK) - .body(Body::from("Recovery completed")) - .unwrap(), - Ok(false) => Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::from("Recovery failed")) - .unwrap(), + Ok(true) => Ok("Recovery completed".to_string()), + Ok(false) => Err(ApiError::Validation("Recovery failed".to_string())), Err(e) => { // Parse the error message if it follows the known format let error_message = if e @@ -567,56 +344,74 @@ pub async fn handle_complete_recovery_request(payload: CompleteRecoveryRequest) .chars() .filter(|c| c.is_ascii()) .collect::(); - Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::from(error_message)) - .unwrap() + Err(ApiError::Internal(error_message)) } } } -pub async fn get_account_salt(payload: GetAccountSaltRequest) -> Response { +/// Retrieves the account salt for a given email address and account code. +/// +/// # Arguments +/// +/// * `payload` - A JSON payload containing the email address and account code. +/// +/// # Returns +/// +/// A `Result` containing the account salt as a `String` or an `ApiError`. +pub async fn get_account_salt( + Json(payload): Json, +) -> Result { let account_salt = calculate_account_salt(&payload.email_addr, &payload.account_code); - - Response::builder() - .status(StatusCode::OK) - .body(Body::from(account_salt)) - .unwrap() + Ok(account_salt) } -pub async fn inactive_guardian(payload: InactiveGuardianRequest) -> Response { +/// Marks a guardian as inactive for a given wallet. +/// +/// # Arguments +/// +/// * `payload` - A JSON payload containing the account and controller Ethereum addresses. +/// +/// # Returns +/// +/// A `Result` containing a `String` message or an `ApiError`. +pub async fn inactive_guardian( + Json(payload): Json, +) -> Result { + // Check if the wallet is activated let is_activated = CLIENT .get_is_activated(&payload.controller_eth_addr, &payload.account_eth_addr) - .await; - match is_activated { - Ok(true) => { - return Response::builder() - .status(StatusCode::BAD_REQUEST) - .body(Body::from("Wallet is activated")) - .unwrap() - } - Ok(false) => {} - Err(e) => { - return Response::builder() - .status(StatusCode::INTERNAL_SERVER_ERROR) - .body(Body::from(e.to_string())) - .unwrap() - } + .await?; + + if is_activated { + return Ok("Wallet is activated".to_string()); } - trace!(LOG, "Inactive guardian"; "is_activated" => is_activated.unwrap()); - let account_eth_addr: Address = payload.account_eth_addr.parse().unwrap(); + + trace!(LOG, "Inactive guardian"; "is_activated" => is_activated); + + // Parse and format the account Ethereum address + let account_eth_addr: Address = payload + .account_eth_addr + .parse() + .map_err(|e| ApiError::Validation(format!("Failed to parse account_eth_addr: {}", e)))?; let account_eth_addr = format!("0x{:x}", &account_eth_addr); trace!(LOG, "Inactive guardian"; "account_eth_addr" => &account_eth_addr); + + // Update the credentials of the inactive guardian DB.update_credentials_of_inactive_guardian(false, &account_eth_addr) - .await - .expect("Failed to update credentials"); + .await?; - Response::builder() - .status(StatusCode::OK) - .body(Body::from("Guardian inactivated")) - .unwrap() + Ok("Guardian inactivated".to_string()) } +/// Parses an error message from contract call data. +/// +/// # Arguments +/// +/// * `error_data` - The error data as a `String`. +/// +/// # Returns +/// +/// A `String` containing the parsed error message or a default error message. fn parse_error_message(error_data: String) -> String { // Attempt to extract and decode the error message if let Some(hex_error) = error_data.split(" ").last() { @@ -631,14 +426,31 @@ fn parse_error_message(error_data: String) -> String { format!("Failed to parse contract error: {}", error_data) } -pub async fn receive_email_api_fn(email: String) -> Result<()> { - let parsed_email = ParsedEmail::new_from_raw_email(&email).await.unwrap(); - let from_addr = parsed_email.get_from_addr().unwrap(); +/// Receives and processes an email. +/// +/// # Arguments +/// +/// * `email` - The raw email as a `String`. +/// +/// # Returns +/// +/// A `Result` containing `()` or an `ApiError`. +pub async fn receive_email_api_fn(email: String) -> Result<(), ApiError> { + let parsed_email = ParsedEmail::new_from_raw_email(&email).await?; + let from_addr = parsed_email.get_from_addr()?; + let original_subject = parsed_email.get_subject_all()?; tokio::spawn(async move { + if !check_is_valid_request(&parsed_email).await.unwrap() { + trace!(LOG, "Got a non valid email request. Ignoring."); + return; + } + + // Send acknowledgment email match handle_email_event(EmailAuthEvent::Ack { email_addr: from_addr.clone(), - subject: parsed_email.get_subject_all().unwrap_or_default(), + command: parsed_email.get_command(false).unwrap_or_default(), original_message_id: parsed_email.get_message_id().ok(), + original_subject, }) .await { @@ -649,6 +461,8 @@ pub async fn receive_email_api_fn(email: String) -> Result<()> { error!(LOG, "Error handling email event: {:?}", e); } } + + // Process the email match handle_email(email.clone()).await { Ok(event) => match handle_email_event(event).await { Ok(_) => {} @@ -658,9 +472,14 @@ pub async fn receive_email_api_fn(email: String) -> Result<()> { }, Err(e) => { error!(LOG, "Error handling email: {:?}", e); + let original_subject = parsed_email + .get_subject_all() + .unwrap_or("Unknown Error".to_string()); match handle_email_event(EmailAuthEvent::Error { email_addr: from_addr, error: e.to_string(), + original_subject, + original_message_id: parsed_email.get_message_id().ok(), }) .await { @@ -674,3 +493,126 @@ pub async fn receive_email_api_fn(email: String) -> Result<()> { }); Ok(()) } + +/// Request status request structure. +#[derive(Serialize, Deserialize)] +pub struct RequestStatusRequest { + /// The unique identifier for the request. + pub request_id: u32, +} + +/// Enum representing the possible statuses of a request. +#[derive(Serialize, Deserialize)] +pub enum RequestStatus { + /// The request does not exist. + NotExist = 0, + /// The request is pending processing. + Pending = 1, + /// The request has been processed. + Processed = 2, +} + +/// Response structure for a request status query. +#[derive(Serialize, Deserialize)] +pub struct RequestStatusResponse { + /// The unique identifier for the request. + pub request_id: u32, + /// The current status of the request. + pub status: RequestStatus, + /// Indicates whether the request was successful. + pub is_success: bool, + /// The email nullifier, if available. + pub email_nullifier: Option, + /// The account salt, if available. + pub account_salt: Option, +} + +/// Request structure for an acceptance request. +#[derive(Serialize, Deserialize)] +pub struct AcceptanceRequest { + /// The Ethereum address of the controller. + pub controller_eth_addr: String, + /// The email address of the guardian. + pub guardian_email_addr: String, + /// The unique account code. + pub account_code: String, + /// The index of the template to use. + pub template_idx: u64, + /// The command to execute. + pub command: String, +} + +/// Response structure for an acceptance request. +#[derive(Serialize, Deserialize)] +pub struct AcceptanceResponse { + /// The unique identifier for the request. + pub request_id: u32, + /// The parameters extracted from the command. + pub command_params: Vec, +} + +/// Request structure for a recovery request. +#[derive(Serialize, Deserialize, Debug)] +pub struct RecoveryRequest { + /// The Ethereum address of the controller. + pub controller_eth_addr: String, + /// The email address of the guardian. + pub guardian_email_addr: String, + /// The index of the template to use. + pub template_idx: u64, + /// The command to execute. + pub command: String, +} + +/// Response structure for a recovery request. +#[derive(Serialize, Deserialize)] +pub struct RecoveryResponse { + /// The unique identifier for the request. + pub request_id: u32, + /// The parameters extracted from the command. + pub command_params: Vec, +} + +/// Request structure for completing a recovery. +#[derive(Serialize, Deserialize)] +pub struct CompleteRecoveryRequest { + /// The Ethereum address of the account to recover. + pub account_eth_addr: String, + /// The Ethereum address of the controller. + pub controller_eth_addr: String, + /// The calldata to complete the recovery. + pub complete_calldata: String, +} + +/// Request structure for retrieving an account salt. +#[derive(Serialize, Deserialize)] +pub struct GetAccountSaltRequest { + /// The unique account code. + pub account_code: String, + /// The email address associated with the account. + pub email_addr: String, +} + +/// Structure representing a permitted wallet. +#[derive(Deserialize)] +struct PermittedWallet { + /// The name of the wallet. + wallet_name: String, + /// The Ethereum address of the controller. + controller_eth_addr: String, + /// The hash of the bytecode of the proxy contract. + hash_of_bytecode_of_proxy: String, + /// The address of the implementation contract. + impl_contract_address: String, + /// The slot location in storage. + slot_location: String, +} + +/// Request structure for marking a guardian as inactive. +#[derive(Serialize, Deserialize)] +pub struct InactiveGuardianRequest { + /// The Ethereum address of the account. + pub account_eth_addr: String, + /// The Ethereum address of the controller. + pub controller_eth_addr: String, +} diff --git a/packages/relayer/src/modules/web_server/server.rs b/packages/relayer/src/modules/web_server/server.rs index 195d651c..b8f371c3 100644 --- a/packages/relayer/src/modules/web_server/server.rs +++ b/packages/relayer/src/modules/web_server/server.rs @@ -1,182 +1,50 @@ use crate::*; -use axum::Router; +use axum::{routing::post, Router}; use relayer_utils::LOG; use tower_http::cors::{AllowHeaders, AllowMethods, Any, CorsLayer}; +/// Runs the server and sets up the API routes. +/// +/// # Returns +/// +/// A `Result` indicating success or failure. pub async fn run_server() -> Result<()> { let addr = WEB_SERVER_ADDRESS.get().unwrap(); + // Initialize the global DB ref before starting the server + DB_CELL + .get_or_init(|| async { + dotenv::dotenv().ok(); + let db = Database::open(&std::env::var("DATABASE_URL").unwrap()) + .await + .unwrap(); + Arc::new(db) + }) + .await; + + info!(LOG, "Testing connection to database"); + if let Err(e) = DB.test_db_connection().await { + error!(LOG, "Failed to initialize db with e: {}", e); + panic!("Forcing panic, since connection to DB could not be established"); + }; + info!(LOG, "Testing connection to database successfull"); + + // Initialize the API routes let mut app = Router::new() .route( "/api/echo", axum::routing::get(move || async move { "Hello, world!" }), ) - .route( - "/api/requestStatus", - axum::routing::post(move |payload: String| async move { - let payload: Result = - serde_json::from_str(&payload).map_err(|e| anyhow::Error::from(e)); - match payload { - Ok(payload) => { - let response = request_status_api(payload).await; - match response { - Ok(response) => { - let body = serde_json::to_string(&response) - .map_err(|e| axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .unwrap(); - Ok::<_, axum::response::Response>( - axum::http::Response::builder() - .status(axum::http::StatusCode::OK) - .body(axum::body::Body::from(body)) - .unwrap(), - ) - } - Err(e) => { - let error_message = serde_json::to_string(&e.to_string()) - .map_err(|_| axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .unwrap(); - Ok(axum::http::Response::builder() - .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .body(serde_json::to_string(&e.to_string()).unwrap().into()) - .unwrap()) - } - } - } - Err(e) => { - let error_message = serde_json::to_string(&e.to_string()) - .map_err(|_| axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .unwrap(); - Ok(axum::http::Response::builder() - .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .body(serde_json::to_string(&e.to_string()).unwrap().into()) - .unwrap()) - } - } - }), - ) - .route( - "/api/acceptanceRequest", - axum::routing::post(move |payload: String| async move { - let payload: Result = - serde_json::from_str(&payload).map_err(|e| anyhow::Error::from(e)); - match payload { - Ok(payload) => { - let acceptance_response = handle_acceptance_request(payload).await; - Ok::<_, axum::response::Response>(acceptance_response) - } - Err(e) => { - let error_message = serde_json::to_string(&e.to_string()) - .map_err(|_| axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .unwrap(); - Ok(axum::http::Response::builder() - .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .body(serde_json::to_string(&e.to_string()).unwrap().into()) - .unwrap()) - } - } - }), - ) - .route( - "/api/recoveryRequest", - axum::routing::post(move |payload: String| async move { - let payload: Result = - serde_json::from_str(&payload).map_err(|e| anyhow::Error::from(e)); - match payload { - Ok(payload) => { - let recovery_response = handle_recovery_request(payload).await; - Ok::<_, axum::response::Response>(recovery_response) - } - Err(e) => { - let error_message = serde_json::to_string(&e.to_string()) - .map_err(|_| axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .unwrap(); - Ok(axum::http::Response::builder() - .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .body(serde_json::to_string(&e.to_string()).unwrap().into()) - .unwrap()) - } - } - }), - ) + .route("/api/requestStatus", post(request_status_api)) + .route("/api/acceptanceRequest", post(handle_acceptance_request)) + .route("/api/recoveryRequest", post(handle_recovery_request)) .route( "/api/completeRequest", - axum::routing::post(move |payload: String| async move { - let payload: Result = - serde_json::from_str(&payload).map_err(|e| anyhow::Error::from(e)); - match payload { - Ok(payload) => { - let recovery_response = handle_complete_recovery_request(payload).await; - Ok::<_, axum::response::Response>(recovery_response) - } - Err(e) => { - let error_message = serde_json::to_string(&e.to_string()) - .map_err(|_| axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .unwrap(); - Ok(axum::http::Response::builder() - .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .body(serde_json::to_string(&e.to_string()).unwrap().into()) - .unwrap()) - } - } - }), - ) - .route( - "/api/getAccountSalt", - axum::routing::post(move |payload: String| async move { - let payload: Result = - serde_json::from_str(&payload).map_err(|e| anyhow::Error::from(e)); - match payload { - Ok(payload) => { - let response = get_account_salt(payload).await; - Ok::<_, axum::response::Response>(response) - } - Err(e) => { - let error_message = serde_json::to_string(&e.to_string()) - .map_err(|_| axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .unwrap(); - Ok(axum::http::Response::builder() - .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .body(serde_json::to_string(&e.to_string()).unwrap().into()) - .unwrap()) - } - } - }), + post(handle_complete_recovery_request), ) - .route( - "/api/inactiveGuardian", - axum::routing::post(move |payload: String| async move { - let payload: Result = - serde_json::from_str(&payload).map_err(|e| anyhow::Error::from(e)); - match payload { - Ok(payload) => { - let response = inactive_guardian(payload).await; - Ok::<_, axum::response::Response>(response) - } - Err(e) => { - let error_message = serde_json::to_string(&e.to_string()) - .map_err(|_| axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .unwrap(); - Ok(axum::http::Response::builder() - .status(axum::http::StatusCode::INTERNAL_SERVER_ERROR) - .body(serde_json::to_string(&e.to_string()).unwrap().into()) - .unwrap()) - } - } - }), - ) - .route( - "/api/receiveEmail", - axum::routing::post::<_, _, (), _>(move |payload: String| async move { - info!(LOG, "Receive email payload: {}", payload); - match receive_email_api_fn(payload).await { - Ok(_) => "Request processed".to_string(), - Err(err) => { - error!(LOG, "Failed to complete the receive email request: {}", err); - err.to_string() - } - } - }), - ); + .route("/api/getAccountSalt", post(get_account_salt)) + .route("/api/inactiveGuardian", post(inactive_guardian)) + .route("/api/receiveEmail", post(receive_email_api_fn)); app = app.layer( CorsLayer::new() @@ -185,6 +53,7 @@ pub async fn run_server() -> Result<()> { .allow_origin(Any), ); + // Start the server trace!(LOG, "Listening API at {}", addr); axum::Server::bind(&addr.parse()?) .serve(app.into_make_service()) diff --git a/packages/relayer/src/utils/strings.rs b/packages/relayer/src/strings.rs similarity index 96% rename from packages/relayer/src/utils/strings.rs rename to packages/relayer/src/strings.rs index c33a1e7f..b9db6720 100644 --- a/packages/relayer/src/utils/strings.rs +++ b/packages/relayer/src/strings.rs @@ -3,7 +3,6 @@ pub const SMTP_SERVER_KEY: &str = "SMTP_SERVER"; pub const RELAYER_EMAIL_ADDR_KEY: &str = "RELAYER_EMAIL_ADDR"; pub const DATABASE_PATH_KEY: &str = "DATABASE_URL"; pub const WEB_SERVER_ADDRESS_KEY: &str = "WEB_SERVER_ADDRESS"; -pub const CIRCUITS_DIR_PATH_KEY: &str = "CIRCUITS_DIR_PATH"; pub const PROVER_ADDRESS_KEY: &str = "PROVER_ADDRESS"; pub const CHAIN_RPC_PROVIDER_KEY: &str = "CHAIN_RPC_PROVIDER"; pub const CHAIN_RPC_EXPLORER_KEY: &str = "CHAIN_RPC_EXPLORER"; @@ -11,6 +10,7 @@ pub const PRIVATE_KEY_KEY: &str = "PRIVATE_KEY"; pub const CHAIN_ID_KEY: &str = "CHAIN_ID"; pub const EMAIL_ACCOUNT_RECOVERY_VERSION_ID_KEY: &str = "EMAIL_ACCOUNT_RECOVERY_VERSION_ID"; pub const EMAIL_TEMPLATES_PATH_KEY: &str = "EMAIL_TEMPLATES_PATH"; +pub const REGEX_JSON_DIR_PATH_KEY: &str = "REGEX_JSON_DIR_PATH"; // Log strings pub const JSON_LOGGER_KEY: &str = "JSON_LOGGER"; diff --git a/packages/relayer/src/utils/mod.rs b/packages/relayer/src/utils/mod.rs deleted file mode 100644 index 361a451a..00000000 --- a/packages/relayer/src/utils/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod strings; -pub mod subject_templates; -pub mod utils; - -pub use strings::*; -pub use subject_templates::*; -pub use utils::*; diff --git a/packages/relayer/src/utils/subject_templates.rs b/packages/relayer/src/utils/subject_templates.rs deleted file mode 100644 index e8ccd734..00000000 --- a/packages/relayer/src/utils/subject_templates.rs +++ /dev/null @@ -1,246 +0,0 @@ -#![allow(clippy::upper_case_acronyms)] - -use crate::*; - -use ethers::abi::{self, Token}; -use ethers::types::{Address, Bytes, I256, U256}; -use regex::Regex; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Clone, Serialize, Deserialize)] -pub enum TemplateValue { - String(String), - Uint(U256), - Int(I256), - Decimals(String), - EthAddr(Address), - Fixed(String), -} - -impl TemplateValue { - pub fn abi_encode(&self, decimal_size: Option) -> Result { - match self { - Self::String(string) => Ok(Bytes::from(abi::encode(&[Token::String(string.clone())]))), - Self::Uint(uint) => Ok(Bytes::from(abi::encode(&[Token::Uint(*uint)]))), - Self::Int(int) => Ok(Bytes::from(abi::encode(&[Token::Int(int.into_raw())]))), - Self::Decimals(string) => Ok(Bytes::from(abi::encode(&[Token::Uint( - Self::decimals_str_to_uint(&string, decimal_size.unwrap_or(18)), - )]))), - Self::EthAddr(address) => Ok(Bytes::from(abi::encode(&[Token::Address(*address)]))), - Self::Fixed(string) => Err(anyhow!("Fixed value must not be passed to abi_encode")), - } - } - - pub fn decimals_str_to_uint(str: &str, decimal_size: u8) -> U256 { - let decimal_size = decimal_size as usize; - let dot = Regex::new("\\.").unwrap().find(str); - let (before_dot_str, mut after_dot_str) = match dot { - Some(dot_match) => ( - str[0..dot_match.start()].to_string(), - str[dot_match.end()..].to_string(), - ), - None => (str.to_string(), "".to_string()), - }; - assert!(after_dot_str.len() <= decimal_size); - let num_leading_zeros = decimal_size - after_dot_str.len(); - after_dot_str.push_str(&"0".repeat(num_leading_zeros)); - U256::from_dec_str(&(before_dot_str + &after_dot_str)) - .expect("composed amount string is not valid decimal") - } -} - -pub fn extract_template_vals_from_command( - input: &str, - templates: Vec, -) -> Result, anyhow::Error> { - // Convert the template to a regex pattern, escaping necessary characters and replacing placeholders - let pattern = templates - .iter() - .map(|template| match template.as_str() { - "{string}" => STRING_REGEX.to_string(), - "{uint}" => UINT_REGEX.to_string(), - "{int}" => INT_REGEX.to_string(), - "{decimals}" => DECIMALS_REGEX.to_string(), - "{ethAddr}" => ETH_ADDR_REGEX.to_string(), - _ => regex::escape(template), - }) - .collect::>() - .join("\\s+"); - - let regex = Regex::new(&pattern).map_err(|e| anyhow!("Regex compilation failed: {}", e))?; - - // Attempt to find the pattern in the input - if let Some(matched) = regex.find(input) { - // Calculate the number of bytes to skip before the match - let skipped_bytes = matched.start(); - - // Extract the values based on the matched pattern - let current_input = &input[skipped_bytes..]; - match extract_template_vals(current_input, templates) { - Ok(vals) => Ok(vals), - Err(e) => Err(e), - } - } else { - // If there's no match, return an error indicating no match was found - Err(anyhow!("Unable to match templates with input")) - } -} - -pub fn extract_template_vals(input: &str, templates: Vec) -> Result> { - let input_decomposed: Vec<&str> = input.split_whitespace().collect(); - let mut template_vals = Vec::new(); - let mut input_idx = 0; - - for template in templates.iter() { - if input_idx >= input_decomposed.len() { - break; // Prevents index out of bounds if input is shorter than template - } - - match template.as_str() { - "{string}" => { - let string_match = Regex::new(STRING_REGEX) - .unwrap() - .find(input_decomposed[input_idx]) - .ok_or(anyhow!("No string found"))?; - if string_match.start() != 0 { - return Err(anyhow!("String must be the whole word")); - } - let mut string = string_match.as_str().to_string(); - if string.contains("") { - string = string.split("").collect::>()[0].to_string(); - } - template_vals.push(TemplateValue::String(string)); - } - "{uint}" => { - let uint_match = Regex::new(UINT_REGEX) - .unwrap() - .find(input_decomposed[input_idx]) - .ok_or(anyhow!("No uint found"))?; - if uint_match.start() != 0 || uint_match.end() != input_decomposed[input_idx].len() - { - return Err(anyhow!("Uint must be the whole word")); - } - let mut uint_match = uint_match.as_str(); - if uint_match.contains("") { - uint_match = uint_match.split("").collect::>()[0]; - } - let uint = U256::from_dec_str(uint_match).unwrap(); - template_vals.push(TemplateValue::Uint(uint)); - } - "{int}" => { - let int_match = Regex::new(INT_REGEX) - .unwrap() - .find(input_decomposed[input_idx]) - .ok_or(anyhow!("No int found"))?; - if int_match.start() != 0 || int_match.end() != input_decomposed[input_idx].len() { - return Err(anyhow!("Int must be the whole word")); - } - let mut int_match = int_match.as_str(); - if int_match.contains("") { - int_match = int_match.split("").collect::>()[0]; - } - let int = I256::from_dec_str(int_match).unwrap(); - template_vals.push(TemplateValue::Int(int)); - } - "{decimals}" => { - let decimals_match = Regex::new(DECIMALS_REGEX) - .unwrap() - .find(input_decomposed[input_idx]) - .ok_or(anyhow!("No decimals found"))?; - if decimals_match.start() != 0 - || decimals_match.end() != input_decomposed[input_idx].len() - { - return Err(anyhow!("Decimals must be the whole word")); - } - let mut decimals = decimals_match.as_str().to_string(); - if decimals.contains("") { - decimals = decimals.split("").collect::>()[0].to_string(); - } - template_vals.push(TemplateValue::Decimals(decimals)); - } - "{ethAddr}" => { - let address_match = Regex::new(ETH_ADDR_REGEX) - .unwrap() - .find(input_decomposed[input_idx]) - .ok_or(anyhow!("No address found"))?; - if address_match.start() != 0 { - return Err(anyhow!("Address must be the whole word")); - } - let address = address_match.as_str().parse::
().unwrap(); - template_vals.push(TemplateValue::EthAddr(address)); - } - _ => {} // Skip unknown placeholders - } - - input_idx += 1; // Move to the next piece of input - } - - Ok(template_vals) -} - -// Generated by Github Copilot! -pub fn uint_to_decimal_string(uint: u128, decimal: usize) -> String { - // Convert amount to string in wei format (no decimals) - let uint_str = uint.to_string(); - let uint_length = uint_str.len(); - - // Create result vector with max length - // If less than 18 decimals, then 2 extra for "0.", otherwise one extra for "." - let mut result = vec![ - '0'; - if uint_length > decimal { - uint_length + 1 - } else { - decimal + 2 - } - ]; - let result_length = result.len(); - - // Difference between result and amount array index when copying - // If more than 18, then 1 index diff for ".", otherwise actual diff in length - let mut delta = if uint_length > decimal { - 1 - } else { - result_length - uint_length - }; - - // Boolean to indicate if we found a non-zero digit when scanning from last to first index - let mut found_non_zero_decimal = false; - - let mut actual_result_len = 0; - - // In each iteration we fill one index of result array (starting from end) - for i in (0..result_length).rev() { - // Check if we have reached the index where we need to add decimal point - if i == result_length - decimal - 1 { - // No need to add "." if there was no value in decimal places - if found_non_zero_decimal { - result[i] = '.'; - actual_result_len += 1; - } - // Set delta to 0, as we have already added decimal point (only for amount_length > 18) - delta = 0; - } - // If amountLength < 18 and we have copied everything, fill zeros - else if uint_length <= decimal && i < result_length - uint_length { - result[i] = '0'; - actual_result_len += 1; - } - // If non-zero decimal is found, or decimal point inserted (delta == 0), copy from amount array - else if found_non_zero_decimal || delta == 0 { - result[i] = uint_str.chars().nth(i - delta).unwrap(); - actual_result_len += 1; - } - // If we find non-zero decimal for the first time (trailing zeros are skipped) - else if uint_str.chars().nth(i - delta).unwrap() != '0' { - result[i] = uint_str.chars().nth(i - delta).unwrap(); - actual_result_len += 1; - found_non_zero_decimal = true; - } - } - - // Create final result string with correct length - let compact_result: String = result.into_iter().take(actual_result_len).collect(); - - compact_result -} diff --git a/packages/relayer/src/utils/utils.rs b/packages/relayer/src/utils/utils.rs deleted file mode 100644 index 49c021c1..00000000 --- a/packages/relayer/src/utils/utils.rs +++ /dev/null @@ -1,98 +0,0 @@ -#![allow(clippy::upper_case_acronyms)] -#![allow(clippy::identity_op)] - -use crate::*; -use ethers::abi::Token; -use ethers::types::{Bytes, U256}; - -use ::serde::Deserialize; - -use relayer_utils::*; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; - -const DOMAIN_FIELDS: usize = 9; -const SUBJECT_FIELDS: usize = 17; -const EMAIL_ADDR_FIELDS: usize = 9; - -#[derive(Debug, Clone, Deserialize)] -pub struct ProverRes { - proof: ProofJson, - pub_signals: Vec, -} - -#[derive(Debug, Clone, Deserialize)] -pub struct ProofJson { - pi_a: Vec, - pi_b: Vec>, - pi_c: Vec, -} - -impl ProofJson { - pub fn to_eth_bytes(&self) -> Result { - let pi_a = Token::FixedArray(vec![ - Token::Uint(U256::from_dec_str(self.pi_a[0].as_str())?), - Token::Uint(U256::from_dec_str(self.pi_a[1].as_str())?), - ]); - let pi_b = Token::FixedArray(vec![ - Token::FixedArray(vec![ - Token::Uint(U256::from_dec_str(self.pi_b[0][1].as_str())?), - Token::Uint(U256::from_dec_str(self.pi_b[0][0].as_str())?), - ]), - Token::FixedArray(vec![ - Token::Uint(U256::from_dec_str(self.pi_b[1][1].as_str())?), - Token::Uint(U256::from_dec_str(self.pi_b[1][0].as_str())?), - ]), - ]); - let pi_c = Token::FixedArray(vec![ - Token::Uint(U256::from_dec_str(self.pi_c[0].as_str())?), - Token::Uint(U256::from_dec_str(self.pi_c[1].as_str())?), - ]); - Ok(Bytes::from(abi::encode(&[pi_a, pi_b, pi_c]))) - } -} - -pub async fn generate_proof( - input: &str, - request: &str, - address: &str, -) -> Result<(Bytes, Vec)> { - let client = reqwest::Client::new(); - info!(LOG, "prover input {}", input); - let res = client - .post(format!("{}/prove/{}", address, request)) - .json(&serde_json::json!({ "input": input })) - .send() - .await? - .error_for_status()?; - let res_json = res.json::().await?; - info!(LOG, "prover response {:?}", res_json); - let proof = res_json.proof.to_eth_bytes()?; - let pub_signals = res_json - .pub_signals - .into_iter() - .map(|str| U256::from_dec_str(&str).expect("pub signal should be u256")) - .collect(); - Ok((proof, pub_signals)) -} - -pub fn calculate_default_hash(input: &str) -> String { - let mut hasher = DefaultHasher::new(); - input.hash(&mut hasher); - let hash_code = hasher.finish(); - - hash_code.to_string() -} - -pub fn calculate_account_salt(email_addr: &str, account_code: &str) -> String { - let padded_email_addr = PaddedEmailAddr::from_email_addr(&email_addr); - let account_code = if account_code.starts_with("0x") { - hex_to_field(&account_code).unwrap() - } else { - hex_to_field(&format!("0x{}", account_code)).unwrap() - }; - let account_code = AccountCode::from(account_code); - let account_salt = AccountSalt::new(&padded_email_addr, account_code).unwrap(); - - field_to_hex(&account_salt.0) -} diff --git a/rust-toolchain b/rust-toolchain index 4fa580ba..97e98527 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -1.80.1 \ No newline at end of file +1.80.1 diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..b8116019 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +max_width = 100 +use_small_heuristics = "Default" +reorder_imports = true +reorder_modules = true