Skip to content

Commit

Permalink
feat: add parse tx to msgs and events & basic test cases (#197)
Browse files Browse the repository at this point in the history
* feat: add parse tx to msgs and events & basic test cases

* test: added testcase for parseWasmEvents func

* test: added testcase for parseWasmEvents func

* test: added testcase for decodeProto func

* chore: pumb oraidex-common 1.0.75

---------

Co-authored-by: trungbach <[email protected]>
  • Loading branch information
ducphamle2 and trungbach authored Mar 12, 2024
1 parent c10ed2c commit 2f571d8
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 20 deletions.
2 changes: 1 addition & 1 deletion packages/oraidex-common/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@oraichain/oraidex-common",
"version": "1.0.74",
"version": "1.0.75",
"main": "build/index.js",
"files": [
"build/"
Expand Down
64 changes: 60 additions & 4 deletions packages/oraidex-common/src/helper.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ExecuteInstruction, toBinary } from "@cosmjs/cosmwasm-stargate";
import { toUtf8 } from "@cosmjs/encoding";
import { Coin, EncodeObject } from "@cosmjs/proto-signing";
import { Event } from "@cosmjs/tendermint-rpc/build/tendermint37";
import { ExecuteInstruction, JsonObject, fromBinary, toBinary, wasmTypes } from "@cosmjs/cosmwasm-stargate";
import { fromAscii, toUtf8 } from "@cosmjs/encoding";
import { Coin, EncodeObject, Registry, decodeTxRaw } from "@cosmjs/proto-signing";
import { Event, Attribute } from "@cosmjs/tendermint-rpc/build/tendermint37";
import { AssetInfo, Uint128 } from "@oraichain/oraidex-contracts-sdk";
import { TokenInfoResponse } from "@oraichain/oraidex-contracts-sdk/build/OraiswapToken.types";
import bech32 from "bech32";
Expand Down Expand Up @@ -31,6 +31,8 @@ import {
} from "./token";
import { StargateMsg, Tx } from "./tx";
import { BigDecimal } from "./bigdecimal";
import { TextProposal } from "cosmjs-types/cosmos/gov/v1beta1/gov";
import { defaultRegistryTypes as defaultStargateTypes, IndexedTx, logs, StargateClient } from "@cosmjs/stargate";

export const getEvmAddress = (bech32Address: string) => {
if (!bech32Address) throw new Error("bech32 address is empty");
Expand Down Expand Up @@ -433,3 +435,57 @@ export function parseAssetInfoOnlyDenom(info: AssetInfo): string {
if ("native_token" in info) return info.native_token.denom;
return info.token.contract_addr;
}

export const decodeProto = (value: JsonObject) => {
if (!value) throw "value is not defined";

const typeUrl = value.type_url || value.typeUrl;
if (typeUrl) {
const customRegistry = new Registry([...defaultStargateTypes, ...wasmTypes]);
customRegistry.register("/cosmos.gov.v1beta1.TextProposal", TextProposal);
// decode proto
return decodeProto(customRegistry.decode({ typeUrl, value: value.value }));
}

for (const k in value) {
if (typeof value[k] === "string") {
try {
value[k] = fromBinary(value[k]);
} catch {}
}
if (typeof value[k] === "object") value[k] = decodeProto(value[k]);
}
if (value.msg instanceof Uint8Array) value.msg = JSON.parse(fromAscii(value.msg));
return value;
};

export const parseWasmEvents = (events: readonly Event[]): { [key: string]: string }[] => {
const wasmEvents = events.filter((e) => e.type.startsWith("wasm"));
const attrs: { [key: string]: string }[] = [];
for (const wasmEvent of wasmEvents) {
let attr: { [key: string]: string };
for (const { key, value } of wasmEvent.attributes) {
if (key === "_contract_address") {
if (attr) attrs.push(attr);
attr = {};
}
attr[key] = value;
}
attrs.push(attr);
}
return attrs;
};

export const parseTxToMsgsAndEvents = (indexedTx: Tx, eventsParser?: (events: readonly Event[]) => Attribute[]) => {
if (!indexedTx) return [];
const { rawLog, tx } = indexedTx;
const { body } = decodeTxRaw(tx);
const messages = body.messages.map(decodeProto);
const logs: logs.Log[] = JSON.parse(rawLog);

return logs.map((log) => {
const index = log.msg_index ?? 0;
const attrs = eventsParser ? eventsParser(log.events) : parseWasmEvents(log.events);
return { attrs, message: messages[index] };
});
};
187 changes: 172 additions & 15 deletions packages/oraidex-common/tests/helper.spec.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
import {
AIRI_CONTRACT,
AVERAGE_COSMOS_GAS_PRICE,
MILKYBSC_ORAICHAIN_DENOM,
MILKY_CONTRACT,
ORAI,
USDC_CONTRACT,
USDT_CONTRACT
} from "../src/constant";
import { AmountDetails, TokenItemType, cosmosTokens, flattenTokens, oraichainTokens } from "../src/token";
import { Coin } from "@cosmjs/amino";
import { toBinary } from "@cosmjs/cosmwasm-stargate";
import { StargateClient } from "@cosmjs/stargate";
import { Event } from "@cosmjs/tendermint-rpc/build/tendermint37";
import { AssetInfo } from "@oraichain/oraidex-contracts-sdk";
import { AIRI_CONTRACT, AVERAGE_COSMOS_GAS_PRICE, MILKYBSC_ORAICHAIN_DENOM, ORAI } from "../src/constant";
import {
calculateMinReceive,
calculateTimeoutTimestamp,
decodeProto,
ethToTronAddress,
findToTokenOnOraiBridge,
getEvmAddress,
getCosmosGasPrice,
getEvmAddress,
getSubAmountDetails,
getTokenOnOraichain,
getTokenOnSpecificChainId,
Expand All @@ -24,6 +21,8 @@ import {
parseAssetInfo,
parseTokenInfo,
parseTokenInfoRawDenom,
parseTxToMsgsAndEvents,
parseWasmEvents,
toAmount,
toAssetInfo,
toDecimal,
Expand All @@ -32,11 +31,9 @@ import {
tronToEthAddress,
validateNumber
} from "../src/helper";
import { CoinGeckoId, NetworkChainId, OraiToken } from "../src/network";
import { AssetInfo } from "@oraichain/oraidex-contracts-sdk";
import { CoinGeckoId, NetworkChainId } from "../src/network";
import { isFactoryV1 } from "../src/pairs";
import { Coin } from "@cosmjs/amino";
import { toBinary } from "@cosmjs/cosmwasm-stargate";
import { AmountDetails, TokenItemType, cosmosTokens, flattenTokens, oraichainTokens } from "../src/token";

describe("should helper functions in helper run exactly", () => {
const amounts: AmountDetails = {
Expand Down Expand Up @@ -393,4 +390,164 @@ describe("should helper functions in helper run exactly", () => {
expect(getCosmosGasPrice({ low: 0, average: 0, high: 0 })).toEqual(0);
expect(getCosmosGasPrice()).toEqual(AVERAGE_COSMOS_GAS_PRICE);
});

// TODO: add more tests for this func
it("test-parseTxToMsgsAndEvents", async () => {
// case 1: undefined input
const reuslt = parseTxToMsgsAndEvents(undefined as any);
expect(reuslt).toEqual([]);

// case 2: real tx with multiple msgs and multiple contract calls
const client = await StargateClient.connect("wss://rpc.orai.io");
const indexedTx = await client.getTx("9B435E4014DEBA5AB80D4BB8F52D766A6C14BFCAC21F821CDB96F4ABB4E29B17");
client.disconnect();

const data = parseTxToMsgsAndEvents(indexedTx!);
expect(data.length).toEqual(2);
expect(data[0].message).toMatchObject({
sender: "orai16hv74w3eu3ek0muqpgp4fekhrqgpzl3hd3qeqk",
contract: "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp",
msg: {
execute_order_book_pair: {
asset_infos: [
{
token: {
contract_addr: "orai1lplapmgqnelqn253stz6kmvm3ulgdaytn89a8mz9y85xq8wd684s6xl3lt"
}
},
{
token: {
contract_addr: "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh"
}
}
],
limit: 100
}
},
funds: []
});
expect(data[0].attrs.length).toEqual(5);
expect(data[1].message).toMatchObject({
sender: "orai16hv74w3eu3ek0muqpgp4fekhrqgpzl3hd3qeqk",
contract: "orai1nt58gcu4e63v7k55phnr3gaym9tvk3q4apqzqccjuwppgjuyjy6sxk8yzp",
msg: {
execute_order_book_pair: {
asset_infos: [
{ native_token: { denom: "orai" } },
{
token: {
contract_addr: "orai12hzjxfh77wl572gdzct2fxv2arxcwh6gykc7qh"
}
}
],
limit: 100
}
},
funds: []
});
expect(data[0].attrs.length).toEqual(5);
}, 20000);

it("test-decodeProto-with-value-input-undefined", () => {
expect(() => decodeProto(undefined)).toThrow("value is not defined");
});

it.each([
[
// case 1: value with type_url and valid value
{
type_url: "/cosmos.gov.v1beta1.TextProposal",
value: Uint8Array.from([10, 3, 97, 98, 99]) // Example byte array
},
{ title: "abc", description: "" }
],

[
// case 2: value with typeUrl and valid value
{
type_url: "/cosmos.gov.v1beta1.TextProposal",
value: Uint8Array.from([10, 3, 97, 98, 99])
},
{ title: "abc", description: "" }
],

// case 3: value is object with binary string and object properties is binary string
[
{
key1: "InZhbHVlMSI=",
key2: {
nestedKey: "Im5lc3RlZC1zdHJpbmctdmFsdWUi"
}
},
{
key1: "value1",
key2: {
nestedKey: "nested-string-value"
}
}
],

// case 4: value is object with text string
[
{
key1: "text-string"
},
{
key1: "text-string"
}
],

// case 5: value.msg is instance of Uint8Array
[
{
msg: Uint8Array.from([123, 34, 107, 101, 121, 34, 58, 34, 118, 97, 108, 117, 101, 34, 125]) // Uint8Array representation of '{"key": "value"}'
},
{
msg: {
key: "value"
}
}
]
])("test-decodeProto", (value, expectation) => {
// act
const res = decodeProto(value);

// assertion
expect(res).toEqual(expectation);
});

it.each<[string, readonly Event[], { [key: string]: string }[]]>([
["empty-events-array", [], []],
["events-with-single-event-without-attributes", [{ type: "wasmEvent", attributes: [] }], []],
[
"events-with-single-event-with-attributes",
[
{
type: "wasmEvent",
attributes: [
{ key: "_contract_address", value: "addr1" },
{ key: "key1", value: "value1" }
]
}
],
[{ _contract_address: "addr1", key1: "value1" }]
],
[
"events-with-multiple-events-with-and-without-attributes",
[
{
type: "wasmEvent",
attributes: [
{ key: "_contract_address", value: "addr1" },
{ key: "key2", value: "value2" }
]
},
{ type: "otherEvent", attributes: [{ key: "key3", value: "value3" }] },
{ type: "wasmEvent", attributes: [{ key: "_contract_address", value: "addr2" }] }
],
[{ _contract_address: "addr1", key2: "value2" }, { _contract_address: "addr2" }]
]
])("test-parseWasmEvents-with-case: %p", (_case, input, expectedOutput) => {
expect(parseWasmEvents(input)).toEqual(expectedOutput);
});
});

0 comments on commit 2f571d8

Please sign in to comment.