From 4653cc0d8418aebac0d16acc97868b707feb6eb2 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Mon, 9 Dec 2019 14:18:13 +0100 Subject: [PATCH] Add council & treasury derives (#1654) --- packages/api-derive/src/council/index.ts | 5 + packages/api-derive/src/council/proposals.ts | 48 ++++++++++ packages/api-derive/src/index.ts | 4 +- packages/api-derive/src/treasury/index.ts | 5 + packages/api-derive/src/treasury/proposals.ts | 94 +++++++++++++++++++ packages/api-derive/src/types.ts | 22 ++++- 6 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 packages/api-derive/src/council/index.ts create mode 100644 packages/api-derive/src/council/proposals.ts create mode 100644 packages/api-derive/src/treasury/index.ts create mode 100644 packages/api-derive/src/treasury/proposals.ts diff --git a/packages/api-derive/src/council/index.ts b/packages/api-derive/src/council/index.ts new file mode 100644 index 0000000000..b8ce486d01 --- /dev/null +++ b/packages/api-derive/src/council/index.ts @@ -0,0 +1,5 @@ +// 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. + +export * from './proposals'; diff --git a/packages/api-derive/src/council/proposals.ts b/packages/api-derive/src/council/proposals.ts new file mode 100644 index 0000000000..d54c088651 --- /dev/null +++ b/packages/api-derive/src/council/proposals.ts @@ -0,0 +1,48 @@ +// 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 { Hash, Proposal, Votes } from '@polkadot/types/interfaces'; +import { DerivedCouncilProposals } from '../types'; + +import { Observable, combineLatest, of } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { Option } from '@polkadot/types'; + +import { memo } from '../util'; + +function retrieveProposals (api: ApiInterfaceRx, hashes: Hash[]): Observable { + return combineLatest([ + api.query.council.proposalOf.multi>(hashes), + api.query.council.voting.multi>(hashes) + ]).pipe( + map(([proposals, votes]: [Option[], Option[]]): DerivedCouncilProposals => { + const result: DerivedCouncilProposals = []; + + proposals.forEach((proposalOpt, index): void => { + if (proposalOpt.isSome) { + result.push({ + hash: hashes[index], + proposal: proposalOpt.unwrap(), + votes: votes[index].unwrapOr(null) + }); + } + }); + + return result; + }) + ); +} + +export function proposals (api: ApiInterfaceRx): () => Observable { + return memo((): Observable => + api.query.council + ? api.query.council.proposals().pipe( + switchMap((proposals: Hash[]): Observable => + retrieveProposals(api, proposals) + ) + ) + : of([] as DerivedCouncilProposals) + ); +} diff --git a/packages/api-derive/src/index.ts b/packages/api-derive/src/index.ts index 0391d7496a..48ae1cc3d9 100644 --- a/packages/api-derive/src/index.ts +++ b/packages/api-derive/src/index.ts @@ -11,11 +11,13 @@ import * as accounts from './accounts'; import * as balances from './balances'; import * as chain from './chain'; import * as contracts from './contracts'; +import * as council from './council'; import * as democracy from './democracy'; import * as elections from './elections'; import * as imOnline from './imOnline'; import * as session from './session'; import * as staking from './staking'; +import * as treasury from './treasury'; export * from './type'; @@ -56,7 +58,7 @@ function injectFunctions (api: ApiInterfaceRx, allSections: AllSect }, {} as DeriveAllSections); } -export const derive = { accounts, balances, chain, contracts, democracy, elections, imOnline, session, staking }; +export const derive = { accounts, balances, chain, contracts, council, democracy, elections, imOnline, session, staking, treasury }; export type ExactDerive = DeriveAllSections; // FIXME The return type of this function should be {...ExactDerive, ...DeriveCustom} diff --git a/packages/api-derive/src/treasury/index.ts b/packages/api-derive/src/treasury/index.ts new file mode 100644 index 0000000000..b8ce486d01 --- /dev/null +++ b/packages/api-derive/src/treasury/index.ts @@ -0,0 +1,5 @@ +// 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. + +export * from './proposals'; diff --git a/packages/api-derive/src/treasury/proposals.ts b/packages/api-derive/src/treasury/proposals.ts new file mode 100644 index 0000000000..ef6618fe38 --- /dev/null +++ b/packages/api-derive/src/treasury/proposals.ts @@ -0,0 +1,94 @@ +// 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 { ProposalIndex, TreasuryProposal } from '@polkadot/types/interfaces'; +import { DerivedCouncilProposals, DerivedTreasuryProposal, DerivedTreasuryProposals } from '../types'; + +import { Observable, combineLatest, of } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { createType, Option } from '@polkadot/types'; + +import { memo } from '../util'; + +interface Result { + allIds: ProposalIndex[]; + allProposals: Option[]; + approvalIds: ProposalIndex[]; + councilProposals: DerivedCouncilProposals; + proposalCount: ProposalIndex; +} + +function parseResult (_api: ApiInterfaceRx, { allIds, allProposals, approvalIds, councilProposals, proposalCount }: Result): DerivedTreasuryProposals { + const approvals: DerivedTreasuryProposal[] = []; + const proposals: DerivedTreasuryProposal[] = []; + const councilTreasury = councilProposals.filter(({ proposal: { methodName, sectionName } }): boolean => + sectionName === 'treasury' && + ['approveProposal', 'rejectProposal'].includes(methodName) + ); + + allIds.forEach((id, index): void => { + if (allProposals[index].isSome) { + const council = councilTreasury + .filter(({ proposal }): boolean => id.eq(proposal.args[0])) + .sort((a, b): number => a.proposal.methodName.localeCompare(b.proposal.methodName)); + const isApproval = approvalIds.some((approvalId): boolean => approvalId.eq(id)); + const derived = { council, id, proposal: allProposals[index].unwrap() }; + + if (isApproval) { + approvals.push(derived); + } else { + proposals.push(derived); + } + } + }); + + return { approvals, proposalCount, proposals }; +} + +function retrieveProposals (api: ApiInterfaceRx, proposalCount: ProposalIndex, approvalIds: ProposalIndex[]): Observable { + const proposalIds: ProposalIndex[] = []; + const count = proposalCount.toNumber(); + + for (let index = 0; index < count; index++) { + const isApproval = approvalIds.some((id): boolean => id.eqn(index)); + + if (!isApproval) { + proposalIds.push(createType(api.registry, 'ProposalIndex', index)); + } + } + + const allIds = [...proposalIds, ...approvalIds]; + + return combineLatest([ + api.query.treasury.proposals.multi>(allIds), + api.derive.council.proposals() + ]).pipe( + map(([allProposals, councilProposals]: [Option[], DerivedCouncilProposals]): DerivedTreasuryProposals => + parseResult(api, { allIds, allProposals, approvalIds, councilProposals, proposalCount }) + ) + ); +} + +/** + * @description Retrieve all active and approved treasury proposals, along with their info + */ +export function proposals (api: ApiInterfaceRx): () => Observable { + return memo((): Observable => + api.query.treasury + ? combineLatest([ + api.query.treasury.proposalCount(), + api.query.treasury.approvals() + ]).pipe( + switchMap(([proposalCount, approvalIds]: [ProposalIndex, ProposalIndex[]]) => + retrieveProposals(api, proposalCount, approvalIds) + ) + ) + : of({ + approvals: [], + proposalCount: createType(api.registry, 'ProposalIndex'), + proposals: [] + } as DerivedTreasuryProposals) + ); +} diff --git a/packages/api-derive/src/types.ts b/packages/api-derive/src/types.ts index 45463aebb1..9b8acd0d4e 100644 --- a/packages/api-derive/src/types.ts +++ b/packages/api-derive/src/types.ts @@ -2,7 +2,7 @@ // This software may be modified and distributed under the terms // of the Apache-2.0 license. See the LICENSE file for details. -import { AccountId, AccountIndex, Balance, BalanceLock, BlockNumber, EraIndex, EraPoints, Exposure, Hash, Index, Keys, Proposal, PropIndex, ReferendumInfo, RewardDestination, SessionIndex, SetIndex, StakingLedger, ValidatorPrefs, Vote, VoteIndex } from '@polkadot/types/interfaces'; +import { AccountId, AccountIndex, Balance, BalanceLock, BlockNumber, EraIndex, EraPoints, Exposure, Hash, Index, Keys, Proposal, PropIndex, ProposalIndex, ReferendumInfo, RewardDestination, SessionIndex, SetIndex, StakingLedger, TreasuryProposal, ValidatorPrefs, Vote, Votes, VoteIndex } from '@polkadot/types/interfaces'; import BN from 'bn.js'; import { u32, Vec } from '@polkadot/types'; @@ -44,6 +44,14 @@ export interface DerivedContractFees { transferFee: BN; } +export interface DerivedCouncilProposal { + hash: Hash; + proposal: Proposal; + votes: Votes | null; +} + +export type DerivedCouncilProposals = DerivedCouncilProposal[]; + export interface DerivedElectionsInfo { candidates: AccountId[]; candidateCount: u32; @@ -164,6 +172,18 @@ export interface DerivedStakingOverview extends DeriveSessionIndexes { validators: AccountId[]; } +export interface DerivedTreasuryProposal { + council: DerivedCouncilProposal[]; + id: ProposalIndex; + proposal: TreasuryProposal; +} + +export interface DerivedTreasuryProposals { + approvals: DerivedTreasuryProposal[]; + proposalCount: ProposalIndex; + proposals: DerivedTreasuryProposal[]; +} + export type DerivedUnlocking = { remainingBlocks: BlockNumber; value: Balance;