Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

disable stake when eoi fp is slashed #639

Merged
merged 4 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
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
22 changes: 22 additions & 0 deletions src/app/hooks/services/useDelegationService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useCallback, useMemo, useState } from "react";
import { DELEGATION_ACTIONS as ACTIONS } from "@/app/constants";
import { useAppState } from "@/app/state";
import { useDelegationV2State } from "@/app/state/DelegationV2State";
import { useFinalityProviderState } from "@/app/state/FinalityProviderState";
import {
DelegationV2,
DelegationV2StakingState as State,
Expand Down Expand Up @@ -69,6 +70,9 @@ export function useDelegationService() {
submitSlashingWithdrawalTx,
} = useTransactionService();

const { getFinalityProvider, getSlashedFinalityProvider } =
useFinalityProviderState();

const validations = useMemo(
() =>
delegations.reduce(
Expand All @@ -92,6 +96,23 @@ export function useDelegationService() {
[confirmationModal, processingDelegations],
);

const slashedStatuses = useMemo(
() =>
delegations.reduce(
(acc, delegation) => ({
...acc,
[delegation.stakingTxHashHex]: {
isSlashed:
getSlashedFinalityProvider(
delegation.finalityProviderBtcPksHex[0],
) !== null,
},
}),
{} as Record<string, { isSlashed: boolean }>,
),
[delegations, getFinalityProvider],
);

const COMMANDS: Record<ActionType, DelegationCommand> = useMemo(
() => ({
[ACTIONS.STAKE]: async ({
Expand Down Expand Up @@ -318,5 +339,6 @@ export function useDelegationService() {
closeConfirmationModal,
fetchMoreDelegations,
executeDelegationAction,
slashedStatuses,
};
}
10 changes: 10 additions & 0 deletions src/app/state/FinalityProviderState.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ interface FinalityProviderState {
getFinalityProvider: (btcPkHex: string) => FinalityProvider | null;
fetchNextPage: () => void;
getFinalityProviderName: (btcPkHex: string) => string | undefined;
getSlashedFinalityProvider: (btcPkHex: string) => FinalityProvider | null;
jeremy-babylonlabs marked this conversation as resolved.
Show resolved Hide resolved
}

const SORT_DIRECTIONS = {
Expand Down Expand Up @@ -72,6 +73,7 @@ const defaultState: FinalityProviderState = {
getFinalityProvider: () => null,
fetchNextPage: () => {},
getFinalityProviderName: () => undefined,
getSlashedFinalityProvider: () => null,
};

const { StateProvider, useState: useFpState } =
Expand Down Expand Up @@ -169,6 +171,12 @@ export function FinalityProviderState({ children }: PropsWithChildren) {
[data?.finalityProviders],
);

const getSlashedFinalityProvider = useCallback(
(btcPkHex: string) =>
data?.finalityProviders.find((fp) => fp.btcPk === btcPkHex) || null,
[data?.finalityProviders],
);

const state = useMemo(
() => ({
filter,
Expand All @@ -182,6 +190,7 @@ export function FinalityProviderState({ children }: PropsWithChildren) {
getFinalityProvider,
fetchNextPage,
getFinalityProviderName,
getSlashedFinalityProvider,
}),
[
filter,
Expand All @@ -195,6 +204,7 @@ export function FinalityProviderState({ children }: PropsWithChildren) {
getFinalityProvider,
fetchNextPage,
getFinalityProviderName,
getSlashedFinalityProvider,
],
);

Expand Down
53 changes: 36 additions & 17 deletions src/components/common/Hint/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ type HintStatus = "default" | "warning" | "error";
interface HintProps {
tooltip?: ReactNode;
status?: HintStatus;
/** Attach tooltip to children instead of showing separate icon */
attachToChildren?: boolean;
}

const STATUS_COLORS = {
Expand All @@ -26,21 +28,37 @@ export function Hint({
children,
tooltip,
status = "default",
attachToChildren = false,
}: PropsWithChildren<HintProps>) {
const id = useId();
const statusColor = STATUS_COLORS[status];

if (!tooltip) {
return (
<div className={twJoin("inline-flex items-center gap-1", statusColor)}>
{children}
</div>
);
}

return (
<div
className={twJoin(
"inline-flex items-center gap-1",
STATUS_COLORS[status],
)}
>
{children && <p>{children}</p>}
{tooltip && (
<div className={twJoin("inline-flex items-center gap-1", statusColor)}>
{attachToChildren ? (
<span
className="cursor-pointer"
data-tooltip-id={id}
data-tooltip-content={
typeof tooltip === "string" ? tooltip : undefined
}
data-tooltip-place="top"
>
{children}
</span>
) : (
<>
{children}
<span
className={twJoin("cursor-pointer text-xs")}
className={twJoin("cursor-pointer text-xs", statusColor)}
data-tooltip-id={id}
data-tooltip-content={
typeof tooltip === "string" ? tooltip : undefined
Expand All @@ -49,16 +67,17 @@ export function Hint({
>
<AiOutlineInfoCircle size={16} className={ICON_COLOR[status]} />
</span>
<Tooltip
id={id}
className="tooltip-wrap"
openOnClick={false}
clickable={true}
>
{typeof tooltip !== "string" && tooltip}
</Tooltip>
</>
)}

<Tooltip
id={id}
className="tooltip-wrap"
openOnClick={false}
clickable={true}
>
{typeof tooltip !== "string" && tooltip}
</Tooltip>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { Button } from "@babylonlabs-io/bbn-core-ui";
import { useId } from "react";
import { Tooltip } from "react-tooltip";

import { DELEGATION_ACTIONS as ACTIONS } from "@/app/constants";
import { ActionType } from "@/app/hooks/services/useDelegationService";
import {
DelegationV2,
DelegationV2StakingState as State,
} from "@/app/types/delegationsV2";
import { Hint } from "@/components/common/Hint";

interface ActionButtonProps {
disabled?: boolean;
tooltip?: string;
tooltip?: string | JSX.Element;
delegation: DelegationV2;
state: string;
onClick?: (action: ActionType, delegation: DelegationV2) => void;
Expand Down Expand Up @@ -48,18 +47,12 @@ const ACTION_BUTTON_PROPS: Record<
};

export function ActionButton(props: ActionButtonProps) {
const tooltipId = useId();
const buttonProps = ACTION_BUTTON_PROPS[props.state];

if (!buttonProps) return null;

return (
<span
className="cursor-pointer"
data-tooltip-id={tooltipId}
data-tooltip-content={props.tooltip}
data-tooltip-place="top"
>
<Hint tooltip={props.tooltip} attachToChildren={true}>
<Button
variant="outlined"
size="small"
Expand All @@ -68,8 +61,6 @@ export function ActionButton(props: ActionButtonProps) {
>
{buttonProps.title}
</Button>

<Tooltip id={tooltipId} className="tooltip-wrap" />
</span>
</Hint>
);
}
37 changes: 33 additions & 4 deletions src/components/delegations/DelegationList/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { Card, Heading } from "@babylonlabs-io/bbn-core-ui";
import Link from "next/link";

import { DOCUMENTATION_LINKS } from "@/app/constants";
import {
ActionType,
useDelegationService,
Expand All @@ -19,6 +21,7 @@ import { TxHash } from "./components/TxHash";
type TableParams = {
validations: Record<string, { valid: boolean; error?: string }>;
handleActionClick: (action: ActionType, delegation: DelegationV2) => void;
slashedStatuses: Record<string, { isSlashed: boolean }>;
};

const networkConfig = getNetworkConfig();
Expand Down Expand Up @@ -57,13 +60,34 @@ const columns: TableColumn<DelegationV2, TableParams>[] = [
{
field: "actions",
headerName: "Action",
renderCell: (row, _, { handleActionClick, validations }) => {
renderCell: (
row,
_,
{ handleActionClick, validations, slashedStatuses },
) => {
const { valid, error } = validations[row.stakingTxHashHex];
const { isSlashed } = slashedStatuses[row.stakingTxHashHex] || {};
const tooltip = isSlashed ? (
<>
<span>
This finality provider has been slashed.{" "}
<Link
className="text-secondary-main"
target="_blank"
href={DOCUMENTATION_LINKS.TECHNICAL_PRELIMINARIES}
>
Learn more
</Link>
</span>
</>
) : (
error
);

return (
<ActionButton
disabled={!valid}
tooltip={error}
disabled={!valid || isSlashed}
tooltip={tooltip}
delegation={row}
state={row.state}
onClick={handleActionClick}
Expand All @@ -85,6 +109,7 @@ export function DelegationList() {
executeDelegationAction,
openConfirmationModal,
closeConfirmationModal,
slashedStatuses,
} = useDelegationService();

return (
Expand All @@ -109,7 +134,11 @@ export function DelegationList() {
cellClassName:
"p-4 first:pl-4 first:rounded-l last:pr-4 last:rounded-r bg-surface flex items-center text-sm justify-start group-even:bg-secondary-highlight text-accent-primary",
}}
params={{ handleActionClick: openConfirmationModal, validations }}
params={{
handleActionClick: openConfirmationModal,
validations,
slashedStatuses,
}}
fallback={<div>No delegations found</div>}
/>

Expand Down