Skip to content
Permalink

Comparing changes

This is a direct comparison between two commits made in this repository or its related repositories. View the default comparison for this range or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: kleros/kleros-v2
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 444d4c2b3c98a58a6fecbce7b1ce491f50a1f8e5
Choose a base ref
..
head repository: kleros/kleros-v2
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: f40ae429eb6b08a42510060d99678e7f2cff9351
Choose a head ref
2 changes: 1 addition & 1 deletion subgraph/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@kleros/kleros-v2-subgraph",
"version": "0.7.0",
"version": "0.7.2",
"license": "MIT",
"scripts": {
"update:core:arbitrum-sepolia-devnet": "./scripts/update.sh arbitrumSepoliaDevnet arbitrum-sepolia core/subgraph.yaml",
88 changes: 88 additions & 0 deletions web/src/components/DottedMenuButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React from "react";
import styled, { css, keyframes } from "styled-components";

import DottedMenu from "svgs/icons/dotted-menu.svg";

const ripple = keyframes`
0% {
opacity: 0;
transform: scale3d(0.5, 0.5, 1);
}
10% {
opacity: 0.5;
transform: scale3d(0.75, 0.75, 1);
}
100% {
opacity: 0;
transform: scale3d(1.75, 1.75, 1);
}
`;

const ring = (duration: string, delay: string) => css`
opacity: 0;
position: absolute;
top: 0;
left: 0;
transform: translate(50%);
content: "";
height: 100%;
width: 100%;
border: 3px solid ${({ theme }) => theme.primaryBlue};
border-radius: 100%;
animation-name: ${ripple};
animation-duration: ${duration};
animation-delay: ${delay};
animation-iteration-count: infinite;
animation-timing-function: cubic-bezier(0.65, 0, 0.34, 1);
z-index: 0;
`;

const Container = styled.div<{ displayRipple: boolean }>`
display: flex;
justify-content: center;
align-items: center;
width: 36px;
height: 36px;
${({ displayRipple }) =>
displayRipple &&
css`
&::after {
${ring("3s", "0s")}
}
&::before {
${ring("3s", "0.5s")}
}
`}
`;

const ButtonContainer = styled.div`
border-radius: 50%;
z-index: 1;
background-color: ${({ theme }) => theme.lightBackground};
`;

const StyledDottedMenu = styled(DottedMenu)`
cursor: pointer;
width: 100%;
height: 100%;
fill: ${({ theme }) => theme.primaryBlue};
`;

interface IMenuButton {
toggle: () => void;
displayRipple: boolean;
className?: string;
}

const DottedMenuButton: React.FC<IMenuButton> = ({ toggle, displayRipple, className }) => {
return (
<Container {...{ displayRipple, className }}>
<ButtonContainer className="button-container">
<StyledDottedMenu onClick={toggle} className="menu-icon" />
</ButtonContainer>
</Container>
);
};

export default DottedMenuButton;
21 changes: 21 additions & 0 deletions web/src/components/Phase.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react";
import styled from "styled-components";

import { useSortitionModulePhase } from "hooks/useSortitionModulePhase";

import { isUndefined } from "src/utils";

export enum Phases {
staking,
generating,
drawing,
}

const StyledLabel = styled.label``;

const Phase: React.FC<{ className?: string }> = ({ className }) => {
const { data: phase } = useSortitionModulePhase();
return <>{isUndefined(phase) ? null : <StyledLabel {...{ className }}>Phase: {Phases[phase]}</StyledLabel>}</>;
};

export default Phase;
20 changes: 7 additions & 13 deletions web/src/layout/Header/navbar/Debug.tsx
Original file line number Diff line number Diff line change
@@ -2,10 +2,11 @@ import React, { useMemo } from "react";
import styled from "styled-components";

import { GIT_BRANCH, GIT_DIRTY, GIT_HASH, GIT_TAGS, GIT_URL, RELEASE_VERSION } from "consts/index";
import { useSortitionModulePhase } from "hooks/useSortitionModulePhase";
import { useToggleTheme } from "hooks/useToggleThemeContext";
import { isUndefined } from "utils/index";

import Phase from "components/Phase";

const Container = styled.div`
display: flex;
flex-direction: column;
@@ -32,6 +33,10 @@ const StyledLabel = styled.label`
padding-left: 8px;
`;

const StyledPhase = styled(Phase)`
padding-left: 8px;
`;

const Version = () => (
<StyledLabel>
v{RELEASE_VERSION}{" "}
@@ -51,23 +56,12 @@ const ServicesStatus = () => {
return <label>{isUndefined(statusUrl) ? null : <StyledIframe src={`${statusUrl + statusUrlParameters}`} />}</label>;
};

enum Phases {
staking,
generating,
drawing,
}

const Phase = () => {
const { data: phase } = useSortitionModulePhase();
return <>{isUndefined(phase) ? null : <StyledLabel>Phase: {Phases[phase as number]}</StyledLabel>}</>;
};

const Debug: React.FC = () => {
return (
<Container>
<ServicesStatus />
<Version />
<Phase />
<StyledPhase />
</Container>
);
};
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ const PassPeriodButton: React.FC<IPassPeriodButton> = ({ id, setIsOpen, period }
const publicClient = usePublicClient();
const { data: maintenanceData } = useDisputeMaintenanceQuery(id);

const isDrawn = useMemo(() => maintenanceData?.dispute?.currentRound.jurorsDrawn, [maintenanceData]);
const isDrawn = useMemo(() => maintenanceData?.dispute?.currentRound.jurorsDrawn ?? false, [maintenanceData]);

const {
data: passPeriodConfig,
Original file line number Diff line number Diff line change
@@ -39,7 +39,6 @@ const WithdrawAppealFees: React.FC<IWithdrawAppealFees> = ({ id, roundIndex, set
const { data: appealData } = useClassicAppealQuery(id);

const localRounds = useMemo(() => getLocalRounds(appealData?.dispute?.disputeKitDispute), [appealData]);
console.log({ localRounds });

const feeDispersed = useMemo(
() =>
4 changes: 2 additions & 2 deletions web/src/pages/Cases/CaseDetails/MaintenanceButtons/index.tsx
Original file line number Diff line number Diff line change
@@ -8,13 +8,13 @@ import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery";
import { Periods } from "src/consts/periods";
import { Period } from "src/graphql/graphql";

import DottedMenuButton from "components/DottedMenuButton";
import { EnsureChain } from "components/EnsureChain";
import { Overlay } from "components/Overlay";

import DistributeRewards from "./DistributeRewards";
import DrawButton from "./DrawButton";
import ExecuteRulingButton from "./ExecuteRuling";
import MenuButton from "./MenuButton";
import PassPeriodButton from "./PassPeriodButton";
import WithdrawAppealFees from "./WithdrawAppealFees";

@@ -128,7 +128,7 @@ const MaintenanceButtons: React.FC = () => {
</PopupContainer>
</>
) : null}
<MenuButton {...{ toggle, displayRipple }} />
<DottedMenuButton {...{ toggle, displayRipple }} />
</Container>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React, { useMemo, useState } from "react";
import styled from "styled-components";

import { usePublicClient } from "wagmi";

import { Button } from "@kleros/ui-components-library";

import {
useReadSortitionModuleDelayedStakeReadIndex,
useReadSortitionModuleDelayedStakeWriteIndex,
useSimulateSortitionModuleExecuteDelayedStakes,
useWriteSortitionModuleExecuteDelayedStakes,
} from "hooks/contracts/generated";
import { useSortitionModulePhase } from "hooks/useSortitionModulePhase";
import { wrapWithToast } from "utils/wrapWithToast";

import { isUndefined } from "src/utils";

import { Phases } from "components/Phase";

import { IBaseStakeMaintenanceButton } from ".";

const StyledButton = styled(Button)`
width: 100%;
`;

type IExecuteStakeDelayedButton = IBaseStakeMaintenanceButton;

const ExecuteDelayedStakeButton: React.FC<IExecuteStakeDelayedButton> = ({ setIsOpen }) => {
const [isSending, setIsSending] = useState(false);
const publicClient = usePublicClient();
const { data: phase } = useSortitionModulePhase();
const { data: delayedStakeWriteIndex } = useReadSortitionModuleDelayedStakeWriteIndex();
const { data: delayedStakeReadIndex } = useReadSortitionModuleDelayedStakeReadIndex();

const canExecute = useMemo(() => {
if (isUndefined(phase) || isUndefined(delayedStakeReadIndex) || isUndefined(delayedStakeWriteIndex)) return false;
return phase === Phases.staking && delayedStakeWriteIndex >= delayedStakeReadIndex;
}, [phase, delayedStakeReadIndex, delayedStakeWriteIndex]);

const {
data: executeDelayedStakeConfig,
isLoading: isLoadingConfig,
isError,
} = useSimulateSortitionModuleExecuteDelayedStakes({
query: {
enabled: canExecute,
},
args: [1n + (delayedStakeWriteIndex ?? 0n) - (delayedStakeReadIndex ?? 0n)],
});

const { writeContractAsync: executeDelayedStake } = useWriteSortitionModuleExecuteDelayedStakes();

const isLoading = useMemo(() => isLoadingConfig || isSending, [isLoadingConfig, isSending]);
const isDisabled = useMemo(() => isError || isLoading || !canExecute, [isError, isLoading, canExecute]);
const handleClick = () => {
if (!executeDelayedStakeConfig || !publicClient || !executeDelayedStake) return;

setIsSending(true);

wrapWithToast(async () => await executeDelayedStake(executeDelayedStakeConfig.request), publicClient).finally(
() => {
setIsSending(false);
setIsOpen(false);
}
);
};
return (
<StyledButton
text="Execute Delayed Stakes"
small
isLoading={isLoading}
disabled={isDisabled}
onClick={handleClick}
/>
);
};

export default ExecuteDelayedStakeButton;
88 changes: 88 additions & 0 deletions web/src/pages/Courts/StakeMaintenanceButton/PassPhaseButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useMemo, useState } from "react";
import styled from "styled-components";

import { usePublicClient } from "wagmi";

import { Button } from "@kleros/ui-components-library";

import {
useReadSortitionModuleDisputesWithoutJurors,
useReadSortitionModuleLastPhaseChange,
useReadSortitionModuleMaxDrawingTime,
useReadSortitionModuleMinStakingTime,
useSimulateSortitionModulePassPhase,
useWriteSortitionModulePassPhase,
} from "hooks/contracts/generated";
import { useSortitionModulePhase } from "hooks/useSortitionModulePhase";
import { wrapWithToast } from "utils/wrapWithToast";

import { isUndefined } from "src/utils";

import { Phases } from "components/Phase";

import { IBaseStakeMaintenanceButton } from ".";

const StyledButton = styled(Button)`
width: 100%;
`;

type IPassPhaseButton = IBaseStakeMaintenanceButton;

const PassPhaseButton: React.FC<IPassPhaseButton> = ({ setIsOpen }) => {
const [isSending, setIsSending] = useState(false);
const publicClient = usePublicClient();
const { data: phase } = useSortitionModulePhase();
const { data: lastPhaseChange } = useReadSortitionModuleLastPhaseChange();
const { data: minStakingTime } = useReadSortitionModuleMinStakingTime();
const { data: maxDrawingTime } = useReadSortitionModuleMaxDrawingTime();
const { data: disputeWithoutJurors } = useReadSortitionModuleDisputesWithoutJurors();

const canChangePhase = useMemo(() => {
if (
isUndefined(phase) ||
isUndefined(lastPhaseChange) ||
isUndefined(minStakingTime) ||
isUndefined(maxDrawingTime) ||
isUndefined(disputeWithoutJurors)
)
return false;

const now = Math.floor(Date.now() / 1000);
switch (phase) {
case Phases.staking:
return BigInt(now) - lastPhaseChange >= minStakingTime;
case Phases.drawing:
return disputeWithoutJurors === 0n || BigInt(now) - lastPhaseChange >= maxDrawingTime;
default:
return true;
}
}, [phase, lastPhaseChange, minStakingTime, maxDrawingTime, disputeWithoutJurors]);

const {
data: passPhaseConfig,
isLoading: isLoadingConfig,
isError,
} = useSimulateSortitionModulePassPhase({
query: {
enabled: canChangePhase,
},
});

const { writeContractAsync: passPhase } = useWriteSortitionModulePassPhase();

const isLoading = useMemo(() => isLoadingConfig || isSending, [isLoadingConfig, isSending]);
const isDisabled = useMemo(() => isError || isLoading || !canChangePhase, [isError, isLoading, canChangePhase]);
const handleClick = () => {
if (!passPhaseConfig || !publicClient || !passPhase) return;

setIsSending(true);

wrapWithToast(async () => await passPhase(passPhaseConfig.request), publicClient).finally(() => {
setIsSending(false);
setIsOpen(false);
});
};
return <StyledButton text="Pass Phase" small isLoading={isLoading} disabled={isDisabled} onClick={handleClick} />;
};

export default PassPhaseButton;
Loading