From 509a7e9164b2219d11a972a7ac1d5e753ccda125 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 15 Jul 2020 16:25:13 +0200 Subject: [PATCH 1/6] sdk38: Add distribution module to LCD --- packages/sdk38/src/lcdapi/distribution.ts | 99 +++++++++++++++++++ packages/sdk38/types/lcdapi/distribution.d.ts | 68 +++++++++++++ 2 files changed, 167 insertions(+) create mode 100644 packages/sdk38/src/lcdapi/distribution.ts create mode 100644 packages/sdk38/types/lcdapi/distribution.d.ts diff --git a/packages/sdk38/src/lcdapi/distribution.ts b/packages/sdk38/src/lcdapi/distribution.ts new file mode 100644 index 00000000..36efd792 --- /dev/null +++ b/packages/sdk38/src/lcdapi/distribution.ts @@ -0,0 +1,99 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Coin } from "../coins"; +import { LcdClient } from "./lcdclient"; + +export interface RewardContainer { + readonly validator_address: string; + readonly reward: readonly Coin[] | null; +} + +export interface DistributionDelegatorRewardsResponse { + readonly height: string; + readonly result: { + readonly rewards: readonly RewardContainer[] | null; + readonly total: readonly Coin[] | null; + }; +} + +export interface DistributionDelegatorRewardResponse { + readonly height: string; + readonly result: readonly Coin[]; +} + +export interface DistributionWithdrawAddressResponse { + readonly height: string; + readonly result: string; +} + +export interface DistributionValidatorResponse { + readonly height: string; + readonly result: { + readonly operator_address: string; + readonly self_bond_rewards: readonly Coin[]; + readonly val_commission: readonly Coin[]; + }; +} + +export interface DistributionValidatorRewardsResponse { + readonly height: string; + readonly result: readonly Coin[]; +} + +export interface DistributionValidatorOutstandingRewardsResponse { + readonly height: string; + readonly result: readonly Coin[]; +} + +export interface DistributionParametersResponse { + readonly height: string; + readonly result: { + readonly community_tax: string; + readonly base_proposer_reward: string; + readonly bonus_proposer_reward: string; + readonly withdraw_addr_enabled: boolean; + }; +} + +export interface DistributionCommunityPoolResponse { + readonly height: string; + readonly result: readonly Coin[]; +} + +export interface DistributionExtension { + readonly distribution: { + readonly delegatorRewards: (delegatorAddress: string) => Promise; + readonly delegatorReward: ( + delegatorAddress: string, + validatorAddress: string, + ) => Promise; + readonly withdrawAddress: (delegatorAddress: string) => Promise; + readonly validator: (validatorAddress: string) => Promise; + readonly validatorRewards: (validatorAddress: string) => Promise; + readonly validatorOutstandingRewards: ( + validatorAddress: string, + ) => Promise; + + readonly parameters: () => Promise; + readonly communityPool: () => Promise; + }; +} + +export function setupDistributionExtension(base: LcdClient): DistributionExtension { + return { + distribution: { + delegatorRewards: async (delegatorAddress: string) => + base.get(`/distribution/delegators/${delegatorAddress}/rewards`), + delegatorReward: async (delegatorAddress: string, validatorAddress: string) => + base.get(`/distribution/delegators/${delegatorAddress}/rewards/${validatorAddress}`), + withdrawAddress: async (delegatorAddress: string) => + base.get(`/distribution/delegators/${delegatorAddress}/withdraw_address`), + validator: async (validatorAddress: string) => base.get(`/distribution/validators/${validatorAddress}`), + validatorRewards: async (validatorAddress: string) => + base.get(`/distribution/validators/${validatorAddress}/rewards`), + validatorOutstandingRewards: async (validatorAddress: string) => + base.get(`/distribution/validators/${validatorAddress}/outstanding_rewards`), + parameters: async () => base.get(`/distribution/parameters`), + communityPool: async () => base.get(`/distribution/community_pool`), + }, + }; +} diff --git a/packages/sdk38/types/lcdapi/distribution.d.ts b/packages/sdk38/types/lcdapi/distribution.d.ts new file mode 100644 index 00000000..59789b17 --- /dev/null +++ b/packages/sdk38/types/lcdapi/distribution.d.ts @@ -0,0 +1,68 @@ +import { Coin } from "../coins"; +import { LcdClient } from "./lcdclient"; +export interface RewardContainer { + readonly validator_address: string; + readonly reward: readonly Coin[] | null; +} +export interface DistributionDelegatorRewardsResponse { + readonly height: string; + readonly result: { + readonly rewards: readonly RewardContainer[] | null; + readonly total: readonly Coin[] | null; + }; +} +export interface DistributionDelegatorRewardResponse { + readonly height: string; + readonly result: readonly Coin[]; +} +export interface DistributionWithdrawAddressResponse { + readonly height: string; + readonly result: string; +} +export interface DistributionValidatorResponse { + readonly height: string; + readonly result: { + readonly operator_address: string; + readonly self_bond_rewards: readonly Coin[]; + readonly val_commission: readonly Coin[]; + }; +} +export interface DistributionValidatorRewardsResponse { + readonly height: string; + readonly result: readonly Coin[]; +} +export interface DistributionValidatorOutstandingRewardsResponse { + readonly height: string; + readonly result: readonly Coin[]; +} +export interface DistributionParametersResponse { + readonly height: string; + readonly result: { + readonly community_tax: string; + readonly base_proposer_reward: string; + readonly bonus_proposer_reward: string; + readonly withdraw_addr_enabled: boolean; + }; +} +export interface DistributionCommunityPoolResponse { + readonly height: string; + readonly result: readonly Coin[]; +} +export interface DistributionExtension { + readonly distribution: { + readonly delegatorRewards: (delegatorAddress: string) => Promise; + readonly delegatorReward: ( + delegatorAddress: string, + validatorAddress: string, + ) => Promise; + readonly withdrawAddress: (delegatorAddress: string) => Promise; + readonly validator: (validatorAddress: string) => Promise; + readonly validatorRewards: (validatorAddress: string) => Promise; + readonly validatorOutstandingRewards: ( + validatorAddress: string, + ) => Promise; + readonly parameters: () => Promise; + readonly communityPool: () => Promise; + }; +} +export declare function setupDistributionExtension(base: LcdClient): DistributionExtension; From fd8ceee9673c1015cb2bf83dafb053564cb73f78 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 15 Jul 2020 17:23:30 +0200 Subject: [PATCH 2/6] sdk38: Add tests for LCD distribution module --- .../sdk38/src/lcdapi/distribution.spec.ts | 194 ++++++++++++++++++ packages/sdk38/src/testutils.spec.ts | 2 + 2 files changed, 196 insertions(+) create mode 100644 packages/sdk38/src/lcdapi/distribution.spec.ts diff --git a/packages/sdk38/src/lcdapi/distribution.spec.ts b/packages/sdk38/src/lcdapi/distribution.spec.ts new file mode 100644 index 00000000..8fb521e1 --- /dev/null +++ b/packages/sdk38/src/lcdapi/distribution.spec.ts @@ -0,0 +1,194 @@ +/* eslint-disable @typescript-eslint/naming-convention */ +import { Bech32 } from "@cosmjs/encoding"; +import { sleep } from "@cosmjs/utils"; + +import { coin, coins } from "../coins"; +import { isPostTxFailure } from "../cosmosclient"; +import { makeSignBytes } from "../encoding"; +import { SigningCosmosClient } from "../signingcosmosclient"; +import { + bigDecimalMatcher, + faucet, + nonNegativeIntegerMatcher, + pendingWithoutWasmd, + validatorAddress, + wasmd, + wasmdEnabled, +} from "../testutils.spec"; +import { Secp256k1Wallet } from "../wallet"; +import { DistributionExtension, setupDistributionExtension } from "./distribution"; +import { LcdClient } from "./lcdclient"; + +function makeDistributionClient(apiUrl: string): LcdClient & DistributionExtension { + return LcdClient.withExtensions({ apiUrl }, setupDistributionExtension); +} + +describe("DistributionExtension", () => { + const defaultFee = { + amount: coins(25000, "ucosm"), + gas: "1500000", // 1.5 million + }; + + beforeAll(async () => { + if (wasmdEnabled()) { + const wallet = await Secp256k1Wallet.fromMnemonic(faucet.mnemonic); + const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, wallet, {}); + + const chainId = await client.getChainId(); + const msg = { + type: "cosmos-sdk/MsgDelegate", + value: { + delegator_address: faucet.address, + validator_address: validatorAddress, + amount: coin(25000, "ustake"), + }, + }; + const memo = "Test delegation for wasmd"; + const { accountNumber, sequence } = await client.getNonce(); + const signBytes = makeSignBytes([msg], defaultFee, chainId, memo, accountNumber, sequence); + const signature = await wallet.sign(faucet.address, signBytes); + const tx = { + msg: [msg], + fee: defaultFee, + memo: memo, + signatures: [signature], + }; + + const receipt = await client.postTx(tx); + if (isPostTxFailure(receipt)) { + throw new Error("Delegation failed"); + } + + await sleep(75); // wait until transactions are indexed + } + }); + + describe("delegatorRewards", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = makeDistributionClient(wasmd.endpoint); + const response = await client.distribution.delegatorRewards(faucet.address); + expect(response).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + result: { + rewards: [ + { + validator_address: validatorAddress, + reward: null, + }, + ], + total: null, + }, + }); + }); + }); + + describe("delegatorReward", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = makeDistributionClient(wasmd.endpoint); + const response = await client.distribution.delegatorReward(faucet.address, validatorAddress); + expect(response).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + result: [], + }); + }); + }); + + describe("withdrawAddress", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = makeDistributionClient(wasmd.endpoint); + const response = await client.distribution.withdrawAddress(faucet.address); + expect(response).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + result: faucet.address, + }); + }); + }); + + describe("validator", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = makeDistributionClient(wasmd.endpoint); + const response = await client.distribution.validator(validatorAddress); + expect(response).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + result: { + // TODO: This smells like a bug in the backend to me + operator_address: Bech32.encode("cosmos", Bech32.decode(validatorAddress).data), + self_bond_rewards: [ + { denom: "ucosm", amount: jasmine.stringMatching(bigDecimalMatcher) }, + { denom: "ustake", amount: jasmine.stringMatching(bigDecimalMatcher) }, + ], + val_commission: [ + { denom: "ucosm", amount: jasmine.stringMatching(bigDecimalMatcher) }, + { denom: "ustake", amount: jasmine.stringMatching(bigDecimalMatcher) }, + ], + }, + }); + }); + }); + + describe("validatorRewards", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = makeDistributionClient(wasmd.endpoint); + const response = await client.distribution.validatorRewards(validatorAddress); + expect(response).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + result: [ + { denom: "ucosm", amount: jasmine.stringMatching(bigDecimalMatcher) }, + { denom: "ustake", amount: jasmine.stringMatching(bigDecimalMatcher) }, + ], + }); + }); + }); + + describe("validatorOutstandingRewards", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = makeDistributionClient(wasmd.endpoint); + const response = await client.distribution.validatorOutstandingRewards(validatorAddress); + expect(response).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + result: [ + { denom: "ucosm", amount: jasmine.stringMatching(bigDecimalMatcher) }, + { denom: "ustake", amount: jasmine.stringMatching(bigDecimalMatcher) }, + ], + }); + }); + }); + + describe("parameters", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = makeDistributionClient(wasmd.endpoint); + const response = await client.distribution.parameters(); + expect(response).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + result: { + community_tax: "0.020000000000000000", + base_proposer_reward: "0.010000000000000000", + bonus_proposer_reward: "0.040000000000000000", + withdraw_addr_enabled: true, + }, + }); + }); + }); + + describe("communityPool", () => { + it("works", async () => { + pendingWithoutWasmd(); + const client = makeDistributionClient(wasmd.endpoint); + const response = await client.distribution.communityPool(); + expect(response).toEqual({ + height: jasmine.stringMatching(nonNegativeIntegerMatcher), + result: [ + { denom: "ucosm", amount: jasmine.stringMatching(bigDecimalMatcher) }, + { denom: "ustake", amount: jasmine.stringMatching(bigDecimalMatcher) }, + ], + }); + }); + }); +}); diff --git a/packages/sdk38/src/testutils.spec.ts b/packages/sdk38/src/testutils.spec.ts index b6c80a73..37356640 100644 --- a/packages/sdk38/src/testutils.spec.ts +++ b/packages/sdk38/src/testutils.spec.ts @@ -38,6 +38,8 @@ export const faucet = { address: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", }; +export const validatorAddress = "cosmosvaloper1gjvanqxc774u6ed9thj4gpn9gj5zus5u32enqn"; + /** Unused account */ export const unused = { pubkey: { From 0eab1b020ec5d544cdef7e1187482049af508f6f Mon Sep 17 00:00:00 2001 From: willclarktech Date: Wed, 15 Jul 2020 17:31:57 +0200 Subject: [PATCH 3/6] sdk38: Export distribution module types and setup function --- packages/sdk38/src/index.ts | 10 ++++++++++ packages/sdk38/src/lcdapi/index.ts | 12 ++++++++++++ packages/sdk38/types/index.d.ts | 10 ++++++++++ packages/sdk38/types/lcdapi/index.d.ts | 12 ++++++++++++ 4 files changed, 44 insertions(+) diff --git a/packages/sdk38/src/index.ts b/packages/sdk38/src/index.ts index d9dd90ce..1032a86f 100644 --- a/packages/sdk38/src/index.ts +++ b/packages/sdk38/src/index.ts @@ -27,6 +27,15 @@ export { BankExtension, BlockResponse, BroadcastMode, + DistributionCommunityPoolResponse, + DistributionDelegatorRewardResponse, + DistributionDelegatorRewardsResponse, + DistributionExtension, + DistributionParametersResponse, + DistributionValidatorOutstandingRewardsResponse, + DistributionValidatorResponse, + DistributionValidatorRewardsResponse, + DistributionWithdrawAddressResponse, EncodeTxResponse, GovExtension, GovParametersResponse, @@ -50,6 +59,7 @@ export { SearchTxsResponse, setupAuthExtension, setupBankExtension, + setupDistributionExtension, setupGovExtension, setupMintExtension, setupSlashingExtension, diff --git a/packages/sdk38/src/lcdapi/index.ts b/packages/sdk38/src/lcdapi/index.ts index 80bd53b7..742a2a1b 100644 --- a/packages/sdk38/src/lcdapi/index.ts +++ b/packages/sdk38/src/lcdapi/index.ts @@ -4,6 +4,18 @@ export { AuthExtension, AuthAccountsResponse, setupAuthExtension } from "./auth"; export { BankBalancesResponse, BankExtension, setupBankExtension } from "./bank"; +export { + DistributionCommunityPoolResponse, + DistributionDelegatorRewardResponse, + DistributionDelegatorRewardsResponse, + DistributionExtension, + DistributionParametersResponse, + DistributionValidatorOutstandingRewardsResponse, + DistributionValidatorResponse, + DistributionValidatorRewardsResponse, + DistributionWithdrawAddressResponse, + setupDistributionExtension, +} from "./distribution"; export { GovExtension, GovParametersResponse, diff --git a/packages/sdk38/types/index.d.ts b/packages/sdk38/types/index.d.ts index 4f22dbbc..12f5c1a6 100644 --- a/packages/sdk38/types/index.d.ts +++ b/packages/sdk38/types/index.d.ts @@ -25,6 +25,15 @@ export { BankExtension, BlockResponse, BroadcastMode, + DistributionCommunityPoolResponse, + DistributionDelegatorRewardResponse, + DistributionDelegatorRewardsResponse, + DistributionExtension, + DistributionParametersResponse, + DistributionValidatorOutstandingRewardsResponse, + DistributionValidatorResponse, + DistributionValidatorRewardsResponse, + DistributionWithdrawAddressResponse, EncodeTxResponse, GovExtension, GovParametersResponse, @@ -48,6 +57,7 @@ export { SearchTxsResponse, setupAuthExtension, setupBankExtension, + setupDistributionExtension, setupGovExtension, setupMintExtension, setupSlashingExtension, diff --git a/packages/sdk38/types/lcdapi/index.d.ts b/packages/sdk38/types/lcdapi/index.d.ts index b9f984ac..77819d6c 100644 --- a/packages/sdk38/types/lcdapi/index.d.ts +++ b/packages/sdk38/types/lcdapi/index.d.ts @@ -1,5 +1,17 @@ export { AuthExtension, AuthAccountsResponse, setupAuthExtension } from "./auth"; export { BankBalancesResponse, BankExtension, setupBankExtension } from "./bank"; +export { + DistributionCommunityPoolResponse, + DistributionDelegatorRewardResponse, + DistributionDelegatorRewardsResponse, + DistributionExtension, + DistributionParametersResponse, + DistributionValidatorOutstandingRewardsResponse, + DistributionValidatorResponse, + DistributionValidatorRewardsResponse, + DistributionWithdrawAddressResponse, + setupDistributionExtension, +} from "./distribution"; export { GovExtension, GovParametersResponse, From 7fd8355d33bcd87c78a365294928e21b8335d5dc Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 16 Jul 2020 11:09:51 +0200 Subject: [PATCH 4/6] sdk38: Use assert util in distribution module test --- packages/sdk38/src/lcdapi/distribution.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/sdk38/src/lcdapi/distribution.spec.ts b/packages/sdk38/src/lcdapi/distribution.spec.ts index 8fb521e1..ce2e1c74 100644 --- a/packages/sdk38/src/lcdapi/distribution.spec.ts +++ b/packages/sdk38/src/lcdapi/distribution.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { Bech32 } from "@cosmjs/encoding"; -import { sleep } from "@cosmjs/utils"; +import { assert, sleep } from "@cosmjs/utils"; import { coin, coins } from "../coins"; import { isPostTxFailure } from "../cosmosclient"; @@ -55,9 +55,7 @@ describe("DistributionExtension", () => { }; const receipt = await client.postTx(tx); - if (isPostTxFailure(receipt)) { - throw new Error("Delegation failed"); - } + assert(!isPostTxFailure(receipt)); await sleep(75); // wait until transactions are indexed } From ebb1526ca60700f11e5e0d37e4596931d7944491 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 16 Jul 2020 11:12:37 +0200 Subject: [PATCH 5/6] sdk38: Specify MsgDelegate type in distribution module test --- packages/sdk38/src/lcdapi/distribution.spec.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/sdk38/src/lcdapi/distribution.spec.ts b/packages/sdk38/src/lcdapi/distribution.spec.ts index ce2e1c74..cfb8b3cd 100644 --- a/packages/sdk38/src/lcdapi/distribution.spec.ts +++ b/packages/sdk38/src/lcdapi/distribution.spec.ts @@ -5,6 +5,7 @@ import { assert, sleep } from "@cosmjs/utils"; import { coin, coins } from "../coins"; import { isPostTxFailure } from "../cosmosclient"; import { makeSignBytes } from "../encoding"; +import { MsgDelegate } from "../msgs"; import { SigningCosmosClient } from "../signingcosmosclient"; import { bigDecimalMatcher, @@ -35,7 +36,7 @@ describe("DistributionExtension", () => { const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, wallet, {}); const chainId = await client.getChainId(); - const msg = { + const msg: MsgDelegate = { type: "cosmos-sdk/MsgDelegate", value: { delegator_address: faucet.address, From 4ca348738332faf0ec92d24bd0b5380059c5dca0 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Thu, 16 Jul 2020 11:52:49 +0200 Subject: [PATCH 6/6] sdk38: Update gov module test --- packages/sdk38/src/lcdapi/gov.spec.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/sdk38/src/lcdapi/gov.spec.ts b/packages/sdk38/src/lcdapi/gov.spec.ts index 594be5d5..18ec60b1 100644 --- a/packages/sdk38/src/lcdapi/gov.spec.ts +++ b/packages/sdk38/src/lcdapi/gov.spec.ts @@ -259,10 +259,10 @@ describe("GovExtension", () => { expect(response).toEqual({ height: jasmine.stringMatching(nonNegativeIntegerMatcher), result: { - yes: "0", - abstain: "0", - no: "0", - no_with_veto: "0", + yes: jasmine.stringMatching(nonNegativeIntegerMatcher), + abstain: jasmine.stringMatching(nonNegativeIntegerMatcher), + no: jasmine.stringMatching(nonNegativeIntegerMatcher), + no_with_veto: jasmine.stringMatching(nonNegativeIntegerMatcher), }, }); });