From 9ddfe6e679f4ed618d31422abd7f3b56c2938917 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 28 Jul 2021 18:40:22 +0200 Subject: [PATCH] Fix and improve gov testing --- packages/stargate/src/queries/gov.spec.ts | 117 +++++++++++++++++----- packages/stargate/src/stargateclient.ts | 39 +++++++- 2 files changed, 125 insertions(+), 31 deletions(-) diff --git a/packages/stargate/src/queries/gov.spec.ts b/packages/stargate/src/queries/gov.spec.ts index b33929f8..e426adff 100644 --- a/packages/stargate/src/queries/gov.spec.ts +++ b/packages/stargate/src/queries/gov.spec.ts @@ -1,4 +1,4 @@ -import { coins } from "@cosmjs/amino"; +import { coin, coins, makeCosmoshubPath } from "@cosmjs/amino"; import { toAscii } from "@cosmjs/encoding"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { Tendermint34Client } from "@cosmjs/tendermint-rpc"; @@ -7,7 +7,11 @@ import { ProposalStatus, TextProposal, VoteOption } from "cosmjs-types/cosmos/go import { Any } from "cosmjs-types/google/protobuf/any"; import Long from "long"; -import { MsgSubmitProposalEncodeObject, MsgVoteEncodeObject } from "../encodeobjects"; +import { + MsgDelegateEncodeObject, + MsgSubmitProposalEncodeObject, + MsgVoteEncodeObject, +} from "../encodeobjects"; import { SigningStargateClient } from "../signingstargateclient"; import { assertIsBroadcastTxSuccess } from "../stargateclient"; import { @@ -17,6 +21,7 @@ import { pendingWithoutSimapp, simapp, simappEnabled, + validator, } from "../testutils.spec"; import { GovExtension, setupGovExtension } from "./gov"; import { QueryClient } from "./queryclient"; @@ -37,12 +42,19 @@ describe("GovExtension", () => { description: "This proposal proposes to test whether this proposal passes", }); const initialDeposit = coins(12300000, "ustake"); + const delegationVoter1 = coin(424242, "ustake"); + const delegationVoter2 = coin(777, "ustake"); + const voter1Address = faucet.address1; + const voter2Address = faucet.address2; let proposalId: string; beforeAll(async () => { if (simappEnabled()) { - const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); - const [firstAccount] = await wallet.getAccounts(); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + // Use address 1 and 2 instead of 0 to avoid conflicts with other delegation tests + // This must match `voterAddress` above. + hdPaths: [makeCosmoshubPath(1), makeCosmoshubPath(2)], + }); const client = await SigningStargateClient.connectWithSigner( simapp.tendermintUrl, wallet, @@ -56,12 +68,12 @@ describe("GovExtension", () => { typeUrl: "/cosmos.gov.v1beta1.TextProposal", value: Uint8Array.from(TextProposal.encode(textProposal).finish()), }), - proposer: faucet.address0, + proposer: voter1Address, initialDeposit: initialDeposit, }, }; const proposalResult = await client.signAndBroadcast( - firstAccount.address, + voter1Address, [proposalMsg], defaultFee, "Test proposal for simd", @@ -73,18 +85,65 @@ describe("GovExtension", () => { .attributes.find(({ key }: any) => key === "proposal_id").value; assert(proposalId.match(nonNegativeIntegerMatcher)); - const voteMsg: MsgVoteEncodeObject = { - typeUrl: "/cosmos.gov.v1beta1.MsgVote", - value: { - proposalId: longify(proposalId), - voter: faucet.address0, - option: VoteOption.VOTE_OPTION_YES, - }, - }; - const voteMemo = "Test vote for simd"; - await client.signAndBroadcast(firstAccount.address, [voteMsg], defaultFee, voteMemo); + // Voter 1 + { + // My vote only counts when I delegate + if (!(await client.getDelegation(voter1Address, validator.validatorAddress))) { + const msgDelegate: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: { + delegatorAddress: voter1Address, + validatorAddress: validator.validatorAddress, + amount: delegationVoter1, + }, + }; + const result = await client.signAndBroadcast(voter1Address, [msgDelegate], defaultFee); + assertIsBroadcastTxSuccess(result); + } + + const voteMsg: MsgVoteEncodeObject = { + typeUrl: "/cosmos.gov.v1beta1.MsgVote", + value: { + proposalId: longify(proposalId), + voter: voter1Address, + option: VoteOption.VOTE_OPTION_YES, + }, + }; + const voteResult = await client.signAndBroadcast(voter1Address, [voteMsg], defaultFee); + assertIsBroadcastTxSuccess(voteResult); + } + + // Voter 2 + { + // My vote only counts when I delegate + if (!(await client.getDelegation(voter2Address, validator.validatorAddress))) { + const msgDelegate: MsgDelegateEncodeObject = { + typeUrl: "/cosmos.staking.v1beta1.MsgDelegate", + value: { + delegatorAddress: voter2Address, + validatorAddress: validator.validatorAddress, + amount: delegationVoter2, + }, + }; + const result = await client.signAndBroadcast(voter2Address, [msgDelegate], defaultFee); + assertIsBroadcastTxSuccess(result); + } + + const voteMsg: MsgVoteEncodeObject = { + typeUrl: "/cosmos.gov.v1beta1.MsgVote", + value: { + proposalId: longify(proposalId), + voter: voter2Address, + option: VoteOption.VOTE_OPTION_NO_WITH_VETO, + }, + }; + const voteResult = await client.signAndBroadcast(voter2Address, [voteMsg], defaultFee); + assertIsBroadcastTxSuccess(voteResult); + } await sleep(75); // wait until transactions are indexed + + client.disconnect(); } }); @@ -155,8 +214,8 @@ describe("GovExtension", () => { const response = await client.gov.proposals( ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD, - faucet.address0, - faucet.address0, + voter1Address, + voter1Address, ); expect(response.proposals.length).toBeGreaterThanOrEqual(1); expect(response.proposals[response.proposals.length - 1]).toEqual({ @@ -212,7 +271,7 @@ describe("GovExtension", () => { expect(response.deposits).toEqual([ { proposalId: longify(proposalId), - depositor: faucet.address0, + depositor: voter1Address, amount: initialDeposit, }, ]); @@ -226,10 +285,10 @@ describe("GovExtension", () => { pendingWithoutSimapp(); const [client, tmClient] = await makeClientWithGov(simapp.tendermintUrl); - const response = await client.gov.deposit(proposalId, faucet.address0); + const response = await client.gov.deposit(proposalId, voter1Address); expect(response.deposit).toEqual({ proposalId: longify(proposalId), - depositor: faucet.address0, + depositor: voter1Address, amount: initialDeposit, }); @@ -244,10 +303,10 @@ describe("GovExtension", () => { const response = await client.gov.tally(proposalId); expect(response.tally).toEqual({ - yes: "0", + yes: delegationVoter1.amount, abstain: "0", no: "0", - noWithVeto: "0", + noWithVeto: delegationVoter2.amount, }); tmClient.disconnect(); @@ -261,9 +320,15 @@ describe("GovExtension", () => { const response = await client.gov.votes(proposalId); expect(response.votes).toEqual([ + // why is vote 2 first? { proposalId: longify(proposalId), - voter: faucet.address0, + voter: voter2Address, + option: VoteOption.VOTE_OPTION_NO_WITH_VETO, + }, + { + proposalId: longify(proposalId), + voter: voter1Address, option: VoteOption.VOTE_OPTION_YES, }, ]); @@ -277,9 +342,9 @@ describe("GovExtension", () => { pendingWithoutSimapp(); const [client, tmClient] = await makeClientWithGov(simapp.tendermintUrl); - const response = await client.gov.vote(proposalId, faucet.address0); + const response = await client.gov.vote(proposalId, voter1Address); expect(response.vote).toEqual({ - voter: faucet.address0, + voter: voter1Address, proposalId: longify(proposalId), option: VoteOption.VOTE_OPTION_YES, }); diff --git a/packages/stargate/src/stargateclient.ts b/packages/stargate/src/stargateclient.ts index 5138dc3c..a0ac1e59 100644 --- a/packages/stargate/src/stargateclient.ts +++ b/packages/stargate/src/stargateclient.ts @@ -7,7 +7,15 @@ import { MsgData } from "cosmjs-types/cosmos/base/abci/v1beta1/abci"; import { Coin } from "cosmjs-types/cosmos/base/v1beta1/coin"; import { Account, accountFromAny } from "./accounts"; -import { AuthExtension, BankExtension, QueryClient, setupAuthExtension, setupBankExtension } from "./queries"; +import { + AuthExtension, + BankExtension, + QueryClient, + setupAuthExtension, + setupBankExtension, + setupStakingExtension, + StakingExtension, +} from "./queries"; import { isSearchByHeightQuery, isSearchBySentFromOrToQuery, @@ -138,7 +146,7 @@ export interface PrivateStargateClient { export class StargateClient { private readonly tmClient: Tendermint34Client | undefined; - private readonly queryClient: (QueryClient & AuthExtension & BankExtension) | undefined; + private readonly queryClient: (QueryClient & AuthExtension & BankExtension & StakingExtension) | undefined; private chainId: string | undefined; public static async connect(endpoint: string): Promise { @@ -149,7 +157,12 @@ export class StargateClient { protected constructor(tmClient: Tendermint34Client | undefined) { if (tmClient) { this.tmClient = tmClient; - this.queryClient = QueryClient.withExtensions(tmClient, setupAuthExtension, setupBankExtension); + this.queryClient = QueryClient.withExtensions( + tmClient, + setupAuthExtension, + setupBankExtension, + setupStakingExtension, + ); } } @@ -166,11 +179,11 @@ export class StargateClient { return this.tmClient; } - protected getQueryClient(): (QueryClient & AuthExtension & BankExtension) | undefined { + protected getQueryClient(): (QueryClient & AuthExtension & BankExtension & StakingExtension) | undefined { return this.queryClient; } - protected forceGetQueryClient(): QueryClient & AuthExtension & BankExtension { + protected forceGetQueryClient(): QueryClient & AuthExtension & BankExtension & StakingExtension { if (!this.queryClient) { throw new Error("Query client not available. You cannot use online functionality in offline mode."); } @@ -254,6 +267,22 @@ export class StargateClient { return this.forceGetQueryClient().bank.allBalances(address); } + public async getDelegation(delegatorAddress: string, validatorAddress: string): Promise { + let delegatedAmount: Coin | undefined; + try { + delegatedAmount = ( + await this.forceGetQueryClient().staking.delegation(delegatorAddress, validatorAddress) + ).delegationResponse?.balance; + } catch (e) { + if (e.toString().includes("key not found")) { + // ignore, `delegatedAmount` remains undefined + } else { + throw e; + } + } + return delegatedAmount || null; + } + public async getTx(id: string): Promise { const results = await this.txsQuery(`tx.hash='${id}'`); return results[0] ?? null;