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

Adapt codebase to work with wasmd 0.50 (Cosmos SDK 0.50) #1564

Merged
merged 15 commits into from
Mar 8, 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
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,20 @@ and this project adheres to
import { parseCoins } from "@cosmjs/amino";
```

- @cosmjs/stargate: Let `parseRawLog` gracefully handle empty strings to better
support Cosmos SDK 0.50 inputs. ([#1564])

[#1564]: https://github.com/cosmos/cosmjs/pull/1564

### Fixed

- @cosmjs/encoding: Avoid using replacement character in doc comment to make
external tools happy. ([#1570])
- @cosmjs/cosmwasm-stargate: Use events instead of log parsing to receive
information in SigningCosmWasmClient. This is required to support Cosmos SDK
0.50+ where the `rawLog` field is empty. ([#1564])

[#1564]: https://github.com/cosmos/cosmjs/pull/1564
[#1570]: https://github.com/cosmos/cosmjs/pull/1570

## [0.32.2] - 2023-12-19
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/examples/cosmwasm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ async function main(hackatomWasmPath: string) {
// Execute contract
const executeFee = calculateFee(300_000, gasPrice);
const result = await client.execute(alice.address0, contractAddress, { release: {} }, executeFee);
const wasmEvent = result.logs[0].events.find((e) => e.type === "wasm");
const wasmEvent = result.events.find((e) => e.type === "wasm");
console.info("The `wasm` event emitted by the contract execution:", wasmEvent);
}

Expand Down
10 changes: 6 additions & 4 deletions packages/cosmwasm-stargate/src/cosmwasmclient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
Registry,
TxBodyEncodeObject,
} from "@cosmjs/proto-signing";
import { assertIsDeliverTxSuccess, coins, logs, MsgSendEncodeObject, StdFee } from "@cosmjs/stargate";
import { assertIsDeliverTxSuccess, coins, MsgSendEncodeObject, StdFee } from "@cosmjs/stargate";
import { assert, sleep } from "@cosmjs/utils";
import { TxRaw } from "cosmjs-types/cosmos/tx/v1beta1/tx";
import { ReadonlyDate } from "readonly-date";
Expand Down Expand Up @@ -188,7 +188,6 @@ describe("CosmWasmClient", () => {
amount: coins(5000, "ucosm"),
gas: "890000",
};

const chainId = await client.getChainId();
const sequenceResponse = await client.getSequence(alice.address0);
assert(sequenceResponse);
Expand Down Expand Up @@ -222,8 +221,11 @@ describe("CosmWasmClient", () => {
const signedTx = Uint8Array.from(TxRaw.encode(txRaw).finish());
const result = await client.broadcastTx(signedTx);
assertIsDeliverTxSuccess(result);
const amountAttr = logs.findAttribute(logs.parseRawLog(result.rawLog), "transfer", "amount");
expect(amountAttr.value).toEqual("1234567ucosm");
const amountAttrs = result.events
.filter((e) => e.type == "transfer")
.flatMap((e) => e.attributes.filter((a) => a.key == "amount"));
expect(amountAttrs[0].value).toEqual("5000ucosm"); // fee
expect(amountAttrs[1].value).toEqual("1234567ucosm"); // MsgSend amount
expect(result.transactionHash).toMatch(/^[0-9A-F]{64}$/);
});
});
Expand Down
23 changes: 11 additions & 12 deletions packages/cosmwasm-stargate/src/modules/wasm/queries.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,14 @@ import {
coin,
coins,
DeliverTxResponse,
logs,
SigningStargateClient,
StdFee,
} from "@cosmjs/stargate";
import { assert, assertDefined } from "@cosmjs/utils";
import { MsgExecuteContract, MsgInstantiateContract, MsgStoreCode } from "cosmjs-types/cosmwasm/wasm/v1/tx";
import { AbsoluteTxPosition, ContractCodeHistoryOperationType } from "cosmjs-types/cosmwasm/wasm/v1/types";

import { SigningCosmWasmClient } from "../../signingcosmwasmclient";
import { findAttribute, SigningCosmWasmClient } from "../../signingcosmwasmclient";
import {
alice,
bech32AddressMatcher,
Expand Down Expand Up @@ -385,12 +384,11 @@ describe("WasmExtension", () => {
{
const result = await uploadContract(wallet, getHackatom());
assertIsDeliverTxSuccess(result);
const parsedLogs = logs.parseLogs(logs.parseRawLog(result.rawLog));
const codeIdAttr = logs.findAttribute(parsedLogs, "store_code", "code_id");
const codeIdAttr = findAttribute(result.events, "store_code", "code_id");
codeId = Number.parseInt(codeIdAttr.value, 10);
expect(codeId).toBeGreaterThanOrEqual(1);
expect(codeId).toBeLessThanOrEqual(200);
const actionAttr = logs.findAttribute(parsedLogs, "message", "module");
const actionAttr = findAttribute(result.events, "message", "module");
expect(actionAttr.value).toEqual("wasm");
}

Expand All @@ -400,12 +398,14 @@ describe("WasmExtension", () => {
{
const result = await instantiateContract(wallet, codeId, beneficiaryAddress, funds);
assertIsDeliverTxSuccess(result);
const parsedLogs = logs.parseLogs(logs.parseRawLog(result.rawLog));
const contractAddressAttr = logs.findAttribute(parsedLogs, "instantiate", "_contract_address");
const contractAddressAttr = findAttribute(result.events, "instantiate", "_contract_address");
contractAddress = contractAddressAttr.value;
const amountAttr = logs.findAttribute(parsedLogs, "transfer", "amount");
expect(amountAttr.value).toEqual("1234ucosm,321ustake");
const actionAttr = logs.findAttribute(parsedLogs, "message", "module");
const amountAttrs = result.events
.filter((e) => e.type == "transfer")
.flatMap((e) => e.attributes.filter((a) => a.key == "amount"));
expect(amountAttrs[0].value).toEqual("5000000ucosm"); // fee
expect(amountAttrs[1].value).toEqual("1234ucosm,321ustake"); // instantiate funds
const actionAttr = findAttribute(result.events, "message", "module");
expect(actionAttr.value).toEqual("wasm");

const balanceUcosm = await client.bank.balance(contractAddress, "ucosm");
Expand All @@ -418,8 +418,7 @@ describe("WasmExtension", () => {
{
const result = await executeContract(wallet, contractAddress, { release: {} });
assertIsDeliverTxSuccess(result);
const parsedLogs = logs.parseLogs(logs.parseRawLog(result.rawLog));
const wasmEvent = parsedLogs.find(() => true)?.events.find((e) => e.type === "wasm");
const wasmEvent = result.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({
Expand Down
28 changes: 18 additions & 10 deletions packages/cosmwasm-stargate/src/signingcosmwasmclient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ describe("SigningCosmWasmClient", () => {

it("works with legacy Amino signer access type", async () => {
pendingWithoutWasmd();
pending("wasmd 0.50 does not work with Amino JSON signing");
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix });
const options = { ...defaultSigningClientOptions, prefix: wasmd.prefix };
const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options);
Expand Down Expand Up @@ -263,6 +264,7 @@ describe("SigningCosmWasmClient", () => {

it("works with legacy Amino signer", async () => {
pendingWithoutWasmd();
pending("wasmd 0.50 does not work with Amino JSON signing");
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix });
const client = await SigningCosmWasmClient.connectWithSigner(
wasmd.endpoint,
Expand Down Expand Up @@ -313,7 +315,7 @@ describe("SigningCosmWasmClient", () => {
const { codeId } = await client.upload(alice.address0, getHackatom().data, defaultUploadFee);
const funds = [coin(1234, "ucosm"), coin(321, "ustake")];
const beneficiaryAddress = makeRandomAddress();
const salt = Uint8Array.from([0x01]);
const salt = Random.getBytes(64); // different salt every time we run the test to avoid address collision erors
const wasm = getHackatom().data;
const msg = {
verifier: alice.address0,
Expand Down Expand Up @@ -346,6 +348,7 @@ describe("SigningCosmWasmClient", () => {

it("works with Amino JSON signing", async () => {
pendingWithoutWasmd();
pending("wasmd 0.50 does not work with Amino JSON signing");
const aminoJsonWallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, {
prefix: wasmd.prefix,
});
Expand Down Expand Up @@ -527,6 +530,7 @@ describe("SigningCosmWasmClient", () => {

it("works with legacy Amino signer", async () => {
pendingWithoutWasmd();
pending("wasmd 0.50 does not work with Amino JSON signing");
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix });
const client = await SigningCosmWasmClient.connectWithSigner(
wasmd.endpoint,
Expand Down Expand Up @@ -607,7 +611,7 @@ describe("SigningCosmWasmClient", () => {
expect(result.height).toBeGreaterThan(0);
expect(result.gasWanted).toBeGreaterThan(0);
expect(result.gasUsed).toBeGreaterThan(0);
const wasmEvent = result.logs[0].events.find((e) => e.type === "wasm");
const wasmEvent = result.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({
Expand All @@ -630,6 +634,7 @@ describe("SigningCosmWasmClient", () => {

it("works with legacy Amino signer", async () => {
pendingWithoutWasmd();
pending("wasmd 0.50 does not work with Amino JSON signing");
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix });
const client = await SigningCosmWasmClient.connectWithSigner(
wasmd.endpoint,
Expand Down Expand Up @@ -660,7 +665,7 @@ describe("SigningCosmWasmClient", () => {
{ release: {} },
defaultExecuteFee,
);
const wasmEvent = result.logs[0].events.find((e) => e.type === "wasm");
const wasmEvent = result.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({
Expand Down Expand Up @@ -727,16 +732,17 @@ describe("SigningCosmWasmClient", () => {
],
"auto",
);
expect(result.logs.length).toEqual(2);
const wasmEvent1 = result.logs[0].events.find((e) => e.type === "wasm");
assert(wasmEvent1, "Event of type wasm expected");
const { events } = result;
const wasmEvents = events.filter((e) => e.type == "wasm");
expect(wasmEvents.length).toEqual(2);
const [wasmEvent1, wasmEvent2] = wasmEvents;
expect(wasmEvent1.type).toEqual("wasm");
expect(wasmEvent1.attributes).toContain({ key: "action", value: "release" });
expect(wasmEvent1.attributes).toContain({
key: "destination",
value: beneficiaryAddress1,
});
const wasmEvent2 = result.logs[1].events.find((e) => e.type === "wasm");
assert(wasmEvent2, "Event of type wasm expected");
expect(wasmEvent2.type).toEqual("wasm");
expect(wasmEvent2.attributes).toContain({ key: "action", value: "release" });
expect(wasmEvent2.attributes).toContain({
key: "destination",
Expand Down Expand Up @@ -777,7 +783,8 @@ describe("SigningCosmWasmClient", () => {
memo,
);
assertIsDeliverTxSuccess(result);
expect(result.rawLog).toBeTruthy();
expect(result.rawLog).toEqual(""); // empty for wasmd >= 0.50.0 (https://github.com/cosmos/cosmos-sdk/pull/15845)
expect(result.events.length).toBeGreaterThanOrEqual(1);

// got tokens
const after = await client.getBalance(beneficiaryAddress, "ucosm");
Expand Down Expand Up @@ -816,7 +823,8 @@ describe("SigningCosmWasmClient", () => {
memo,
);
assertIsDeliverTxSuccess(result);
expect(result.rawLog).toBeTruthy();
expect(result.rawLog).toEqual(""); // empty for wasmd >= 0.50.0 (https://github.com/cosmos/cosmos-sdk/pull/15845)
expect(result.events.length).toBeGreaterThanOrEqual(1);

// got tokens
const after = await client.getBalance(beneficiaryAddress, "ucosm");
Expand Down
39 changes: 30 additions & 9 deletions packages/cosmwasm-stargate/src/signingcosmwasmclient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
} from "@cosmjs/proto-signing";
import {
AminoTypes,
Attribute,
calculateFee,
Coin,
createDefaultAminoConverters,
Expand Down Expand Up @@ -72,6 +73,7 @@ export interface UploadResult {
readonly compressedSize: number;
/** The ID of the code asigned by the chain */
readonly codeId: number;
/** @deprecated Not filled in Cosmos SDK >= 0.50. Use events instead. */
readonly logs: readonly logs.Log[];
/** Block height in which the transaction is included */
readonly height: number;
Expand Down Expand Up @@ -106,6 +108,7 @@ export interface InstantiateOptions {
export interface InstantiateResult {
/** The address of the newly instantiated contract */
readonly contractAddress: string;
/** @deprecated Not filled in Cosmos SDK >= 0.50. Use events instead. */
readonly logs: readonly logs.Log[];
/** Block height in which the transaction is included */
readonly height: number;
Expand All @@ -120,6 +123,7 @@ export interface InstantiateResult {
* Result type of updateAdmin and clearAdmin
*/
export interface ChangeAdminResult {
/** @deprecated Not filled in Cosmos SDK >= 0.50. Use events instead. */
readonly logs: readonly logs.Log[];
/** Block height in which the transaction is included */
readonly height: number;
Expand All @@ -131,6 +135,7 @@ export interface ChangeAdminResult {
}

export interface MigrateResult {
/** @deprecated Not filled in Cosmos SDK >= 0.50. Use events instead. */
readonly logs: readonly logs.Log[];
/** Block height in which the transaction is included */
readonly height: number;
Expand All @@ -148,6 +153,7 @@ export interface ExecuteInstruction {
}

export interface ExecuteResult {
/** @deprecated Not filled in Cosmos SDK >= 0.50. Use events instead. */
readonly logs: readonly logs.Log[];
/** Block height in which the transaction is included */
readonly height: number;
Expand All @@ -158,6 +164,24 @@ export interface ExecuteResult {
readonly gasUsed: bigint;
}

/**
* Searches in events for an event of the given event type which contains an
* attribute for with the given key.
*
* Throws if the attribute was not found.
*/
export function findAttribute(events: readonly Event[], eventType: string, attrKey: string): Attribute {
// all attributes from events with the right event type
const attributes = events.filter((event) => event.type === eventType).flatMap((e) => e.attributes);
const out = 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;
}

function createDeliverTxResponseErrorMessage(result: DeliverTxResponse): string {
return `Error when broadcasting tx ${result.transactionHash} at height ${result.height}. Code: ${result.code}; Raw log: ${result.rawLog}`;
}
Expand Down Expand Up @@ -288,14 +312,13 @@ export class SigningCosmWasmClient extends CosmWasmClient {
if (isDeliverTxFailure(result)) {
throw new Error(createDeliverTxResponseErrorMessage(result));
}
const parsedLogs = logs.parseRawLog(result.rawLog);
const codeIdAttr = logs.findAttribute(parsedLogs, "store_code", "code_id");
const codeIdAttr = findAttribute(result.events, "store_code", "code_id");
return {
checksum: toHex(sha256(wasmCode)),
originalSize: wasmCode.length,
compressedSize: compressed.length,
codeId: Number.parseInt(codeIdAttr.value, 10),
logs: parsedLogs,
logs: logs.parseRawLog(result.rawLog),
height: result.height,
transactionHash: result.transactionHash,
events: result.events,
Expand Down Expand Up @@ -327,11 +350,10 @@ export class SigningCosmWasmClient extends CosmWasmClient {
if (isDeliverTxFailure(result)) {
throw new Error(createDeliverTxResponseErrorMessage(result));
}
const parsedLogs = logs.parseRawLog(result.rawLog);
const contractAddressAttr = logs.findAttribute(parsedLogs, "instantiate", "_contract_address");
const contractAddressAttr = findAttribute(result.events, "instantiate", "_contract_address");
return {
contractAddress: contractAddressAttr.value,
logs: parsedLogs,
logs: logs.parseRawLog(result.rawLog),
height: result.height,
transactionHash: result.transactionHash,
events: result.events,
Expand Down Expand Up @@ -366,11 +388,10 @@ export class SigningCosmWasmClient extends CosmWasmClient {
if (isDeliverTxFailure(result)) {
throw new Error(createDeliverTxResponseErrorMessage(result));
}
const parsedLogs = logs.parseRawLog(result.rawLog);
const contractAddressAttr = logs.findAttribute(parsedLogs, "instantiate", "_contract_address");
const contractAddressAttr = findAttribute(result.events, "instantiate", "_contract_address");
return {
contractAddress: contractAddressAttr.value,
logs: parsedLogs,
logs: logs.parseRawLog(result.rawLog),
height: result.height,
transactionHash: result.transactionHash,
events: result.events,
Expand Down
8 changes: 1 addition & 7 deletions packages/cosmwasm-stargate/src/testutils.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,18 +101,12 @@ export const unused = {
};

export const validator = {
/**
* delegator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/wasmd/template/.wasmd/config/genesis.json
*
* `jq ".app_state.genutil.gen_txs[0].body.messages[0].delegator_address" scripts/wasmd/template/.wasmd/config/genesis.json`
*/
delegatorAddress: "wasm1g6kvj7w4c8g0vhl35kjgpe3jmuauet0e5tnevj",
/**
* validator_address from /cosmos.staking.v1beta1.MsgCreateValidator in scripts/wasmd/template/.wasmd/config/genesis.json
*
* `jq ".app_state.genutil.gen_txs[0].body.messages[0].validator_address" scripts/wasmd/template/.wasmd/config/genesis.json`
*/
validatorAddress: "wasmvaloper1g6kvj7w4c8g0vhl35kjgpe3jmuauet0ephx9zg",
validatorAddress: "wasmvaloper1k2vfqeu2upskfv7awn29g5kvxxnmugkzy6rch0",
accountNumber: 0,
sequence: 1,
};
Expand Down
5 changes: 4 additions & 1 deletion packages/stargate/src/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ export function parseLogs(input: unknown): readonly Log[] {
return input.map(parseLog);
}

export function parseRawLog(input = "[]"): readonly Log[] {
export function parseRawLog(input: string | undefined): readonly Log[] {
// Cosmos SDK >= 0.50 gives us an empty string here. This should be handled like undefined.
if (!input) return [];

const logsToParse = JSON.parse(input).map(({ events }: { events: readonly unknown[] }, i: number) => ({
msg_index: i,
events,
Expand Down
2 changes: 2 additions & 0 deletions packages/stargate/src/signingstargateclient.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ describe("SigningStargateClient", () => {
} else {
expect(result.rawLog).toBeTruthy();
}
expect(result.events.length).toBeGreaterThanOrEqual(1);

// got tokens
const after = await client.getBalance(beneficiaryAddress, "ucosm");
Expand Down Expand Up @@ -167,6 +168,7 @@ describe("SigningStargateClient", () => {
} else {
expect(result.rawLog).toBeTruthy();
}
expect(result.events.length).toBeGreaterThanOrEqual(1);

// got tokens
const after = await client.getBalance(beneficiaryAddress, "ucosm");
Expand Down
Loading
Loading