cosmwasm-stargate: Add tests for SigningCosmWasmClient Amino signing
This commit is contained in:
parent
5fc678fc26
commit
0af2d6739b
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue
Block a user