diff --git a/packages/api-derive/src/elections/approvalsOf.ts b/packages/api-derive/src/elections/approvalsOf.ts index 8d8bdce20a..68081bc825 100644 --- a/packages/api-derive/src/elections/approvalsOf.ts +++ b/packages/api-derive/src/elections/approvalsOf.ts @@ -2,22 +2,35 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { Observable } from 'rxjs'; -import { switchMap, map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; +import { Observable } from 'rxjs'; import { AccountId, Vector, SetIndex, ApprovalFlag } from '@polkadot/types'; +import { switchMap, map } from 'rxjs/operators'; +import { approvalFlagsToBools } from '../util/approvalFlagsToBools'; import { drr } from '../util/drr'; -export function approvalsOf (api: ApiInterfaceRx): (who: AccountId) => Observable> { - return (who: AccountId): Observable> => - api.query.elections.nextVoterSet().pipe( - switchMap((nextVoterSet: SetIndex): Observable[]> => - api.query.elections.approvalsOf.multi( - [...Array(+nextVoterSet + 1)].map((_, i): [AccountId, number] => [who, i]) - ) - ), - map((votes: Vector[]): Vector => - votes.find((v): boolean => v.length > 0) || new Vector(ApprovalFlag, [])), - drr() - ); +/** + * @name approvalsOf + * @returns A nested array of boolean approvals for the given account in each voter set index, converted from its ApprovalFlag(s). + * @example + *
+ * + * ```javascript + * api.derive.elections.approvalsOf(ALICE, (approvals) => { + * // approvals === [[false], [true, false], ...] + * }); + * ``` + */ +export function approvalsOf (api: ApiInterfaceRx): (who: AccountId) => Observable { + return (who: AccountId): Observable => + (api.query.elections.nextVoterSet()) + .pipe( + switchMap((nextVoterSet: SetIndex): Observable[]> => + api.query.elections.approvalsOf.multi([...Array(nextVoterSet.toNumber() + 1).keys()].map((i): [string, number] => [who.toString(), i])) as any as Observable[]> + ), + map((votes: Vector[]): boolean[][] => + votes.map((flags: Vector): boolean[] => approvalFlagsToBools(flags)) + ), + drr() + ); } diff --git a/packages/api-derive/src/elections/approvalsOfAt.ts b/packages/api-derive/src/elections/approvalsOfAt.ts new file mode 100644 index 0000000000..ba426e75c1 --- /dev/null +++ b/packages/api-derive/src/elections/approvalsOfAt.ts @@ -0,0 +1,31 @@ +// Copyright 2017-2019 @polkadot/api-derive authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { ApiInterfaceRx } from '@polkadot/api/types'; +import { Observable } from 'rxjs'; +import { AccountId, Vector, SetIndex, ApprovalFlag } from '@polkadot/types'; +import { map } from 'rxjs/operators'; +import { approvalFlagsToBools } from '../util/approvalFlagsToBools'; +import { drr } from '../util/drr'; + +/** + * @name approvalsOfAt + * @returns An array of boolean approvals for the account and set index, converted from the returned ApprovalFlag. + * @example + *
+ * + * ```javascript + * api.derive.elections.approvalsOfAt(ALICE, new BN(0), (approvals) => { + * // approvals === [false, true, ...] + * }); + * ``` + */ +export function approvalsOfAt (api: ApiInterfaceRx): (who: AccountId, at: SetIndex) => Observable { + return (who: AccountId, at: SetIndex): Observable => + (api.query.elections.approvalsOf>([who.toString(), at])) + .pipe( + map((flags: Vector): boolean[] => approvalFlagsToBools(flags)), + drr() + ); +} diff --git a/packages/api-derive/src/elections/index.ts b/packages/api-derive/src/elections/index.ts index e81201c702..d68257b380 100644 --- a/packages/api-derive/src/elections/index.ts +++ b/packages/api-derive/src/elections/index.ts @@ -3,4 +3,7 @@ // of the Apache-2.0 license. See the LICENSE file for details. export * from './approvalsOf'; +export * from './approvalsOfAt'; +export * from './info'; export * from './voters'; +export * from './voterSets'; diff --git a/packages/api-derive/src/elections/info.ts b/packages/api-derive/src/elections/info.ts new file mode 100644 index 0000000000..72c917da25 --- /dev/null +++ b/packages/api-derive/src/elections/info.ts @@ -0,0 +1,58 @@ +// Copyright 2017-2019 @polkadot/api-derive authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import BN from 'bn.js'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { ApiInterfaceRx } from '@polkadot/api/types'; +import { AccountId, BlockNumber, SetIndex, VoteIndex } from '@polkadot/types'; + +import { DerivedElectionsInfo } from '../types'; +import { drr } from '../util/drr'; + +/** + * @name info + * @returns An object containing the combined results of the storage queries for + * all relevant election module properties. + * @example + *
+ * + * ```javascript + * api.derive.elections.info(([members, candidates]) => { + * console.log(`There are currently ${members.length} council members and ${candidates.length} prospective council candidates.`); + * }); + * ``` + */ +export function info (api: ApiInterfaceRx): () => Observable { + return (): Observable => { + return ( + api.queryMulti([ + api.query.elections.members, + api.query.elections.candidates, + api.query.elections.candidateCount, + api.query.elections.desiredSeats, + api.query.elections.termDuration, + api.query.elections.voteCount, + api.query.elections.voterCount + ]) as any as Observable<[[AccountId, BlockNumber][], AccountId[], BN, BN, BlockNumber, VoteIndex, SetIndex]> + ).pipe( + map(([members, candidates, candidateCount, desiredSeats, termDuration, voteCount, voterCount]): DerivedElectionsInfo => ({ + members: members.reduce( + (record: Record, [accountId, blockNumber]): Record => { + record[accountId.toString()] = blockNumber; + return record; + }, + {} + ), + candidates, + candidateCount, + desiredSeats, + termDuration, + voteCount, + voterCount + } as unknown as DerivedElectionsInfo)), + drr() + ); + }; +} diff --git a/packages/api-derive/src/elections/voterSets.ts b/packages/api-derive/src/elections/voterSets.ts new file mode 100644 index 0000000000..62ee5c8e2a --- /dev/null +++ b/packages/api-derive/src/elections/voterSets.ts @@ -0,0 +1,46 @@ +// Copyright 2017-2019 @polkadot/api-derive authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { ApiInterfaceRx } from '@polkadot/api/types'; +import { Observable } from 'rxjs'; +import { AccountId, Vector, Option, SetIndex } from '@polkadot/types'; +import { switchMap, map } from 'rxjs/operators'; +import { drr } from '../util/drr'; + +/** + * @name voterSets + * @returns An mapping of all current voter accounts to their voter set index. + * @example + *
+ * + * ```javascript + * api.derive.elections.voterSets((voters) => { + * console.log(`ALICE is a voter in the voter set with index ${voters[ALICE].toString()}.`); + * }); + * ``` + */ +export function voterSets (api: ApiInterfaceRx): () => Observable> { + return (): Observable> => + api.query.elections.nextVoterSet().pipe( + switchMap((nextVoterSet: SetIndex): Observable>[]> => + api.query.elections.voters.multi([...Array(+nextVoterSet + 1).keys()].map((_, i): [number] => [i])) as any as Observable>[]> + ), + map((voters: Vector>[]): Record => + voters.reduce((result: Record, vec, setIndex): Record => { + vec.forEach((e): void => { + // re-create the index based on position 0 is [0][0] and likewise + // 64 (0..63 in first) is [1][0] (the first index value in set 2) + const accountId: AccountId | null = e.unwrapOr(null); + + if (accountId) { + result[accountId.toString()] = new SetIndex(setIndex); + } + }); + + return result; + }, {}) + ), + drr() + ); +} diff --git a/packages/api-derive/src/elections/voters.ts b/packages/api-derive/src/elections/voters.ts index 7e4d160102..420a190290 100644 --- a/packages/api-derive/src/elections/voters.ts +++ b/packages/api-derive/src/elections/voters.ts @@ -2,22 +2,37 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { Observable } from 'rxjs'; -import { switchMap, map } from 'rxjs/operators'; import { ApiInterfaceRx } from '@polkadot/api/types'; -import { AccountId, Vector, Option, SetIndex } from '@polkadot/types'; +import { Observable } from 'rxjs'; +import { AccountId, Vector, SetIndex } from '@polkadot/types'; +import { map } from 'rxjs/operators'; import { drr } from '../util/drr'; +import { voterSets } from './voterSets'; -export function voters (api: ApiInterfaceRx): () => Observable[]> { - return (): Observable[]> => - api.query.elections.nextVoterSet().pipe( - switchMap((nextVoterSet: SetIndex): Observable>[]> => - api.query.elections.voters.multi>>( - [...Array(+nextVoterSet + 1)].map((_, i): number => i) - ) +/** + * @name voters + * @returns An array of all current voters from all sets. + * @example + *
+ * + * ```javascript + * api.derive.elections.voters((voters) => { + * console.log(`There are ${voters.length} current voters.`); + * }); + * ``` + */ +export function voters (api: ApiInterfaceRx): () => Observable> { + return (): Observable> => + voterSets(api)().pipe( + map( + (voterSets: Record): Vector => + new Vector( + AccountId, + Object.keys(voterSets).map( + address => new AccountId(address) + ) + ) ), - map((voters: Vector>[]): Option[] => - Array.prototype.concat.apply([], voters)), drr() ); } diff --git a/packages/api-derive/src/index.spec.ts b/packages/api-derive/src/index.spec.ts index b029b2cfd8..ed81d80289 100644 --- a/packages/api-derive/src/index.spec.ts +++ b/packages/api-derive/src/index.spec.ts @@ -54,6 +54,11 @@ describe.skip('derive', (): void => { testFunction(api)('democracy', 'referendumVotesFor', []); testFunction(api)('democracy', 'votes', []); + testFunction(api)('elections', 'approvalsOf', []); + testFunction(api)('elections', 'approvalsOfAt', []); + testFunction(api)('elections', 'info', []); + testFunction(api)('elections', 'voters', []); + testFunction(api)('session', 'eraLength', []); testFunction(api)('session', 'eraProgress', []); testFunction(api)('session', 'sessionProgress', []); diff --git a/packages/api-derive/src/types.ts b/packages/api-derive/src/types.ts index 29b8109f76..fa8ae466db 100644 --- a/packages/api-derive/src/types.ts +++ b/packages/api-derive/src/types.ts @@ -3,7 +3,7 @@ // of the Apache-2.0 license. See the LICENSE file for details. import BN from 'bn.js'; -import { AccountId, Balance, Exposure, Index, RewardDestination, StakingLedger, ValidatorPrefs, Vote } from '@polkadot/types'; +import { AccountId, Balance, BlockNumber, Exposure, Index, RewardDestination, SetIndex, StakingLedger, ValidatorPrefs, Vote, VoteIndex } from '@polkadot/types'; export interface DerivedBalances { accountId: AccountId; @@ -31,6 +31,16 @@ export interface DerivedContractFees { transferFee: BN; } +export interface DerivedElectionsInfo { + members: [AccountId, BlockNumber][]; + candidates: AccountId[]; + candidateCount: BN; + desiredSeats: BN; + termDuration: BlockNumber; + voteCount: VoteIndex; + voterCount: SetIndex; +} + export interface DerivedFees { creationFee: BN; existentialDeposit: BN; diff --git a/packages/api-derive/src/util/approvalFlagsToBools.ts b/packages/api-derive/src/util/approvalFlagsToBools.ts new file mode 100644 index 0000000000..b187781f5c --- /dev/null +++ b/packages/api-derive/src/util/approvalFlagsToBools.ts @@ -0,0 +1,16 @@ +// Copyright 2017-2019 @polkadot/api-derive authors & contributors +// This software may be modified and distributed under the terms +// of the Apache-2.0 license. See the LICENSE file for details. + +import { Vector, ApprovalFlag } from '@polkadot/types'; + +export function approvalFlagsToBools (flags: Vector): boolean[] { + const bools: boolean[] = []; + flags.forEach((flag: ApprovalFlag): void => { + const str = flag.toString(2); + for (const bit of str) { + bools.push(!!parseInt(bit, 10)); + } + }); + return bools; +} diff --git a/packages/api/test/e2e/api-derive/promise.spec.ts b/packages/api/test/e2e/api-derive/promise.spec.ts index a8443dc354..5c83644f28 100644 --- a/packages/api/test/e2e/api-derive/promise.spec.ts +++ b/packages/api/test/e2e/api-derive/promise.spec.ts @@ -9,7 +9,7 @@ import { RewardDestination } from '@polkadot/types'; import { SubmittableResult } from '@polkadot/api'; import { HeaderExtended } from '@polkadot/api-derive/type'; -import { DerivedBalances, DerivedFees, DerivedSessionInfo, DerivedStaking } from '@polkadot/api-derive/types'; +import { DerivedBalances, DerivedElectionsInfo, DerivedFees, DerivedSessionInfo, DerivedStaking } from '@polkadot/api-derive/types'; const ALICE_STASH = testingPairs().alice_stash.address; const WS = 'ws://127.0.0.1:9944/'; @@ -57,6 +57,14 @@ describe.skip('derive e2e', (): void => { }); }); + it('retrieves elections info', (done): Promise<() => void> => { + return api.derive.elections.info((info: DerivedElectionsInfo): void => { + console.error('fees', JSON.stringify(info)); + + done(); + }); + }); + it('retrieves the balances', (done): Promise<() => void> => { return api.derive.balances.all('5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y', (balance: DerivedBalances): void => { console.error(JSON.stringify(balance)); diff --git a/packages/api/test/e2e/api-derive/rx.spec.ts b/packages/api/test/e2e/api-derive/rx.spec.ts index 44e8112576..755bb2582c 100644 --- a/packages/api/test/e2e/api-derive/rx.spec.ts +++ b/packages/api/test/e2e/api-derive/rx.spec.ts @@ -6,7 +6,7 @@ import BN from 'bn.js'; import ApiRx from '@polkadot/api/rx/Api'; import { HeaderExtended } from '@polkadot/api-derive'; -import { DerivedBalances, DerivedContractFees, DerivedFees, DerivedSessionInfo } from '@polkadot/api-derive/types'; +import { DerivedBalances, DerivedContractFees, DerivedElectionsInfo, DerivedFees, DerivedSessionInfo } from '@polkadot/api-derive/types'; import { AccountId, AccountIndex, BlockNumber, Index } from '@polkadot/types'; import { WsProvider } from '@polkadot/rpc-provider'; @@ -225,6 +225,24 @@ describe.skip('Api-RX derive e2e', (): void => { }); }); + describe('derive.elections', (): void => { + describe('info', (): void => { + it('It returns an object with all relevant elections properties', async (done): Promise => { + api.derive.elections.info().subscribe((info: DerivedElectionsInfo): void => { + expect(info).toEqual(expect.objectContaining({ + members: expect.anything(), + candidates: expect.anything(), + candidateCount: expect.any(BN), + desiredSeats: expect.any(BN), + termDuration: expect.any(BN), + voteCount: expect.any(BN) + })); + done(); + }); + }); + }); + }); + describe('derive.session', (): void => { describe('sessionProgress', (): void => { it('derive.session.sessionProgress', async (done): Promise => {