cosmwasm-stargate: Add tests for SigningCosmWasmClient Amino signing

This commit is contained in:
willclarktech 2021-01-13 13:22:52 +00:00
parent 5fc678fc26
commit 0af2d6739b
No known key found for this signature in database
GPG Key ID: 551A86E2E398ADF7
2 changed files with 279 additions and 5 deletions

View File

@ -2,12 +2,21 @@
import { UploadMeta } from "@cosmjs/cosmwasm-launchpad";
import { sha256 } from "@cosmjs/crypto";
import { toHex } from "@cosmjs/encoding";
import { coin, coins, GasPrice } from "@cosmjs/launchpad";
import { DirectSecp256k1HdWallet, Registry } from "@cosmjs/proto-signing";
import { assertIsBroadcastTxSuccess, codec } from "@cosmjs/stargate";
import {
coin,
coins,
GasPrice,
MsgDelegate as LaunchpadMsgDelegate,
Secp256k1HdWallet,
} from "@cosmjs/launchpad";
import { Coin, cosmosField, DirectSecp256k1HdWallet, registered, Registry } from "@cosmjs/proto-signing";
import { AminoTypes, assertIsBroadcastTxSuccess, codec } from "@cosmjs/stargate";
import { assert, sleep } from "@cosmjs/utils";
import Long from "long";
import pako from "pako";
import { Message } from "protobufjs";
import { cosmwasm } from "./codec";
import { PrivateSigningCosmWasmClient, SigningCosmWasmClient } from "./signingcosmwasmclient";
import {
alice,
@ -15,12 +24,18 @@ import {
makeRandomAddress,
makeWasmClient,
ModifyingDirectSecp256k1HdWallet,
ModifyingSecp256k1HdWallet,
pendingWithoutWasmd,
unused,
validator,
wasmd,
} from "./testutils.spec";
type IMsgSend = codec.cosmos.bank.v1beta1.IMsgSend;
type IMsgDelegate = codec.cosmos.staking.v1beta1.IMsgDelegate;
type IMsgStoreCode = cosmwasm.wasm.v1beta1.IMsgStoreCode;
const { MsgSend } = codec.cosmos.bank.v1beta1;
const { MsgDelegate } = codec.cosmos.staking.v1beta1;
const { Tx } = codec.cosmos.tx.v1beta1;
@ -33,6 +48,17 @@ describe("SigningCosmWasmClient", () => {
expect(client).toBeTruthy();
});
it("can be constructed with custom registry", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic);
const registry = new Registry();
registry.register("/custom.MsgCustom", MsgSend);
const options = { registry: registry };
const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options);
const openedClient = (client as unknown) as PrivateSigningCosmWasmClient;
expect(openedClient.registry.lookupType("/custom.MsgCustom")).toEqual(MsgSend);
});
it("can be constructed with custom gas price", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
@ -411,7 +437,7 @@ describe("SigningCosmWasmClient", () => {
});
describe("sendTokens", () => {
it("works", async () => {
it("works with direct signer", async () => {
pendingWithoutWasmd();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet);
@ -434,6 +460,30 @@ describe("SigningCosmWasmClient", () => {
assert(after);
expect(after).toEqual(transferAmount[0]);
});
it("works with legacy Amino signer", async () => {
pendingWithoutWasmd();
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet);
const transferAmount = coins(7890, "ucosm");
const beneficiaryAddress = makeRandomAddress();
const memo = "for dinner";
// no tokens here
const before = await client.getBalance(beneficiaryAddress, "ucosm");
expect(before).toBeNull();
// send
const result = await client.sendTokens(alice.address0, beneficiaryAddress, transferAmount, memo);
assertIsBroadcastTxSuccess(result);
expect(result.rawLog).toBeTruthy();
// got tokens
const after = await client.getBalance(beneficiaryAddress, "ucosm");
assert(after);
expect(after).toEqual(transferAmount[0]);
});
});
describe("signAndBroadcast", () => {
@ -506,5 +556,188 @@ describe("SigningCosmWasmClient", () => {
expect(tx.authInfo!.fee!.gasLimit!.toNumber()).toEqual(333333);
});
});
describe("legacy Amino mode", () => {
// NOTE: One custom registry shared between tests
// See https://github.com/protobufjs/protobuf.js#using-decorators
// > Decorated types reside in protobuf.roots["decorated"] using a flat structure, so no duplicate names.
const customRegistry = new Registry();
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
@registered(customRegistry, msgDelegateTypeUrl)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class CustomMsgDelegate extends Message {
@cosmosField.string(1)
public readonly custom_delegator_address?: string;
@cosmosField.string(2)
public readonly custom_validator_address?: string;
@cosmosField.message(3, Coin)
public readonly custom_amount?: Coin;
}
it("works with bank MsgSend", async () => {
pendingWithoutWasmd();
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet);
const msgSend: IMsgSend = {
fromAddress: alice.address0,
toAddress: makeRandomAddress(),
amount: coins(1234, "ucosm"),
};
const msgAny = {
typeUrl: "/cosmos.bank.v1beta1.MsgSend",
value: msgSend,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "200000",
};
const memo = "Use your tokens wisely";
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
it("works with staking MsgDelegate", async () => {
pendingWithoutWasmd();
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet);
const msgDelegate: IMsgDelegate = {
delegatorAddress: alice.address0,
validatorAddress: validator.validatorAddress,
amount: coin(1234, "ustake"),
};
const msgAny = {
typeUrl: "/cosmos.staking.v1beta1.MsgDelegate",
value: msgDelegate,
};
const fee = {
amount: coins(2000, "ustake"),
gas: "200000",
};
const memo = "Use your tokens wisely";
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
it("works with wasm MsgStoreCode", async () => {
pendingWithoutWasmd();
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet);
const { data, builder, source } = getHackatom();
const msgStoreCode: IMsgStoreCode = {
sender: alice.address0,
wasmByteCode: pako.gzip(data),
source: source,
builder: builder,
};
const msgAny = {
typeUrl: "/cosmwasm.wasm.v1beta1.MsgStoreCode",
value: msgStoreCode,
};
const fee = {
amount: coins(2000, "ustake"),
gas: "1500000",
};
const memo = "Use your tokens wisely";
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
it("works with a custom registry and custom message", async () => {
pendingWithoutWasmd();
const wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, wasmd.prefix);
const customAminoTypes = new AminoTypes({
additions: {
"/cosmos.staking.v1beta1.MsgDelegate": {
aminoType: "cosmos-sdk/MsgDelegate",
toAmino: ({
custom_delegator_address,
custom_validator_address,
custom_amount,
}: CustomMsgDelegate): LaunchpadMsgDelegate["value"] => {
assert(custom_delegator_address, "missing custom_delegator_address");
assert(custom_validator_address, "missing validator_address");
assert(custom_amount, "missing amount");
assert(custom_amount.amount, "missing amount.amount");
assert(custom_amount.denom, "missing amount.denom");
return {
delegator_address: custom_delegator_address,
validator_address: custom_validator_address,
amount: {
amount: custom_amount.amount,
denom: custom_amount.denom,
},
};
},
fromAmino: ({
delegator_address,
validator_address,
amount,
}: LaunchpadMsgDelegate["value"]): CustomMsgDelegate =>
CustomMsgDelegate.create({
custom_delegator_address: delegator_address,
custom_validator_address: validator_address,
custom_amount: Coin.create(amount),
}),
},
},
});
const options = { registry: customRegistry, aminoTypes: customAminoTypes };
const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet, options);
const msg = {
custom_delegator_address: alice.address0,
custom_validator_address: validator.validatorAddress,
custom_amount: coin(1234, "ustake"),
};
const msgAny = {
typeUrl: "/cosmos.staking.v1beta1.MsgDelegate",
value: msg,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "200000",
};
const memo = "Use your power wisely";
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
it("works with a modifying signer", async () => {
pendingWithoutWasmd();
const wallet = await ModifyingSecp256k1HdWallet.fromMnemonic(alice.mnemonic, undefined, "wasm");
const client = await SigningCosmWasmClient.connectWithSigner(wasmd.endpoint, wallet);
const msg = {
delegatorAddress: alice.address0,
validatorAddress: validator.validatorAddress,
amount: coin(1234, "ustake"),
};
const msgAny = {
typeUrl: msgDelegateTypeUrl,
value: msg,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "200000",
};
const memo = "Use your power wisely";
const result = await client.signAndBroadcast(alice.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
await sleep(1000);
const searchResult = await client.getTx(result.transactionHash);
assert(searchResult, "Must find transaction");
const tx = Tx.decode(searchResult.tx);
// From ModifyingSecp256k1HdWallet
expect(tx.body!.memo).toEqual("This was modified");
expect({ ...tx.authInfo!.fee!.amount![0] }).toEqual(coin(3000, "ucosm"));
expect(tx.authInfo!.fee!.gasLimit!.toNumber()).toEqual(333333);
});
});
});
});

View File

@ -1,7 +1,13 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Bip39, EnglishMnemonic, Random, Secp256k1, Slip10, Slip10Curve } from "@cosmjs/crypto";
import { Bech32, fromBase64 } from "@cosmjs/encoding";
import { coins, makeCosmoshubPath } from "@cosmjs/launchpad";
import {
AminoSignResponse,
coins,
makeCosmoshubPath,
Secp256k1HdWallet,
StdSignDoc,
} from "@cosmjs/launchpad";
import { DirectSecp256k1HdWallet, DirectSignResponse, makeAuthInfoBytes } from "@cosmjs/proto-signing";
import {
AuthExtension,
@ -205,6 +211,41 @@ export async function makeWasmClient(
return QueryClient.withExtensions(tmClient, setupAuthExtension, setupBankExtension, setupWasmExtension);
}
/**
* A class for testing clients using an Amino signer which modifies the transaction it receives before signing
*/
export class ModifyingSecp256k1HdWallet extends Secp256k1HdWallet {
public static async fromMnemonic(
mnemonic: string,
hdPath = makeCosmoshubPath(0),
prefix = "cosmos",
): Promise<ModifyingSecp256k1HdWallet> {
const mnemonicChecked = new EnglishMnemonic(mnemonic);
const seed = await Bip39.mnemonicToSeed(mnemonicChecked);
const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath);
const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey;
return new ModifyingSecp256k1HdWallet(
mnemonicChecked,
hdPath,
privkey,
Secp256k1.compressPubkey(uncompressed),
prefix,
);
}
public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise<AminoSignResponse> {
const modifiedSignDoc = {
...signDoc,
fee: {
amount: coins(3000, "ucosm"),
gas: "333333",
},
memo: "This was modified",
};
return super.signAmino(signerAddress, modifiedSignDoc);
}
}
/**
* A class for testing clients using a direct signer which modifies the transaction it receives before signing
*/