Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

contract query errors returned as strings #165

Open
NoahSaso opened this issue Apr 4, 2024 · 0 comments
Open

contract query errors returned as strings #165

NoahSaso opened this issue Apr 4, 2024 · 0 comments

Comments

@NoahSaso
Copy link

NoahSaso commented Apr 4, 2024

query.compute.queryContract throws errors in some cases and returns error strings in other cases (like when a query does not exist), which is confusing and problematic, especially because strings are valid responses from queries. thus the caller cannot verify if a string is an error or a successful response from a contract.

i suspect the error strings should actually be thrown as errors instead, unless i'm misunderstanding the purpose here.

in src/query/compute.ts:

  /**
   * Query a Secret Contract.
   * May return a string on error.
   */
  async queryContract<T extends object, R extends any>(
    {
      contract_address: contractAddress,
      code_hash: codeHash,
      query,
    }: QueryContractRequest<T>,
    headers?: HeadersInit,
  ): Promise<R> {
    if (!codeHash) {
      console.warn(getMissingCodeHashWarning("queryContract()"));
      ({ code_hash: codeHash } = await this.codeHashByContractAddress({
        contract_address: contractAddress,
      }));
    }
    codeHash = codeHash!.replace("0x", "").toLowerCase();

    const encryptedQuery = await this.encryption!.encrypt(codeHash, query);
    const nonce = encryptedQuery.slice(0, 32);

    try {
      const { data: encryptedResult } = await Query.QuerySecretContract(
        {
          contract_address: contractAddress,
          query: encryptedQuery,
        },
        {
          headers,
          pathPrefix: this.url,
        },
      );

      const decryptedBase64Result = await this.encryption!.decrypt(
        fromBase64(encryptedResult as unknown as string)!,
        nonce,
      );

      // Don't try to parse JSON in case the result is empty.
      // This seems kinda stupid but if the contract for some reason returns `Binary::default()`
      // the JSON parsing will fail (empty bytes)
      if (!decryptedBase64Result?.length) {
        return {} as R;
      }

      return JSON.parse(fromUtf8(fromBase64(fromUtf8(decryptedBase64Result))));
    } catch (err) {
      try {
        const errorMessageRgx =
          /encrypted: (.+?): (?:instantiate|execute|query|reply to|migrate) contract failed/g;
        const rgxMatches = errorMessageRgx.exec(err.message);
        if (rgxMatches == null || rgxMatches?.length != 2) {
          throw err;
        }

        const encryptedError = fromBase64(rgxMatches[1]);

        const decryptedBase64Error = await this.encryption!.decrypt(
          encryptedError,
          nonce,
        );

        try {
          //@ts-ignore
          // return the error string
          return fromUtf8(fromBase64(fromUtf8(decryptedBase64Error)));
        } catch (parseError) {
          if (parseError.message === "Invalid base64 string format") {
            //@ts-ignore
            // return the error string
            return fromUtf8(decryptedBase64Error);
          } else {
            throw err;
          }
        }
      } catch (decryptionError) {
        throw err;
      }
    }
  }

i feel like the return statements in the main catch statement should probably be throw new Error(...) instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant