Support for crowdloan refund (#4982)

* Support for crowdloan refund

* Cleanup ending checks

* Remove old comment

* Withdrawal type only on non-refund

* Additional events support

* Additional comments

* Update packages/page-parachains/src/Crowdloan/Fund.tsx
This commit is contained in:
Jaco Greeff 2021-04-06 16:27:13 +03:00 committed by GitHub
parent f1427d3ab0
commit d4e61d4f3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 63 additions and 83 deletions

View File

@ -43,14 +43,17 @@ function extractContributors (allAccounts: string[], keys: StorageKey[]): Contri
};
}
function Fund ({ bestNumber, className, isOngoing, leasePeriod, value: { childKey, info: { cap, depositor, end, firstSlot, lastSlot, raised, retiring }, isCapped, isEnded, isRetired, isWinner, paraId, retireEnd } }: Props): React.ReactElement<Props> {
function Fund ({ bestNumber, className, isOngoing, leasePeriod, value: { childKey, info: { cap, depositor, end, firstSlot, lastSlot, raised }, isCapped, isEnded, isWinner, paraId } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts, isAccount } = useAccounts();
const [{ myAccounts, uniqueKeys }, setContributors] = useState<Contributions>(NO_CONTRIB);
const trigger = useEventTrigger([api.events.crowdloan?.Contributed, api.events.crowdloan?.Withdrew], useCallback(
({ event: { data: [, fundIndex] } }: EventRecord) =>
(fundIndex as ParaId).eq(paraId),
const trigger = useEventTrigger([api.events.crowdloan?.Contributed, api.events.crowdloan?.Withdrew, api.events.crowdloan?.AllRefunded, api.events.crowdloan?.PartiallyRefunded], useCallback(
({ event: { data } }: EventRecord) =>
((data.length === 1
? data[0] // AllRefunded, PartiallyRefunded [ParaId]
: data[1] // Contributed, Withdrew [AccountId, ParaId, Balance]
) as ParaId).eq(paraId),
[paraId]
));
@ -69,22 +72,13 @@ function Fund ({ bestNumber, className, isOngoing, leasePeriod, value: { childKe
[depositor, isAccount]
);
const [blocksLeft, retiringLeft] = useMemo(
() => bestNumber
? [
end.gt(bestNumber)
? end.sub(bestNumber)
: null,
retireEnd?.gt(bestNumber)
? retireEnd.sub(bestNumber)
: null
]
: [null, null],
[bestNumber, end, retireEnd]
const blocksLeft = useMemo(
() => bestNumber && end.gt(bestNumber)
? end.sub(bestNumber)
: null,
[bestNumber, end]
);
// TODO Dissolve should look at retirement and the actual period
const percentage = useMemo(
() => cap.isZero()
? '100.00%'
@ -92,14 +86,14 @@ function Fund ({ bestNumber, className, isOngoing, leasePeriod, value: { childKe
[cap, raised]
);
const isLeaseOver = !blocksLeft && !!leasePeriod && (
const hasEnded = !blocksLeft && !!leasePeriod && (
isWinner
? leasePeriod.currentPeriod.gt(lastSlot)
: leasePeriod.currentPeriod.gt(firstSlot)
);
const canContribute = !!blocksLeft && isOngoing && !isCapped && !isWinner && retiring.isFalse;
const canDissolve = raised.isZero() || (retiring.isTrue && isLeaseOver);
const canWithdraw = !raised.isZero() && isLeaseOver;
const canContribute = isOngoing && !isCapped && !isWinner && !!blocksLeft;
const canDissolve = raised.isZero() || hasEnded;
const canWithdraw = !raised.isZero() && hasEnded;
return (
<tr className={className}>
@ -108,35 +102,17 @@ function Fund ({ bestNumber, className, isOngoing, leasePeriod, value: { childKe
<td className='media--800'>
{isWinner
? t<string>('Winner')
: isRetired
? retiring.isTrue
? t<string>('Retired')
: t<string>('Completed')
: retiring.isTrue
? t<string>('Retiring')
: blocksLeft
? isCapped
? t<string>('Capped')
: isOngoing
? t<string>('Active')
: t<string>('Past')
: t<string>('Ended')
: blocksLeft
? isCapped
? t<string>('Capped')
: isOngoing
? t<string>('Active')
: t<string>('Past')
: t<string>('Ended')
}
</td>
<td className='address media--1400'><AddressMini value={depositor} /></td>
{!isOngoing && (
<td className='all number together media--1000'>
{(isRetired || retiring.isTrue) && (
<>
{retiringLeft && (
<BlockToTime value={retiringLeft} />
)}
#{formatNumber(retireEnd)}
</>
)}
</td>
)}
<td className='all number together media--1000'>
<td className='all number together media--1100'>
{blocksLeft && (
<BlockToTime value={blocksLeft} />
)}

View File

@ -76,7 +76,6 @@ function Funds ({ bestNumber, className, leasePeriod, value }: Props): React.Rea
[t('completed'), 'start', 2],
[undefined, 'media--800'],
[undefined, 'media--1400'],
[t('retired'), 'media--1000'],
[t('ending'), 'media--1000'],
[t('leases')],
[t('raised')],

View File

@ -79,24 +79,38 @@ function Refund ({ allAccounts, className, myAccounts, paraId }: Props): React.R
value={accountId}
/>
</Modal.Columns>
<Modal.Columns hint={t<string>('The type of withdrawal, all accounts or only own accounts (if available)')}>
<Dropdown
label={t<string>('withdrawal type')}
onChange={setWithdrawType}
options={typeOptions}
value={withdrawType}
/>
</Modal.Columns>
{!api.tx.crowdloan.refund && (
<Modal.Columns hint={t<string>('The type of withdrawal, all accounts or only own accounts (if available)')}>
<Dropdown
label={t<string>('withdrawal type')}
onChange={setWithdrawType}
options={typeOptions}
value={withdrawType}
/>
</Modal.Columns>
)}
</Modal.Content>
<Modal.Actions onCancel={toggleOpen}>
<TxButton
accountId={accountId}
extrinsic={extrinsics}
icon='credit-card'
isDisabled={!extrinsics || !extrinsics.length || !accountId}
label={t<string>('Refund')}
onStart={toggleOpen}
/>
{api.tx.crowdloan.refund
? (
<TxButton
accountId={accountId}
icon='credit-card'
label={t<string>('Refund')}
onStart={toggleOpen}
tx={api.tx.crowdloan.refund}
/>
)
: (
<TxButton
accountId={accountId}
extrinsic={extrinsics}
icon='credit-card'
isDisabled={!extrinsics || !extrinsics.length || !accountId}
label={t<string>('Refund')}
onStart={toggleOpen}
/>
)}
</Modal.Actions>
</Modal>
)}

View File

@ -53,9 +53,7 @@ export interface Campaign extends WinnerData {
info: FundInfo;
isCapped?: boolean;
isEnded?: boolean;
isRetired?: boolean;
isWinner?: boolean;
retireEnd?: BN;
}
export interface LeasePeriod {

View File

@ -49,28 +49,25 @@ function createChildKey (trieIndex: TrieIndex): string {
}
// map into a campaign
function updateFund (bestNumber: BN, minContribution: BN, retirementPeriod: BN, data: Campaign, leased: ParaId[]): Campaign {
function updateFund (bestNumber: BN, minContribution: BN, data: Campaign, leased: ParaId[]): Campaign {
data.isCapped = data.info.cap.sub(data.info.raised).lt(minContribution);
data.isEnded = bestNumber.gt(data.info.end);
data.retireEnd = data.info.end.add(retirementPeriod);
data.isRetired = bestNumber.gt(data.retireEnd);
data.isWinner = hasLease(data.paraId, leased);
return data;
}
function isFundUpdated (bestNumber: BlockNumber, minContribution: BN, retirementPeriod: BN, { info: { cap, end, raised }, paraId }: Campaign, leased: ParaId[], allPrev: Campaigns): boolean {
function isFundUpdated (bestNumber: BlockNumber, minContribution: BN, { info: { cap, end, raised }, paraId }: Campaign, leased: ParaId[], allPrev: Campaigns): boolean {
const prev = allPrev.funds?.find((p) => p.paraId.eq(paraId));
return !prev ||
(!prev.isEnded && bestNumber.gt(end)) ||
(!prev.isCapped && cap.sub(raised).lt(minContribution)) ||
(!prev.isRetired && bestNumber.gt(end.add(retirementPeriod))) ||
(!prev.isWinner && hasLease(paraId, leased));
}
// compare the current campaigns against the previous, manually adding ending and calculating the new totals
function createResult (bestNumber: BlockNumber, minContribution: BN, retirementPeriod: BN, funds: Campaign[], leased: ParaId[], prev: Campaigns): Campaigns {
function createResult (bestNumber: BlockNumber, minContribution: BN, funds: Campaign[], leased: ParaId[], prev: Campaigns): Campaigns {
const [activeRaised, activeCap, totalRaised, totalCap] = funds.reduce(([ar, ac, tr, tc], { info: { cap, end, raised } }) => [
bestNumber.gt(end) ? ar : ar.iadd(raised),
bestNumber.gt(end) ? ac : ac.iadd(cap),
@ -84,7 +81,7 @@ function createResult (bestNumber: BlockNumber, minContribution: BN, retirementP
const hasChanged =
!prev.funds || prev.funds.length !== funds.length ||
hasNewActiveCap || hasNewActiveRaised || hasNewTotalCap || hasNewTotalRaised ||
funds.some((c) => isFundUpdated(bestNumber, minContribution, retirementPeriod, c, leased, prev));
funds.some((c) => isFundUpdated(bestNumber, minContribution, c, leased, prev));
if (!hasChanged) {
return prev;
@ -98,7 +95,7 @@ function createResult (bestNumber: BlockNumber, minContribution: BN, retirementP
? activeRaised
: prev.activeRaised,
funds: funds
.map((c) => updateFund(bestNumber, minContribution, retirementPeriod, c, leased))
.map((c) => updateFund(bestNumber, minContribution, c, leased))
.sort((a, b) =>
a.isWinner !== b.isWinner
? a.isWinner
@ -108,15 +105,11 @@ function createResult (bestNumber: BlockNumber, minContribution: BN, retirementP
? a.isCapped
? -1
: 1
: a.isRetired !== b.isRetired
? a.isRetired
: a.isEnded !== b.isEnded
? a.isEnded
? 1
: -1
: a.isEnded !== b.isEnded
? a.isEnded
? 1
: -1
: 0
: 0
),
totalCap: hasNewTotalCap
? totalCap
@ -187,7 +180,7 @@ export default function useFunds (): Campaigns {
// here we manually add the actual ending status and calculate the totals
useEffect((): void => {
bestNumber && campaigns && leases && setResult((prev) =>
createResult(bestNumber, api.consts.crowdloan.minContribution as BlockNumber, api.consts.crowdloan.retirementPeriod as BlockNumber, campaigns, leases, prev)
createResult(bestNumber, api.consts.crowdloan.minContribution as BlockNumber, campaigns, leases, prev)
);
}, [api, bestNumber, campaigns, leases]);