stargate: Add signAndBroadcast tests for modifying signers

This commit is contained in:
willclarktech 2020-12-01 16:52:22 +00:00
parent e22f71ba55
commit cdbb7a532c
No known key found for this signature in database
GPG Key ID: 551A86E2E398ADF7
2 changed files with 235 additions and 53 deletions

View File

@ -1,13 +1,25 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { coin, coins, GasPrice, Secp256k1HdWallet } from "@cosmjs/launchpad";
import { Coin, cosmosField, DirectSecp256k1HdWallet, registered, Registry } from "@cosmjs/proto-signing";
import { assert } from "@cosmjs/utils";
import { assert, sleep } from "@cosmjs/utils";
import { Message } from "protobufjs";
import { cosmos } from "./codec";
import { PrivateSigningStargateClient, SigningStargateClient } from "./signingstargateclient";
import { assertIsBroadcastTxSuccess } from "./stargateclient";
import { faucet, makeRandomAddress, pendingWithoutSimapp, simapp, validator } from "./testutils.spec";
import {
faucet,
makeRandomAddress,
ModifyingDirectSecp256k1HdWallet,
ModifyingSecp256k1HdWallet,
pendingWithoutSimapp,
simapp,
validator,
} from "./testutils.spec";
const { MsgSend } = cosmos.bank.v1beta1;
const { MsgDelegate } = cosmos.staking.v1beta1;
const { Tx } = cosmos.tx.v1beta1;
describe("SigningStargateClient", () => {
describe("constructor", () => {
@ -33,11 +45,11 @@ describe("SigningStargateClient", () => {
pendingWithoutSimapp();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic);
const registry = new Registry();
registry.register("/custom.MsgCustom", cosmos.bank.v1beta1.MsgSend);
registry.register("/custom.MsgCustom", MsgSend);
const options = { registry: registry };
const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet, options);
const openedClient = (client as unknown) as PrivateSigningStargateClient;
expect(openedClient.registry.lookupType("/custom.MsgCustom")).toEqual(cosmos.bank.v1beta1.MsgSend);
expect(openedClient.registry.lookupType("/custom.MsgCustom")).toEqual(MsgSend);
});
it("can be constructed with custom gas price", async () => {
@ -133,44 +145,81 @@ describe("SigningStargateClient", () => {
});
describe("signAndBroadcast", () => {
it("works with direct mode", async () => {
pendingWithoutSimapp();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic);
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
const registry = new Registry();
registry.register(msgDelegateTypeUrl, cosmos.staking.v1beta1.MsgDelegate);
const options = { registry: registry };
const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet, options);
describe("direct mode", () => {
it("works", async () => {
pendingWithoutSimapp();
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic);
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
const registry = new Registry();
registry.register(msgDelegateTypeUrl, MsgDelegate);
const options = { registry: registry };
const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet, options);
const msg = cosmos.staking.v1beta1.MsgDelegate.create({
delegatorAddress: faucet.address0,
validatorAddress: validator.validatorAddress,
amount: coin(1234, "ustake"),
const msg = MsgDelegate.create({
delegatorAddress: faucet.address0,
validatorAddress: validator.validatorAddress,
amount: coin(1234, "ustake"),
});
const msgAny = {
typeUrl: msgDelegateTypeUrl,
value: msg,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "180000", // 180k
};
const memo = "Use your power wisely";
const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
it("works with a modifying signer", async () => {
pendingWithoutSimapp();
const wallet = await ModifyingDirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic);
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
const registry = new Registry();
registry.register(msgDelegateTypeUrl, MsgDelegate);
const options = { registry: registry };
const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet, options);
const msg = MsgDelegate.create({
delegatorAddress: faucet.address0,
validatorAddress: validator.validatorAddress,
amount: coin(1234, "ustake"),
});
const msgAny = {
typeUrl: msgDelegateTypeUrl,
value: msg,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "180000", // 180k
};
const memo = "Use your power wisely";
const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
await sleep(1000);
const searchResult = await client.searchTx({ id: result.transactionHash });
const tx = Tx.decode(searchResult[0].tx);
// From ModifyingDirectSecp256k1HdWallet
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);
});
const msgAny = {
typeUrl: msgDelegateTypeUrl,
value: msg,
};
const fee = {
amount: coins(2000, "ucosm"),
gas: "180000", // 180k
};
const memo = "Use your power wisely";
const result = await client.signAndBroadcast(faucet.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
it("works with legacy Amino mode", async () => {
pendingWithoutSimapp();
const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic);
const coinTypeUrl = "/cosmos.base.v1beta1.Coin";
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
describe("legacy Amino mode", () => {
// NOTE: One 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 registry = new Registry();
registry.register(coinTypeUrl, Coin);
const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate";
@registered(registry, msgDelegateTypeUrl)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
class MsgDelegate extends Message {
class CustomMsgDelegate extends Message {
@cosmosField.string(1)
public readonly delegator_address?: string;
@cosmosField.string(2)
@ -179,25 +228,62 @@ describe("SigningStargateClient", () => {
public readonly amount?: Coin;
}
const options = { registry: registry };
const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet, options);
it("works", async () => {
pendingWithoutSimapp();
const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic);
const options = { registry: registry };
const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet, options);
const msg = {
delegator_address: faucet.address0,
validator_address: 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(faucet.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
const msg = {
delegator_address: faucet.address0,
validator_address: 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(faucet.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
});
it("works with a modifying signer", async () => {
pendingWithoutSimapp();
const wallet = await ModifyingSecp256k1HdWallet.fromMnemonic(faucet.mnemonic);
const options = { registry: registry };
const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet, options);
const msg = {
delegator_address: faucet.address0,
validator_address: 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(faucet.address0, [msgAny], fee, memo);
assertIsBroadcastTxSuccess(result);
await sleep(1000);
const searchResult = await client.searchTx({ id: result.transactionHash });
const tx = Tx.decode(searchResult[0].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,5 +1,18 @@
import { Random } from "@cosmjs/crypto";
/* eslint-disable @typescript-eslint/naming-convention */
import { Bip39, EnglishMnemonic, Random, Secp256k1, Slip10, Slip10Curve } from "@cosmjs/crypto";
import { Bech32 } from "@cosmjs/encoding";
import {
AminoSignResponse,
coins,
makeCosmoshubPath,
Secp256k1HdWallet,
StdSignDoc,
} from "@cosmjs/launchpad";
import { DirectSecp256k1HdWallet, DirectSignResponse, makeAuthInfoBytes } from "@cosmjs/proto-signing";
import { cosmos } from "./codec";
const { AuthInfo, TxBody } = cosmos.tx.v1beta1;
export function simappEnabled(): boolean {
return !!process.env.SIMAPP_ENABLED;
@ -70,3 +83,86 @@ export const nonExistentAddress = "cosmos1p79apjaufyphcmsn4g07cynqf0wyjuezqu84hd
export const nonNegativeIntegerMatcher = /^[0-9]+$/;
export const tendermintIdMatcher = /^[0-9A-F]{64}$/;
/**
* 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
*/
export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet {
public static async fromMnemonic(
mnemonic: string,
hdPath = makeCosmoshubPath(0),
prefix = "cosmos",
): Promise<DirectSecp256k1HdWallet> {
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 ModifyingDirectSecp256k1HdWallet(
mnemonicChecked,
hdPath,
privkey,
Secp256k1.compressPubkey(uncompressed),
prefix,
);
}
public async signDirect(address: string, signDoc: cosmos.tx.v1beta1.ISignDoc): Promise<DirectSignResponse> {
const txBody = TxBody.decode(signDoc.bodyBytes!);
const modifiedTxBody = TxBody.create({
...txBody,
memo: "This was modified",
});
const authInfo = AuthInfo.decode(signDoc.authInfoBytes!);
const pubkeys = authInfo.signerInfos.map((signerInfo) => signerInfo.publicKey!);
const sequence = authInfo.signerInfos[0].sequence!.toNumber();
const modifiedFeeAmount = coins(3000, "ucosm");
const modifiedGasLimit = 333333;
const modifiedSignDoc = {
...signDoc,
bodyBytes: Uint8Array.from(TxBody.encode(modifiedTxBody).finish()),
authInfoBytes: makeAuthInfoBytes(
pubkeys,
modifiedFeeAmount,
modifiedGasLimit,
sequence,
cosmos.tx.signing.v1beta1.SignMode.SIGN_MODE_DIRECT,
),
};
return super.signDirect(address, modifiedSignDoc);
}
}