namillum
Version:
Bubble Protocol SDK
647 lines (592 loc) • 15.6 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, RPCMethod } from "@zilliqa-js/core";
import {
isValidChecksumAddress,
randomBytes,
toBech32Address,
toChecksumAddress,
} from "@zilliqa-js/crypto";
import { BN, Long } from "@zilliqa-js/util";
import { Transaction } from "../src/transaction";
import { Wallet } from "../src/wallet";
import fetch, { MockParams } from "jest-fetch-mock";
const provider = new HTTPProvider("https://mock.com");
const wallet = new Wallet(provider);
const generateChecksumAddress = () => toChecksumAddress(randomBytes(20));
describe("Transaction", () => {
for (let i = 0; i < 10; i++) {
wallet.create();
}
afterEach(() => {
fetch.resetMocks();
});
it("should return a checksummed address from txParams", () => {
const tx = new Transaction(
{
version: 0,
toAddr: generateChecksumAddress(),
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
);
// FIXME: remove 0x when this is fixed on the core side
expect(isValidChecksumAddress(tx.txParams.toAddr)).toBe(true);
});
it("should accept bech32 toAddr", () => {
const b16 = toChecksumAddress(randomBytes(20));
const b32 = toBech32Address(b16);
const tx = new Transaction(
{
version: 0,
toAddr: b32,
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
);
expect(isValidChecksumAddress(tx.txParams.toAddr)).toBe(true);
expect(tx.txParams.toAddr).toEqual(b16);
});
it("should not accept normal address", () => {
const addr = randomBytes(20);
try {
const tx = new Transaction(
{
version: 0,
toAddr: addr,
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
);
console.log(tx.txParams);
} catch (error) {
if (error instanceof Error) {
expect(error.message).toEqual(
"Wrong address format, should be either bech32 or checksummed address"
);
}
}
});
it("should poll and call queued handlers on confirmation", async () => {
const responses = [
{
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
TranID: "some_hash",
Info: "Non-contract txn, sent to shard",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
ID: "some_hash",
receipt: { cumulative_gas: 1000, success: true },
},
},
].map(
(res) => [JSON.stringify(res), { status: 200 }] as [string, MockParams]
);
fetch.mockResponses(...responses);
const pending = await wallet.sign(
new Transaction(
{
version: 0,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
)
);
await provider.send(RPCMethod.CreateTransaction, pending.txParams);
const confirmed = await pending.confirm("some_hash");
const state = confirmed.txParams;
expect(confirmed.isConfirmed()).toBeTruthy();
expect(state.receipt).toEqual({ success: true, cumulative_gas: 1000 });
});
it("should reject the promise if there is a network error", async () => {
fetch
.once(
JSON.stringify({
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
})
)
.mockRejectOnce(new Error("something bad happened"));
const tx = await wallet.sign(
new Transaction(
{
version: 0,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
)
);
await expect(
provider.send(RPCMethod.CreateTransaction, tx.txParams)
).rejects.toThrow();
});
it("should not reject the promise if receipt.success === false", async () => {
const responses = [
{
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
TranID: "some_hash",
Info: "Non-contract txn, sent to shard",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
ID: "some_hash",
receipt: { cumulative_gas: 1000, success: false },
},
},
].map(
(res) => [JSON.stringify(res), { status: 200 }] as [string, MockParams]
);
fetch.mockResponses(...responses);
const tx = await wallet.sign(
new Transaction(
{
version: 0,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
)
);
const res = await provider.send(RPCMethod.CreateTransaction, tx.txParams);
const rejected = await tx.confirm(res.result.TranID);
await expect(
rejected.txParams.receipt && rejected.txParams.receipt.success
).toEqual(false);
});
it("should try for n attempts before timing out", async () => {
const responses = [
{
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
TranID: "some_hash",
Info: "Non-contract txn, sent to shard",
},
},
...(() => {
const mocks = [];
for (let i = 0; i < 40; i++) {
mocks.push({
id: 1,
jsonrpc: "2.0",
error: {
code: -888,
message: "Not found",
},
});
}
return mocks;
})(),
{
id: 1,
jsonrpc: "2.0",
result: {
ID: "some_hash",
receipt: { cumulative_gas: 1000, success: true },
},
},
].map(
(res) => [JSON.stringify(res), { status: 200 }] as [string, MockParams]
);
fetch.mockResponses(...responses);
const pending = await wallet.sign(
new Transaction(
{
version: 0,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
)
);
await provider.send(RPCMethod.CreateTransaction, pending.txParams);
await expect(pending.confirm("some_hash", 40, 0)).rejects.toThrow(
"The transaction is still not confirmed after 40 attempts."
);
});
it("should use block confirm to confirm a txn", async () => {
const responses = [
{
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
TranID: "some_hash",
Info: "Non-contract txn, sent to shard",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
header: {
BlockNum: "10000",
},
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
header: {
BlockNum: "10000",
},
},
},
{
id: 1,
jsonrpc: "2.0",
error: {
code: -888,
message: "Not found",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
header: {
BlockNum: "10001",
},
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
ID: "some_hash",
receipt: { cumulative_gas: 1000, success: true },
},
},
].map(
(res) => [JSON.stringify(res), { status: 200 }] as [string, MockParams]
);
fetch.mockResponses(...responses);
const tx = await wallet.sign(
new Transaction(
{
version: 0,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
)
);
const res = await provider.send(RPCMethod.CreateTransaction, tx.txParams);
const confirmed = await tx.blockConfirm(res.result.TranID);
const state = confirmed.txParams;
expect(confirmed.isConfirmed()).toBeTruthy();
expect(state.receipt).toEqual({ success: true, cumulative_gas: 1000 });
expect(confirmed.blockConfirmation).toEqual(1);
});
it("should get hash without sending to network", async () => {
const tx = new Transaction(
{
version: 21823489,
nonce: 41,
toAddr: "zil124sjf3g7jnxua7wnx4egu0vcnm4nzlef5zcdu3",
amount: new BN(100000000),
pubKey:
"026c7f3b8ac6f615c00c34186cbe4253a2c5acdc524b1cfae544c629d8e3564cfc",
gasPrice: new BN(1000000000),
gasLimit: Long.fromNumber(1),
signature:
"8bb3404b033b780421ae5d31b9e130161e58e750ca052ab07d9f71924d0353d525353ef6217b4c56303c7434245110c0e0faa578c6d993c2b91b5c8574b79cf7",
},
provider
);
expect(tx.hash).toEqual(
"f0f1cefeed3e6124e80eadc668908a89fb45d130c90619004589b1c726fb178f"
);
});
it("should use block confirm to reject a txn", async () => {
const responses = [
{
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
TranID: "some_hash",
Info: "Non-contract txn, sent to shard",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
header: {
BlockNum: "10001",
},
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
header: {
BlockNum: "10001",
},
},
},
{
id: 1,
jsonrpc: "2.0",
error: {
code: -888,
message: "Not found",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
header: {
BlockNum: "10002",
},
},
},
{
id: 1,
jsonrpc: "2.0",
error: {
code: -888,
message: "Not found",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
header: {
BlockNum: "10003",
},
},
},
{
id: 1,
jsonrpc: "2.0",
error: {
code: -888,
message: "Not found",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
header: {
BlockNum: "10004",
},
},
},
{
id: 1,
jsonrpc: "2.0",
error: {
code: -888,
message: "Not found",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
header: {
BlockNum: "10004",
},
},
},
].map(
(res) => [JSON.stringify(res), { status: 200 }] as [string, MockParams]
);
fetch.mockResponses(...responses);
const tx = await wallet.sign(
new Transaction(
{
version: 0,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
)
);
const res = await provider.send(RPCMethod.CreateTransaction, tx.txParams);
await expect(tx.blockConfirm(res.result.TranID, 4, 1000)).rejects.toThrow(
"The transaction is still not confirmed after 4 blocks."
);
});
it("should not have error if offline sign is false", async () => {
const responses = [
{
id: 1,
jsonrpc: "2.0",
result: {
TranID: "some_hash",
Info: "Non-contract txn, sent to shard",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
ID: "some_hash",
receipt: { cumulative_gas: 1000, success: true },
},
},
].map(
(res) => [JSON.stringify(res), { status: 200 }] as [string, MockParams]
);
fetch.mockResponses(...responses);
const tx = new Transaction(
{
version: 1,
nonce: 1,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
);
const pending = await wallet.sign(tx, false);
await provider.send(RPCMethod.CreateTransaction, pending.txParams);
const confirmed = await pending.confirm("some_hash");
const state = confirmed.txParams;
expect(confirmed.isConfirmed()).toBeTruthy();
expect(state.receipt).toEqual({ success: true, cumulative_gas: 1000 });
});
it("should not have error if offline sign is explicit false", async () => {
const responses = [
{
id: 1,
jsonrpc: "2.0",
result: {
balance: "39999999000000000",
nonce: 1,
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
TranID: "some_hash",
Info: "Non-contract txn, sent to shard",
},
},
{
id: 1,
jsonrpc: "2.0",
result: {
ID: "some_hash",
receipt: { cumulative_gas: 1000, success: true },
},
},
].map(
(res) => [JSON.stringify(res), { status: 200 }] as [string, MockParams]
);
fetch.mockResponses(...responses);
const tx = new Transaction(
{
version: 1,
toAddr: "0x1234567890123456789012345678901234567890",
amount: new BN(0),
gasPrice: new BN(1000),
gasLimit: Long.fromNumber(1000),
},
provider
);
const pending = await wallet.sign(tx, false); // explicit FALSE
await provider.send(RPCMethod.CreateTransaction, pending.txParams);
const confirmed = await pending.confirm("some_hash");
const state = confirmed.txParams;
expect(confirmed.isConfirmed()).toBeTruthy();
expect(state.receipt).toEqual({ success: true, cumulative_gas: 1000 });
});
});