Skip to content
Merged
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
211 changes: 151 additions & 60 deletions apps/staking/src/components/OracleIntegrityStaking/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ const PublisherList = ({
const scrollTarget = useRef<HTMLDivElement | null>(null);
const [search, setSearch] = useState("");
const [yoursFirst, setYoursFirst] = useState(true);
const [sort, setSort] = useState(SortOption.RemainingPoolDescending);
const [sort, setSort] = useState(SortOption.SelfStakeDescending);
const filter = useFilter({ sensitivity: "base", usage: "search" });
const [currentPage, setPage] = useState(1);
const collator = useCollator();
Expand All @@ -606,7 +606,7 @@ const PublisherList = ({
return 1;
}
}
return doSort(collator, a, b, yieldRate, sort);
return compare(collator, a, b, yieldRate, sort);
}),
[publishers, search, sort, filter, yieldRate, yoursFirst, collator],
);
Expand Down Expand Up @@ -654,8 +654,16 @@ const PublisherList = ({
[setYoursFirst, updatePage],
);

const updatePageSize = useCallback<typeof setPageSize>(
(newPageSize) => {
setPageSize(newPageSize);
updatePage(1);
},
[setPageSize, updatePage],
);

const numPages = useMemo(
() => Math.floor(filteredSortedPublishers.length / pageSize),
() => Math.ceil(filteredSortedPublishers.length / pageSize),
[filteredSortedPublishers, pageSize],
);

Expand Down Expand Up @@ -816,7 +824,7 @@ const PublisherList = ({
label="Page size"
options={PageSize}
selectedKey={pageSize}
onSelectionChange={setPageSize}
onSelectionChange={updatePageSize}
/>
<Paginator
currentPage={currentPage}
Expand Down Expand Up @@ -910,7 +918,7 @@ const getPageRange = (
return { first, count: Math.min(numPages - first + 1, 5) };
};

const doSort = (
const compare = (
collator: Intl.Collator,
a: PublisherProps["publisher"],
b: PublisherProps["publisher"],
Expand All @@ -920,80 +928,163 @@ const doSort = (
switch (sort) {
case SortOption.PublisherNameAscending:
case SortOption.PublisherNameDescending: {
const value = collator.compare(
a.name ?? a.publicKey.toBase58(),
b.name ?? b.publicKey.toBase58(),
// No need for a fallback sort since each publisher has a unique value.
return compareName(
collator,
a,
b,
sort === SortOption.PublisherNameAscending,
);
return sort === SortOption.PublisherNameAscending ? -1 * value : value;
}
case SortOption.ApyAscending:
case SortOption.ApyDescending: {
const value =
calculateApy({
isSelf: false,
selfStake: a.selfStake + a.selfStakeDelta,
poolCapacity: a.poolCapacity,
poolUtilization: a.poolUtilization + a.poolUtilizationDelta,
yieldRate,
}) -
calculateApy({
isSelf: false,
selfStake: b.selfStake + b.selfStakeDelta,
poolCapacity: b.poolCapacity,
poolUtilization: b.poolUtilization + b.poolUtilizationDelta,
yieldRate,
});
return sort === SortOption.ApyDescending ? -1 * value : value;
}
case SortOption.NumberOfFeedsAscending: {
return Number(a.numFeeds - b.numFeeds);
const ascending = sort === SortOption.ApyAscending;
return compareInOrder([
() => compareApy(a, b, yieldRate, ascending),
() => compareSelfStake(a, b, ascending),
() => comparePoolCapacity(a, b, ascending),
() => compareName(collator, a, b, ascending),
]);
}
case SortOption.NumberOfFeedsAscending:
case SortOption.NumberOfFeedsDescending: {
return Number(b.numFeeds - a.numFeeds);
const ascending = sort === SortOption.NumberOfFeedsAscending;
return compareInOrder([
() => (ascending ? -1 : 1) * Number(b.numFeeds - a.numFeeds),
() => compareSelfStake(a, b, ascending),
() => comparePoolCapacity(a, b, ascending),
() => compareApy(a, b, yieldRate, ascending),
() => compareName(collator, a, b, ascending),
]);
}
case SortOption.RemainingPoolAscending:
case SortOption.RemainingPoolDescending: {
if (a.poolCapacity === 0n && b.poolCapacity === 0n) {
return 0;
} else if (a.poolCapacity === 0n) {
return 1;
} else if (b.poolCapacity === 0n) {
return -1;
} else {
const remainingPoolA =
a.poolCapacity - a.poolUtilization - a.poolUtilizationDelta;
const remainingPoolB =
b.poolCapacity - b.poolUtilization - b.poolUtilizationDelta;
const value = Number(remainingPoolA - remainingPoolB);
return sort === SortOption.RemainingPoolDescending ? -1 * value : value;
}
const ascending = sort === SortOption.RemainingPoolAscending;
return compareInOrder([
() => comparePoolCapacity(a, b, ascending),
() => compareSelfStake(a, b, ascending),
() => compareApy(a, b, yieldRate, ascending),
() => compareName(collator, a, b, ascending),
]);
}
case SortOption.QualityRankingDescending:
case SortOption.QualityRankingAscending: {
if (a.qualityRanking === 0 && b.qualityRanking === 0) {
return 0;
} else if (a.qualityRanking === 0) {
return 1;
} else if (b.qualityRanking === 0) {
return -1;
} else {
const value = Number(a.qualityRanking - b.qualityRanking);
return sort === SortOption.QualityRankingAscending ? -1 * value : value;
}
}
case SortOption.SelfStakeAscending: {
return Number(
a.selfStake + a.selfStakeDelta - b.selfStake - b.selfStakeDelta,
// No need for a fallback sort since each publisher has a unique value.
return compareQualityRanking(
a,
b,
sort === SortOption.QualityRankingAscending,
);
}
case SortOption.SelfStakeAscending:
case SortOption.SelfStakeDescending: {
return Number(
b.selfStake + b.selfStakeDelta - a.selfStake - a.selfStakeDelta,
);
const ascending = sort === SortOption.SelfStakeAscending;
return compareInOrder([
() => compareSelfStake(a, b, ascending),
() => comparePoolCapacity(a, b, ascending),
() => compareApy(a, b, yieldRate, ascending),
() => compareName(collator, a, b, ascending),
]);
}
}
};

const compareInOrder = (comparisons: (() => number)[]): number => {
for (const compare of comparisons) {
const value = compare();
if (value !== 0) {
return value;
}
}
return 0;
};

const compareName = (
collator: Intl.Collator,
a: PublisherProps["publisher"],
b: PublisherProps["publisher"],
reverse?: boolean,
) =>
(reverse ? -1 : 1) *
collator.compare(
a.name ?? a.publicKey.toBase58(),
b.name ?? b.publicKey.toBase58(),
);

const compareApy = (
a: PublisherProps["publisher"],
b: PublisherProps["publisher"],
yieldRate: bigint,
reverse?: boolean,
) =>
(reverse ? -1 : 1) *
(calculateApy({
isSelf: false,
selfStake: b.selfStake + b.selfStakeDelta,
poolCapacity: b.poolCapacity,
poolUtilization: b.poolUtilization + b.poolUtilizationDelta,
yieldRate,
}) -
calculateApy({
isSelf: false,
selfStake: a.selfStake + a.selfStakeDelta,
poolCapacity: a.poolCapacity,
poolUtilization: a.poolUtilization + a.poolUtilizationDelta,
yieldRate,
}));

const comparePoolCapacity = (
a: PublisherProps["publisher"],
b: PublisherProps["publisher"],
reverse?: boolean,
) => {
if (a.poolCapacity === 0n && b.poolCapacity === 0n) {
return 0;
} else if (a.poolCapacity === 0n) {
return 1;
Copy link
Contributor

Choose a reason for hiding this comment

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

I think a weird case here is that pools with 0 capacity always appear at the end no matter what. (whether it's inverse or not)

Copy link
Contributor

@guibescos guibescos Oct 7, 2024

Choose a reason for hiding this comment

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

This is weird because when you inverse a sort you expect the bottom of the first to be the top of the latter

Copy link
Contributor

Choose a reason for hiding this comment

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

Why is it not okay to just return remainingPoolB - remainingPoolA

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah so this was actually intentional -- we had discussed just after launch and we basically never wanted to move drained pools to the top even if the sort was reversed to avoid people accidentally staking to an empty pool -- remember we were originally going to hide these entirely.

I'm cool with revisiting but I'd prefer to do it in a separate PR since this isn't anything new

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's also worth noting that sorting for pools that have zero capacity, or those that are over capacity, is not well defined in this sort.

This sort is specifically the amount of tokens remaining until the cap. In those cases, all such pools would be 0. So once you reach pools that have zero cap or are over-utilized, this sorting definition becomes nondeterministic.

As a result, reversing the sort order and adding these to the top is somewhat nonsensical.

We could revisit the definition of "sort by pool capacity" so that it isn't undefined in those cases but again I'd prefer to hold that for a separate change.

} else if (b.poolCapacity === 0n) {
return -1;
} else {
const remainingPoolA =
a.poolCapacity - a.poolUtilization - a.poolUtilizationDelta;
const remainingPoolB =
b.poolCapacity - b.poolUtilization - b.poolUtilizationDelta;
if (remainingPoolA <= 0n && remainingPoolB <= 0n) {
return 0;
} else if (remainingPoolA <= 0n && remainingPoolB > 0n) {
return 1;
} else if (remainingPoolB <= 0n && remainingPoolA > 0n) {
return -1;
} else {
return (reverse ? -1 : 1) * Number(remainingPoolB - remainingPoolA);
}
}
};

const compareQualityRanking = (
a: PublisherProps["publisher"],
b: PublisherProps["publisher"],
reverse?: boolean,
) => {
if (a.qualityRanking === 0 && b.qualityRanking === 0) {
return 0;
} else if (a.qualityRanking === 0) {
return 1;
} else if (b.qualityRanking === 0) {
return -1;
} else {
return (reverse ? -1 : 1) * Number(a.qualityRanking - b.qualityRanking);
}
};

const compareSelfStake = (
a: PublisherProps["publisher"],
b: PublisherProps["publisher"],
reverse?: boolean,
) =>
(reverse ? -1 : 1) *
Number(b.selfStake + b.selfStakeDelta - (a.selfStake + a.selfStakeDelta));

type SortablePublisherTableHeaderProps = Omit<
ComponentProps<typeof BaseButton>,
"children"
Expand Down
Loading