namillum
Version:
Bubble Protocol SDK
431 lines (379 loc) • 12 kB
text/typescript
// Copyright (C) 2018 Zilliqa
//
// This file is part of zilliqa-js
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import { HTTPProvider } from "@zilliqa-js/core";
import { getPubKeyFromPrivateKey, schnorr } from "@zilliqa-js/crypto";
import { BN, Long } from "@zilliqa-js/util";
import fetch, { MockParams } from "jest-fetch-mock";
import { createWallet } from "./util";
import { Account } from "../src/account";
import { Wallet } from "../src/wallet";
import { Transaction } from "../src/transaction";
import { Buffer } from "buffer";
const provider = new HTTPProvider("https://mock.com");
describe("Wallet", () => {
afterEach(() => {
fetch.resetMocks();
});
it("should be able to bootstrap with an array of Accounts ", () => {
const accounts: Account[] = [];
for (let i = 0; i < 10; i++) {
accounts.push(new Account(schnorr.generatePrivateKey()));
}
const wallet = new Wallet(provider, accounts);
expect(Object.keys(wallet.accounts).length).toEqual(10);
});
it("should be able to bootstrap from a mnemonic", async () => {
const mnemonic =
"cart hat drip lava jelly keep device journey bean mango rocket festival";
const wallet = new Wallet(provider);
const addresses = [];
for (let i = 0; i < 10; i++) {
const address = await wallet.addByMnemonic(mnemonic, i);
addresses.push(address);
}
expect(Object.keys(wallet.accounts).length).toEqual(10);
expect(Object.keys(wallet.accounts).sort()).toEqual(addresses.sort());
expect(Object.keys(wallet.accounts).sort()).toEqual([
"0x0237F40D30d3c37C9b77577ACbB11C972Cc58664",
"0x0723DD96243491eE84A925eDB657f24582AEc899",
"0x4878d8EB9A63493A6de066eB1458CaB672Dc8CfD",
"0x68275607E8bDf7cFA248b5f5a07B576F9Ef39cD1",
"0x852F52532c3c928269bdd3B83Ac88E25A04D6B3b",
"0x9165AE9ceeb155fB75D9C1fee2041f12C6e1f5eA",
"0xAACDF9c84Bba51878C8681C72f035B62135d6d7e",
"0xCd6cb5bC8F3EE8fF7a91B060Ce341FEb6Fc40E21",
"0xEcD9D875C7366432a7Ce403A7702dFa3e7F09602",
"0xbEA456Fb58094Be1C7f99BB6D1584DCEc642B0B0",
]);
});
it("should be able to export a json keystore", async () => {
const [walletA, [address]] = createWallet(1);
const keystore = await walletA.export(address, "stronk");
const [walletB] = createWallet(0);
const importedAddress = await walletB.addByKeystore(keystore, "stronk");
expect(importedAddress).toEqual(address);
});
it("should sign transactions with the default account", async () => {
const [wallet] = createWallet(1);
const pubKey = (wallet.defaultAccount &&
wallet.defaultAccount.publicKey) as string;
const tx = new Transaction(
{
version: 1,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
pubKey,
},
provider
);
fetch.once(
JSON.stringify({
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
})
);
const signed = await wallet.sign(tx);
const signature = schnorr.toSignature(signed.txParams.signature as string);
const lgtm = schnorr.verify(
signed.bytes,
signature,
Buffer.from(pubKey, "hex")
);
expect(fetch).toHaveBeenCalledTimes(1);
expect(lgtm).toBeTruthy();
});
it("should throw funds not sufficient error", async () => {
const [wallet] = createWallet(1);
const pubKey = (wallet.defaultAccount &&
wallet.defaultAccount.publicKey) as string;
const tx = new Transaction(
{
version: 1,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
pubKey,
},
provider
);
fetch.once(
JSON.stringify({
id: 1,
jsonrpc: "2.0",
result: {
balance: "100",
nonce: 1,
},
})
);
await expect(wallet.sign(tx)).rejects.toThrow();
});
it("should respect the supplied nonce, if any", async () => {
const [wallet] = createWallet(1);
const pubKey = (wallet.defaultAccount &&
wallet.defaultAccount.publicKey) as string;
const tx = new Transaction(
{
version: 0,
nonce: 888,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(888),
gasPrice: new BN(888),
gasLimit: Long.fromNumber(888),
pubKey,
},
provider
);
fetch.once(
JSON.stringify({
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
})
);
const signed = await wallet.sign(tx);
const signature = schnorr.toSignature(signed.txParams.signature as string);
const lgtm = schnorr.verify(
signed.bytes,
signature,
Buffer.from(pubKey, "hex")
);
expect(lgtm).toBeTruthy();
});
it("should throw an error if asked to sign with no accounts available", () => {
const pubKey = getPubKeyFromPrivateKey(schnorr.generatePrivateKey());
const [wallet] = createWallet(0);
const tx = new Transaction(
{
version: 1,
nonce: 1,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
pubKey,
},
provider
);
expect(() => wallet.sign(tx)).toThrow();
});
it("should sign batch transactions with default account", async () => {
const [wallet] = createWallet(1);
const pubKey = (wallet.defaultAccount &&
wallet.defaultAccount.publicKey) as string;
const txList = [];
for (let i = 0; i < 2; i++) {
const tx = new Transaction(
{
version: 1,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
pubKey,
},
provider
);
txList.push(tx);
}
// first balance is for get balance before looping txlist
// the other two balance is when it calls signWith
// the last is for return value
const responses = [
{
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
ID: "some_hash",
receipt: { success: true },
},
},
].map(
(res) => [JSON.stringify(res), { status: 200 }] as [string, MockParams]
);
fetch.mockResponses(...responses);
const batchResult = await wallet.signBatch(txList);
for (const signedTx of batchResult) {
const signature = schnorr.toSignature(
signedTx.txParams.signature as string
);
const lgtm = schnorr.verify(
signedTx.bytes,
signature,
Buffer.from(pubKey, "hex")
);
expect(lgtm).toBeTruthy();
}
});
it("should throw an error for sign batch with no default account", async () => {
const pubKey = getPubKeyFromPrivateKey(schnorr.generatePrivateKey());
const [wallet] = createWallet(0);
const txList: Transaction[] = [];
for (let i = 0; i < 2; i++) {
const tx = new Transaction(
{
version: 1,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
pubKey,
},
provider
);
txList.push(tx);
}
await expect(wallet.signBatch(txList)).rejects.toThrow(
"This wallet has no default account."
);
});
it("should throw an error if offline sign is true and txn does not have explicit nonce", async () => {
const [wallet] = createWallet(1);
const pubKey = (wallet.defaultAccount &&
wallet.defaultAccount.publicKey) as string;
const tx = new Transaction(
{
version: 0,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(888),
gasPrice: new BN(888),
gasLimit: Long.fromNumber(888),
pubKey,
},
provider
);
await expect(wallet.sign(tx, true)).rejects.toThrow();
});
it("should not have error if offline sign is true and txn HAS explicit nonce", async () => {
const [wallet] = createWallet(1);
const pubKey = (wallet.defaultAccount &&
wallet.defaultAccount.publicKey) as string;
const tx = new Transaction(
{
version: 1,
toAddr: "0x1234567890123456789012345678901234567890",
nonce: 1,
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
pubKey,
},
provider
);
const signed = await wallet.sign(tx, true);
const signature = schnorr.toSignature(signed.txParams.signature as string);
const lgtm = schnorr.verify(
signed.bytes,
signature,
Buffer.from(pubKey, "hex")
);
expect(lgtm).toBeTruthy();
});
it("should not have error if offline sign is explicit false and no explicit nonce", async () => {
const [wallet] = createWallet(1);
const pubKey = (wallet.defaultAccount &&
wallet.defaultAccount.publicKey) as string;
const tx = new Transaction(
{
version: 1,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
pubKey,
},
provider
);
fetch.once(
JSON.stringify({
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
})
);
const signed = await wallet.sign(tx, false); // explicit FALSE
const signature = schnorr.toSignature(signed.txParams.signature as string);
const lgtm = schnorr.verify(
signed.bytes,
signature,
Buffer.from(pubKey, "hex")
);
expect(lgtm).toBeTruthy();
});
it("should not have error if offline sign is explicit false and txn HAS explicit nonce", async () => {
const [wallet] = createWallet(1);
const pubKey = (wallet.defaultAccount &&
wallet.defaultAccount.publicKey) as string;
const tx = new Transaction(
{
version: 1,
toAddr: "0x1234567890123456789012345678901234567890",
nonce: 1,
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
pubKey,
},
provider
);
const signed = await wallet.sign(tx, false); // explicit FALSE
const signature = schnorr.toSignature(signed.txParams.signature as string);
const lgtm = schnorr.verify(
signed.bytes,
signature,
Buffer.from(pubKey, "hex")
);
expect(lgtm).toBeTruthy();
});
});