From b30c84de6537ae483b21d3b16bef04432f8d6b18 Mon Sep 17 00:00:00 2001 From: Jaco Greeff Date: Fri, 26 Mar 2021 15:18:53 +0100 Subject: [PATCH] Adjust fund retrieval (#4949) * Adjust fund retrieval * Naming * Adjust --- packages/page-parachains/src/Auctions/Bid.tsx | 30 +++-- .../page-parachains/src/Auctions/WinRange.tsx | 8 +- .../page-parachains/src/Auctions/types.ts | 3 +- .../src/Auctions/useWinningData.ts | 44 ++++---- .../page-parachains/src/Crowdloan/useFunds.ts | 105 ++++++++---------- 5 files changed, 97 insertions(+), 93 deletions(-) diff --git a/packages/page-parachains/src/Auctions/Bid.tsx b/packages/page-parachains/src/Auctions/Bid.tsx index d4bdb47c80..46c352f00d 100644 --- a/packages/page-parachains/src/Auctions/Bid.tsx +++ b/packages/page-parachains/src/Auctions/Bid.tsx @@ -24,6 +24,8 @@ interface Props { } interface Option { + firstSlot: number; + lastSlot: number; text: string; value: number; } @@ -39,19 +41,29 @@ function Bid ({ auctionInfo, className, lastWinners, ownedIds }: Props): React.R const [isOpen, toggleOpen] = useToggle(); const rangeOpts = useMemo( - (): Option[] => - auctionInfo.leasePeriod - ? RANGES.map(([first, last], value): Option => ({ - text: `${formatNumber(auctionInfo.leasePeriod?.addn(first))} - ${formatNumber(auctionInfo.leasePeriod?.addn(last))}`, - value - })) - : [], + (): Option[] => { + if (!auctionInfo.leasePeriod) { + return []; + } + + const leasePeriod = auctionInfo.leasePeriod.toNumber(); + + return RANGES.map(([first, last], value): Option => ({ + firstSlot: leasePeriod + first, + lastSlot: leasePeriod + last, + text: `${formatNumber(leasePeriod + first)} - ${formatNumber(leasePeriod + last)}`, + value + })); + }, [auctionInfo] ); const currentWinner = useMemo( - () => lastWinners && lastWinners.winners.find(({ range: [first, last] }) => RANGES[range][0] === first && RANGES[range][1] === last), - [lastWinners, range] + () => lastWinners && lastWinners.winners.find(({ firstSlot, lastSlot }) => + rangeOpts[range].firstSlot === firstSlot && + rangeOpts[range].lastSlot === lastSlot + ), + [lastWinners, range, rangeOpts] ); const isAmountLess = !!(amount && currentWinner) && amount.lte(currentWinner.value); diff --git a/packages/page-parachains/src/Auctions/WinRange.tsx b/packages/page-parachains/src/Auctions/WinRange.tsx index 21cd27a485..8df9bc5fc7 100644 --- a/packages/page-parachains/src/Auctions/WinRange.tsx +++ b/packages/page-parachains/src/Auctions/WinRange.tsx @@ -22,7 +22,7 @@ interface Props { value: WinnerData; } -function WinRanges ({ auctionInfo, blockNumber, className = '', isFirst, isLatest, value: { accountId, paraId, range, value } }: Props): React.ReactElement { +function WinRanges ({ auctionInfo, blockNumber, className = '', isFirst, isLatest, value: { accountId, firstSlot, lastSlot, paraId, value } }: Props): React.ReactElement { const { t } = useTranslation(); return ( @@ -38,9 +38,9 @@ function WinRanges ({ auctionInfo, blockNumber, className = '', isFirst, isLates

{formatNumber(paraId)}

- {auctionInfo.leasePeriod && ( - - )} + + + ); diff --git a/packages/page-parachains/src/Auctions/types.ts b/packages/page-parachains/src/Auctions/types.ts index 3c4a3d8466..93b4852e7a 100644 --- a/packages/page-parachains/src/Auctions/types.ts +++ b/packages/page-parachains/src/Auctions/types.ts @@ -6,8 +6,9 @@ import type { AccountId, BalanceOf, ParaId } from '@polkadot/types/interfaces'; export interface WinnerData { accountId: AccountId; + firstSlot: number; + lastSlot: number; paraId: ParaId; - range: [number, number]; value: BalanceOf; } diff --git a/packages/page-parachains/src/Auctions/useWinningData.ts b/packages/page-parachains/src/Auctions/useWinningData.ts index 98bca50380..7935a2dd34 100644 --- a/packages/page-parachains/src/Auctions/useWinningData.ts +++ b/packages/page-parachains/src/Auctions/useWinningData.ts @@ -21,28 +21,30 @@ function isNewWinners (a: WinnerData[], b: WinnerData[]): boolean { } function isNewOrdering (a: WinnerData[], b: WinnerData[]): boolean { - return a.length !== b.length || a.some(({ paraId, range }, index) => + return a.length !== b.length || a.some(({ firstSlot, lastSlot, paraId }, index) => !paraId.eq(b[index].paraId) || - range[0] !== b[index].range[0] || - range[1] !== b[index].range[1] + firstSlot !== b[index].firstSlot || + lastSlot !== b[index].lastSlot ); } -function extractWinners (optData: Option): WinnerData[] { +function extractWinners (auctionInfo: AuctionInfo, optData: Option): WinnerData[] { return optData.isNone ? [] : optData.unwrap().reduce((winners, optEntry, index): WinnerData[] => { if (optEntry.isSome) { const [accountId, paraId, value] = optEntry.unwrap(); + const period = auctionInfo.leasePeriod?.toNumber() || 0; + const [first, last] = RANGES[index]; - winners.push({ accountId, paraId, range: RANGES[index], value }); + winners.push({ accountId, firstSlot: first + period, lastSlot: last + period, paraId, value }); } return winners; }, []); } -function createWinning (endBlock: BlockNumber | null, blockOffset: BN | null | undefined, winners: WinnerData[]): Winning { +function createWinning ({ endBlock }: AuctionInfo, blockOffset: BN | null | undefined, winners: WinnerData[]): Winning { return { blockNumber: endBlock && blockOffset ? blockOffset.add(endBlock) @@ -53,24 +55,24 @@ function createWinning (endBlock: BlockNumber | null, blockOffset: BN | null | u }; } -function extractData (endBlock: BlockNumber | null, values: [StorageKey<[BlockNumber]>, Option][]): Winning[] { +function extractData (auctionInfo: AuctionInfo, values: [StorageKey<[BlockNumber]>, Option][]): Winning[] { return values .sort(([{ args: [a] }], [{ args: [b] }]) => a.cmp(b)) .reduce((all: Winning[], [{ args: [blockOffset] }, optData]): Winning[] => { - const winners = extractWinners(optData); + const winners = extractWinners(auctionInfo, optData); winners.length && ( all.length === 0 || isNewWinners(winners, all[all.length - 1].winners) - ) && all.push(createWinning(endBlock, blockOffset, winners)); + ) && all.push(createWinning(auctionInfo, blockOffset, winners)); return all; }, []) .reverse(); } -function mergeCurrent (prev: Winning[] | null, optCurrent: Option, endBlock: BlockNumber, blockOffset: BN): Winning[] | null { - const current = createWinning(endBlock, blockOffset, extractWinners(optCurrent)); +function mergeCurrent (auctionInfo: AuctionInfo, prev: Winning[] | null, optCurrent: Option, blockOffset: BN): Winning[] | null { + const current = createWinning(auctionInfo, blockOffset, extractWinners(auctionInfo, optCurrent)); if (current.winners.length) { if (!prev || !prev.length) { @@ -91,10 +93,10 @@ function mergeCurrent (prev: Winning[] | null, optCurrent: Option, return prev; } -function mergeFirst (prev: Winning[] | null, optFirstData: Option): Winning[] | null { +function mergeFirst (auctionInfo: AuctionInfo, prev: Winning[] | null, optFirstData: Option): Winning[] | null { if (prev && prev.length <= 1) { const updated: Winning[] = prev || []; - const firstEntry = createWinning(null, null, extractWinners(optFirstData)); + const firstEntry = createWinning(auctionInfo, null, extractWinners(auctionInfo, optFirstData)); if (!firstEntry.winners.length) { return updated; @@ -122,34 +124,32 @@ export default function useWinningData (auctionInfo: AuctionInfo): Winning[] | n // should be fired once, all entries as an initial round useEffect((): void => { allEntries && setResult( - extractData(auctionInfo.endBlock, allEntries) + extractData(auctionInfo, allEntries) ); }, [allEntries, auctionInfo]); // when block 0 changes, update (typically in non-ending-period, static otherwise) useEffect((): void => { optFirstData && setResult((prev) => - mergeFirst(prev, optFirstData) + mergeFirst(auctionInfo, prev, optFirstData) ); - }, [optFirstData]); + }, [auctionInfo, optFirstData]); // on a bid event, get the new entry (assuming the event really triggered, i.e. not just a block) // and add it to the list when not duplicated. Additionally we cleanup after ourselves when endBlock // gets cleared useEffect((): void => { - const { endBlock } = auctionInfo; - - if (!endBlock) { + if (!auctionInfo.endBlock) { setResult((prev) => prev && prev.length ? [] : prev); - } else if (bestNumber && bestNumber.gt(endBlock) && triggerRef.current !== trigger) { - const blockOffset = bestNumber.sub(endBlock).iadd(BN_ONE); + } else if (bestNumber && bestNumber.gt(auctionInfo.endBlock) && triggerRef.current !== trigger) { + const blockOffset = bestNumber.sub(auctionInfo.endBlock).iadd(BN_ONE); triggerRef.current = trigger; api.query.auctions ?.winning>(blockOffset) .then((optCurrent) => setResult((prev) => - mergeCurrent(prev, optCurrent, endBlock, blockOffset) + mergeCurrent(auctionInfo, prev, optCurrent, blockOffset) )) .catch(console.error); } diff --git a/packages/page-parachains/src/Crowdloan/useFunds.ts b/packages/page-parachains/src/Crowdloan/useFunds.ts index 9a654f81e9..108e881a1c 100644 --- a/packages/page-parachains/src/Crowdloan/useFunds.ts +++ b/packages/page-parachains/src/Crowdloan/useFunds.ts @@ -4,15 +4,15 @@ import type { Option } from '@polkadot/types'; import type { AccountId, BalanceOf, BlockNumber, FundInfo, ParaId } from '@polkadot/types/interfaces'; import type { ITuple } from '@polkadot/types/types'; -import type { Campaign, Lease } from './types'; +import type { Campaign } from './types'; import BN from 'bn.js'; import { useEffect, useState } from 'react'; import { useApi, useBestNumber, useCall, useEventTrigger } from '@polkadot/react-hooks'; -import { BN_ZERO, stringToU8a, u8aConcat, u8aEq } from '@polkadot/util'; +import { BN_ZERO, stringToU8a, u8aConcat } from '@polkadot/util'; -interface Result { +interface FundResult { activeCap: BN; activeRaised: BN; funds: Campaign[] | null; @@ -20,7 +20,7 @@ interface Result { totalRaised: BN; } -const EMPTY: Result = { +const EMPTY: FundResult = { activeCap: BN_ZERO, activeRaised: BN_ZERO, funds: null, @@ -29,55 +29,39 @@ const EMPTY: Result = { }; const PREFIX = stringToU8a('modlpy/cfund'); +const EMPTY_U8A = new Uint8Array(32); -function hasLease (paraId: ParaId, leases: Lease[]): boolean { - const info = leases.find((l) => paraId.eq(l.paraId)); +function isCrowdloadAccount (paraId: ParaId, accountId: AccountId): boolean { + return accountId.eq(u8aConcat(PREFIX, paraId.toU8a(), EMPTY_U8A).subarray(0, 32)); +} - if (info) { - const paraAccountId = u8aConcat(PREFIX, paraId.toU8a()); - - return info.leases.some(({ accountId }) => - u8aEq(paraAccountId, accountId.toU8a().slice(0, paraAccountId.length)) - ); - } - - return false; +function hasLease (paraId: ParaId, leased: ParaId[]): boolean { + return leased.some((l) => l.eq(paraId)); } // map into a campaign -function updateCampaign (bestNumber: BN, minContribution: BN, retirementPeriod: BN, data: Campaign, leases: Lease[]): Campaign { - if (!data.isEnded && bestNumber.gt(data.info.end)) { - data.isEnded = true; - } - - if (!data.isCapped && data.info.cap.sub(data.info.raised).lt(minContribution)) { - data.isCapped = true; - } - - if (!data.retireEnd) { - data.retireEnd = data.info.end.add(retirementPeriod); - } - - if (!data.isRetired && bestNumber.gt(data.retireEnd)) { - data.isRetired = true; - } - - if (!data.isWinner && hasLease(data.paraId, leases)) { - data.isWinner = true; - } +function updateFund (bestNumber: BN, minContribution: BN, retirementPeriod: 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 }, isCapped, isEnded, isRetired, isWinner, paraId }: Campaign, leases: Lease[]): boolean { - return (!isEnded && bestNumber.gt(end)) || - (!isCapped && cap.sub(raised).lt(minContribution)) || - (!isRetired && bestNumber.gt(end.add(retirementPeriod))) || - (!isWinner && hasLease(paraId, leases)); +function isFundUpdated (bestNumber: BlockNumber, minContribution: BN, retirementPeriod: BN, { info: { cap, end, raised }, paraId }: Campaign, leased: ParaId[], allPrev: FundResult): 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[], leases: Lease[], prev: Result): Result { +function createResult (bestNumber: BlockNumber, minContribution: BN, retirementPeriod: BN, funds: Campaign[], leased: ParaId[], prev: FundResult): FundResult { 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), @@ -90,10 +74,8 @@ function createResult (bestNumber: BlockNumber, minContribution: BN, retirementP const hasNewTotalRaised = !prev.totalRaised.eq(totalRaised); const hasChanged = !prev.funds || prev.funds.length !== funds.length || - hasNewActiveCap || hasNewActiveRaised || - hasNewTotalCap || hasNewTotalRaised || - funds.some((c) => isFundUpdated(bestNumber, minContribution, retirementPeriod, c, leases)) || - true; + hasNewActiveCap || hasNewActiveRaised || hasNewTotalCap || hasNewTotalRaised || + funds.some((c) => isFundUpdated(bestNumber, minContribution, retirementPeriod, c, leased, prev)); if (!hasChanged) { return prev; @@ -107,7 +89,7 @@ function createResult (bestNumber: BlockNumber, minContribution: BN, retirementP ? activeRaised : prev.activeRaised, funds: funds - .map((c) => updateCampaign(bestNumber, minContribution, retirementPeriod, c, leases)) + .map((c) => updateFund(bestNumber, minContribution, retirementPeriod, c, leased)) .sort((a, b) => a.isWinner !== b.isWinner ? a.isWinner @@ -141,31 +123,40 @@ const optFundMulti = { paraIds .map((paraId, i): [ParaId, FundInfo | null] => [paraId, optFunds[i].unwrapOr(null)]) .filter((v): v is [ParaId, FundInfo] => !!v[1]) - .map(([paraId, info]) => ({ info, key: paraId.toString(), paraId })) - .sort((a, b) => a.info.firstSlot.cmp(b.info.firstSlot) || a.info.lastSlot.cmp(b.info.lastSlot) || a.paraId.cmp(b.paraId)), + .map(([paraId, info]): Campaign => ({ + info, + key: paraId.toString(), + paraId + })) + .sort((a, b) => + a.info.end.cmp(b.info.end) || + a.info.firstSlot.cmp(b.info.firstSlot) || + a.info.lastSlot.cmp(b.info.lastSlot) || + a.paraId.cmp(b.paraId) + ), withParamsTransform: true }; const optLeaseMulti = { - transform: ([[paraIds], leases]: [[ParaId[]], Option>[][]]): Lease[] => - paraIds.map((paraId, i): Lease => ({ - leases: leases[i] + transform: ([[paraIds], leases]: [[ParaId[]], Option>[][]]): ParaId[] => + paraIds.filter((paraId, i) => + leases[i] .map((o) => o.unwrapOr(null)) .filter((v): v is ITuple<[AccountId, BalanceOf]> => !!v) - .map(([accountId, balance]) => ({ accountId, balance })), - paraId - })), + .filter(([accountId]) => isCrowdloadAccount(paraId, accountId)) + .length !== 0 + ), withParamsTransform: true }; -export default function useFunds (): Result { +export default function useFunds (): FundResult { const { api } = useApi(); const bestNumber = useBestNumber(); const [paraIds, setParaIds] = useState([]); const trigger = useEventTrigger([api.events.crowdloan.Created]); const campaigns = useCall(api.query.crowdloan.funds.multi, [paraIds], optFundMulti); - const leases = useCall(api.query.slots.leases.multi, [paraIds], optLeaseMulti); - const [result, setResult] = useState(EMPTY); + const leases = useCall(api.query.slots.leases.multi, [paraIds], optLeaseMulti); + const [result, setResult] = useState(EMPTY); // on event triggers, update the available paraIds useEffect((): void => {