Merge pull request #464 from cosmos/460-logs

🔥 Deduplicate launchpad logs module
This commit is contained in:
mergify[bot] 2020-10-27 09:53:11 +00:00 committed by GitHub
commit 6def7276b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 36 additions and 321 deletions

View File

@ -4,6 +4,7 @@ import { Bech32, fromHex, fromUtf8, toAscii, toBase64 } from "@cosmjs/encoding";
import {
assertIsBroadcastTxSuccess,
isWrappedStdTx,
logs,
makeSignDoc,
makeStdTx,
MsgSend,
@ -14,7 +15,6 @@ import { assert, sleep } from "@cosmjs/utils";
import { ReadonlyDate } from "readonly-date";
import { Code, CosmWasmClient, PrivateCosmWasmClient } from "./cosmwasmclient";
import { findAttribute } from "./logs";
import { SigningCosmWasmClient } from "./signingcosmwasmclient";
import cosmoshub from "./testdata/cosmoshub.json";
import {
@ -244,10 +244,9 @@ describe("CosmWasmClient", () => {
const signedTx = makeStdTx(signed, signature);
const result = await client.broadcastTx(signedTx);
assertIsBroadcastTxSuccess(result);
const { logs, transactionHash } = result;
const amountAttr = findAttribute(logs, "transfer", "amount");
const amountAttr = logs.findAttribute(result.logs, "transfer", "amount");
expect(amountAttr.value).toEqual("1234567ucosm");
expect(transactionHash).toMatch(/^[0-9A-F]{64}$/);
expect(result.transactionHash).toMatch(/^[0-9A-F]{64}$/);
});
});

View File

