Skip to content

Commit

Permalink
disable stake when eoi fp is slashed (#639)
Browse files Browse the repository at this point in the history
* disable stake when eoi fp is slashed

* resolve comments

* resolve comments

* resolve comments
  • Loading branch information
jeremy-babylonlabs authored Jan 31, 2025
1 parent d5bf77f commit 7bbcbb7
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 34 deletions.
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;
}

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

0 comments on commit 7bbcbb7

Please sign in to comment.