Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure evenly divisible thresholds are adjusted #6924

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions packages/apps-config/src/api/params/proposalThresholds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

import type { ApiPromise } from '@polkadot/api';

import { BN } from '@polkadot/util';

import { KULUPU_GENESIS, KUSAMA_GENESIS, POLKADOT_GENESIS } from '../constants.js';

// normal fast-track proposals
Expand Down Expand Up @@ -52,3 +54,20 @@ export function getSlashProposalThreshold (api: ApiPromise): number {
export function getTreasuryProposalThreshold (api: ApiPromise): number {
return TREASURY[api.genesisHash.toHex()] || TREASURY.default;
}

export function calcThreshold (members: unknown[], threshold: number): BN {
const frac = members.length * threshold;
const ceil = Math.ceil(frac);

return new BN(
Math.min(
members.length,
ceil + (
// for evenly divisible, adjust upwards
Math.floor(frac) === ceil
? 1
: 0
)
)
);
}
Comment on lines +58 to +73
Copy link
Contributor

@h4x3rotab h4x3rotab May 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function is identical to:

function calcThreshold (members: unknown[], threshold: number): BN {
  return new BN(
    Math.min(
      members.length,
      Math.floor(members.length * threshold) + 1
    )
  );
}

Tested with:

for (let len = 0; len < 10; len++) {
  for (let threshold = 0; threshold <= 1; threshold += 0.1) {
    // assert left(members, threshold) == right(members, threshold)
  }
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

import type { DeriveCollectiveProposal } from '@polkadot/api-derive/types';
import type { BountyIndex } from '@polkadot/types/interfaces';
import type { BN } from '@polkadot/util';

import React, { useEffect, useMemo, useRef, useState } from 'react';

import { getTreasuryProposalThreshold } from '@polkadot/apps-config';
import { calcThreshold, getTreasuryProposalThreshold } from '@polkadot/apps-config';
import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useCollectiveInstance, useCollectiveMembers, useToggle } from '@polkadot/react-hooks';
import { BN } from '@polkadot/util';

import { truncateTitle } from '../helpers/index.js';
import { useBounties } from '../hooks/index.js';
Expand All @@ -35,7 +35,7 @@ function BountyInitiateVoting ({ description, index, proposals }: Props): React.

useEffect((): void => {
members && setThreshold(
new BN(Math.ceil(members.length * getTreasuryProposalThreshold(api)))
calcThreshold(members, getTreasuryProposalThreshold(api))
);
}, [api, members]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@

import type { DeriveCollectiveProposal } from '@polkadot/api-derive/types';
import type { Balance, BountyIndex } from '@polkadot/types/interfaces';
import type { BN } from '@polkadot/util';

import React, { useEffect, useMemo, useState } from 'react';

import { getTreasuryProposalThreshold } from '@polkadot/apps-config';
import { calcThreshold, getTreasuryProposalThreshold } from '@polkadot/apps-config';
import { Button, InputAddress, InputBalance, MarkError, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useCollectiveInstance, useCollectiveMembers, useToggle } from '@polkadot/react-hooks';
import { BN } from '@polkadot/util';

import { truncateTitle } from '../helpers/index.js';
import { useBounties } from '../hooks/index.js';
Expand Down Expand Up @@ -39,7 +39,7 @@ function ProposeCuratorAction ({ description, index, proposals, value }: Props):

useEffect((): void => {
members && setThreshold(
new BN(Math.ceil(members.length * getTreasuryProposalThreshold(api)))
calcThreshold(members, getTreasuryProposalThreshold(api))
);
}, [api, members]);

Expand Down
6 changes: 3 additions & 3 deletions packages/page-bounties/src/BountyExtraActions/CloseBounty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
// SPDX-License-Identifier: Apache-2.0

import type { BountyIndex } from '@polkadot/types/interfaces';
import type { BN } from '@polkadot/util';

import React, { useEffect, useRef, useState } from 'react';

import { getTreasuryProposalThreshold } from '@polkadot/apps-config';
import { calcThreshold, getTreasuryProposalThreshold } from '@polkadot/apps-config';
import { InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useCollectiveInstance, useCollectiveMembers } from '@polkadot/react-hooks';
import { BN } from '@polkadot/util';

import { truncateTitle } from '../helpers/index.js';
import { useBounties } from '../hooks/index.js';
Expand All @@ -31,7 +31,7 @@ function CloseBounty ({ description, index, toggleOpen }: Props): React.ReactEle

useEffect((): void => {
members && setThreshold(
new BN(Math.ceil(members.length * getTreasuryProposalThreshold(api)))
calcThreshold(members, getTreasuryProposalThreshold(api))
);
}, [api, members]);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@

import type { SubmittableExtrinsicFunction } from '@polkadot/api/types';
import type { AccountId, BountyIndex } from '@polkadot/types/interfaces';
import type { BN } from '@polkadot/util';
import type { ValidUnassignCuratorAction } from '../types.js';

import React, { useEffect, useMemo, useState } from 'react';

import { getTreasuryProposalThreshold } from '@polkadot/apps-config';
import { calcThreshold, getTreasuryProposalThreshold } from '@polkadot/apps-config';
import { InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useAccounts, useApi, useCollectiveInstance, useCollectiveMembers } from '@polkadot/react-hooks';
import { BN } from '@polkadot/util';

import { truncateTitle } from '../helpers/index.js';
import { useBounties } from '../hooks/index.js';
Expand Down Expand Up @@ -46,7 +46,7 @@ function SlashCurator ({ action, curatorId, description, index, toggleOpen }: Pr

useEffect((): void => {
members && setThreshold(
new BN(Math.ceil(members.length * getTreasuryProposalThreshold(api)))
calcThreshold(members, getTreasuryProposalThreshold(api))
);
}, [api, members]);

Expand Down
7 changes: 5 additions & 2 deletions packages/page-council/src/Motions/ProposeExternal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { HexString } from '@polkadot/util/types';

import React, { useCallback, useEffect, useMemo, useState } from 'react';

import { getProposalThreshold } from '@polkadot/apps-config';
import { calcThreshold, getProposalThreshold } from '@polkadot/apps-config';
import { Button, Input, InputAddress, InputNumber, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useCollectiveInstance, usePreimage, useToggle } from '@polkadot/react-hooks';
import { BN_ZERO, isFunction, isHex } from '@polkadot/util';
Expand Down Expand Up @@ -47,7 +47,10 @@ function ProposeExternal ({ className = '', isMember, members }: Props): React.R
const modLocation = useCollectiveInstance('council');
const preimage = usePreimage(hash);

const threshold = Math.min(members.length, Math.ceil((members.length || 0) * getProposalThreshold(api)));
const threshold = useMemo(
() => calcThreshold(members || [], getProposalThreshold(api)),
[api, members]
);

const isCurrentPreimage = useMemo(
() => isFunction(api.tx.preimage?.notePreimage) && !isFunction(api.tx.democracy?.notePreimage),
Expand Down
7 changes: 4 additions & 3 deletions packages/page-council/src/Motions/ProposeMotion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@
// SPDX-License-Identifier: Apache-2.0

import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { BN } from '@polkadot/util';

import React, { useCallback, useEffect, useState } from 'react';

import { getProposalThreshold } from '@polkadot/apps-config';
import { calcThreshold, getProposalThreshold } from '@polkadot/apps-config';
import { Button, InputAddress, InputNumber, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useCollectiveInstance, useToggle } from '@polkadot/react-hooks';
import { Extrinsic } from '@polkadot/react-params';
import { BN, BN_ZERO } from '@polkadot/util';
import { BN_ZERO } from '@polkadot/util';

import { useTranslation } from '../translate.js';

Expand Down Expand Up @@ -40,7 +41,7 @@ function Propose ({ isMember, members }: Props): React.ReactElement<Props> | nul
useEffect((): void => {
members && setThreshold({
isThresholdValid: members.length !== 0,
threshold: new BN(Math.min(members.length, Math.ceil(members.length * getProposalThreshold(api))))
threshold: calcThreshold(members, getProposalThreshold(api))
});
}, [api, members]);

Expand Down
7 changes: 5 additions & 2 deletions packages/page-council/src/Motions/Slashing.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type { SubmittableExtrinsic } from '@polkadot/api/types';

import React, { useEffect, useMemo, useState } from 'react';

import { getSlashProposalThreshold } from '@polkadot/apps-config';
import { calcThreshold, getSlashProposalThreshold } from '@polkadot/apps-config';
import { Button, Dropdown, Input, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useAvailableSlashes, useCollectiveInstance, useToggle } from '@polkadot/react-hooks';

Expand Down Expand Up @@ -37,7 +37,10 @@ function Slashing ({ className = '', isMember, members }: Props): React.ReactEle
const [selectedEra, setSelectedEra] = useState(0);
const modLocation = useCollectiveInstance('council');

const threshold = Math.ceil((members.length || 0) * getSlashProposalThreshold(api));
const threshold = useMemo(
() => calcThreshold(members || [], getSlashProposalThreshold(api)),
[api, members]
);

const eras = useMemo(
() => (slashes || []).map(([era, slashes]): Option => ({
Expand Down
8 changes: 2 additions & 6 deletions packages/page-democracy/src/Overview/Fasttrack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { HexString } from '@polkadot/util/types';

import React, { useEffect, useMemo, useState } from 'react';

import { getFastTrackThreshold } from '@polkadot/apps-config';
import { calcThreshold, getFastTrackThreshold } from '@polkadot/apps-config';
import { Button, Input, InputAddress, InputNumber, Modal, Toggle, TxButton } from '@polkadot/react-components';
import { useApi, useCall, useCollectiveInstance, useToggle } from '@polkadot/react-hooks';
import { BN, isString } from '@polkadot/util';
Expand Down Expand Up @@ -43,11 +43,7 @@ function Fasttrack ({ imageHash, members, threshold }: Props): React.ReactElemen
const proposalCount = useCall<BN>(modLocation && api.query[modLocation].proposalCount);

const memberThreshold = useMemo(
() => new BN(
Math.ceil(
members.length * getFastTrackThreshold(api, !votingBlocks || api.consts.democracy.fastTrackVotingPeriod.lte(votingBlocks))
)
),
() => calcThreshold(members, getFastTrackThreshold(api, !votingBlocks || api.consts.democracy.fastTrackVotingPeriod.lte(votingBlocks))),
[api, members, votingBlocks]
);

Expand Down
3 changes: 2 additions & 1 deletion packages/page-staking/src/Slashes/Era.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// SPDX-License-Identifier: Apache-2.0

import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { BN } from '@polkadot/util';
import type { SlashEra } from './types.js';

import React, { useCallback, useRef, useState } from 'react';
Expand All @@ -17,7 +18,7 @@ import Summary from './Summary.js';
interface Props {
buttons: React.ReactNode;
councilId: string | null;
councilThreshold: number;
councilThreshold: BN;
slash: SlashEra;
}

Expand Down
9 changes: 6 additions & 3 deletions packages/page-staking/src/Slashes/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { Slash, SlashEra } from './types.js';

import React, { useMemo, useRef, useState } from 'react';

import { getSlashProposalThreshold } from '@polkadot/apps-config';
import { calcThreshold, getSlashProposalThreshold } from '@polkadot/apps-config';
import { Table, ToggleGroup } from '@polkadot/react-components';
import { useAccounts, useApi, useCollectiveMembers } from '@polkadot/react-hooks';
import { BN, BN_ONE, formatNumber } from '@polkadot/util';
Expand Down Expand Up @@ -116,6 +116,11 @@ function Slashes ({ ownStashes = [], slashes }: Props): React.ReactElement<Props
[allAccounts, members]
);

const councilThreshold = useMemo(
() => calcThreshold(members || [], getSlashProposalThreshold(api)),
[api, members]
);

const emptyHeader = useRef<[React.ReactNode?, string?, number?][]>([
[t<string>('unapplied'), 'start']
]);
Expand All @@ -129,8 +134,6 @@ function Slashes ({ ownStashes = [], slashes }: Props): React.ReactElement<Props
);
}

const councilThreshold = Math.ceil((members.length || 0) * getSlashProposalThreshold(api));

return (
<Era
buttons={
Expand Down
9 changes: 6 additions & 3 deletions packages/page-treasury/src/Overview/Council.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { ProposalIndex } from '@polkadot/types/interfaces';

import React, { useEffect, useRef, useState } from 'react';
import React, { useEffect, useMemo, useRef, useState } from 'react';

import { getTreasuryProposalThreshold } from '@polkadot/apps-config';
import { calcThreshold, getTreasuryProposalThreshold } from '@polkadot/apps-config';
import { Button, Dropdown, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useCollectiveInstance, useToggle } from '@polkadot/react-hooks';

Expand All @@ -32,7 +32,10 @@ function Council ({ id, isDisabled, members }: Props): React.ReactElement<Props>
const [{ proposal, proposalLength }, setProposal] = useState<ProposalState>(() => ({ proposalLength: 0 }));
const modCouncil = useCollectiveInstance('council');

const threshold = Math.ceil((members?.length || 0) * getTreasuryProposalThreshold(api));
const threshold = useMemo(
() => calcThreshold(members || [], getTreasuryProposalThreshold(api)),
[api, members]
);

const councilTypeOptRef = useRef([
{ text: t<string>('Acceptance proposal to council'), value: 'accept' },
Expand Down