Council propose/close with length & weights (#2807)
* Council propose/close with length & weights * Adjust for consistency * Retrieve weight via paymentInfo * Closing and voting in conjunction
This commit is contained in:
parent
82f8fd7576
commit
fbbc1dd7fd
@ -2,11 +2,11 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { Hash, Proposal, ProposalIndex } from '@polkadot/types/interfaces';
|
||||
import { Hash, Proposal, ProposalIndex, Weight } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Button, InputAddress, Modal, ProposedAction, TxButton } from '@polkadot/react-components';
|
||||
import { useToggle } from '@polkadot/react-hooks';
|
||||
import { useAccounts, useApi, useToggle } from '@polkadot/react-hooks';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
@ -20,8 +20,18 @@ interface Props {
|
||||
|
||||
function Close ({ hash, idNumber, isDisabled, members, proposal }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { allAccounts } = useAccounts();
|
||||
const [isOpen, toggleOpen] = useToggle();
|
||||
const [accountId, setAccountId] = useState<string | null>(null);
|
||||
const [proposalLength] = useState(proposal.encodedLength);
|
||||
const [proposalWeight, setProposalWeight] = useState<Weight | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
allAccounts.length && (async () => setProposalWeight(
|
||||
(await api.tx(proposal).paymentInfo(allAccounts[0])).weight
|
||||
))().catch(console.error);
|
||||
}, [api, allAccounts, proposal]);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -60,8 +70,13 @@ function Close ({ hash, idNumber, isDisabled, members, proposal }: Props): React
|
||||
<Modal.Actions onCancel={toggleOpen}>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
isDisabled={!proposalWeight}
|
||||
onStart={toggleOpen}
|
||||
params={[hash, idNumber]}
|
||||
params={
|
||||
api.tx.council.close.meta.args.length === 4
|
||||
? [hash, idNumber, proposalWeight, proposalLength]
|
||||
: [hash, idNumber]
|
||||
}
|
||||
tx='council.close'
|
||||
/>
|
||||
</Modal.Actions>
|
||||
|
||||
@ -2,10 +2,11 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountId, BlockNumber } from '@polkadot/types/interfaces';
|
||||
import { AccountId, BlockNumber, Votes } from '@polkadot/types/interfaces';
|
||||
import { DeriveCollectiveProposal } from '@polkadot/api-derive/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ApiPromise } from '@polkadot/api';
|
||||
import ProposalCell from '@polkadot/app-democracy/Overview/ProposalCell';
|
||||
import { LinkExternal } from '@polkadot/react-components';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
@ -13,7 +14,7 @@ import { BlockToTime } from '@polkadot/react-query';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import Close from './Close';
|
||||
import Votes from './Votes';
|
||||
import Voters from './Votes';
|
||||
import Voting from './Voting';
|
||||
|
||||
interface Props {
|
||||
@ -24,9 +25,44 @@ interface Props {
|
||||
prime: AccountId | null;
|
||||
}
|
||||
|
||||
interface Status {
|
||||
isCloseable: boolean;
|
||||
isVotable: boolean;
|
||||
}
|
||||
|
||||
function getStatus (api: ApiPromise, bestNumber: BlockNumber, votes: Votes): Status {
|
||||
if (!votes.end) {
|
||||
return {
|
||||
isCloseable: false,
|
||||
isVotable: true
|
||||
};
|
||||
}
|
||||
|
||||
const isEnd = bestNumber.gte(votes.end);
|
||||
|
||||
if (api.tx.council.close.meta.args.length === 2) {
|
||||
return {
|
||||
isCloseable: isEnd,
|
||||
isVotable: !isEnd
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
isCloseable: isEnd || votes.threshold.eq(votes.ayes.length),
|
||||
isVotable: !isEnd
|
||||
};
|
||||
}
|
||||
|
||||
function Motion ({ className = '', isMember, members, motion: { hash, proposal, votes }, prime }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const bestNumber = useCall<BlockNumber>(api.derive.chain.bestNumber, []);
|
||||
const [{ isCloseable, isVotable }, setStatus] = useState<Status>({ isCloseable: false, isVotable: false });
|
||||
|
||||
useEffect((): void => {
|
||||
bestNumber && votes && setStatus(
|
||||
getStatus(api, bestNumber, votes)
|
||||
);
|
||||
}, [api, bestNumber, votes]);
|
||||
|
||||
if (!votes) {
|
||||
return null;
|
||||
@ -52,31 +88,27 @@ function Motion ({ className = '', isMember, members, motion: { hash, proposal,
|
||||
</>
|
||||
)}
|
||||
</td>
|
||||
<Votes votes={ayes} />
|
||||
<Votes votes={nays} />
|
||||
<Voters votes={ayes} />
|
||||
<Voters votes={nays} />
|
||||
<td className='button'>
|
||||
{bestNumber && (
|
||||
// end may not be existing (older versions)
|
||||
!end || end.gt(bestNumber)
|
||||
? (
|
||||
<Voting
|
||||
hash={hash}
|
||||
idNumber={index}
|
||||
isDisabled={!isMember}
|
||||
members={members}
|
||||
prime={prime}
|
||||
proposal={proposal}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<Close
|
||||
hash={hash}
|
||||
idNumber={index}
|
||||
isDisabled={!isMember}
|
||||
members={members}
|
||||
proposal={proposal}
|
||||
/>
|
||||
)
|
||||
{isVotable && (
|
||||
<Voting
|
||||
hash={hash}
|
||||
idNumber={index}
|
||||
isDisabled={!isMember}
|
||||
members={members}
|
||||
prime={prime}
|
||||
proposal={proposal}
|
||||
/>
|
||||
)}
|
||||
{isCloseable && (
|
||||
<Close
|
||||
hash={hash}
|
||||
idNumber={index}
|
||||
isDisabled={!isMember}
|
||||
members={members}
|
||||
proposal={proposal}
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td className='mini'>
|
||||
|
||||
@ -21,13 +21,17 @@ interface HashState {
|
||||
hash?: string;
|
||||
isHashValid: boolean;
|
||||
}
|
||||
interface ProposalState {
|
||||
proposal?: SubmittableExtrinsic<'promise'> | null;
|
||||
proposalLength: number;
|
||||
}
|
||||
|
||||
function ProposeExternal ({ className = '', isMember, members }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [isVisible, toggleVisible] = useToggle();
|
||||
const [accountId, setAcountId] = useState<string | null>(null);
|
||||
const [proposal, setProposal] = useState<SubmittableExtrinsic<'promise'> | null>(null);
|
||||
const [{ proposal, proposalLength }, setProposal] = useState<ProposalState>({ proposalLength: 0 });
|
||||
const [{ hash, isHashValid }, setHash] = useState<HashState>({ hash: '', isHashValid: false });
|
||||
const threshold = Math.ceil((members.length || 0) * 0.5);
|
||||
|
||||
@ -38,9 +42,17 @@ function ProposeExternal ({ className = '', isMember, members }: Props): React.R
|
||||
|
||||
useEffect((): void => {
|
||||
if (isHashValid && hash) {
|
||||
setProposal(() => api.tx.democracy.externalProposeMajority(hash));
|
||||
const proposal = api.tx.democracy.externalProposeMajority(hash);
|
||||
|
||||
setProposal({
|
||||
proposal,
|
||||
proposalLength: proposal.encodedLength || 0
|
||||
});
|
||||
} else {
|
||||
setProposal(null);
|
||||
setProposal({
|
||||
proposal: null,
|
||||
proposalLength: 0
|
||||
});
|
||||
}
|
||||
}, [api, hash, isHashValid]);
|
||||
|
||||
@ -97,7 +109,11 @@ function ProposeExternal ({ className = '', isMember, members }: Props): React.R
|
||||
isPrimary
|
||||
label={t<string>('Propose')}
|
||||
onStart={toggleVisible}
|
||||
params={[threshold, proposal]}
|
||||
params={
|
||||
api.tx.council.propose.meta.args.length === 3
|
||||
? [threshold, proposal, proposalLength]
|
||||
: [threshold, proposal]
|
||||
}
|
||||
tx='council.propose'
|
||||
/>
|
||||
</Modal.Actions>
|
||||
|
||||
@ -21,12 +21,17 @@ interface Threshold {
|
||||
threshold?: BN;
|
||||
}
|
||||
|
||||
interface ProposalState {
|
||||
proposal?: SubmittableExtrinsic<'promise'> | null;
|
||||
proposalLength: number;
|
||||
}
|
||||
|
||||
function Propose ({ isMember, members }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { apiDefaultTxSudo } = useApi();
|
||||
const { api, apiDefaultTxSudo } = useApi();
|
||||
const [isOpen, toggleOpen] = useToggle();
|
||||
const [accountId, setAcountId] = useState<string | null>(null);
|
||||
const [method, setMethod] = useState<SubmittableExtrinsic<'promise'> | null | undefined>();
|
||||
const [{ proposal, proposalLength }, setProposal] = useState<ProposalState>({ proposalLength: 0 });
|
||||
const [{ isThresholdValid, threshold }, setThreshold] = useState<Threshold>({ isThresholdValid: false });
|
||||
|
||||
useEffect((): void => {
|
||||
@ -37,7 +42,10 @@ function Propose ({ isMember, members }: Props): React.ReactElement<Props> {
|
||||
}, [members]);
|
||||
|
||||
const _setMethod = useCallback(
|
||||
(method?: SubmittableExtrinsic<'promise'> | null) => setMethod(() => method),
|
||||
(proposal?: SubmittableExtrinsic<'promise'> | null) => setProposal({
|
||||
proposal,
|
||||
proposalLength: proposal?.encodedLength || 0
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
@ -110,9 +118,14 @@ function Propose ({ isMember, members }: Props): React.ReactElement<Props> {
|
||||
<Modal.Actions onCancel={toggleOpen}>
|
||||
<TxButton
|
||||
accountId={accountId}
|
||||
isDisabled={!method || !isThresholdValid}
|
||||
isDisabled={!proposal || !isThresholdValid}
|
||||
label={t<string>('Propose')}
|
||||
params={[threshold, method]}
|
||||
onStart={toggleOpen}
|
||||
params={
|
||||
api.tx.council.propose.meta.args.length === 3
|
||||
? [threshold, proposal, proposalLength]
|
||||
: [threshold, proposal]
|
||||
}
|
||||
tx='council.propose'
|
||||
/>
|
||||
</Modal.Actions>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user