Merge pull request #900 from cosmos/fix-AminoMsgInstantiateContract

Fix message encoding for Amino JSON signing in CosmWasm messages
This commit is contained in:
Simon Warta
2021-10-12 13:06:33 +02:00
committed by GitHub
4 changed files with 261 additions and 104 deletions
+5
View File
@@ -9,6 +9,11 @@ and this project adheres to
### Fixed
- @cosmjs/stargate: remove extra space in messageTimeout registry.
- @cosmjs/cosmwasm-stargate: Fix Amino JSON representation of
`MsgInstantiateContract`, `MsgMigrateContract` and `MsgExecuteContract` to
match the wasmd expectation. This was broken since the wasmd upgrade to
Stargate such that no Ledger signing was possible for those message types in
the meantime.
## [0.26.1] - 2021-09-30
+111 -86
View File
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { fromBase64, toUtf8 } from "@cosmjs/encoding";
import { fromBase64, toBase64, toUtf8 } from "@cosmjs/encoding";
import { AminoTypes, coins } from "@cosmjs/stargate";
import {
MsgClearAdmin,
@@ -44,36 +44,61 @@ describe("AminoTypes", () => {
});
it("works for MsgInstantiateContract", () => {
const msg: MsgInstantiateContract = {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
codeId: Long.fromString("12345"),
label: "sticky",
msg: toUtf8(
JSON.stringify({
foo: "bar",
}),
),
funds: coins(1234, "ucosm"),
admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5",
};
const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({
typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract",
value: msg,
});
const expected: AminoMsgInstantiateContract = {
type: "wasm/MsgInstantiateContract",
value: {
// With admin
{
const msg: MsgInstantiateContract = {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
code_id: "12345",
codeId: Long.fromString("12345"),
label: "sticky",
msg: {
foo: "bar",
},
msg: toUtf8(`{"foo":"bar"}`),
funds: coins(1234, "ucosm"),
admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5",
},
};
expect(aminoMsg).toEqual(expected);
};
const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({
typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract",
value: msg,
});
const expected: AminoMsgInstantiateContract = {
type: "wasm/MsgInstantiateContract",
value: {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
code_id: "12345",
label: "sticky",
msg: toBase64(toUtf8(`{"foo":"bar"}`)),
funds: coins(1234, "ucosm"),
admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5",
},
};
expect(aminoMsg).toEqual(expected);
}
// Without admin
{
const msg: MsgInstantiateContract = {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
codeId: Long.fromString("12345"),
label: "sticky",
msg: toUtf8(`{"foo":"bar"}`),
funds: coins(1234, "ucosm"),
admin: "",
};
const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({
typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract",
value: msg,
});
const expected: AminoMsgInstantiateContract = {
type: "wasm/MsgInstantiateContract",
value: {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
code_id: "12345",
label: "sticky",
msg: toBase64(toUtf8(`{"foo":"bar"}`)),
funds: coins(1234, "ucosm"),
admin: undefined,
},
};
expect(aminoMsg).toEqual(expected);
}
});
it("works for MsgUpdateAdmin", () => {
@@ -120,11 +145,7 @@ describe("AminoTypes", () => {
const msg: MsgExecuteContract = {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k",
msg: toUtf8(
JSON.stringify({
foo: "bar",
}),
),
msg: toUtf8(`{"foo":"bar"}`),
funds: coins(1234, "ucosm"),
};
const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({
@@ -136,9 +157,7 @@ describe("AminoTypes", () => {
value: {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k",
msg: {
foo: "bar",
},
msg: toBase64(toUtf8(`{"foo":"bar"}`)),
funds: coins(1234, "ucosm"),
},
};
@@ -150,11 +169,7 @@ describe("AminoTypes", () => {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k",
codeId: Long.fromString("98765"),
msg: toUtf8(
JSON.stringify({
foo: "bar",
}),
),
msg: toUtf8(`{"foo":"bar"}`),
};
const aminoMsg = new AminoTypes({ additions: cosmWasmTypes }).toAmino({
typeUrl: "/cosmwasm.wasm.v1.MsgMigrateContract",
@@ -166,9 +181,7 @@ describe("AminoTypes", () => {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k",
code_id: "98765",
msg: {
foo: "bar",
},
msg: toBase64(toUtf8(`{"foo":"bar"}`)),
},
};
expect(aminoMsg).toEqual(expected);
@@ -197,36 +210,60 @@ describe("AminoTypes", () => {
});
it("works for MsgInstantiateContract", () => {
const aminoMsg: AminoMsgInstantiateContract = {
type: "wasm/MsgInstantiateContract",
value: {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
code_id: "12345",
label: "sticky",
msg: {
foo: "bar",
// With admin
{
const aminoMsg: AminoMsgInstantiateContract = {
type: "wasm/MsgInstantiateContract",
value: {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
code_id: "12345",
label: "sticky",
msg: toBase64(toUtf8(`{"foo":"bar"}`)),
funds: coins(1234, "ucosm"),
admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5",
},
};
const msg = new AminoTypes({ additions: cosmWasmTypes }).fromAmino(aminoMsg);
const expectedValue: MsgInstantiateContract = {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
codeId: Long.fromString("12345"),
label: "sticky",
msg: toUtf8(`{"foo":"bar"}`),
funds: coins(1234, "ucosm"),
admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5",
},
};
const msg = new AminoTypes({ additions: cosmWasmTypes }).fromAmino(aminoMsg);
const expectedValue: MsgInstantiateContract = {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
codeId: Long.fromString("12345"),
label: "sticky",
msg: toUtf8(
JSON.stringify({
foo: "bar",
}),
),
funds: coins(1234, "ucosm"),
admin: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5",
};
expect(msg).toEqual({
typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract",
value: expectedValue,
});
};
expect(msg).toEqual({
typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract",
value: expectedValue,
});
}
// Without admin
{
const aminoMsg: AminoMsgInstantiateContract = {
type: "wasm/MsgInstantiateContract",
value: {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
code_id: "12345",
label: "sticky",
msg: toBase64(toUtf8(`{"foo":"bar"}`)),
funds: coins(1234, "ucosm"),
},
};
const msg = new AminoTypes({ additions: cosmWasmTypes }).fromAmino(aminoMsg);
const expectedValue: MsgInstantiateContract = {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
codeId: Long.fromString("12345"),
label: "sticky",
msg: toUtf8(`{"foo":"bar"}`),
funds: coins(1234, "ucosm"),
admin: "",
};
expect(msg).toEqual({
typeUrl: "/cosmwasm.wasm.v1.MsgInstantiateContract",
value: expectedValue,
});
}
});
it("works for MsgUpdateAdmin", () => {
@@ -275,9 +312,7 @@ describe("AminoTypes", () => {
value: {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k",
msg: {
foo: "bar",
},
msg: toBase64(toUtf8(`{"foo":"bar"}`)),
funds: coins(1234, "ucosm"),
},
};
@@ -285,11 +320,7 @@ describe("AminoTypes", () => {
const expectedValue: MsgExecuteContract = {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k",
msg: toUtf8(
JSON.stringify({
foo: "bar",
}),
),
msg: toUtf8(`{"foo":"bar"}`),
funds: coins(1234, "ucosm"),
};
expect(msg).toEqual({
@@ -305,9 +336,7 @@ describe("AminoTypes", () => {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k",
code_id: "98765",
msg: {
foo: "bar",
},
msg: toBase64(toUtf8(`{"foo":"bar"}`)),
},
};
const msg = new AminoTypes({ additions: cosmWasmTypes }).fromAmino(aminoMsg);
@@ -315,11 +344,7 @@ describe("AminoTypes", () => {
sender: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6",
contract: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k",
codeId: Long.fromString("98765"),
msg: toUtf8(
JSON.stringify({
foo: "bar",
}),
),
msg: toUtf8(`{"foo":"bar"}`),
};
expect(msg).toEqual({
typeUrl: "/cosmwasm.wasm.v1.MsgMigrateContract",
+14 -14
View File
@@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding";
import { fromBase64, toBase64 } from "@cosmjs/encoding";
import { AminoConverter, Coin } from "@cosmjs/stargate";
import {
MsgClearAdmin,
@@ -45,8 +45,8 @@ export interface AminoMsgExecuteContract {
readonly sender: string;
/** Bech32 account address */
readonly contract: string;
/** Handle message as JavaScript object */
readonly msg: any;
/** Execute message as base64 encoded JSON */
readonly msg: string;
readonly funds: readonly Coin[];
};
}
@@ -65,8 +65,8 @@ export interface AminoMsgInstantiateContract {
readonly code_id: string;
/** Human-readable label for this contract */
readonly label: string;
/** Instantiate message as JavaScript object */
readonly msg: any;
/** Instantiate message as base64 encoded JSON */
readonly msg: string;
readonly funds: readonly Coin[];
/** Bech32-encoded admin address */
readonly admin?: string;
@@ -87,8 +87,8 @@ export interface AminoMsgMigrateContract {
readonly contract: string;
/** The new code */
readonly code_id: string;
/** Migrate message as JavaScript object */
readonly msg: any;
/** Migrate message as base64 encoded JSON */
readonly msg: string;
};
}
@@ -150,9 +150,9 @@ export const cosmWasmTypes: Record<string, AminoConverter> = {
sender: sender,
code_id: codeId.toString(),
label: label,
msg: JSON.parse(fromUtf8(msg)),
msg: toBase64(msg),
funds: funds,
admin: admin ?? undefined,
admin: admin || undefined,
}),
fromAmino: ({
sender,
@@ -165,7 +165,7 @@ export const cosmWasmTypes: Record<string, AminoConverter> = {
sender: sender,
codeId: Long.fromString(code_id),
label: label,
msg: toUtf8(JSON.stringify(msg)),
msg: fromBase64(msg),
funds: [...funds],
admin: admin ?? "",
}),
@@ -199,13 +199,13 @@ export const cosmWasmTypes: Record<string, AminoConverter> = {
toAmino: ({ sender, contract, msg, funds }: MsgExecuteContract): AminoMsgExecuteContract["value"] => ({
sender: sender,
contract: contract,
msg: JSON.parse(fromUtf8(msg)),
msg: toBase64(msg),
funds: funds,
}),
fromAmino: ({ sender, contract, msg, funds }: AminoMsgExecuteContract["value"]): MsgExecuteContract => ({
sender: sender,
contract: contract,
msg: toUtf8(JSON.stringify(msg)),
msg: fromBase64(msg),
funds: [...funds],
}),
},
@@ -215,7 +215,7 @@ export const cosmWasmTypes: Record<string, AminoConverter> = {
sender: sender,
contract: contract,
code_id: codeId.toString(),
msg: JSON.parse(fromUtf8(msg)),
msg: toBase64(msg),
}),
fromAmino: ({
sender,
@@ -226,7 +226,7 @@ export const cosmWasmTypes: Record<string, AminoConverter> = {
sender: sender,
contract: contract,
codeId: Long.fromString(code_id),
msg: toUtf8(JSON.stringify(msg)),
msg: fromBase64(msg),
}),
},
};
@@ -34,6 +34,7 @@ import {
defaultSigningClientOptions,
defaultUpdateAdminFee,
defaultUploadFee,
deployedHackatom,
getHackatom,
makeRandomAddress,
makeWasmClient,
@@ -150,7 +151,7 @@ describe("SigningCosmWasmClient", () => {
const options = { ...defaultSigningClientOptions, prefix: wasmd.prefix };
const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options);
const { codeId } = await client.upload(alice.address0, getHackatom().data, defaultUploadFee);
const contractAddress1 = await client.instantiate(
const { contractAddress: address1 } = await client.instantiate(
alice.address0,
codeId,
{
@@ -160,7 +161,7 @@ describe("SigningCosmWasmClient", () => {
"contract 1",
defaultInstantiateFee,
);
const contractAddress2 = await client.instantiate(
const { contractAddress: address2 } = await client.instantiate(
alice.address0,
codeId,
{
@@ -170,7 +171,41 @@ describe("SigningCosmWasmClient", () => {
"contract 2",
defaultInstantiateFee,
);
expect(contractAddress1).not.toEqual(contractAddress2);
expect(address1).not.toEqual(address2);
client.disconnect();
});
it("works with legacy Amino signer", async () => {
pendingWithoutWasmd();
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);
// With admin
await client.instantiate(
alice.address0,
deployedHackatom.codeId,
{
verifier: alice.address0,
beneficiary: makeRandomAddress(),
},
"contract 1",
defaultInstantiateFee,
{ admin: makeRandomAddress() },
);
// Without admin
await client.instantiate(
alice.address0,
deployedHackatom.codeId,
{
verifier: alice.address0,
beneficiary: makeRandomAddress(),
},
"contract 1",
defaultInstantiateFee,
);
client.disconnect();
});
});
@@ -247,7 +282,7 @@ describe("SigningCosmWasmClient", () => {
});
describe("migrate", () => {
it("can can migrate from one code ID to another", async () => {
it("works", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, { prefix: wasmd.prefix });
const options = { ...defaultSigningClientOptions, prefix: wasmd.prefix };
@@ -290,6 +325,48 @@ describe("SigningCosmWasmClient", () => {
client.disconnect();
});
it("works with legacy Amino signer", async () => {
pendingWithoutWasmd();
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);
const { codeId: codeId1 } = await client.upload(alice.address0, getHackatom().data, defaultUploadFee);
const { codeId: codeId2 } = await client.upload(alice.address0, getHackatom().data, defaultUploadFee);
const beneficiaryAddress = makeRandomAddress();
const { contractAddress } = await client.instantiate(
alice.address0,
codeId1,
{
verifier: alice.address0,
beneficiary: beneficiaryAddress,
},
"My cool label",
defaultInstantiateFee,
{ admin: alice.address0 },
);
const wasmClient = await makeWasmClient(wasmd.endpoint);
const { contractInfo: contractInfo1 } = await wasmClient.wasm.getContractInfo(contractAddress);
assert(contractInfo1);
expect(contractInfo1.admin).toEqual(alice.address0);
const newVerifier = makeRandomAddress();
await client.migrate(
alice.address0,
contractAddress,
codeId2,
{ verifier: newVerifier },
defaultMigrateFee,
);
const { contractInfo: contractInfo2 } = await wasmClient.wasm.getContractInfo(contractAddress);
assert(contractInfo2);
expect({ ...contractInfo2 }).toEqual({
...contractInfo1,
codeId: Long.fromNumber(codeId2, true),
});
client.disconnect();
});
});
describe("execute", () => {
@@ -342,6 +419,56 @@ describe("SigningCosmWasmClient", () => {
client.disconnect();
});
it("works with legacy Amino signer", async () => {
pendingWithoutWasmd();
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);
const { codeId } = await client.upload(alice.address0, getHackatom().data, defaultUploadFee);
// instantiate
const funds = [coin(233444, "ucosm"), coin(5454, "ustake")];
const beneficiaryAddress = makeRandomAddress();
const { contractAddress } = await client.instantiate(
alice.address0,
codeId,
{
verifier: alice.address0,
beneficiary: beneficiaryAddress,
},
"amazing random contract",
defaultInstantiateFee,
{
funds: funds,
},
);
// execute
const result = await client.execute(
alice.address0,
contractAddress,
{ release: {} },
defaultExecuteFee,
);
const wasmEvent = result.logs[0].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({
key: "destination",
value: beneficiaryAddress,
});
// Verify token transfer from contract to beneficiary
const wasmClient = await makeWasmClient(wasmd.endpoint);
const beneficiaryBalanceUcosm = await wasmClient.bank.balance(beneficiaryAddress, "ucosm");
expect(beneficiaryBalanceUcosm).toEqual(funds[0]);
const beneficiaryBalanceUstake = await wasmClient.bank.balance(beneficiaryAddress, "ustake");
expect(beneficiaryBalanceUstake).toEqual(funds[1]);
const contractBalanceUcosm = await wasmClient.bank.balance(contractAddress, "ucosm");
expect(contractBalanceUcosm).toEqual(coin(0, "ucosm"));
const contractBalanceUstake = await wasmClient.bank.balance(contractAddress, "ustake");
expect(contractBalanceUstake).toEqual(coin(0, "ustake"));
client.disconnect();
});
});
describe("sendTokens", () => {