derive.referendum extended info (#2086)

* derive.referendum extended info

* update

* util bump
This commit is contained in:
Jaco Greeff
2020-03-24 17:39:37 +01:00
committed by GitHub
parent d70930587c
commit 602bb1406b
19 changed files with 477 additions and 375 deletions
@@ -3,7 +3,4 @@
// of the Apache-2.0 license. See the LICENSE file for details.
export * from './proposals';
export * from './referendumInfo';
export * from './referendumInfos';
export * from './referendums';
export * from './referendumVotes';
@@ -1,25 +0,0 @@
// Copyright 2017-2020 @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 { ReferendumInfo, ReferendumInfoTo239 } from '@polkadot/types/interfaces';
import { DerivedReferendum } from '../types';
import BN from 'bn.js';
import { Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { ApiInterfaceRx } from '@polkadot/api/types';
import { Option } from '@polkadot/types';
import { memo } from '../util';
import { retrieveInfo } from './util';
export function referendumInfo (api: ApiInterfaceRx): (index: BN | number) => Observable<DerivedReferendum | null> {
return memo((index: BN | number): Observable<DerivedReferendum | null> =>
api.query.democracy.referendumInfoOf<Option<ReferendumInfo | ReferendumInfoTo239>>(index).pipe(
switchMap((info): Observable<DerivedReferendum | null> =>
retrieveInfo(api, index, info)
)
)
);
}
@@ -1,35 +0,0 @@
// Copyright 2017-2020 @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 { ReferendumInfo } from '@polkadot/types/interfaces/democracy';
import { DerivedReferendum } from '../types';
import BN from 'bn.js';
import { Observable, combineLatest, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { ApiInterfaceRx } from '@polkadot/api/types';
import { Option } from '@polkadot/types';
import { memo } from '../util';
import { retrieveInfo } from './util';
export function referendumInfos (api: ApiInterfaceRx): (ids?: (BN | number)[]) => Observable<DerivedReferendum[]> {
return memo((ids: (BN | number)[] = []): Observable<DerivedReferendum[]> =>
(
!ids?.length
? of([] as Option<ReferendumInfo>[])
: api.query.democracy.referendumInfoOf.multi<Option<ReferendumInfo>>(ids)
).pipe(
switchMap((infos): Observable<(DerivedReferendum | null)[]> =>
combineLatest(
ids.map((id, index): Observable<DerivedReferendum | null> =>
retrieveInfo(api, id, infos[index])
)
)
),
map((infos): DerivedReferendum[] =>
infos.filter((referendum): referendum is DerivedReferendum => !!referendum)
)
));
}
@@ -1,89 +0,0 @@
// Copyright 2017-2020 @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 { AccountId, Vote, Voting, VotingDirectVote } from '@polkadot/types/interfaces';
import BN from 'bn.js';
import { Observable, combineLatest, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { ApiInterfaceRx } from '@polkadot/api/types';
import { StorageKey, Vec } from '@polkadot/types';
import { DerivedBalancesAccount, DerivedReferendumVote } from '../types';
import { memo } from '../util';
function votesPrev (api: ApiInterfaceRx, referendumId: BN | number): Observable<DerivedReferendumVote[]> {
return api.query.democracy.votersFor<Vec<AccountId>>(referendumId).pipe(
switchMap((votersFor): Observable<[Vec<AccountId>, Vote[], DerivedBalancesAccount[]]> =>
combineLatest([
of(votersFor),
!votersFor || !votersFor.length
? of([] as Vote[])
: api.query.democracy.voteOf.multi<Vote>(
votersFor.map((accountId): [BN | number, AccountId] =>
[referendumId, accountId]
)
),
api.derive.balances.votingBalances(votersFor)
])
),
map(([votersFor, votes, balances]): DerivedReferendumVote[] =>
votersFor.map((accountId, index): DerivedReferendumVote => ({
accountId,
balance: balances[index].votingBalance || api.registry.createType('Balance'),
vote: votes[index] || api.registry.createType('Vote')
} as DerivedReferendumVote))
)
);
}
function mapVotes (referendumId: BN | number, allVoting: [StorageKey, Voting][]): DerivedReferendumVote[] {
return allVoting
.map(([key, voting]): [AccountId, Voting] => [
key.args[0] as AccountId,
voting
])
// FIXME We are ignoring delegated votes
.filter(([, voting]) => voting.isDirect)
.map(([accountId, voting]): [AccountId, VotingDirectVote[]] => [
accountId,
voting.asDirect.votes.filter(([idx]) => idx.eq(referendumId))
])
.filter(([, directVotes]) => !!directVotes.length)
.reduce((result: DerivedReferendumVote[], [accountId, votes]): DerivedReferendumVote[] =>
// FIXME We are ignoring split votes
votes.reduce((result: DerivedReferendumVote[], [, vote]): DerivedReferendumVote[] => {
if (vote.isStandard) {
result.push({
accountId,
...vote.asStandard
});
}
return result;
}, result), []
);
}
function votes (api: ApiInterfaceRx, referendumId: BN | number): Observable<DerivedReferendumVote[]> {
// Use referendumInfo as a trigger, entries are non-subscribable
return api.derive.democracy.referendumInfo(referendumId).pipe(
switchMap((info): Observable<[StorageKey, Voting][]> =>
info
? api.query.democracy.votingOf.entries<Voting>()
: of([])
),
map((allVoting): DerivedReferendumVote[] =>
mapVotes(referendumId, allVoting)
)
);
}
export function referendumVotes (api: ApiInterfaceRx): (referendumId: BN | number) => Observable<DerivedReferendumVote[]> {
return memo((referendumId: BN | number): Observable<DerivedReferendumVote[]> =>
api.query.democracy.votingOf
? votes(api, referendumId)
: votesPrev(api, referendumId)
);
}
+154 -18
View File
@@ -2,33 +2,169 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ReferendumIndex } from '@polkadot/types/interfaces/democracy';
import { DerivedReferendum } from '../types';
import { ApiInterfaceRx } from '@polkadot/api/types';
import { AccountId, Balance, ReferendumIndex, ReferendumInfo, ReferendumInfoTo239, ReferendumStatus, Vote, Voting, VotingDirectVote } from '@polkadot/types/interfaces';
import { DerivedBalancesAccount, DerivedReferendum, DerivedReferendumExt, DerivedReferendumVote } from '../types';
import BN from 'bn.js';
import { Observable, of } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { ApiInterfaceRx } from '@polkadot/api/types';
import { Observable, of, combineLatest } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
import { Option, Vec } from '@polkadot/types';
import { bnSqrt } from '@polkadot/util';
import { memo } from '../util';
import { PreImage } from './proposals';
import { calcVotes, getStatus } from './util';
export function referendums (api: ApiInterfaceRx): () => Observable<DerivedReferendum[]> {
return memo((): Observable<DerivedReferendum[]> =>
function votesPrev (api: ApiInterfaceRx, referendumId: BN): Observable<DerivedReferendumVote[]> {
return api.query.democracy.votersFor<Vec<AccountId>>(referendumId).pipe(
switchMap((votersFor): Observable<[Vec<AccountId>, Vote[], DerivedBalancesAccount[]]> =>
combineLatest([
of(votersFor),
!votersFor || !votersFor.length
? of([] as Vote[])
: api.query.democracy.voteOf.multi<Vote>(
votersFor.map((accountId): [BN | number, AccountId] =>
[referendumId, accountId]
)
),
api.derive.balances.votingBalances(votersFor)
])
),
map(([votersFor, votes, balances]): DerivedReferendumVote[] =>
votersFor.map((accountId, index): DerivedReferendumVote => ({
accountId,
balance: balances[index].votingBalance || api.registry.createType('Balance'),
vote: votes[index] || api.registry.createType('Vote')
} as DerivedReferendumVote))
)
);
}
function votesCurr (api: ApiInterfaceRx, referendumId: BN): Observable<DerivedReferendumVote[]> {
return api.query.democracy.votingOf.entries<Voting>().pipe(
map((allVoting): DerivedReferendumVote[] =>
allVoting
.map(([key, voting]): [AccountId, Voting] => [key.args[0] as AccountId, voting])
// FIXME We are ignoring delegated votes
.filter(([, voting]) => voting.isDirect)
.map(([accountId, voting]): [AccountId, VotingDirectVote[]] => [
accountId,
voting.asDirect.votes.filter(([idx]) => idx.eq(referendumId))
])
.filter(([, directVotes]) => !!directVotes.length)
.reduce((result: DerivedReferendumVote[], [accountId, votes]) =>
// FIXME We are ignoring split votes
votes.reduce((result: DerivedReferendumVote[], [, vote]): DerivedReferendumVote[] => {
if (vote.isStandard) {
result.push({
accountId,
...vote.asStandard
});
}
return result;
}, result), []
)
)
);
}
function constructInfo (api: ApiInterfaceRx, index: BN | number, status: ReferendumStatus | ReferendumInfoTo239, _preimage?: PreImage): DerivedReferendum | null {
const preImage = _preimage?.isSome
? _preimage.unwrap()
: null;
return {
index: api.registry.createType('PropIndex', index),
hash: status.proposalHash,
proposal: preImage
? api.registry.createType('Proposal', preImage[0].toU8a(true))
: undefined,
preimage: preImage
? {
at: preImage[3],
balance: preImage[2],
proposer: preImage[1]
}
: undefined,
status
};
}
function retrieveInfo (api: ApiInterfaceRx, index: BN | number, info: Option<ReferendumInfo | ReferendumInfoTo239>): Observable<DerivedReferendum | null> {
const status = getStatus(info);
return status
? api.query.democracy.preimages(status.proposalHash).pipe(
map((preimage?: PreImage): DerivedReferendum | null =>
constructInfo(api, index, status, preimage)
)
)
: of(null);
}
function referendumInfos (api: ApiInterfaceRx, ids: BN[]): Observable<DerivedReferendum[]> {
return api.query.democracy.referendumInfoOf.multi<Option<ReferendumInfo>>(ids).pipe(
switchMap((infos): Observable<(DerivedReferendum | null)[]> =>
combineLatest(
ids.map((id, index): Observable<DerivedReferendum | null> =>
retrieveInfo(api, id, infos[index])
)
)
),
map((infos) => infos.filter((referendum): referendum is DerivedReferendum => !!referendum))
);
}
function retrieveDerived (api: ApiInterfaceRx, totalIssuance: Balance, earliest: ReferendumIndex, referendumCount: ReferendumIndex): Observable<[Balance, DerivedReferendum[]]> {
return combineLatest([
of(totalIssuance),
referendumCount.gt(earliest)
? referendumInfos(api, [
...Array(referendumCount.sub(earliest).toNumber())
].map((_, i): BN => earliest.addn(i)))
: of([])
]);
}
function retrieveVotes (api: ApiInterfaceRx, totalIssuance: Balance, referendums: DerivedReferendum[]): Observable<[Balance, DerivedReferendum[], DerivedReferendumVote[][]]> {
return combineLatest([
of(totalIssuance),
of(referendums),
combineLatest(
referendums.map((referendum): Observable<DerivedReferendumVote[]> =>
api.query.democracy.votingOf
? votesCurr(api, referendum.index)
: votesPrev(api, referendum.index)
)
)
]);
}
export function referendums (api: ApiInterfaceRx): () => Observable<DerivedReferendumExt[]> {
return memo((): Observable<DerivedReferendumExt[]> =>
api.query.democracy?.lowestUnbaked
? api.queryMulti<[ReferendumIndex, ReferendumIndex]>([
? api.queryMulti<[Balance, ReferendumIndex, ReferendumIndex]>([
api.query.balances.totalIssuance,
api.query.democracy.lowestUnbaked,
api.query.democracy.referendumCount
]).pipe(
switchMap(([earliest, referendumCount]): Observable<DerivedReferendum[]> =>
referendumCount?.gt(earliest) && referendumCount?.gtn(0)
? api.derive.democracy.referendumInfos(
[...Array(referendumCount.sub(earliest).toNumber())].map((_, i): BN =>
earliest.addn(i)
)
)
: of([])
)
switchMap(([totalIssuance, earliest, referendumCount]) =>
retrieveDerived(api, totalIssuance, earliest, referendumCount)
),
switchMap(([totalIssuance, referendums]) =>
retrieveVotes(api, totalIssuance, referendums)
),
map(([totalIssuance, referendums, votes]) => {
const sqrtElectorate = bnSqrt(totalIssuance);
return referendums.map((referendum, index): DerivedReferendumExt => ({
...referendum,
...calcVotes(sqrtElectorate, referendum, votes[index])
}));
})
)
: of([] as DerivedReferendum[])
: of([] as DerivedReferendumExt[])
);
}
@@ -0,0 +1,57 @@
// Copyright 2017-2020 @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 { AccountId, Balance, BlockNumber, Hash, Proposal, PropIndex, ReferendumInfoTo239, ReferendumStatus, Vote } from '@polkadot/types/interfaces';
import BN from 'bn.js';
import { Vec } from '@polkadot/types';
export interface DeriveProposalPreImage {
at: BlockNumber;
balance: Balance;
proposer: AccountId;
}
export interface DeriveProposal {
balance?: Balance;
hash: Hash;
index: PropIndex;
preimage?: DeriveProposalPreImage;
proposal?: Proposal;
proposer: AccountId;
seconds: Vec<AccountId>;
}
export interface DerivedReferendum {
hash: Hash;
index: PropIndex;
preimage?: DeriveProposalPreImage;
proposal?: Proposal;
status: ReferendumStatus | ReferendumInfoTo239;
}
export interface DerivedReferendumVote {
accountId: AccountId;
balance: Balance;
vote: Vote;
}
export interface DerivedReferendumVoteState {
allAye: DerivedReferendumVote[];
allNay: DerivedReferendumVote[];
voteCount: number;
voteCountAye: number;
voteCountNay: number;
votedAye: BN;
votedNay: BN;
votedTotal: BN;
}
export interface DerivedReferendumVotes extends DerivedReferendumVoteState {
isPassing: boolean;
votes: DerivedReferendumVote[];
}
export interface DerivedReferendumExt extends DerivedReferendum, DerivedReferendumVotes {
}
+119 -43
View File
@@ -2,28 +2,138 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ReferendumInfo, ReferendumInfoTo239, ReferendumStatus } from '@polkadot/types/interfaces';
import { DerivedReferendum } from '../types';
import { PreImage } from './proposals';
import { ReferendumInfo, ReferendumInfoTo239, ReferendumStatus, Tally, VoteThreshold } from '@polkadot/types/interfaces';
import { DerivedReferendum, DerivedReferendumVote, DerivedReferendumVotes, DerivedReferendumVoteState } from '../types';
import BN from 'bn.js';
import { Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { ApiInterfaceRx } from '@polkadot/api/types';
import { Option } from '@polkadot/types';
import { bnSqrt } from '@polkadot/util';
function isOld (info: ReferendumInfo | ReferendumInfoTo239): info is ReferendumInfoTo239 {
function isOldInfo (info: ReferendumInfo | ReferendumInfoTo239): info is ReferendumInfoTo239 {
return !!(info as ReferendumInfoTo239).proposalHash;
}
function getStatus (info: Option<ReferendumInfo | ReferendumInfoTo239>): ReferendumStatus | ReferendumInfoTo239 | null {
function isCurrentStatus (status: ReferendumStatus | ReferendumInfoTo239): status is ReferendumStatus {
return !!(status as ReferendumStatus).tally;
}
export function compareRationals (n1: BN, d1: BN, n2: BN, d2: BN): boolean {
while (true) {
const q1 = n1.div(d1);
const q2 = n2.div(d2);
if (q1.lt(q2)) {
return true;
} else if (q2.lt(q1)) {
return false;
}
const r1 = n1.mod(d1);
const r2 = n2.mod(d2);
if (r2.isZero()) {
return false;
} else if (r1.isZero()) {
return true;
}
n1 = d2;
n2 = d1;
d1 = r2;
d2 = r1;
}
}
function isPassing (threshold: VoteThreshold, sqrtElectorate: BN, { votedAye, votedNay, votedTotal }: DerivedReferendumVoteState): boolean {
const sqrtVoters = bnSqrt(votedTotal);
return sqrtVoters.isZero()
? false
: threshold.isSimplemajority
? votedAye.gt(votedNay)
: threshold.isSupermajorityapproval
? compareRationals(votedNay, sqrtVoters, votedAye, sqrtElectorate)
: compareRationals(votedNay, sqrtElectorate, votedAye, sqrtVoters);
}
function calcVotesPrev (votesFor: DerivedReferendumVote[]): DerivedReferendumVoteState {
return votesFor.reduce((state: DerivedReferendumVoteState, derived): DerivedReferendumVoteState => {
const { balance, vote } = derived;
const isDefault = vote.conviction.index === 0;
const counted = balance
.muln(isDefault ? 1 : vote.conviction.index)
.divn(isDefault ? 10 : 1);
if (vote.isAye) {
state.allAye.push(derived);
state.voteCountAye++;
state.votedAye = state.votedAye.add(counted);
} else {
state.allNay.push(derived);
state.voteCountNay++;
state.votedNay = state.votedNay.add(counted);
}
state.voteCount++;
state.votedTotal = state.votedTotal.add(counted);
return state;
}, {
allAye: [],
allNay: [],
voteCount: 0,
voteCountAye: 0,
voteCountNay: 0,
votedAye: new BN(0),
votedNay: new BN(0),
votedTotal: new BN(0)
});
}
function calcVotesCurrent (tally: Tally, votes: DerivedReferendumVote[]): DerivedReferendumVoteState {
const allAye: DerivedReferendumVote[] = [];
const allNay: DerivedReferendumVote[] = [];
votes.forEach((derived): void => {
if (derived.vote.isAye) {
allAye.push(derived);
} else {
allNay.push(derived);
}
});
return {
allAye,
allNay,
voteCount: allAye.length + allNay.length,
voteCountAye: allAye.length,
voteCountNay: allNay.length,
votedAye: tally.ayes,
votedNay: tally.nays,
votedTotal: tally.turnout
};
}
export function calcVotes (sqrtElectorate: BN, referendum: DerivedReferendum, votes: DerivedReferendumVote[]): DerivedReferendumVotes {
const state = isCurrentStatus(referendum.status)
? calcVotesCurrent(referendum.status.tally, votes)
: calcVotesPrev(votes);
return {
...state,
isPassing: isPassing(referendum.status.threshold, sqrtElectorate, state),
votes
};
}
export function getStatus (info: Option<ReferendumInfo | ReferendumInfoTo239>): ReferendumStatus | ReferendumInfoTo239 | null {
if (info.isNone) {
return null;
}
const unwrapped = info.unwrap();
if (isOld(unwrapped)) {
if (isOldInfo(unwrapped)) {
return unwrapped;
} else if (unwrapped.isOngoing) {
return unwrapped.asOngoing;
@@ -32,37 +142,3 @@ function getStatus (info: Option<ReferendumInfo | ReferendumInfoTo239>): Referen
// done, we don't include it here... only currently active
return null;
}
function constructInfo (api: ApiInterfaceRx, index: BN | number, status: ReferendumStatus | ReferendumInfoTo239, _preimage?: PreImage): DerivedReferendum | null {
const preImage = _preimage?.isSome
? _preimage.unwrap()
: null;
return {
index: api.registry.createType('PropIndex', index),
hash: status.proposalHash,
proposal: preImage
? api.registry.createType('Proposal', preImage[0].toU8a(true))
: undefined,
preimage: preImage
? {
at: preImage[3],
balance: preImage[2],
proposer: preImage[1]
}
: undefined,
status
};
}
export function retrieveInfo (api: ApiInterfaceRx, index: BN | number, info: Option<ReferendumInfo | ReferendumInfoTo239>): Observable<DerivedReferendum | null> {
const status = getStatus(info);
return status
? api.query.democracy.preimages(status.proposalHash).pipe(
map((preimage?: PreImage): DerivedReferendum | null =>
constructInfo(api, index, status, preimage)
)
)
: of(null);
}