Add motions to bounty derive (#3016)

* Add proposals to BountyDerive

* Extract filterBountiesProposals method

* Apply review suggestions
This commit is contained in:
Ewa Kowalska
2021-01-05 10:37:45 +01:00
committed by GitHub
parent 970039775d
commit 0c5fc15935
6 changed files with 139 additions and 17 deletions
@@ -1,16 +1,20 @@
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiInterfaceRx } from '@polkadot/api/types';
import type { ApiInterfaceRx, SubmittableExtrinsic } from '@polkadot/api/types';
import type { Bytes, Option, StorageKey } from '@polkadot/types';
import type { Bounty, BountyIndex } from '@polkadot/types/interfaces';
import type { Bounty, BountyIndex, ProposalIndex } from '@polkadot/types/interfaces';
import type { Codec, InterfaceTypes } from '@polkadot/types/types';
import { ApiPromise } from '@polkadot/api';
import { DeriveCollectiveProposal } from '@polkadot/api-derive/types';
import { Proposal } from '@polkadot/types/interfaces';
import { of } from '@polkadot/x-rxjs';
import { BountyFactory } from '../../test/bountyFactory';
import { BytesFactory } from '../../test/bytesFactory';
import { createApiWithAugmentations } from '../../test/helpers';
import { ProposalFactory } from '../../test/proposalFactory';
import { bounties } from '.';
const DEFAULT_PROPOSER = '5C4hrfjw9DjXZTzV3MwzrrAr9P1MJhSrvWGWqi1eSuyUpnhM';
@@ -21,22 +25,62 @@ describe('bounties derive', () => {
let emptyOption: <T extends Codec> (typeName: keyof InterfaceTypes) => Option<T>;
let optionOf: <T extends Codec> (value: T)=> Option<T>;
let bountyIndex: (index: number) => BountyIndex;
let proposalIndex: (index: number) => ProposalIndex;
let bytes: (value: string) => Bytes;
let api: ApiPromise;
let createProposal: (method: SubmittableExtrinsic<'promise'>) => Proposal;
let defaultMockApi: ApiInterfaceRx;
beforeAll(() => {
const api = createApiWithAugmentations();
api = createApiWithAugmentations();
({ bountyIndex, defaultBounty, emptyOption, optionOf, storageKey } = new BountyFactory(api));
({ bytes } = new BytesFactory(api.registry));
({ createProposal, proposalIndex } = new ProposalFactory(api));
});
beforeEach(() => {
defaultMockApi = {
derive: {
council: {
proposals: () => of([] as DeriveCollectiveProposal[])
}
},
query: {
council: {
proposalCount: () => of(proposalIndex(2))
},
treasury: {
bounties: {
keys: () => of([storageKey(0), storageKey(1)]),
multi: () => of([optionOf(defaultBounty()), optionOf(defaultBounty())])
},
bountyCount: () => of(bountyIndex(2)),
bountyDescriptions: {
multi: () => of([
optionOf(bytes('make polkadot even better')),
optionOf(bytes('this will be totally ignored'))
])
}
}
},
tx: api.tx
} as unknown as ApiInterfaceRx;
});
it('creates storage key', function () {
expect(storageKey(194).args[0].eq(194)).toBe(true);
});
it('creates proposal', function () {
expect(createProposal(api.tx.balances.transfer('5EYCAe5ijiYfyeZ2JJCGq56LmPyNRAKzpG4QkoQkkQNB5e6Z', 1))).toBeTruthy();
});
it('combines bounties with descriptions', async () => {
const mockApi = {
...defaultMockApi,
query: {
...defaultMockApi.query,
treasury: {
bounties: {
keys: () => of([storageKey(0), storageKey(2), storageKey(3)]),
@@ -64,4 +108,32 @@ describe('bounties derive', () => {
expect(result[1].description).toEqual('');
expect(result[1].index.eq(3)).toBe(true);
});
it('returns motions', async () => {
const result = await bounties('', defaultMockApi)().toPromise();
expect(result).toHaveLength(2);
expect(result[0].proposals).toHaveLength(0);
expect(result[1].proposals).toHaveLength(0);
});
it('combines bounties with motions', async () => {
const mockApi = {
...defaultMockApi,
derive: {
council: {
proposals: () => of([
{ proposal: createProposal(api.tx.bounties.approveBounty(1)) },
{ proposal: createProposal(api.tx.treasury.approveProposal(1)) }] as DeriveCollectiveProposal[])
}
}
} as unknown as ApiInterfaceRx;
const result = await bounties('', mockApi)().toPromise();
expect(result).toHaveLength(2);
expect(result[0].proposals).toHaveLength(0);
expect(result[1].proposals).toHaveLength(1);
expect(result[1].proposals[0].proposal.method).toEqual('approveBounty');
});
});
+23 -13
View File
@@ -2,17 +2,19 @@
// SPDX-License-Identifier: Apache-2.0
import type { ApiInterfaceRx } from '@polkadot/api/types';
import type { DeriveBounties } from '@polkadot/api-derive/types';
import type { DeriveBounties, DeriveCollectiveProposal } from '@polkadot/api-derive/types';
import type { Bytes, Option } from '@polkadot/types';
import type { Bounty, BountyIndex } from '@polkadot/types/interfaces';
import type { Bounty, BountyIndex, ProposalIndex } from '@polkadot/types/interfaces';
import { memo } from '@polkadot/api-derive/util';
import { combineLatest, Observable, of } from '@polkadot/x-rxjs';
import { map, switchMap } from '@polkadot/x-rxjs/operators';
type Result = [Option<Bounty>[], Option<Bytes>[], BountyIndex[]];
import { filterBountiesProposals } from './helpers/filterBountyProposals';
function parseResult (api: ApiInterfaceRx, [maybeBounties, maybeDescriptions, ids]: Result): DeriveBounties {
type Result = [Option<Bounty>[], Option<Bytes>[], BountyIndex[], DeriveCollectiveProposal[]];
function parseResult ([maybeBounties, maybeDescriptions, ids, bountyProposals]: Result): DeriveBounties {
const bounties: DeriveBounties = [];
maybeBounties.forEach((bounty, index) => {
@@ -20,7 +22,8 @@ function parseResult (api: ApiInterfaceRx, [maybeBounties, maybeDescriptions, id
bounties.push({
bounty: bounty.unwrap(),
description: maybeDescriptions[index].unwrapOrDefault().toUtf8(),
index: ids[index]
index: ids[index],
proposals: bountyProposals.filter((bountyProposal) => ids[index].eq(bountyProposal.proposal.args[0]))
});
}
});
@@ -29,23 +32,30 @@ function parseResult (api: ApiInterfaceRx, [maybeBounties, maybeDescriptions, id
}
export function bounties (instanceId: string, api: ApiInterfaceRx): () => Observable<DeriveBounties> {
const bountyBase = api.query.bounties
? api.query.bounties
: api.query.treasury;
const bountyBase = api.query.bounties ? api.query.bounties : api.query.treasury;
return memo(instanceId, (): Observable<DeriveBounties> =>
bountyBase.bountyCount<BountyIndex>().pipe(
switchMap(() => bountyBase.bounties.keys()),
switchMap((keys): Observable<Result> => {
combineLatest([
bountyBase.bountyCount<BountyIndex>(),
api.query.council.proposalCount<ProposalIndex>()
]).pipe(
switchMap(() =>
combineLatest([
bountyBase.bounties.keys(),
api.derive.council.proposals()
])
),
switchMap(([keys, proposals]): Observable<Result> => {
const ids = keys.map(({ args: [id] }) => id as BountyIndex);
return combineLatest([
bountyBase.bounties.multi<Option<Bounty>>(ids),
bountyBase.bountyDescriptions.multi<Option<Bytes>>(ids),
of(ids)
of(ids),
of(filterBountiesProposals(api, proposals))
]);
}),
map(([maybeBounties, maybeDescriptions, ids]) => parseResult(api, [maybeBounties, maybeDescriptions, ids]))
map(parseResult)
)
);
}
@@ -0,0 +1,12 @@
// Copyright 2017-2021 @polkadot/api-derive authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ApiInterfaceRx } from '@polkadot/api/types';
import { DeriveCollectiveProposal } from '@polkadot/api-derive/types';
export function filterBountiesProposals (api: ApiInterfaceRx, allProposals: DeriveCollectiveProposal[]): DeriveCollectiveProposal[] {
const bountyTxBase = api.tx.bounties ? api.tx.bounties : api.tx.treasury;
const bountyProposalCalls = [bountyTxBase.approveBounty, bountyTxBase.closeBounty, bountyTxBase.proposeCurator, bountyTxBase.unassignCurator];
return allProposals.filter((proposal) => bountyProposalCalls.find((bountyCall) => bountyCall.is(proposal.proposal)));
}
+1
View File
@@ -132,6 +132,7 @@ export interface DeriveBounty {
bounty: Bounty;
description: string;
index: BountyIndex;
proposals: DeriveCollectiveProposal[];
}
export type DeriveBounties = DeriveBounty[];
+3 -1
View File
@@ -10,7 +10,9 @@ export function createApiWithAugmentations (): ApiPromise {
const registry = new TypeRegistry();
const metadata = new Metadata(registry, metaStatic);
const api = new ApiPromise({ provider: new WsProvider('ws://', false) });
registry.setMetadata(metadata);
const api = new ApiPromise({ provider: new WsProvider('ws://', false), registry });
api.injectMetadata(metadata, true);
@@ -0,0 +1,25 @@
// Copyright 2017-2021 @polkadot/api authors & contributors
// SPDX-License-Identifier: Apache-2.0
import { ApiPromise } from '@polkadot/api';
import { SubmittableExtrinsic } from '@polkadot/api/types';
import { Proposal, ProposalIndex } from '@polkadot/types/interfaces';
import { Registry } from '@polkadot/types/types';
export class ProposalFactory {
readonly #api: ApiPromise;
readonly #registry: Registry;
constructor (api: ApiPromise) {
this.#api = api;
this.#registry = this.#api.registry;
}
public createProposal = (method: SubmittableExtrinsic<'promise'>): Proposal => {
return this.#registry.createType('Proposal', method);
};
public proposalIndex = (index: number): ProposalIndex => {
return this.#registry.createType('ProposalIndex', index);
}
}