diff --git a/.github/workflows/deploy-subgraph.yml b/.github/workflows/deploy-subgraph.yml index b31ab20c1..4b32f972b 100644 --- a/.github/workflows/deploy-subgraph.yml +++ b/.github/workflows/deploy-subgraph.yml @@ -3,15 +3,15 @@ name: Deploy the Subgraph on: workflow_dispatch: inputs: - network: - description: The network to deploy the subgraph to + graph_environment: + description: The Graph environment to deploy to required: true - default: 'arbitrum-sepolia' + default: 'graph-studio-devnet' type: choice options: - - arbitrum-sepolia-devnet - - arbitrum-sepolia - - arbitrum + - graph-studio-devnet + - graph-studio-testnet + - graph-studio-mainnet subgraph: description: The name of the subgraph to deploy required: true @@ -32,13 +32,17 @@ permissions: jobs: buildAndDeploy: runs-on: ubuntu-latest - environment: kleros-org-subgraph + environment: ${{ inputs.graph_environment }} steps: - name: Harden Runner uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.5.0 with: egress-policy: audit + - name: Validate Network environment variable + if: ${{!startsWith(vars.NETWORK, 'arbitrum')}} + run: echo ${{vars.NETWORK}} && exit 1 + - name: Checkout code uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 @@ -66,7 +70,7 @@ jobs: if: ${{ inputs.update }} run: | export PATH=$PWD/../bin:$PATH - yarn update:${{ inputs.subgraph }}:${{ inputs.network }} + yarn update:${{ inputs.subgraph }}:${{ vars.NETWORK }} working-directory: subgraph - name: Build the subgraph @@ -75,10 +79,10 @@ jobs: yarn build:${{ inputs.subgraph }} working-directory: subgraph - - name: Authenticate with TheGraph - run: yarn graph auth "${{ secrets.SUBGRAPH_AUTH_TOKEN }}" --product hosted-service + - name: Authenticate with TheGraph Studio + run: yarn graph auth "${{ secrets.SUBGRAPH_AUTH_TOKEN }}" --studio working-directory: subgraph - name: Deploy the subgraph - run: yarn deploy:${{ inputs.subgraph }}:${{ inputs.network }} + run: yarn deploy:${{ inputs.subgraph }}:${{ vars.NETWORK }} working-directory: subgraph diff --git a/contracts/deploy/utils/deployERC20AndFaucet.ts b/contracts/deploy/utils/deployERC20AndFaucet.ts index d3b9b2e77..ed0ce1a50 100644 --- a/contracts/deploy/utils/deployERC20AndFaucet.ts +++ b/contracts/deploy/utils/deployERC20AndFaucet.ts @@ -9,11 +9,7 @@ export const deployERC20AndFaucet = async ( ticker: string, faucetFundingAmount: BigNumber = hre.ethers.utils.parseUnits("100000") ): Promise => { - let erc20 = await hre.ethers.getContractOrNull(ticker); - if (erc20) { - return erc20; - } - erc20 = await getContractOrDeploy(hre, ticker, { + const erc20 = await getContractOrDeploy(hre, ticker, { from: deployer, contract: "TestERC20", args: [ticker, ticker], @@ -27,7 +23,8 @@ export const deployERC20AndFaucet = async ( }); const faucetBalance = await erc20.balanceOf(faucet.address); const deployerBalance = await erc20.balanceOf(deployer); - if (deployerBalance.gte(faucetFundingAmount) && faucetBalance.isZero()) { + if (deployerBalance.gte(faucetFundingAmount) && faucetBalance.lt(faucetFundingAmount.div(5))) { + // Fund the faucet if deployer has enough tokens and if the faucet has less than 20% of the faucetFundingAmount console.log(`funding ${ticker}Faucet with ${faucetFundingAmount}`); await erc20.transfer(faucet.address, faucetFundingAmount); } diff --git a/contracts/scripts/keeperBot.ts b/contracts/scripts/keeperBot.ts index 4ae827f4c..792a13f55 100644 --- a/contracts/scripts/keeperBot.ts +++ b/contracts/scripts/keeperBot.ts @@ -1,4 +1,4 @@ -import { DisputeKitClassic, KlerosCore, PNK, RandomizerRNG, SortitionModule } from "../typechain-types"; +import { DisputeKitClassic, KlerosCore, PNK, RandomizerRNG, BlockHashRNG, SortitionModule } from "../typechain-types"; import request from "graphql-request"; import env from "./utils/env"; import loggerFactory from "./utils/logger"; @@ -36,9 +36,10 @@ const getContracts = async () => { const core = (await ethers.getContract("KlerosCore")) as KlerosCore; const sortition = (await ethers.getContract("SortitionModule")) as SortitionModule; const randomizerRng = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG; + const blockHashRNG = (await ethers.getContract("BlockHashRNG")) as BlockHashRNG; const disputeKitClassic = (await ethers.getContract("DisputeKitClassic")) as DisputeKitClassic; const pnk = (await ethers.getContract("PNK")) as PNK; - return { core, sortition, randomizerRng, disputeKitClassic, pnk }; + return { core, sortition, randomizerRng, blockHashRNG, disputeKitClassic, pnk }; }; type Contribution = { @@ -150,15 +151,32 @@ const handleError = (e: any) => { }; const isRngReady = async () => { - const { randomizerRng, sortition } = await getContracts(); - const requesterID = await randomizerRng.requesterToID(sortition.address); - const n = await randomizerRng.randomNumbers(requesterID); - if (Number(n) === 0) { - logger.info("RandomizerRNG is NOT ready yet"); - return false; + const { randomizerRng, blockHashRNG, sortition } = await getContracts(); + const currentRng = await sortition.rng(); + if (currentRng === randomizerRng.address) { + const requesterID = await randomizerRng.requesterToID(sortition.address); + const n = await randomizerRng.randomNumbers(requesterID); + if (Number(n) === 0) { + logger.info("RandomizerRNG is NOT ready yet"); + return false; + } else { + logger.info(`RandomizerRNG is ready: ${n.toString()}`); + return true; + } + } else if (currentRng === blockHashRNG.address) { + const requestBlock = await sortition.randomNumberRequestBlock(); + const lookahead = await sortition.rngLookahead(); + const n = await blockHashRNG.callStatic.receiveRandomness(requestBlock.add(lookahead)); + if (Number(n) === 0) { + logger.info("BlockHashRNG is NOT ready yet"); + return false; + } else { + logger.info(`BlockHashRNG is ready: ${n.toString()}`); + return true; + } } else { - logger.info(`RandomizerRNG is ready: ${n.toString()}`); - return true; + logger.error("Unknown RNG at ", currentRng); + return false; } }; diff --git a/scripts/act-subgraph.yml b/scripts/act-subgraph.yml index 08b4c4d6d..392575317 100755 --- a/scripts/act-subgraph.yml +++ b/scripts/act-subgraph.yml @@ -1,3 +1,3 @@ #!/usr/bin/env bash -act workflow_dispatch -j buildAndDeploy --input network=arbitrum-sepolia,update=true +act workflow_dispatch -j buildAndDeploy --input graph_environment=graph-studio-devnet,update=true --env network=arbitrum-sepolia-devnet diff --git a/subgraph/README.md b/subgraph/README.md index 25a69b51b..843c3ce21 100644 --- a/subgraph/README.md +++ b/subgraph/README.md @@ -40,7 +40,14 @@ $ yarn run graph auth --studio #### Deployment ```bash +# bump the package version number +yarn version patch + +# deploy the new version yarn deploy:arbitrum-sepolia-devnet + +# commit the new version number +git commit -m "chore: subgraph deployment" ``` ### Using the Kleros organization account diff --git a/subgraph/core/schema.graphql b/subgraph/core/schema.graphql index e8715ab2d..e0acc6811 100644 --- a/subgraph/core/schema.graphql +++ b/subgraph/core/schema.graphql @@ -181,6 +181,7 @@ type Round @entity { penalties: BigInt! drawnJurors: [Draw!]! @derivedFrom(field: "round") dispute: Dispute! + court: Court! feeToken: FeeToken } diff --git a/subgraph/core/src/KlerosCore.ts b/subgraph/core/src/KlerosCore.ts index a8dee3fdc..389572dfb 100644 --- a/subgraph/core/src/KlerosCore.ts +++ b/subgraph/core/src/KlerosCore.ts @@ -78,7 +78,7 @@ export function handleDisputeCreation(event: DisputeCreation): void { court.save(); createDisputeFromEvent(event); const roundInfo = contract.getRoundInfo(disputeID, ZERO); - createRoundFromRoundInfo(disputeID, ZERO, roundInfo); + createRoundFromRoundInfo(KlerosCore.bind(event.address), disputeID, ZERO, roundInfo); const arbitrable = event.params._arbitrable.toHexString(); updateArbitrableCases(arbitrable, ONE); updateCases(ONE, event.block.timestamp); @@ -163,7 +163,7 @@ export function handleAppealDecision(event: AppealDecision): void { dispute.currentRound = roundID; dispute.save(); const roundInfo = contract.getRoundInfo(disputeID, newRoundIndex); - createRoundFromRoundInfo(disputeID, newRoundIndex, roundInfo); + createRoundFromRoundInfo(KlerosCore.bind(event.address), disputeID, newRoundIndex, roundInfo); } export function handleCourtJump(event: CourtJump): void { diff --git a/subgraph/core/src/entities/Round.ts b/subgraph/core/src/entities/Round.ts index 5ffb518e6..47380099e 100644 --- a/subgraph/core/src/entities/Round.ts +++ b/subgraph/core/src/entities/Round.ts @@ -1,8 +1,9 @@ import { BigInt } from "@graphprotocol/graph-ts"; -import { KlerosCore__getRoundInfoResultValue0Struct } from "../../generated/KlerosCore/KlerosCore"; +import { KlerosCore, KlerosCore__getRoundInfoResultValue0Struct } from "../../generated/KlerosCore/KlerosCore"; import { Round } from "../../generated/schema"; export function createRoundFromRoundInfo( + contract: KlerosCore, disputeID: BigInt, roundIndex: BigInt, roundInfo: KlerosCore__getRoundInfoResultValue0Struct @@ -19,5 +20,7 @@ export function createRoundFromRoundInfo( round.repartitions = roundInfo.repartitions; round.penalties = roundInfo.pnkPenalties; round.dispute = disputeID.toString(); + const courtID = contract.disputes(disputeID).value0.toString(); + round.court = courtID; round.save(); } diff --git a/subgraph/package.json b/subgraph/package.json index c8cb22540..147dd0100 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -1,5 +1,6 @@ { "name": "@kleros/kleros-v2-subgraph", + "version": "0.3.1", "license": "MIT", "scripts": { "update:core:arbitrum-sepolia-devnet": "./scripts/update.sh arbitrumSepoliaDevnet arbitrum-sepolia core/subgraph.yaml", @@ -10,9 +11,9 @@ "build:core": "graph build --output-dir core/build/ core/subgraph.yaml", "test:core": "cd core && graph test", "clean:core": "graph clean --codegen-dir core/generated/ --build-dir core/build/ && rm core/subgraph.yaml.bak.*", - "deploy:core:arbitrum-sepolia-devnet": "graph deploy --product subgraph-studio kleros-v2-core-devnet -l v0.0.2 core/subgraph.yaml", - "deploy:core:arbitrum-sepolia": "graph deploy --product subgraph-studio kleros-v2-core-testnet -l v0.0.2 core/subgraph.yaml", - "deploy:core:arbitrum": "graph deploy --product subgraph-studio kleros-v2-core -l v0.0.2 core/subgraph.yaml", + "deploy:core:arbitrum-sepolia-devnet": "graph deploy --product subgraph-studio kleros-v2-core-devnet -l v$npm_package_version core/subgraph.yaml", + "deploy:core:arbitrum-sepolia": "graph deploy --product subgraph-studio kleros-v2-core-testnet -l v$npm_package_version core/subgraph.yaml", + "deploy:core:arbitrum": "graph deploy --product subgraph-studio kleros-v2-core -l v$npm_package_version core/subgraph.yaml", "": "------------------------------------------------------------------------------------------", "update:drt:arbitrum-sepolia-devnet": "./scripts/update.sh arbitrumSepoliaDevnet arbitrum-sepolia dispute-template-registry/subgraph.yaml", "update:drt:arbitrum-sepolia": "./scripts/update.sh arbitrumSepolia arbitrum-sepolia dispute-template-registry/subgraph.yaml", @@ -22,8 +23,8 @@ "build:drt": "graph build --output-dir dispute-template-registry/generated/ dispute-template-registry/subgraph.yaml", "test:drt": "cd dispute-template-registry && graph test ", "clean:drt": "graph clean --codegen-dir dispute-template-registry/generated/ --build-dir dispute-template-registry/build/ && rm dispute-template-registry/subgraph.yaml.bak.*", - "deploy:drt:arbitrum-sepolia-devnet": "graph deploy --product subgraph-studio kleros-v2-drt-arbisep-devnet -l v0.0.2 dispute-template-registry/subgraph.yaml", - "deploy:drt:arbitrum-sepolia": "graph deploy --product subgraph-studio kleros-v2-drt-arbisep-testnet -l v0.0.2 dispute-template-registry/subgraph.yaml", + "deploy:drt:arbitrum-sepolia-devnet": "graph deploy --product subgraph-studio kleros-v2-drt-arbisep-devnet -l v$npm_package_version dispute-template-registry/subgraph.yaml", + "deploy:drt:arbitrum-sepolia": "graph deploy --product subgraph-studio kleros-v2-drt-arbisep-testnet -l v$npm_package_version dispute-template-registry/subgraph.yaml", " ": "-----------------------------------------------------------------------------------------", "update:arbitrum-sepolia-devnet": "./scripts/all.sh update arbitrum-sepolia-devnet", "update:arbitrum-sepolia": "./scripts/all.sh update arbitrum-sepolia", diff --git a/web/src/assets/svgs/icons/paperclip.svg b/web/src/assets/svgs/icons/paperclip.svg new file mode 100644 index 000000000..733c87215 --- /dev/null +++ b/web/src/assets/svgs/icons/paperclip.svg @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/web/src/components/Verdict/DisputeTimeline.tsx b/web/src/components/Verdict/DisputeTimeline.tsx index ec085ce2a..1d3601e98 100644 --- a/web/src/components/Verdict/DisputeTimeline.tsx +++ b/web/src/components/Verdict/DisputeTimeline.tsx @@ -87,7 +87,7 @@ const useItems = (disputeDetails?: DisputeDetailsQuery, arbitrable?: `0x${string acc.push({ title: `Jury Decision - Round ${index + 1}`, party: isOngoing ? "Voting is ongoing" : getVoteChoice(parsedRoundChoice, answers), - subtitle: eventDate, + subtitle: `${eventDate} / ${votingHistory?.dispute?.rounds.at(index)?.court.name}`, rightSided: true, variant: theme.secondaryPurple, Icon: icon !== "" ? icon : undefined, diff --git a/web/src/hooks/queries/useVotingHistory.ts b/web/src/hooks/queries/useVotingHistory.ts index 8ba728f9d..e021ee418 100644 --- a/web/src/hooks/queries/useVotingHistory.ts +++ b/web/src/hooks/queries/useVotingHistory.ts @@ -10,6 +10,10 @@ const votingHistoryQuery = graphql(` id rounds { nbVotes + court { + id + name + } } disputeKitDispute { localRounds { diff --git a/web/src/pages/Cases/CaseDetails/Overview/Policies.tsx b/web/src/pages/Cases/CaseDetails/Overview/Policies.tsx index 460dcee41..8e0155adc 100644 --- a/web/src/pages/Cases/CaseDetails/Overview/Policies.tsx +++ b/web/src/pages/Cases/CaseDetails/Overview/Policies.tsx @@ -5,6 +5,7 @@ import { IPFS_GATEWAY } from "consts/index"; import PolicyIcon from "svgs/icons/policy.svg"; import { isUndefined } from "utils/index"; import { responsiveSize } from "styles/responsiveSize"; +import PaperclipIcon from "svgs/icons/paperclip.svg"; const ShadeArea = styled.div` display: flex; @@ -46,21 +47,38 @@ const StyledPolicyIcon = styled(PolicyIcon)` fill: ${({ theme }) => theme.primaryBlue}; `; +const StyledPaperclipIcon = styled(PaperclipIcon)` + width: 16px; + fill: ${({ theme }) => theme.primaryBlue}; +`; + const LinkContainer = styled.div` display: flex; - gap: ${responsiveSize(8, 24)}; + gap: ${responsiveSize(16, 24)}; + flex-wrap: wrap; `; +type Attachment = { + label?: string; + uri: string; +}; interface IPolicies { disputePolicyURI?: string; courtId?: string; + attachment?: Attachment; } -export const Policies: React.FC = ({ disputePolicyURI, courtId }) => { +export const Policies: React.FC = ({ disputePolicyURI, courtId, attachment }) => { return ( Make sure you read and understand the Policies + {!isUndefined(attachment) && !isUndefined(attachment.uri) ? ( + + + {attachment.label ?? "Attachment"} + + ) : null} {isUndefined(disputePolicyURI) ? null : ( diff --git a/web/src/pages/Cases/CaseDetails/Overview/index.tsx b/web/src/pages/Cases/CaseDetails/Overview/index.tsx index ef9a003d5..84f22374b 100644 --- a/web/src/pages/Cases/CaseDetails/Overview/index.tsx +++ b/web/src/pages/Cases/CaseDetails/Overview/index.tsx @@ -73,7 +73,11 @@ const Overview: React.FC = ({ arbitrable, courtID, currentPeriodIndex {...{ rewards, category }} /> - + ); }; diff --git a/web/src/pages/Cases/CaseDetails/Voting/VotingHistory.tsx b/web/src/pages/Cases/CaseDetails/Voting/VotingHistory.tsx index 9aed8c3c4..eedc86cb1 100644 --- a/web/src/pages/Cases/CaseDetails/Voting/VotingHistory.tsx +++ b/web/src/pages/Cases/CaseDetails/Voting/VotingHistory.tsx @@ -133,7 +133,8 @@ const VotingHistory: React.FC<{ arbitrable?: `0x${string}`; isQuestion: boolean ? "All jurors voted" : localRounds.at(currentTab)?.totalVoted.toString() + ` vote${localRounds.at(currentTab)?.totalVoted.toString() === "1" ? "" : "s"} cast out of ` + - rounds.at(currentTab)?.nbVotes} + rounds.at(currentTab)?.nbVotes}{" "} + - {rounds.at(currentTab)?.court.name}