diff --git a/.github/workflows/contracts-testing.yml b/.github/workflows/contracts-testing.yml index fac5dc6e0..b209a2f16 100644 --- a/.github/workflows/contracts-testing.yml +++ b/.github/workflows/contracts-testing.yml @@ -14,14 +14,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Setup Node.js environment - uses: actions/setup-node@v3.4.1 + uses: actions/setup-node@v3.5.1 with: node-version: 16.x - uses: actions/checkout@v3 - name: Cache node modules - uses: actions/cache@v3.0.7 + uses: actions/cache@v3.0.11 env: cache-name: cache-node-modules with: @@ -52,7 +52,7 @@ jobs: working-directory: contracts - name: Upload a build artifact - uses: actions/upload-artifact@v3.1.0 + uses: actions/upload-artifact@v3.1.1 with: name: code-coverage-report path: contracts/coverage diff --git a/.github/workflows/deploy-bots.yml b/.github/workflows/deploy-bots.yml index 32a0fc80c..883854c89 100644 --- a/.github/workflows/deploy-bots.yml +++ b/.github/workflows/deploy-bots.yml @@ -4,9 +4,9 @@ jobs: build-deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 - - uses: aws-actions/setup-sam@v1 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + - uses: aws-actions/setup-sam@v2 - uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.STAGING_AWS_ACCESS_KEY }} diff --git a/bot-pinner/package.json b/bot-pinner/package.json index 61fb68b78..ce0296a96 100644 --- a/bot-pinner/package.json +++ b/bot-pinner/package.json @@ -19,6 +19,6 @@ "node": ">=16.13.0" }, "devDependencies": { - "@dappnode/dappnodesdk": "^0.2.65" + "@dappnode/dappnodesdk": "^0.2.68" } } diff --git a/contracts/package.json b/contracts/package.json index f0e71053d..d8ee3e539 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -25,39 +25,39 @@ "publish": "yarn npm publish --access public --tag $(cat package.json | jq .version)" }, "devDependencies": { - "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", + "@nomicfoundation/hardhat-chai-matchers": "^1.0.4", "@nomiclabs/hardhat-ethers": "npm:hardhat-deploy-ethers@0.3.0-beta.13", "@nomiclabs/hardhat-waffle": "^2.0.3", "@openzeppelin/contracts": "^4.7.3", "@tenderly/hardhat-tenderly": "^1.1.6", "@typechain/ethers-v5": "^10.0.0", - "@typechain/hardhat": "^6.1.2", + "@typechain/hardhat": "^6.1.4", "@types/chai": "^4.3.3", - "@types/mocha": "^9.1.1", + "@types/mocha": "^10.0.0", "@types/node": "^16", - "@typescript-eslint/eslint-plugin": "^5.33.1", - "@typescript-eslint/parser": "^5.33.1", + "@typescript-eslint/eslint-plugin": "^5.41.0", + "@typescript-eslint/parser": "^5.41.0", "chai": "^4.3.6", "chai-ethers": "^0.0.1", - "dotenv": "^16.0.1", + "dotenv": "^16.0.3", "ethereum-waffle": "^3.4.4", "ethereumjs-util": "^7.1.4", "ethers": "^5.6.7", "follow-redirects": "^1.15.0", - "hardhat": "^2.10.1", + "hardhat": "^2.11.2", "hardhat-contract-sizer": "^2.6.1", - "hardhat-deploy": "^0.11.12", + "hardhat-deploy": "^0.11.18", "hardhat-deploy-ethers": "^0.3.0-beta.13", "hardhat-docgen": "^1.3.0", - "hardhat-gas-reporter": "^1.0.8", - "hardhat-watcher": "^2.3.0", + "hardhat-gas-reporter": "^1.0.9", + "hardhat-watcher": "^2.5.0", "json-schema": "^0.4.0", - "mocha": "^10.0.0", + "mocha": "^10.1.0", "node-fetch": "^3.2.10", "npm-run-all": "^4.1.5", "shelljs": "^0.8.5", "solhint": "^3.3.7", - "solidity-coverage": "^0.7.21", + "solidity-coverage": "^0.8.2", "ts-node": "^10.8.0", "typechain": "^8.0.0", "typescript": "^4.6.4" diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index bdbb11595..05379597a 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -598,9 +598,7 @@ contract KlerosCore is IArbitrator { if (drawnAddress != address(0)) { // In case no one has staked at the court yet. jurors[drawnAddress].lockedTokens[dispute.subcourtID] += round.tokensAtStakePerJuror; - emit Draw(drawnAddress, _disputeID, currentRound, round.drawnJurors.length); - round.drawnJurors.push(drawnAddress); } } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index 13ecf6774..1cf08e4b6 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -630,7 +630,8 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { _coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1 ); - (uint256 stakedTokens, uint256 lockedTokens, ) = core.getJurorBalance(_juror, subcourtID); - return stakedTokens >= lockedTokens + lockedAmountPerJuror; + (uint256 stakedTokens, uint256 lockedTokens) = core.getJurorBalance(_juror, subcourtID); + (, , uint256 minStake, , , ) = core.courts(subcourtID); + return stakedTokens >= lockedTokens + lockedAmountPerJuror && stakedTokens >= minStake; } } diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index 6bcffcd26..f68dab4f1 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -649,9 +649,9 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { _coreDisputeID, core.getNumberOfRounds(_coreDisputeID) - 1 ); - (uint256 stakedTokens, uint256 lockedTokens, ) = core.getJurorBalance(_juror, subcourtID); - - if (stakedTokens < lockedTokens + lockedAmountPerJuror) { + (uint256 stakedTokens, uint256 lockedTokens) = core.getJurorBalance(_juror, subcourtID); + (, , uint256 minStake, , , ) = core.courts(subcourtID); + if (stakedTokens < lockedTokens + lockedAmountPerJuror || stakedTokens < minStake) { return false; } else { return proofOfHumanity(_juror); diff --git a/package.json b/package.json index 45f06d5a6..f1940ab33 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "node": "16.15.1" }, "devDependencies": { - "@commitlint/cli": "^14.1.0", + "@commitlint/cli": "^17.2.0", "@commitlint/config-conventional": "^14.1.0", "conventional-changelog-cli": "^2.1.1", "depcheck": "^1.4.2", @@ -41,7 +41,7 @@ "eslint-import-resolver-parcel": "^1.10.5", "eslint-plugin-import": "^2.25.3", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^3.4.1", + "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-promise": "^5.1.1", "eslint-utils": "^3.0.0", "husky": "^7.0.0", diff --git a/subgraph-fastbridge/package.json b/subgraph-fastbridge/package.json index 8c76fa70b..42b28ac9b 100644 --- a/subgraph-fastbridge/package.json +++ b/subgraph-fastbridge/package.json @@ -10,8 +10,8 @@ "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 kleros/fastbridge-arbitrum-rinkeby" }, "dependencies": { - "@graphprotocol/graph-cli": "^0.33.0", - "@graphprotocol/graph-ts": "^0.27.0" + "@graphprotocol/graph-cli": "^0.34.0", + "@graphprotocol/graph-ts": "^0.28.1" }, "volta": { "node": "16.17.0" diff --git a/subgraph/package.json b/subgraph/package.json index dbee98760..d1fb4b33a 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -10,7 +10,7 @@ "deploy-local": "graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 KlerosCore" }, "dependencies": { - "@graphprotocol/graph-cli": "^0.33.0", - "@graphprotocol/graph-ts": "^0.27.0" + "@graphprotocol/graph-cli": "^0.34.0", + "@graphprotocol/graph-ts": "^0.28.1" } } diff --git a/web/package.json b/web/package.json index 663d950b7..87672ae1b 100644 --- a/web/package.json +++ b/web/package.json @@ -33,18 +33,18 @@ }, "devDependencies": { "@parcel/transformer-svg-react": "^2.7.0", - "@types/react": "^18.0.14", + "@types/react": "^18.0.24", "@types/react-dom": "^18.0.6", - "@types/styled-components": "^5.1.21", - "@typescript-eslint/eslint-plugin": "^5.33.1", - "@typescript-eslint/parser": "^5.33.1", + "@types/styled-components": "^5.1.26", + "@typescript-eslint/eslint-plugin": "^5.41.0", + "@typescript-eslint/parser": "^5.41.0", "@typescript-eslint/utils": "^5.29.0", "eslint": "^8.16.0", "eslint-config-prettier": "^8.3.0", "eslint-import-resolver-parcel": "^1.10.6", "eslint-plugin-import": "^2.25.4", - "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-react": "^7.30.1", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.31.8", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-security": "^1.4.0", "eslint-utils": "^3.0.0", @@ -54,11 +54,12 @@ "typescript": "^4.5.5" }, "dependencies": { - "@graphql-codegen/cli": "^2.11.6", + "@graphql-codegen/cli": "^2.13.11", "@graphql-codegen/typescript": "^2.7.3", - "@graphql-codegen/typescript-operations": "^2.5.3", + "@graphql-codegen/typescript-operations": "^2.5.6", "@kleros/kleros-v2-contracts": "workspace:^", - "@kleros/ui-components-library": "^1.7.0", + "@kleros/ui-components-library": "^1.8.1", + "@types/react-modal": "^3.13.1", "@web3-react/core": "^6.1.9", "@web3-react/injected-connector": "^6.0.7", "@web3-react/types": "^6.0.7", @@ -67,15 +68,18 @@ "core-js": "^3.21.1", "ethers": "^5.7.0", "graphql": "^16.4.0", - "graphql-request": "^4.2.0", + "graphql-request": "^5.0.0", "moment": "^2.29.4", "react": "^18.2.0", "react-chartjs-2": "^4.3.1", "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", "react-is": "^18.2.0", - "react-router-dom": "6", - "styled-components": "^5.3.5", + "react-jazzicon": "^1.0.4", + "react-loading-skeleton": "^3.1.0", + "react-modal": "^3.16.1", + "react-router-dom": "^6.4.2", + "styled-components": "^5.3.6", "swr": "^1.3.0" } } diff --git a/web/src/app.tsx b/web/src/app.tsx index e03e75d9e..f27ab1889 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -2,6 +2,7 @@ import React from "react"; import { SWRConfig } from "swr"; import { request } from "graphql-request"; import { Routes, Route } from "react-router-dom"; +import "react-loading-skeleton/dist/skeleton.css"; import Web3Provider from "context/Web3Provider"; import StyledComponentsProvider from "context/StyledComponentsProvider"; import WrongChainBoundary from "components/WrongChainBoundary"; @@ -13,7 +14,7 @@ import Dashboard from "./pages/Dashboard"; const fetcherBuilder = (url: string) => ({ query, variables }: { query: string; variables?: any }) => { - console.log("fetch"); + console.log("fetching subgraph"); return request(url, query, variables); }; @@ -32,7 +33,7 @@ const App: React.FC = () => { }> } /> - } /> + } /> Courts} /> } /> + + + + + + + + + diff --git a/web/src/assets/svgs/icons/bullhorn.svg b/web/src/assets/svgs/icons/bullhorn.svg new file mode 100644 index 000000000..e3ad46660 --- /dev/null +++ b/web/src/assets/svgs/icons/bullhorn.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/assets/svgs/icons/doc.svg b/web/src/assets/svgs/icons/doc.svg new file mode 100644 index 000000000..bf2adf79d --- /dev/null +++ b/web/src/assets/svgs/icons/doc.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/assets/svgs/icons/eye.svg b/web/src/assets/svgs/icons/eye.svg new file mode 100644 index 000000000..c0ca4bf57 --- /dev/null +++ b/web/src/assets/svgs/icons/eye.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/src/assets/svgs/icons/policy.svg b/web/src/assets/svgs/icons/policy.svg new file mode 100644 index 000000000..14de6afe1 --- /dev/null +++ b/web/src/assets/svgs/icons/policy.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index 09ee766cb..a552db8fb 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -1,6 +1,7 @@ import React from "react"; import styled from "styled-components"; import { StandardPagination } from "@kleros/ui-components-library"; +import { CasesPageQuery } from "queries/useCasesQuery"; import DisputeCard from "components/DisputeCard"; const Container = styled.div` @@ -18,42 +19,34 @@ const StyledPagination = styled(StandardPagination)` `; export interface ICasesGrid { + disputes: CasesPageQuery["disputes"]; + currentPage: number; + setCurrentPage: (newPage: number) => void; + numberDisputes: number; casesPerPage: number; } -const CasesGrid: React.FC = ({ casesPerPage }) => ( - <> - - = ({ + disputes, + currentPage, + setCurrentPage, + numberDisputes, + casesPerPage, +}) => { + return ( + <> + + {disputes.map((dispute, i) => { + return ; + })} + + setCurrentPage(page)} /> - - - - {}} /> - -); + + ); +}; export default CasesGrid; diff --git a/web/src/components/CasesDisplay/index.tsx b/web/src/components/CasesDisplay/index.tsx index d924a1bca..bb6ae4b21 100644 --- a/web/src/components/CasesDisplay/index.tsx +++ b/web/src/components/CasesDisplay/index.tsx @@ -15,6 +15,10 @@ interface ICasesDisplay extends ICasesGrid { } const CasesDisplay: React.FC = ({ + disputes, + currentPage, + setCurrentPage, + numberDisputes, casesPerPage, title = "Cases", className, @@ -24,7 +28,15 @@ const CasesDisplay: React.FC = ({ - + ); diff --git a/web/src/components/DisputeCard/DisputeInfo.tsx b/web/src/components/DisputeCard/DisputeInfo.tsx index f52557d67..327096d96 100644 --- a/web/src/components/DisputeCard/DisputeInfo.tsx +++ b/web/src/components/DisputeCard/DisputeInfo.tsx @@ -45,9 +45,11 @@ const Container = styled.div` const getPeriodPhrase = (period: Periods) => { switch (period) { - case Periods.appeal: + case Periods.Evidence: + return "Voting Starts"; + case Periods.Appeal: return "Appeal Deadline"; - case Periods.execution: + case Periods.Execution: return "Final Decision"; default: return "Voting Deadline"; @@ -55,11 +57,11 @@ const getPeriodPhrase = (period: Periods) => { }; export interface IDisputeInfo { - court: string; - category: string; - rewards: string; - period: Periods; - date: number; + court?: string; + category?: string; + rewards?: string; + period?: Periods; + date?: number; } const DisputeInfo: React.FC = ({ @@ -70,14 +72,18 @@ const DisputeInfo: React.FC = ({ date, }) => ( - - - - + {category && } + {court && } + {rewards && ( + + )} + {typeof period !== "undefined" && date && ( + + )} ); diff --git a/web/src/components/DisputeCard/PeriodBanner.tsx b/web/src/components/DisputeCard/PeriodBanner.tsx index 7e1ee779f..4d22fa41e 100644 --- a/web/src/components/DisputeCard/PeriodBanner.tsx +++ b/web/src/components/DisputeCard/PeriodBanner.tsx @@ -9,9 +9,9 @@ export interface IPeriodBanner { const getPeriodColors = (period: Periods, theme: Theme): [string, string] => { switch (period) { - case Periods.appeal: + case Periods.Appeal: return [theme.tint, theme.tintMedium]; - case Periods.execution: + case Periods.Execution: return [theme.secondaryPurple, theme.mediumPurple]; default: return [theme.primaryBlue, theme.mediumBlue]; @@ -56,9 +56,9 @@ const Container = styled.div>` const getPeriodLabel = (period: Periods) => { switch (period) { - case Periods.appeal: + case Periods.Appeal: return "Crowdfunding Appeal"; - case Periods.execution: + case Periods.Execution: return "Closed"; default: return "In Progress"; diff --git a/web/src/components/DisputeCard/index.tsx b/web/src/components/DisputeCard/index.tsx index e06b894f4..4cb47bfc6 100644 --- a/web/src/components/DisputeCard/index.tsx +++ b/web/src/components/DisputeCard/index.tsx @@ -1,8 +1,16 @@ import React from "react"; import styled from "styled-components"; +import { useNavigate } from "react-router-dom"; +import { utils } from "ethers"; +import Skeleton from "react-loading-skeleton"; import { Card } from "@kleros/ui-components-library"; -import PeriodBanner, { IPeriodBanner } from "./PeriodBanner"; -import DisputeInfo, { IDisputeInfo } from "./DisputeInfo"; +import { Periods } from "consts/periods"; +import { useGetMetaEvidence } from "queries/useGetMetaEvidence"; +import { useCourtPolicy } from "queries/useCourtPolicy"; +import { useIPFSQuery } from "hooks/useIPFSQuery"; +import { CasesPageQuery } from "queries/useCasesQuery"; +import PeriodBanner from "./PeriodBanner"; +import DisputeInfo from "./DisputeInfo"; const StyledCard = styled(Card)` max-width: 380px; @@ -22,26 +30,52 @@ const Container = styled.div` } `; -interface IDisputeCard extends IPeriodBanner, Omit { - title: string; -} +const getTimeLeft = ( + lastPeriodChange: string, + currentPeriodIndex: number, + timesPerPeriod: string[] +) => { + const durationCurrentPeriod = parseInt(timesPerPeriod[currentPeriodIndex]); + return parseInt(lastPeriodChange) + durationCurrentPeriod; +}; -const DisputeCard: React.FC = ({ +const DisputeCard: React.FC = ({ id, + arbitrated, period, - title, - court, - category, - rewards, - date, -}) => ( - - - -

{title}

- -
-
-); + lastPeriodChange, + subcourtID, +}) => { + const currentPeriodIndex = Periods[period]; + const rewards = `≥ ${utils.formatEther(subcourtID.feeForJuror)} ETH`; + const date = + currentPeriodIndex === 4 + ? lastPeriodChange + : getTimeLeft( + lastPeriodChange, + currentPeriodIndex, + subcourtID.timesPerPeriod + ); + const { data: metaEvidence } = useGetMetaEvidence(id, arbitrated); + const title = metaEvidence ? metaEvidence.title : ; + const { data: courtPolicyPath } = useCourtPolicy(parseInt(subcourtID.id)); + const { data: courtPolicy } = useIPFSQuery(courtPolicyPath?.args._policy); + const courtName = courtPolicy?.name; + const category = metaEvidence ? metaEvidence.category : undefined; + const navigate = useNavigate(); + return ( + navigate(`/cases/${id.toString()}`)}> + + +

{title}

+ +
+
+ ); +}; export default DisputeCard; diff --git a/web/src/components/EvidenceCard.tsx b/web/src/components/EvidenceCard.tsx new file mode 100644 index 000000000..7c1febae4 --- /dev/null +++ b/web/src/components/EvidenceCard.tsx @@ -0,0 +1,91 @@ +import React from "react"; +import styled from "styled-components"; +import Jazzicon, { jsNumberForAddress } from "react-jazzicon"; +import { Card } from "@kleros/ui-components-library"; +import AttachmentIcon from "svgs/icons/attachment.svg"; +import { useIPFSQuery } from "hooks/useIPFSQuery"; +import { shortenAddress } from "utils/shortenAddress"; + +interface IEvidenceCard { + evidence: string; + sender: string; + index: number; +} + +const EvidenceCard: React.FC = ({ evidence, sender, index }) => { + const { data } = useIPFSQuery(evidence.at(0) === "/" ? evidence : undefined); + return ( + + + #{index}: + {data ? ( + <> +

{data.name}

+

{data.description}

+ + ) : ( +

{evidence}

+ )} +
+ + +

{shortenAddress(sender)}

+ {data && ( + + + + )} +
+
+ ); +}; + +const StyledCard = styled(Card)` + width: 100%; + height: auto; +`; + +const TextContainer = styled.div` + padding: 8px; + > * { + overflow-wrap: break-word; + margin: 0; + } + > h3 { + display: inline-block; + margin: 0px 4px; + } +`; + +const Index = styled.p` + display: inline-block; +`; + +const BottomShade = styled.div` + background-color: ${({ theme }) => theme.lightBlue}; + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + > * { + flex-basis: 1; + flex-shrink: 0; + margin: 0; + } +`; + +const StyledA = styled.a` + margin-left: auto; + margin-right: 8px; + display: flex; + > svg { + width: 16px; + fill: ${({ theme }) => theme.primaryBlue}; + } +`; + +export default EvidenceCard; diff --git a/web/src/components/WrongChainBoundary.tsx b/web/src/components/WrongChainBoundary.tsx index 4ba9b2471..4987705a5 100644 --- a/web/src/components/WrongChainBoundary.tsx +++ b/web/src/components/WrongChainBoundary.tsx @@ -11,6 +11,7 @@ const WrongChainRecovery: React.FC<{ resetErrorBoundary: () => void }> = ({ return (