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

Add code coverage #52

Merged
merged 4 commits into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ jobs:
- name: Install & Build
run: yarn && yarn build

- name: Lint & Unit Test
- name: Lint & Unit Test & Coverage
run: |
yarn lint
yarn test unit
yarn verify
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v20.13.1
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ Intended to be used on server-side applications only.

## Local Testing

This project requires the Node.js version 20.0.0+.
If you are using nvm, you can run `nvm use` and use the node version in `.nvmrc`.

```sh
# Install
yarn
Expand Down
12 changes: 10 additions & 2 deletions jest.config.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
{
"preset": "ts-jest",
"testEnvironment": "node"
}
"testEnvironment": "node",
"coverageThreshold": {
"global": {
"branches": 60,
"functions": 60,
"lines": 60,
"statements": 60
}
}
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
"build": "rm -rf ./dist && tsc",
"lint": "eslint . --ignore-pattern dist/",
"test": "jest --testTimeout 30000",
"coverage": "yarn test --coverage",
"verify": "yarn lint && yarn coverage unit",
"fmt": "prettier --write '{src,examples,tests}/**/*.{js,jsx,ts,tsx}'"
},
"devDependencies": {
Expand Down
2 changes: 2 additions & 0 deletions src/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
arbitrum,
optimism,
optimismSepolia,
localhost,
} from "viem/chains";

// All supported networks
Expand All @@ -18,6 +19,7 @@ const SUPPORTED_NETWORKS = createNetworkMap([
arbitrum,
optimism,
optimismSepolia,
localhost,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is interesting... so we can use it with ganache now?

]);

interface NetworkFields {
Expand Down
2 changes: 1 addition & 1 deletion src/utils/kdf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export function najPublicKeyStrToUncompressedHexPoint(
export async function deriveChildPublicKey(
parentUncompressedPublicKeyHex: string,
signerId: string,
path = ""
path: string = ""
bh2smith marked this conversation as resolved.
Show resolved Hide resolved
): Promise<string> {
const ec = new EC("secp256k1");
const scalar = await sha256Hash(
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/utils.kdf.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const DECOMPRESSED_HEX =
"04a410e78ef8a4f81ffc7e1f2e60c6fd6ccd5ed1689ea83b980215a58ded51871d16b2c99f3772e6b017b35a2367883552ea6b545f82552e3b05bae56975d40241";
const CHILD_PK =
"04445b302250c5ba69e6a45d39b73a4cefc99a7e6e75ac164080c8bc68aa8c16fc332cf9b485f0f8ed0d815affdf3f9ad7e450c2658351fb09de2ad54e0f60795d";
const CHILD_PK_NO_PATH =
"043e6054b729c98e7b5fd46d4c182e7e243c1b035290cf3e5db1cb0e032382dd5065675c05934a1f7cfdf524aa8af2ede5251f03ba7ea5f154cdbadd65d0e9c36a";

describe("Crypto Functions", () => {
it("converts NEAR public key string to uncompressed hex point", () => {
Expand All @@ -27,6 +29,14 @@ describe("Crypto Functions", () => {
expect(result).toEqual(CHILD_PK);
});

it("derives child public key without path", async () => {
const parentHex = DECOMPRESSED_HEX;
const signerId = "ethdenver2024.testnet";
const result = await deriveChildPublicKey(parentHex, signerId);
expect(result).toMatch(/^04[0-9a-f]+$/);
expect(result).toEqual(CHILD_PK_NO_PATH);
});

it("converts uncompressed hex point to EVM address", () => {
const uncompressedHex = CHILD_PK;
const result = uncompressedHexPointToEvmAddress(uncompressedHex);
Expand Down
30 changes: 29 additions & 1 deletion tests/unit/utils.signature.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { signatureFromTxHash } from "../../src/utils/signature";
import {
signatureFromTxHash,
pickValidSignature,
} from "../../src/utils/signature";

describe("utility: get Signature", () => {
const url: string = "https://archival-rpc.testnet.near.org";
Expand All @@ -20,3 +23,28 @@ describe("utility: get Signature", () => {
);
});
});

describe("utility: pickValidSignature", () => {
const sig0 = "0x88LS5pkj99pd6B6noZU6sagQ1QDwHHoSy3qpHr5xLNsR";
const sig1 = "0xHaG9L4HnP69v6wSnAmKfzsCUhDaVMRZWNGhGqnepsMTD";

it("No signature is valid, should throw error", async () => {
expect(() => pickValidSignature([false, false], [sig0, sig1]))
.toThrow("Invalid signature");
});

it("both sig0 and sig1 are valid, should return sig0", async () => {
const sig = pickValidSignature([true, true], [sig0, sig1]);
expect(sig).toEqual(sig0);
});

it("sig0 is valid, should return sig0", async () => {
const sig = pickValidSignature([true, false], [sig0, sig1]);
expect(sig).toEqual(sig0);
});

it("sig1 is valid, should return sig1", async () => {
const sig = pickValidSignature([false, true], [sig0, sig1]);
expect(sig).toEqual(sig1);
});
});
26 changes: 25 additions & 1 deletion tests/unit/utils.transaction.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TransactionWithSignature } from "../../src";
import { buildTxPayload, addSignature } from "../../src/utils/transaction";
import { buildTxPayload, addSignature, toPayload } from "../../src/utils/transaction";

describe("Transaction Builder Functions", () => {
it("buildTxPayload", async () => {
Expand All @@ -11,6 +11,15 @@ describe("Transaction Builder Functions", () => {
36, 9, 101, 199, 230, 132, 140, 98, 211, 7, 68, 130, 233, 88, 145, 179,
]);
});

it("fails: toPayload", async () => {
const txHash =
"0x02e783aa36a7808309e8bb84773f7cbb8094deadbeef0000000000000000000000000b00b1e50180c00";
expect(() => toPayload(txHash)).toThrow(
`Payload Hex must have 32 bytes: ${txHash}`
);
});

it("addSignature", async () => {
const testTx: TransactionWithSignature = {
transaction:
Expand All @@ -27,4 +36,19 @@ describe("Transaction Builder Functions", () => {
"0x02f86b83aa36a780845974e6f084d0aa7af08094deadbeef0000000000000000000000000b00b1e50180c001a0ef532579e267c932b959a1adb9e455ac3c5397d0473471c4c3dd5d62fd4d7edea07c195e658c713d601d245311a259115bb91ec87c86acb07c03bd9c1936a6a9e8"
);
});

it("fails: addSignature", async () => {
const testTx: TransactionWithSignature = {
transaction:
"0x02e883aa36a780845974e6f084d0aa7af08094deadbeef0000000000000000000000000b00b1e50180c0",
signature: {
big_r: "02EF532579E267C932B959A1ADB9E455AC3C5397D0473471C4C3DD5D62FD4D7EDE",
big_s: "7C195E658C713D601D245311A259115BB91EC87C86ACB07C03BD9C1936A6A9E8",
},
};
const sender = "0xInvalidSenderAddress";
expect(() => addSignature(testTx, sender)).toThrow(
"Signature is not valid"
);
});
});
91 changes: 90 additions & 1 deletion tests/unit/wc.handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
offChainRecovery,
wcRouter,
} from "../../src/wallet-connect/handlers";
import { MessageData } from "../../src/types/types";
import { MessageData, TypedMessageData } from "../../src/types/types";

describe("Wallet Connect", () => {
const chainId = "eip155:11155111";
Expand Down Expand Up @@ -33,6 +33,20 @@ describe("Wallet Connect", () => {
]);
});

it("fail with wrong method", async () => {
const messageString = "Hello!";
const request = {
method: "eth_fail",
params: [from, toHex(messageString)],
};

await expect(wcRouter(
request.method,
chainId,
request.params as PersonalSignParams
)).rejects.toThrow("Unhandled session_request method: eth_fail");
});

it("opensea login", async () => {
const request = {
method: "personal_sign",
Expand Down Expand Up @@ -277,5 +291,80 @@ Challenge: 4113fc3ab2cc60f5d595b2e55349f1eec56fd0c70d4287081fe7156848263626`
"0x491e245db3914b85807f3807f2125b9ed9722d0e9f3fa0fe325b31893fa5e693387178ae4a51f304556c1b2e9dd24f1120d073f93017af006ad801a639214ea61b"
);
});

