Skip to content

Commit

Permalink
feat: add ETHEREUM_PROVIDER env variable for cloudflare
Browse files Browse the repository at this point in the history
  • Loading branch information
Nebulis committed Jul 7, 2020
1 parent 8af3c5c commit 2a62dd4
Show file tree
Hide file tree
Showing 17 changed files with 4,514 additions and 3,132 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"import/prefer-default-export": "off",
"import/extensions": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": "off",
"no-unused-expressions": "off",
"no-else-return": "off"
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ console.log(isValid(fragments)); // display true

## Advanced usage

### Environment Variables
- `ETHEREUM_PROVIDER`: let you pick the provider you want to use. Available values: `cloudflare`. The provider will default to `infura` if the variable is not set.
- `INFURA_API_KEY`: let you provide your own `INFURA` API key.

### Verify

By default the provided `verify` method performs multiple checks on a document
Expand Down
7,472 changes: 4,446 additions & 3,026 deletions package-lock.json

Large diffs are not rendered by default.

38 changes: 19 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,33 +21,33 @@
"author": "",
"license": "GPL-3.0",
"dependencies": {
"@govtechsg/dnsprove": "^2.0.5",
"@govtechsg/open-attestation": "3.7.0",
"@govtechsg/dnsprove": "^2.0.6",
"@govtechsg/open-attestation": "^3.9.0",
"debug": "^4.1.1",
"ethers": "^4.0.47"
"ethers": "^5.0.4"
},
"devDependencies": {
"@commitlint/cli": "^8.3.5",
"@commitlint/config-conventional": "^8.3.4",
"@commitlint/prompt": "^8.3.5",
"@commitlint/cli": "^9.0.1",
"@commitlint/config-conventional": "^9.0.1",
"@commitlint/prompt": "^9.0.1",
"@ls-age/commitlint-circle": "1.0.0",
"@types/debug": "^4.1.5",
"@types/jest": "^25.2.1",
"@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0",
"@types/jest": "^26.0.3",
"@typescript-eslint/eslint-plugin": "^3.5.0",
"@typescript-eslint/parser": "^3.5.0",
"commitizen": "^4.1.2",
"eslint": "^6.8.0",
"eslint-config-airbnb-base": "^14.1.0",
"eslint": "^7.4.0",
"eslint-config-airbnb-base": "^14.2.0",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jest": "^23.9.0",
"eslint-plugin-prettier": "^3.1.3",
"git-cz": "^4.3.1",
"jest": "^25.5.4",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-jest": "^23.18.0",
"eslint-plugin-prettier": "^3.1.4",
"git-cz": "^4.7.0",
"jest": "^26.1.0",
"prettier": "^2.0.5",
"semantic-release": "^17.0.7",
"ts-jest": "^25.5.0",
"typescript": "^3.8.3"
"semantic-release": "^17.1.1",
"ts-jest": "^26.1.1",
"typescript": "^3.9.6"
},
"publishConfig": {
"access": "public"
Expand Down
16 changes: 4 additions & 12 deletions src/common/smartContract/contractInstance.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
import * as ethers from "ethers";
import { INFURA_API_KEY } from "../../config";
import { Hash } from "../../types/core";
import { getLogger } from "../logger";

const logger = getLogger("contractInstance");
interface ContractInstance {
abi: any; // type is any of json file in abi folder
network: string;
contractAddress: Hash;
}

export const getProvider = (options: { network: string }): ethers.providers.Provider =>
new ethers.providers.InfuraProvider(options.network, process?.env?.INFURA_API_KEY || INFURA_API_KEY);
process.env.ETHEREUM_PROVIDER === "cloudflare"
? new ethers.providers.CloudflareProvider()
: new ethers.providers.InfuraProvider(options.network, process.env.INFURA_API_KEY || INFURA_API_KEY);

export const contractInstance = (options: ContractInstance) => {
const contract = new ethers.Contract(options.contractAddress, options.abi, getProvider(options));

// this is done to prevent uncaught exception to raise because an address is invalid
contract.addressPromise.catch(() => {
logger.trace(
`oa-verify caught an error from ethers when trying to resolve the address of the provided address ${options.contractAddress}`
);
});
return contract;
return new ethers.Contract(options.contractAddress, options.abi, getProvider(options));
};
4 changes: 2 additions & 2 deletions src/common/smartContract/documentStoreContractInterface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ export const createDocumentStoreContract = (address: string, { network }: { netw
};

export const isIssuedOnDocumentStore = async (smartContract: Contract, hash: Hash): Promise<boolean> => {
return smartContract.functions.isIssued(hash);
return (await smartContract.functions.isIssued(hash))[0];
};

export const isRevokedOnDocumentStore = async (smartContract: Contract, hash: Hash): Promise<boolean> => {
return smartContract.functions.isRevoked(hash);
return (await smartContract.functions.isRevoked(hash))[0];
};
8 changes: 7 additions & 1 deletion src/common/smartContract/documentStoreErrors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,17 @@ export const contractRevoked = (merkleRoot: string, address: string): Reason =>

export const getErrorReason = (error: EthersError, address: string): Reason | null => {
const reason = error.reason && Array.isArray(error.reason) ? error.reason[0] : error.reason ?? "";
if (reason.toLowerCase() === "contract not deployed".toLowerCase() && error.code === errors.UNSUPPORTED_OPERATION) {
if (
!error.reason &&
(error.method?.toLowerCase() === "isRevoked(bytes32)".toLowerCase() ||
error.method?.toLowerCase() === "isIssued(bytes32)".toLowerCase()) &&
error.code === errors.CALL_EXCEPTION
) {
return contractNotFound(address);
} else if (
(reason.toLowerCase() === "ENS name not configured".toLowerCase() && error.code === errors.UNSUPPORTED_OPERATION) ||
(reason.toLowerCase() === "bad address checksum".toLowerCase() && error.code === errors.INVALID_ARGUMENT) ||
error.message?.toLowerCase() === "name not found".toLowerCase() ||
(reason.toLowerCase() === "invalid address".toLowerCase() && error.code === errors.INVALID_ARGUMENT)
) {
return contractAddressInvalid(address);
Expand Down
3 changes: 1 addition & 2 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
// export const INFURA_API_KEY = "92c9a51428b946c1b8c1ac5a237616e4";
export const INFURA_API_KEY = "9f83339cff8c408093a75f98e0001b1a";
export const INFURA_API_KEY = "bb46da3f80e040e8ab73c0a9ff365d18";
1 change: 1 addition & 0 deletions src/types/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export enum OpenAttestationHashCode {
export interface EthersError extends Error {
reason?: string | string[];
code?: string;
method?: string;
}

export interface Reason {
Expand Down
5 changes: 3 additions & 2 deletions src/verifiers/dnsText/openAttestationDnsTxt.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getData, v2, v3, WrappedDocument, utils as oaUtils } from "@govtechsg/open-attestation";
import { getDocumentStoreRecords } from "@govtechsg/dnsprove";
import { utils } from "ethers";
import { getDefaultProvider } from "ethers";
import { VerificationFragmentType, VerificationManagerOptions, Verifier } from "../../types/core";
import { OpenAttestationDnsTxtCode } from "../../types/error";

Expand All @@ -20,11 +20,12 @@ const resolveIssuerIdentity = async (
const location = issuer?.identityProof?.location ?? "";
if (type !== "DNS-TXT") throw new Error("Identity type not supported");
if (!location) throw new Error("Location is missing");
const network = await getDefaultProvider(options.network).getNetwork();
const records = await getDocumentStoreRecords(location);
const matchingRecord = records.find(
(record) =>
record.addr.toLowerCase() === smartContractAddress.toLowerCase() &&
record.netId === utils.getNetwork(options.network).chainId.toString(10) &&
record.netId === network.chainId.toString(10) &&
record.type === "openatts" &&
record.net === "ethereum"
);
Expand Down
2 changes: 1 addition & 1 deletion src/verifiers/dnsText/openAttestationDnsTxt.v2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ describe("OpenAttestationDnsTxt v2 document", () => {
],
},
};
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore valid error, need to ignore
const fragment = await verify(document, {
network: "ropsten",
Expand Down
4 changes: 2 additions & 2 deletions src/verifiers/dnsText/openAttestationDnsTxt.v3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ describe("OpenAttestationDnsTxt v3 document", () => {
},
},
};
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore valid error, need to ignore
const fragment = await openAttestationDnsTxt.verify(document, {
network: "ropsten",
Expand Down Expand Up @@ -94,7 +94,7 @@ describe("OpenAttestationDnsTxt v3 document", () => {
},
},
};
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore valid error, need to ignore
const fragment = await openAttestationDnsTxt.verify(document, {
network: "ropsten",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
documentRopstenValidWithTokenRegistry,
} from "../../../test/fixtures/v3/documentRopstenValid";
import { documentRopstenRevoked } from "../../../test/fixtures/v3/documentRopstenRevoked";
import { documentRopstenValidWithDocumentStore as v2documentRopstenValidWithDocumentStore } from "../../../test/fixtures/v2/documentRopstenValidWithDocumentStore";
import { documentRopstenNotIssuedWithTokenRegistry } from "../../../test/fixtures/v2/documentRopstenNotIssuedWithTokenRegistry";

describe("openAttestationEthereumDocumentStoreRevoked", () => {
Expand Down Expand Up @@ -244,55 +243,6 @@ describe("openAttestationEthereumDocumentStoreRevoked", () => {
status: "VALID",
});
});
it("should return an error fragment when document mixes document store and other verifier method", async () => {
const fragment = await openAttestationEthereumDocumentStoreRevoked.verify(
{
...v2documentRopstenValidWithDocumentStore,
data: {
...v2documentRopstenValidWithDocumentStore.data,
issuers: [
v2documentRopstenValidWithDocumentStore.data.issuers[0],
{
identityProof: v2documentRopstenValidWithDocumentStore.data.issuers[0].identityProof,
name: "Foo Issuer",
},
],
},
},
{
network: "ropsten",
}
);
expect(fragment).toStrictEqual({
name: "OpenAttestationEthereumDocumentStoreRevoked",
type: "DOCUMENT_STATUS",
data: {
details: [
{
address: "0x8Fc57204c35fb9317D91285eF52D6b892EC08cD3",
revoked: false,
},
{
address: "",
reason: {
code: 2,
codeString: "CONTRACT_ADDRESS_INVALID",
message: "Contract address is invalid",
},
revoked: true,
},
],
revokedOnAny: true,
},

reason: {
code: 2,
codeString: "CONTRACT_ADDRESS_INVALID",
message: "Contract address is invalid",
},
status: "INVALID",
});
});
});
describe("v3", () => {
it("should return an invalid fragment when document with document store has been revoked", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const verify = verificationBuilder([openAttestationSignedProof]);
describe("OpenAttestationSignedProof", () => {
describe("SKIPPED", () => {
it("should return a valid SKIPPED fragment when document does not have a proof a block", async () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore error is valid, inoring for test purpose
const fragment = await verify(documentMainnetValidWithCertificateStore, {
network: "ropsten",
Expand Down
16 changes: 10 additions & 6 deletions src/verifiers/tokenRegistryMinted/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,23 @@ export const contractNotMinted = (merkleRoot: Hash, address: string): Reason =>

export const getErrorReason = (error: EthersError, address: string, hash: Hash): Reason => {
const reason = error.reason && Array.isArray(error.reason) ? error.reason[0] : error.reason ?? "";
if (reason.toLowerCase() === "contract not deployed".toLowerCase() && error.code === errors.UNSUPPORTED_OPERATION) {
if (
reason.toLowerCase() === "ERC721: owner query for nonexistent token".toLowerCase() &&
error.code === errors.CALL_EXCEPTION
) {
return contractNotMinted(hash, address);
} else if (
!error.reason &&
error.method?.toLowerCase() === "ownerOf(uint256)".toLowerCase() &&
error.code === errors.CALL_EXCEPTION
) {
return contractNotFound(address);
} else if (
(reason.toLowerCase() === "ENS name not configured".toLowerCase() && error.code === errors.UNSUPPORTED_OPERATION) ||
(reason.toLowerCase() === "bad address checksum".toLowerCase() && error.code === errors.INVALID_ARGUMENT) ||
(reason.toLowerCase() === "invalid address".toLowerCase() && error.code === errors.INVALID_ARGUMENT)
) {
return contractAddressInvalid(address);
} else if (
reason.toLowerCase() === "ERC721: owner query for nonexistent token".toLowerCase() &&
error.code === errors.CALL_EXCEPTION
) {
return contractNotMinted(hash, address);
}
return {
message: `Error with smart contract ${address}: ${error.reason}`,
Expand Down
18 changes: 11 additions & 7 deletions src/verify.v2.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import {
} from "../test/fixtures/v2/documentRinkebyWithDocumentStore";

describe("verify(integration)", () => {
afterEach(() => {
delete process.env.ETHEREUM_PROVIDER;
});
it("should fail for everything when document's hash is invalid and certificate store is invalid", async () => {
const results = await verify(tamperedDocumentWithCertificateStore, {
network: "ropsten",
Expand Down Expand Up @@ -53,19 +56,19 @@ describe("verify(integration)", () => {
{
address: "0x20bc9C354A18C8178A713B9BcCFFaC2152b53990",
reason: {
code: 3,
codeString: "ETHERS_UNHANDLED_ERROR",
message: "Error with smart contract 0x20bc9C354A18C8178A713B9BcCFFaC2152b53990: call exception",
code: 404,
codeString: "CONTRACT_NOT_FOUND",
message: "Contract 0x20bc9C354A18C8178A713B9BcCFFaC2152b53990 was not found",
},
issued: false,
},
],
issuedOnAll: false,
},
reason: {
code: 3,
codeString: "ETHERS_UNHANDLED_ERROR",
message: "Error with smart contract 0x20bc9C354A18C8178A713B9BcCFFaC2152b53990: call exception",
code: 404,
codeString: "CONTRACT_NOT_FOUND",
message: "Contract 0x20bc9C354A18C8178A713B9BcCFFaC2152b53990 was not found",
},
status: "INVALID",
name: "OpenAttestationEthereumDocumentStoreIssued",
Expand Down Expand Up @@ -210,7 +213,8 @@ describe("verify(integration)", () => {
expect(isValid(results, ["DOCUMENT_INTEGRITY"])).toStrictEqual(false);
});

it("should be valid for all checks when document with certificate store is valid on mainnet", async () => {
it("should be valid for all checks when document with certificate store is valid on mainnet using cloudflare", async () => {
process.env.ETHEREUM_PROVIDER = "cloudflare";
const results = await verify(documentMainnetValidWithCertificateStore, {
network: "homestead",
});
Expand Down
2 changes: 1 addition & 1 deletion test/fixtures/v2/documentSignedProofInvalidProofType.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const documentSignedProofInvalidProofType: SignedWrappedDocument<v2.OpenA
merkleRoot: "e9917f70b00bca5ac54086ac9e6a7e3f9e269d7a46bfd1f77f2b8caf63872dbf",
},
proof: {
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore error is valid, need to ignore for test purposes
type: "notSupported",
created: "2020-05-04T14:07:41.079Z",
Expand Down

0 comments on commit 2a62dd4

Please sign in to comment.