Adjust fund retrieval (#4949)
* Adjust fund retrieval * Naming * Adjust
This commit is contained in:
parent
ad5df51605
commit
b30c84de65
@ -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);
|
||||
|
||||
@ -22,7 +22,7 @@ interface Props {
|
||||
value: WinnerData;
|
||||
}
|
||||
|
||||
function WinRanges ({ auctionInfo, blockNumber, className = '', isFirst, isLatest, value: { accountId, paraId, range, value } }: Props): React.ReactElement<Props> {
|
||||
function WinRanges ({ auctionInfo, blockNumber, className = '', isFirst, isLatest, value: { accountId, firstSlot, lastSlot, paraId, value } }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@ -38,9 +38,9 @@ function WinRanges ({ auctionInfo, blockNumber, className = '', isFirst, isLates
|
||||
<td className='number'><h1>{formatNumber(paraId)}</h1></td>
|
||||
<td className='badge'><ParaLink id={paraId} /></td>
|
||||
<td className='address'><AddressMini value={accountId} /></td>
|
||||
<td className='all number'>{auctionInfo.leasePeriod && (
|
||||
<Digits value={`${formatNumber(auctionInfo.leasePeriod.addn(range[0]))} - ${formatNumber(auctionInfo.leasePeriod.addn(range[1]))}`} />
|
||||
)}</td>
|
||||
<td className='all number'>
|
||||
<Digits value={`${formatNumber(firstSlot)} - ${formatNumber(lastSlot)}`} />
|
||||
</td>
|
||||
<td className='number'><FormatBalance value={value} /></td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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<WinningData>): WinnerData[] {
|
||||
function extractWinners (auctionInfo: AuctionInfo, optData: Option<WinningData>): WinnerData[] {
|
||||
return optData.isNone
|
||||
? []
|
||||
: optData.unwrap().reduce<WinnerData[]>((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<WinningData>][]): Winning[] {
|
||||
function extractData (auctionInfo: AuctionInfo, values: [StorageKey<[BlockNumber]>, Option<WinningData>][]): 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<WinningData>, endBlock: BlockNumber, blockOffset: BN): Winning[] | null {
|
||||
const current = createWinning(endBlock, blockOffset, extractWinners(optCurrent));
|
||||
function mergeCurrent (auctionInfo: AuctionInfo, prev: Winning[] | null, optCurrent: Option<WinningData>, 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<WinningData>,
|
||||
return prev;
|
||||
}
|
||||
|
||||
function mergeFirst (prev: Winning[] | null, optFirstData: Option<WinningData>): Winning[] | null {
|
||||
function mergeFirst (auctionInfo: AuctionInfo, prev: Winning[] | null, optFirstData: Option<WinningData>): 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<Option<WinningData>>(blockOffset)
|
||||
.then((optCurrent) => setResult((prev) =>
|
||||
mergeCurrent(prev, optCurrent, endBlock, blockOffset)
|
||||
mergeCurrent(auctionInfo, prev, optCurrent, blockOffset)
|
||||
))
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
@ -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<ITuple<[AccountId, BalanceOf]>>[][]]): Lease[] =>
|
||||
paraIds.map((paraId, i): Lease => ({
|
||||
leases: leases[i]
|
||||
transform: ([[paraIds], leases]: [[ParaId[]], Option<ITuple<[AccountId, BalanceOf]>>[][]]): 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<ParaId[]>([]);
|
||||
const trigger = useEventTrigger([api.events.crowdloan.Created]);
|
||||
const campaigns = useCall<Campaign[]>(api.query.crowdloan.funds.multi, [paraIds], optFundMulti);
|
||||
const leases = useCall<Lease[]>(api.query.slots.leases.multi, [paraIds], optLeaseMulti);
|
||||
const [result, setResult] = useState<Result>(EMPTY);
|
||||
const leases = useCall<ParaId[]>(api.query.slots.leases.multi, [paraIds], optLeaseMulti);
|
||||
const [result, setResult] = useState<FundResult>(EMPTY);
|
||||
|
||||
// on event triggers, update the available paraIds
|
||||
useEffect((): void => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user