it("recovering eth_signTypedData", async () => {
bh2smith marked this conversation as resolved.
Show resolved Hide resolved
// TODO: Find a real examples of Ethereum apps and simulate them on the test
const recoveryData = {
type: "eth_signTypedData",
data: {
address: "0xf11c22d61ecd7b1adcb6b43542fe8a96b9328dc7",
message: {
from: {
name: "Cow",
wallet: "0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826",
},
to: {
name: "Bob",
wallet: "0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB",
},
contents: "Hello, Bob!",
},
types: {
Person: [
{ name: "name", type: "string" },
{ name: "wallet", type: "address" },
],
Mail: [
{ name: "from", type: "Person" },
{ name: "to", type: "Person" },
{ name: "contents", type: "string" },
],
},
primaryType: "Mail",
domain: {
name: "Ether Mail",
version: "1",
chainId: 1,
verifyingContract: "0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC",
}
} as TypedMessageData,
};
const r =
"0x491E245DB3914B85807F3807F2125B9ED9722D0E9F3FA0FE325B31893FA5E693";
const s =
"0x387178AE4A51F304556C1B2E9DD24F1120D073F93017AF006AD801A639214EA6";
const sigs: [Hex, Hex] = [
serializeSignature({ r, s, yParity: 0 }),
serializeSignature({ r, s, yParity: 1 }),
];

await expect(offChainRecovery(recoveryData, sigs)).rejects.toThrow(
"Invalid signature"
);
});

it("fail with wrong type", async () => {
const recoveryData = {
type: "wrong_type",
data: {
address: "0xf11c22d61ecd7b1adcb6b43542fe8a96b9328dc7",
message: {
raw: "0x57656c636f6d6520746f204f70656e536561210a0a436c69636b20746f207369676e20696e20616e642061636365707420746865204f70656e536561205465726d73206f662053657276696365202868747470733a2f2f6f70656e7365612e696f2f746f732920616e64205072697661637920506f6c696379202868747470733a2f2f6f70656e7365612e696f2f70726976616379292e0a0a5468697320726571756573742077696c6c206e6f742074726967676572206120626c6f636b636861696e207472616e73616374696f6e206f7220636f737420616e792067617320666565732e0a0a57616c6c657420616464726573733a0a3078663131633232643631656364376231616463623662343335343266653861393662393332386463370a0a4e6f6e63653a0a63336432623238622d623964652d346239662d383935362d316336663739373133613431",
},
} as MessageData,
};
const r =
"0x491E245DB3914B85807F3807F2125B9ED9722D0E9F3FA0FE325B31893FA5E693";
const s =
"0x387178AE4A51F304556C1B2E9DD24F1120D073F93017AF006AD801A639214EA6";
const sigs: [Hex, Hex] = [
serializeSignature({ r, s, yParity: 0 }),
serializeSignature({ r, s, yParity: 1 }),
];

await expect(offChainRecovery(recoveryData, sigs)).rejects.toThrow(
"Invalid Path"
);
});
});
});