@ -7,6 +7,7 @@ import {
Coin,
IndexedTx,
LcdClient,
logs,
normalizePubkey,
PubKey,
setupAuthExtension,
@ -17,7 +18,6 @@ import {
import { Uint53 } from "@cosmjs/math";
import { setupWasmExtension, WasmExtension } from "./lcdapi/wasm";
import { parseLogs } from "./logs";
import { JsonObject } from "./types";
export interface GetSequenceResult {
@ -335,7 +335,7 @@ export class CosmWasmClient {
rawLog: result.raw_log || "",
}
: {
logs: result.logs ? parseLogs(result.logs) : [],
logs: result.logs ? logs.parseLogs(result.logs) : [],
rawLog: result.raw_log || "",
transactionHash: result.txhash,
data: result.data ? fromHex(result.data) : undefined,
@ -470,7 +470,7 @@ export class CosmWasmClient {
hash: restItem.txhash,
code: restItem.code || 0,
rawLog: restItem.raw_log,
logs: parseLogs(restItem.logs || []),
logs: logs.parseLogs(restItem.logs || []),
tx: restItem.tx,
timestamp: restItem.timestamp,
}),

View File

@ -1,6 +1,3 @@
import * as logs from "./logs";
export { logs };
export { setupWasmExtension, WasmExtension } from "./lcdapi/wasm";
export {
Account,

View File

@ -10,6 +10,7 @@ import {
coin,
coins,
LcdClient,
logs,
makeSignDoc,
makeStdTx,
OfflineSigner,
@ -20,7 +21,6 @@ import {
} from "@cosmjs/launchpad";
import { assert } from "@cosmjs/utils";
import { findAttribute, parseLogs } from "../logs";
import {
isMsgInstantiateContract,
isMsgStoreCode,
@ -142,14 +142,14 @@ describe("WasmExtension", () => {
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic);
const result = await uploadContract(wallet, hackatom);
assertIsBroadcastTxSuccess(result);
const logs = parseLogs(result.logs);
const codeIdAttr = findAttribute(logs, "message", "code_id");
const parsedLogs = logs.parseLogs(result.logs);
const codeIdAttr = logs.findAttribute(parsedLogs, "message", "code_id");
hackatomCodeId = Number.parseInt(codeIdAttr.value, 10);
const instantiateResult = await instantiateContract(wallet, hackatomCodeId, makeRandomAddress());
assertIsBroadcastTxSuccess(instantiateResult);
const instantiateLogs = parseLogs(instantiateResult.logs);
const contractAddressAttr = findAttribute(instantiateLogs, "message", "contract_address");
const instantiateLogs = logs.parseLogs(instantiateResult.logs);
const contractAddressAttr = logs.findAttribute(instantiateLogs, "message", "contract_address");
hackatomContractAddress = contractAddressAttr.value;
}
});
@ -205,8 +205,8 @@ describe("WasmExtension", () => {
const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, transferAmount);
assertIsBroadcastTxSuccess(result);
const logs = parseLogs(result.logs);
const contractAddressAttr = findAttribute(logs, "message", "contract_address");
const parsedLogs = logs.parseLogs(result.logs);
const contractAddressAttr = logs.findAttribute(parsedLogs, "message", "contract_address");
const myAddress = contractAddressAttr.value;
const newContractsByCode = await client.wasm.listContractsByCodeId(hackatomCodeId);
@ -254,8 +254,8 @@ describe("WasmExtension", () => {
// create new instance and compare before and after
const result = await instantiateContract(wallet, hackatomCodeId, beneficiaryAddress, transferAmount);
assertIsBroadcastTxSuccess(result);
const logs = parseLogs(result.logs);
const contractAddressAttr = findAttribute(logs, "message", "contract_address");
const parsedLogs = logs.parseLogs(result.logs);
const contractAddressAttr = logs.findAttribute(parsedLogs, "message", "contract_address");
const myAddress = contractAddressAttr.value;
const history = await client.wasm.getContractCodeHistory(myAddress);
@ -496,8 +496,8 @@ describe("WasmExtension", () => {
// console.log("Raw log:", result.raw_log);
const result = await uploadContract(wallet, getHackatom());
assertIsBroadcastTxSuccess(result);
const logs = parseLogs(result.logs);
const codeIdAttr = findAttribute(logs, "message", "code_id");
const parsedLogs = logs.parseLogs(result.logs);
const codeIdAttr = logs.findAttribute(parsedLogs, "message", "code_id");
codeId = Number.parseInt(codeIdAttr.value, 10);
expect(codeId).toBeGreaterThanOrEqual(1);
expect(codeId).toBeLessThanOrEqual(200);
@ -511,10 +511,10 @@ describe("WasmExtension", () => {
const result = await instantiateContract(wallet, codeId, beneficiaryAddress, transferAmount);
assertIsBroadcastTxSuccess(result);
// console.log("Raw log:", result.raw_log);
const logs = parseLogs(result.logs);
const contractAddressAttr = findAttribute(logs, "message", "contract_address");
const parsedLogs = logs.parseLogs(result.logs);
const contractAddressAttr = logs.findAttribute(parsedLogs, "message", "contract_address");
contractAddress = contractAddressAttr.value;
const amountAttr = findAttribute(logs, "transfer", "amount");
const amountAttr = logs.findAttribute(parsedLogs, "transfer", "amount");
expect(amountAttr.value).toEqual("1234ucosm,321ustake");
expect(result.data).toEqual(Bech32.decode(contractAddress).data);
@ -528,8 +528,8 @@ describe("WasmExtension", () => {
assert(!result.code);
expect(result.data).toEqual("F00BAA");
// console.log("Raw log:", result.logs);
const logs = parseLogs(result.logs);
const wasmEvent = logs.find(() => true)?.events.find((e) => e.type === "wasm");
const parsedLogs = logs.parseLogs(result.logs);
const wasmEvent = parsedLogs.find(() => true)?.events.find((e) => e.type === "wasm");
assert(wasmEvent, "Event of type wasm expected");
expect(wasmEvent.attributes).toContain({ key: "action", value: "release" });
expect(wasmEvent.attributes).toContain({

View File

@ -1,165 +0,0 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { parseAttribute, parseEvent, parseLog, parseLogs } from "./logs";
describe("logs", () => {
describe("parseAttribute", () => {
it("works", () => {
const attr = parseAttribute({ key: "a", value: "b" });
expect(attr).toEqual({ key: "a", value: "b" });
});
it("works for empty value", () => {
const attr = parseAttribute({ key: "foobar", value: "" });
expect(attr).toEqual({ key: "foobar", value: "" });
});
it("normalized unset value to empty string", () => {
const attr = parseAttribute({ key: "amount" });
expect(attr).toEqual({ key: "amount", value: "" });
});
});
describe("parseEvent", () => {
it("works", () => {
const original = {
type: "message",
attributes: [
{
key: "action",
value: "store-code",
},
{
key: "module",
value: "wasm",
},
{
key: "action",
value: "store-code",
},
{
key: "sender",
value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
},
{
key: "code_id",
value: "1",
},
],
} as const;
const event = parseEvent(original);
expect(event).toEqual(original);
});
it("works for transfer event", () => {
const original = {
type: "transfer",
attributes: [
{
key: "recipient",
value: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
},
{
key: "amount",
},
],
} as const;
const expected = {
type: "transfer",
attributes: [
{
key: "recipient",
value: "cosmos18vd8fpwxzck93qlwghaj6arh4p7c5n89uzcee5",
},
{
key: "amount",
value: "",
},
],
} as const;
const event = parseEvent(original);
expect(event).toEqual(expected);
});
});
describe("parseLog", () => {
it("works", () => {
const original = {
msg_index: 0,
log: "",
events: [
{
type: "message",
attributes: [
{
key: "action",
value: "store-code",
},
{
key: "module",
value: "wasm",
},
{
key: "action",
value: "store-code",
},
{
key: "sender",
value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
},
{
key: "code_id",
value: "1",
},
],
},
],
} as const;
const log = parseLog(original);
expect(log).toEqual(original);
});
});
describe("parseLogs", () => {
it("works", () => {
const original = [
{
msg_index: 0,
log: "",
events: [
{
type: "message",
attributes: [
{
key: "action",
value: "store-code",
},
{
key: "module",
value: "wasm",
},
{
key: "action",
value: "store-code",
},
{
key: "sender",
value: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
},
{
key: "code_id",
value: "1",
},
],
},
],
},
] as const;
const logs = parseLogs(original);
expect(logs).toEqual(original);
});
});
});

View File

@ -1,86 +0,0 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { isNonNullObject } from "@cosmjs/utils";
export interface Attribute {
readonly key: string;
readonly value: string;
}
export interface Event {
readonly type: string;
readonly attributes: readonly Attribute[];
}
export interface Log {
readonly msg_index: number;
readonly log: string;
readonly events: readonly Event[];
}
export function parseAttribute(input: unknown): Attribute {
if (!isNonNullObject(input)) throw new Error("Attribute must be a non-null object");
const { key, value } = input as any;
if (typeof key !== "string" || !key) throw new Error("Attribute's key must be a non-empty string");
if (typeof value !== "string" && typeof value !== "undefined") {
throw new Error("Attribute's value must be a string or unset");
}
return {
key: key,
value: value || "",
};
}
export function parseEvent(input: unknown): Event {
if (!isNonNullObject(input)) throw new Error("Event must be a non-null object");
const { type, attributes } = input as any;
if (typeof type !== "string" || type === "") {
throw new Error(`Event type must be a non-empty string`);
}
if (!Array.isArray(attributes)) throw new Error("Event's attributes must be an array");
return {
type: type,
attributes: attributes.map(parseAttribute),
};
}
export function parseLog(input: unknown): Log {
if (!isNonNullObject(input)) throw new Error("Log must be a non-null object");
const { msg_index, log, events } = input as any;
if (typeof msg_index !== "number") throw new Error("Log's msg_index must be a number");
if (typeof log !== "string") throw new Error("Log's log must be a string");
if (!Array.isArray(events)) throw new Error("Log's events must be an array");
return {
msg_index: msg_index,
log: log,
events: events.map(parseEvent),
};
}
export function parseLogs(input: unknown): readonly Log[] {
if (!Array.isArray(input)) throw new Error("Logs must be an array");
return input.map(parseLog);
}
/**
* Searches in logs for the first event of the given event type and in that event
* for the first first attribute with the given attribute key.
*
* Throws if the attribute was not found.
*/
export function findAttribute(
logs: readonly Log[],
eventType: "message" | "transfer",
attrKey: string,
): Attribute {
const firstLogs = logs.find(() => true);
const out = firstLogs?.events
.find((event) => event.type === eventType)
?.attributes.find((attr) => attr.key === attrKey);
if (!out) {
throw new Error(
`Could not find attribute '${attrKey}' in first event of type '${eventType}' in first log.`,
);
}
return out;
}

View File

@ -11,6 +11,7 @@ import {
GasLimits,
GasPrice,
isBroadcastTxFailure,
logs,
makeSignDoc,
makeStdTx,
Msg,
@ -23,7 +24,6 @@ import pako from "pako";
import { isValidBuilder } from "./builder";
import { Account, CosmWasmClient, GetSequenceResult } from "./cosmwasmclient";
import { findAttribute, Log } from "./logs";
import {
MsgClearAdmin,
MsgExecuteContract,
@ -91,7 +91,7 @@ export interface UploadResult {
readonly compressedChecksum: string;
/** The ID of the code asigned by the chain */
readonly codeId: number;
readonly logs: readonly Log[];
readonly logs: readonly logs.Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
@ -113,7 +113,7 @@ export interface InstantiateOptions {
export interface InstantiateResult {
/** The address of the newly instantiated contract */
readonly contractAddress: string;
readonly logs: readonly Log[];
readonly logs: readonly logs.Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
@ -122,19 +122,19 @@ export interface InstantiateResult {
* Result type of updateAdmin and clearAdmin
*/
export interface ChangeAdminResult {
readonly logs: readonly Log[];
readonly logs: readonly logs.Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export interface MigrateResult {
readonly logs: readonly Log[];
readonly logs: readonly logs.Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export interface ExecuteResult {
readonly logs: readonly Log[];
readonly logs: readonly logs.Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
@ -209,7 +209,7 @@ export class SigningCosmWasmClient extends CosmWasmClient {
if (isBroadcastTxFailure(result)) {
throw new Error(createBroadcastTxErrorMessage(result));
}
const codeIdAttr = findAttribute(result.logs, "message", "code_id");
const codeIdAttr = logs.findAttribute(result.logs, "message", "code_id");
return {
originalSize: wasmCode.length,
originalChecksum: toHex(sha256(wasmCode)),
@ -242,7 +242,7 @@ export class SigningCosmWasmClient extends CosmWasmClient {
if (isBroadcastTxFailure(result)) {
throw new Error(createBroadcastTxErrorMessage(result));
}
const contractAddressAttr = findAttribute(result.logs, "message", "contract_address");
const contractAddressAttr = logs.findAttribute(result.logs, "message", "contract_address");
return {
contractAddress: contractAddressAttr.value,
logs: result.logs,

View File

@ -1,5 +1,3 @@
import * as logs from "./logs";
export { logs };
export { setupWasmExtension, WasmExtension } from "./lcdapi/wasm";
export {
Account,

View File

@ -1,28 +0,0 @@
export interface Attribute {
readonly key: string;
readonly value: string;
}
export interface Event {
readonly type: string;
readonly attributes: readonly Attribute[];
}
export interface Log {
readonly msg_index: number;
readonly log: string;
readonly events: readonly Event[];
}
export declare function parseAttribute(input: unknown): Attribute;
export declare function parseEvent(input: unknown): Event;
export declare function parseLog(input: unknown): Log;
export declare function parseLogs(input: unknown): readonly Log[];
/**
* Searches in logs for the first event of the given event type and in that event
* for the first first attribute with the given attribute key.
*
* Throws if the attribute was not found.
*/
export declare function findAttribute(
logs: readonly Log[],
eventType: "message" | "transfer",
attrKey: string,
): Attribute;

View File

@ -5,12 +5,12 @@ import {
CosmosFeeTable,
GasLimits,
GasPrice,
logs,
Msg,
OfflineSigner,
StdFee,
} from "@cosmjs/launchpad";
import { Account, CosmWasmClient, GetSequenceResult } from "./cosmwasmclient";
import { Log } from "./logs";
/**
* These fees are used by the higher level methods of SigningCosmWasmClient
*/
@ -48,7 +48,7 @@ export interface UploadResult {
readonly compressedChecksum: string;
/** The ID of the code asigned by the chain */
readonly codeId: number;
readonly logs: readonly Log[];
readonly logs: readonly logs.Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
@ -68,7 +68,7 @@ export interface InstantiateOptions {
export interface InstantiateResult {
/** The address of the newly instantiated contract */
readonly contractAddress: string;
readonly logs: readonly Log[];
readonly logs: readonly logs.Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
@ -76,17 +76,17 @@ export interface InstantiateResult {
* Result type of updateAdmin and clearAdmin
*/
export interface ChangeAdminResult {
readonly logs: readonly Log[];
readonly logs: readonly logs.Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export interface MigrateResult {
readonly logs: readonly Log[];
readonly logs: readonly logs.Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}
export interface ExecuteResult {
readonly logs: readonly Log[];
readonly logs: readonly logs.Log[];
/** Transaction hash (might be used as transaction ID). Guaranteed to be non-empty upper-case hex */
readonly transactionHash: string;
}