diff --git a/contracts/deploy/00-home-chain-leaderboard-offset.ts b/contracts/deploy/00-home-chain-leaderboard-offset.ts new file mode 100644 index 000000000..e4dab271a --- /dev/null +++ b/contracts/deploy/00-home-chain-leaderboard-offset.ts @@ -0,0 +1,26 @@ +import { HardhatRuntimeEnvironment } from "hardhat/types"; +import { DeployFunction } from "hardhat-deploy/types"; +import { HomeChains, isSkipped } from "./utils"; +import { getContractOrDeploy } from "./utils/getContractOrDeploy"; + +const deployLeaderboardOffset: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { + const { getNamedAccounts, getChainId } = hre; + + // fallback to hardhat node signers on local network + const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; + const chainId = Number(await getChainId()); + console.log("deploying to %s with deployer %s", HomeChains[chainId], deployer); + + await getContractOrDeploy(hre, "LeaderboardOffset", { + from: deployer, + args: [deployer], + log: true, + }); +}; + +deployLeaderboardOffset.tags = ["LeaderboardOffset"]; +deployLeaderboardOffset.skip = async ({ network }) => { + return isSkipped(network, !HomeChains[network.config.chainId ?? 0]); +}; + +export default deployLeaderboardOffset; diff --git a/contracts/deployments/arbitrumSepoliaDevnet/LeaderboardOffset.json b/contracts/deployments/arbitrumSepoliaDevnet/LeaderboardOffset.json new file mode 100644 index 000000000..5aead7c00 --- /dev/null +++ b/contracts/deployments/arbitrumSepoliaDevnet/LeaderboardOffset.json @@ -0,0 +1,194 @@ +{ + "address": "0x811eC94d73445Df262D3Bf43571B85caD122bBD7", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "governor_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "InvalidGovernor", + "type": "error" + }, + { + "inputs": [], + "name": "NotGovernor", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "oldGovernor", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "GovernorUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "offset", + "type": "int256" + }, + { + "indexed": true, + "internalType": "address", + "name": "arbitrator", + "type": "address" + } + ], + "name": "Offset", + "type": "event" + }, + { + "inputs": [], + "name": "governor", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "juror", + "type": "address" + }, + { + "internalType": "int256", + "name": "offset", + "type": "int256" + }, + { + "internalType": "address", + "name": "arbitrator", + "type": "address" + } + ], + "name": "setOffset", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newGovernor", + "type": "address" + } + ], + "name": "updateGovernor", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0xbb59c3799cd0cdb002ab5d50a73ce4df0f7a31a6da376d35d651695c4c035553", + "receipt": { + "to": null, + "from": "0x88AB19C0c7b57EeBa545acbD4368748194cd796B", + "contractAddress": "0x811eC94d73445Df262D3Bf43571B85caD122bBD7", + "transactionIndex": 4, + "gasUsed": "188293", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x1eda1fac08593a4d20c0da0b64d5c6608e59100b9e7f928142175bf7ab2b1807", + "transactionHash": "0xbb59c3799cd0cdb002ab5d50a73ce4df0f7a31a6da376d35d651695c4c035553", + "logs": [], + "blockNumber": 217101551, + "cumulativeGasUsed": "583258", + "status": 1, + "byzantium": true + }, + "args": [ + "0x88AB19C0c7b57EeBa545acbD4368748194cd796B" + ], + "numDeployments": 1, + "solcInputHash": "5b02862a3326bb805f178280f9f88e2d", + "metadata": "{\"compiler\":{\"version\":\"0.8.30+commit.73712a01\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"governor_\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"InvalidGovernor\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"NotGovernor\",\"type\":\"error\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"oldGovernor\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"GovernorUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"user\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"int256\",\"name\":\"offset\",\"type\":\"int256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"arbitrator\",\"type\":\"address\"}],\"name\":\"Offset\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"governor\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"juror\",\"type\":\"address\"},{\"internalType\":\"int256\",\"name\":\"offset\",\"type\":\"int256\"},{\"internalType\":\"address\",\"name\":\"arbitrator\",\"type\":\"address\"}],\"name\":\"setOffset\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newGovernor\",\"type\":\"address\"}],\"name\":\"updateGovernor\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{\"setOffset(address,int256,address)\":{\"params\":{\"arbitrator\":\"The arbitrator address.\",\"juror\":\"The address of the affected juror.\",\"offset\":\"The signed integer offset (+ or -).\"}},\"updateGovernor(address)\":{\"params\":{\"newGovernor\":\"The new governor address.\"}}},\"title\":\"LeaderboardOffset\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"setOffset(address,int256,address)\":{\"notice\":\"Emits an offset event for a given juror.\"},\"updateGovernor(address)\":{\"notice\":\"Updates the governor address.\"}},\"notice\":\"Emits event to offset juror score for coherency\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/governance/LeaderboardOffset.sol\":\"LeaderboardOffset\"},\"evmVersion\":\"cancun\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":850},\"remappings\":[],\"viaIR\":true},\"sources\":{\"src/governance/LeaderboardOffset.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\npragma solidity ^0.8.28;\\n\\n/**\\n * @title LeaderboardOffset\\n * @notice Emits event to offset juror score for coherency\\n */\\ncontract LeaderboardOffset {\\n // ************************************* //\\n // * Events * //\\n // ************************************* //\\n event Offset(address indexed user, int256 offset, address indexed arbitrator);\\n\\n event GovernorUpdated(address indexed oldGovernor, address indexed newGovernor);\\n\\n // ************************************* //\\n // * Storage * //\\n // ************************************* //\\n\\n address public governor;\\n\\n // ************************************* //\\n // * Constructor * //\\n // ************************************* //\\n constructor(address governor_) {\\n if (governor_ == address(0)) revert InvalidGovernor();\\n governor = governor_;\\n }\\n\\n // ************************************* //\\n // * Function Modifiers * //\\n // ************************************* //\\n modifier onlyGovernor() {\\n if (msg.sender != governor) revert NotGovernor();\\n _;\\n }\\n\\n // ************************************* //\\n // * Governance * //\\n // ************************************* //\\n\\n /**\\n * @notice Emits an offset event for a given juror.\\n * @param juror The address of the affected juror.\\n * @param offset The signed integer offset (+ or -).\\n * @param arbitrator The arbitrator address.\\n */\\n function setOffset(address juror, int256 offset, address arbitrator) external onlyGovernor {\\n emit Offset(juror, offset, arbitrator);\\n }\\n\\n /**\\n * @notice Updates the governor address.\\n * @param newGovernor The new governor address.\\n */\\n function updateGovernor(address newGovernor) external onlyGovernor {\\n if (newGovernor == address(0)) revert InvalidGovernor();\\n\\n emit GovernorUpdated(governor, newGovernor);\\n governor = newGovernor;\\n }\\n\\n // ************************************* //\\n // * Errors * //\\n // ************************************* //\\n error NotGovernor();\\n error InvalidGovernor();\\n}\\n\",\"keccak256\":\"0x902182a029a64bf2fa34d2ec8a8ed887e951b25325c9e43afdf5eba08dca5521\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x608034608357601f61029e38819003918201601f19168301916001600160401b03831184841017608757808492602094604052833981010312608357516001600160a01b0381169081900360835780156074575f80546001600160a01b031916919091179055604051610202908161009c8239f35b636630f75560e11b5f5260045ffd5b5f80fd5b634e487b7160e01b5f52604160045260245ffdfe6080806040526004361015610012575f80fd5b5f3560e01c9081630c340a24146101945750806362f384ad146100e15763d5c002611461003d575f80fd5b346100dd5760603660031901126100dd576100566101b6565b604435906001600160a01b0382168092036100dd576001600160a01b035f541633036100b5577ffa924885f0d4e35a861265635470acad0f0bd25cabcc09fcde0c0f07aaa0d3e260206001600160a01b036040519360243585521692a3005b7fee3675d4000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b346100dd5760203660031901126100dd576100fa6101b6565b5f54906001600160a01b038216908133036100b5576001600160a01b031691821561016c57827fffffffffffffffffffffffff0000000000000000000000000000000000000000927f5af6a85e864342d4f108c43dd574d98480c91f1de0ac2a9f66d826dee49bd9bb5f80a316175f55005b7fcc61eeaa000000000000000000000000000000000000000000000000000000005f5260045ffd5b346100dd575f3660031901126100dd576020906001600160a01b035f54168152f35b600435906001600160a01b03821682036100dd5756fea2646970667358221220c134b6c3d9ae5644bb802d1fd43d4318cdef95997eaa60d5389c777c3918dcba64736f6c634300081e0033", + "deployedBytecode": "0x6080806040526004361015610012575f80fd5b5f3560e01c9081630c340a24146101945750806362f384ad146100e15763d5c002611461003d575f80fd5b346100dd5760603660031901126100dd576100566101b6565b604435906001600160a01b0382168092036100dd576001600160a01b035f541633036100b5577ffa924885f0d4e35a861265635470acad0f0bd25cabcc09fcde0c0f07aaa0d3e260206001600160a01b036040519360243585521692a3005b7fee3675d4000000000000000000000000000000000000000000000000000000005f5260045ffd5b5f80fd5b346100dd5760203660031901126100dd576100fa6101b6565b5f54906001600160a01b038216908133036100b5576001600160a01b031691821561016c57827fffffffffffffffffffffffff0000000000000000000000000000000000000000927f5af6a85e864342d4f108c43dd574d98480c91f1de0ac2a9f66d826dee49bd9bb5f80a316175f55005b7fcc61eeaa000000000000000000000000000000000000000000000000000000005f5260045ffd5b346100dd575f3660031901126100dd576020906001600160a01b035f54168152f35b600435906001600160a01b03821682036100dd5756fea2646970667358221220c134b6c3d9ae5644bb802d1fd43d4318cdef95997eaa60d5389c777c3918dcba64736f6c634300081e0033", + "devdoc": { + "kind": "dev", + "methods": { + "setOffset(address,int256,address)": { + "params": { + "arbitrator": "The arbitrator address.", + "juror": "The address of the affected juror.", + "offset": "The signed integer offset (+ or -)." + } + }, + "updateGovernor(address)": { + "params": { + "newGovernor": "The new governor address." + } + } + }, + "title": "LeaderboardOffset", + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "setOffset(address,int256,address)": { + "notice": "Emits an offset event for a given juror." + }, + "updateGovernor(address)": { + "notice": "Updates the governor address." + } + }, + "notice": "Emits event to offset juror score for coherency", + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 18, + "contract": "src/governance/LeaderboardOffset.sol:LeaderboardOffset", + "label": "governor", + "offset": 0, + "slot": "0", + "type": "t_address" + } + ], + "types": { + "t_address": { + "encoding": "inplace", + "label": "address", + "numberOfBytes": "20" + } + } + } +} diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index a69bc5d75..6c8b63e15 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -2,6 +2,7 @@ import * as dotenv from "dotenv"; import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-chai-matchers"; +import "@nomicfoundation/hardhat-verify"; import "@nomiclabs/hardhat-solhint"; import "@typechain/hardhat"; import "hardhat-deploy-tenderly"; @@ -13,6 +14,7 @@ import "hardhat-watcher"; import "hardhat-docgen"; import "hardhat-contract-sizer"; import "hardhat-tracer"; +import "./tasks/verify-all"; require("./scripts/populatePolicyRegistry"); require("./scripts/populateCourts"); require("./scripts/changeOwner"); @@ -298,6 +300,9 @@ const config: HardhatUserConfig = { apiKey: process.env.ETHERSCAN_API_KEY_FIX, }, }, + etherscan: { + apiKey: process.env.ETHERSCAN_API_KEY_FIX, + }, watcher: { compilation: { tasks: ["compile"], diff --git a/contracts/package.json b/contracts/package.json index bec551678..d571c8488 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -80,7 +80,7 @@ "bot:relayer-from-sepolia": "NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-fetch hardhat run ./scripts/disputeRelayerBotFromSepolia.ts", "bot:relayer-from-hardhat": "NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-fetch hardhat run ./scripts/disputeRelayerBotFromHardhat.ts", "bot:disputor": "NODE_NO_WARNINGS=1 yarn hardhat run ./scripts/disputeCreatorBot.ts", - "etherscan-verify": "hardhat etherscan-verify", + "etherscan-verify": "hardhat verify-all", "etherscan-verify-proxies": "scripts/verifyProxies.sh", "sourcify": "hardhat sourcify --write-failing-metadata", "size": "hardhat size-contracts --no-compile", @@ -111,6 +111,7 @@ "@logtail/pino": "^0.5.0", "@nomicfoundation/hardhat-chai-matchers": "^2.1.2", "@nomicfoundation/hardhat-ethers": "^3.1.2", + "@nomicfoundation/hardhat-verify": "^2.0.14", "@nomiclabs/hardhat-solhint": "^4.1.2", "@openzeppelin/upgrades-core": "^1.44.2", "@typechain/ethers-v6": "^0.5.1", diff --git a/contracts/src/governance/LeaderboardOffset.sol b/contracts/src/governance/LeaderboardOffset.sol new file mode 100644 index 000000000..eb57bdc5a --- /dev/null +++ b/contracts/src/governance/LeaderboardOffset.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.28; + +/** + * @title LeaderboardOffset + * @notice Emits event to offset juror score for coherency + */ +contract LeaderboardOffset { + // ************************************* // + // * Events * // + // ************************************* // + event Offset(address indexed user, int256 offset, address indexed arbitrator); + + event GovernorUpdated(address indexed oldGovernor, address indexed newGovernor); + + // ************************************* // + // * Storage * // + // ************************************* // + + address public governor; + + // ************************************* // + // * Constructor * // + // ************************************* // + constructor(address governor_) { + if (governor_ == address(0)) revert InvalidGovernor(); + governor = governor_; + } + + // ************************************* // + // * Function Modifiers * // + // ************************************* // + modifier onlyGovernor() { + if (msg.sender != governor) revert NotGovernor(); + _; + } + + // ************************************* // + // * Governance * // + // ************************************* // + + /** + * @notice Emits an offset event for a given juror. + * @param juror The address of the affected juror. + * @param offset The signed integer offset (+ or -). + * @param arbitrator The arbitrator address. + */ + function setOffset(address juror, int256 offset, address arbitrator) external onlyGovernor { + emit Offset(juror, offset, arbitrator); + } + + /** + * @notice Updates the governor address. + * @param newGovernor The new governor address. + */ + function updateGovernor(address newGovernor) external onlyGovernor { + if (newGovernor == address(0)) revert InvalidGovernor(); + + emit GovernorUpdated(governor, newGovernor); + governor = newGovernor; + } + + // ************************************* // + // * Errors * // + // ************************************* // + error NotGovernor(); + error InvalidGovernor(); +} diff --git a/contracts/tasks/verify-all.ts b/contracts/tasks/verify-all.ts new file mode 100644 index 000000000..b4965abd3 --- /dev/null +++ b/contracts/tasks/verify-all.ts @@ -0,0 +1,42 @@ +import { task } from "hardhat/config"; +import "hardhat-deploy"; + +task("verify-all", "Verify all deployed contracts using Hardhat-Deploy deployments") + .addOptionalParam("contract", "Verify only a specific contract name") + .setAction(async ({ contract }, hre) => { + const { deployments, run, network } = hre; + + console.log(`\nšŸ” Network: ${network.name}`); + + const allDeployments = await deployments.all(); + const entries = Object.entries(allDeployments); + + for (const [name, deployment] of entries) { + if (contract && name !== contract) continue; + + // skip proxy files (we only skip by naming convention) + if (name.endsWith("_Proxy")) { + console.log(`Skipping proxy: ${name}`); + continue; + } + + const address = deployment.address; + const args = deployment.args || []; + + try { + await run("verify:verify", { + address, + constructorArguments: args, + }); + } catch (err: any) { + const msg = err.message || err.toString(); + + if (msg.includes("Already Verified")) { + console.log(` āœ” Already verified\n`); + continue; + } + + console.log(` āŒ Verification failed: ${msg}\n`); + } + } + }); diff --git a/subgraph/.gitignore b/subgraph/.gitignore new file mode 100644 index 000000000..cc4c19c5d --- /dev/null +++ b/subgraph/.gitignore @@ -0,0 +1,3 @@ +.bin +*.wasm +.latest.json \ No newline at end of file diff --git a/subgraph/core/schema.graphql b/subgraph/core/schema.graphql index 0ffeb44d7..7996ba126 100644 --- a/subgraph/core/schema.graphql +++ b/subgraph/core/schema.graphql @@ -79,6 +79,7 @@ type User @entity { totalCoherentVotes: BigInt! totalResolvedVotes: BigInt! coherenceScore: BigInt! + leaderboardOffset: BigInt! votes: [Vote!]! @derivedFrom(field: "juror") contributions: [Contribution!]! @derivedFrom(field: "contributor") evidences: [ClassicEvidence!]! @derivedFrom(field: "sender") diff --git a/subgraph/core/src/LeaderboardOffset.ts b/subgraph/core/src/LeaderboardOffset.ts new file mode 100644 index 000000000..cfda91a56 --- /dev/null +++ b/subgraph/core/src/LeaderboardOffset.ts @@ -0,0 +1,13 @@ +import { log } from "@graphprotocol/graph-ts"; +import { Offset } from "../generated/LeaderboardOffset/LeaderboardOffset"; +import { User } from "../generated/schema"; + +export function handleLeaderboardOffset(event: Offset): void { + const user = User.load(event.params.user.toHexString()); + if (!user) { + log.error(`User {} not found.`, [event.params.user.toHexString()]); + return; + } + user.leaderboardOffset = user.leaderboardOffset.plus(event.params.offset); + user.save(); +} diff --git a/subgraph/core/src/entities/User.ts b/subgraph/core/src/entities/User.ts index 95651dc32..9ed2469c9 100644 --- a/subgraph/core/src/entities/User.ts +++ b/subgraph/core/src/entities/User.ts @@ -40,6 +40,7 @@ export function createUserFromAddress(id: string): User { user.totalCoherentVotes = ZERO; user.totalResolvedVotes = ZERO; user.coherenceScore = ZERO; + user.leaderboardOffset = ZERO; user.save(); return user; diff --git a/subgraph/core/subgraph.template.yaml b/subgraph/core/subgraph.template.yaml index 6f45c81d4..bec5675a8 100644 --- a/subgraph/core/subgraph.template.yaml +++ b/subgraph/core/subgraph.template.yaml @@ -273,3 +273,23 @@ dataSources: - event: StakeSet(indexed address,uint256,uint256,uint256) handler: handleStakeSet file: ./src/SortitionModule.ts + - kind: ethereum + name: LeaderboardOffset + network: _PLACEHOLDER_ + source: + address: "_PLACEHOLDER_" + abi: LeaderboardOffset + startBlock: _PLACEHOLDER_ + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - User + abis: + - name: LeaderboardOffset + file: ../../contracts/deployments/_PLACEHOLDER_/LeaderboardOffset.json + eventHandlers: + - event: Offset(indexed address,int256,indexed address) + handler: handleLeaderboardOffset + file: ./src/LeaderboardOffset.ts diff --git a/subgraph/core/subgraph.yaml b/subgraph/core/subgraph.yaml index 10f76c090..168d0609c 100644 --- a/subgraph/core/subgraph.yaml +++ b/subgraph/core/subgraph.yaml @@ -274,3 +274,23 @@ dataSources: - event: StakeSet(indexed address,uint256,uint256,uint256) handler: handleStakeSet file: ./src/SortitionModule.ts + - kind: ethereum + name: LeaderboardOffset + network: arbitrum-sepolia + source: + address: "0x811eC94d73445Df262D3Bf43571B85caD122bBD7" + abi: LeaderboardOffset + startBlock: 217101551 + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + entities: + - User + abis: + - name: LeaderboardOffset + file: ../../contracts/deployments/arbitrumSepoliaDevnet/LeaderboardOffset.json + eventHandlers: + - event: Offset(indexed address,int256,indexed address) + handler: handleLeaderboardOffset + file: ./src/LeaderboardOffset.ts diff --git a/subgraph/core/tests/leaderboard-offset-utils.ts b/subgraph/core/tests/leaderboard-offset-utils.ts new file mode 100644 index 000000000..a909fa8c5 --- /dev/null +++ b/subgraph/core/tests/leaderboard-offset-utils.ts @@ -0,0 +1,51 @@ +import { Address, BigInt, ethereum } from "@graphprotocol/graph-ts"; +import { newMockEvent } from "matchstick-as/assembly/index"; +import { Offset } from "../generated/LeaderboardOffset/LeaderboardOffset"; +import { ZERO } from "../src/utils"; +import { User } from "../generated/schema"; + +export const createOffsetEvent = (user: Address, offset: BigInt): Offset => { + let mockEvent = newMockEvent(); + let offsetEvent = new Offset( + mockEvent.address, + mockEvent.logIndex, + mockEvent.transactionLogIndex, + mockEvent.logType, + mockEvent.block, + mockEvent.transaction, + mockEvent.parameters, + mockEvent.receipt + ); + + offsetEvent.parameters = new Array(); + + let userParam = new ethereum.EventParam("user", ethereum.Value.fromAddress(user)); + let offsetParam = new ethereum.EventParam("offset", ethereum.Value.fromSignedBigInt(offset)); + let arbitratorParam = new ethereum.EventParam("arbitrator", ethereum.Value.fromAddress(user)); + + offsetEvent.parameters.push(userParam); + offsetEvent.parameters.push(offsetParam); + offsetEvent.parameters.push(arbitratorParam); + + return offsetEvent; +}; + +export const createUser = (address: Address): User => { + const user = new User(address.toHexString()); + user.userAddress = address.toHexString().toLowerCase(); + user.totalStake = ZERO; + user.totalDelayed = ZERO; + user.activeDisputes = ZERO; + user.disputes = []; + user.rounds = []; + user.resolvedDisputes = []; + user.totalResolvedDisputes = ZERO; + user.totalAppealingDisputes = ZERO; + user.totalDisputes = ZERO; + user.totalCoherentVotes = ZERO; + user.totalResolvedVotes = ZERO; + user.coherenceScore = ZERO; + user.leaderboardOffset = ZERO; + user.save(); + return user; +}; diff --git a/subgraph/core/tests/leaderboard-offset.test.ts b/subgraph/core/tests/leaderboard-offset.test.ts new file mode 100644 index 000000000..c6f56cacd --- /dev/null +++ b/subgraph/core/tests/leaderboard-offset.test.ts @@ -0,0 +1,26 @@ +import { assert, test, describe, clearStore, beforeEach } from "matchstick-as/assembly/index"; +import { BigInt, Address } from "@graphprotocol/graph-ts"; +import { createOffsetEvent, createUser } from "./leaderboard-offset-utils"; +import { handleLeaderboardOffset } from "../src/LeaderboardOffset"; + +const address = Address.fromString("0x0000000000000000000000000000000000000001"); + +describe("Handle leaderboard offset", () => { + beforeEach(() => { + clearStore(); + }); + + test("Should add a positive offset to the user's leaderboard score", () => { + const user = createUser(address); + const event = createOffsetEvent(address, BigInt.fromI32(100)); + handleLeaderboardOffset(event); + assert.fieldEquals("User", user.id, "leaderboardOffset", "100"); + }); + + test("Should add a negative offset to the user's leaderboard score", () => { + const user = createUser(address); + const event = createOffsetEvent(address, BigInt.fromI32(-100)); + handleLeaderboardOffset(event); + assert.fieldEquals("User", user.id, "leaderboardOffset", "-100"); + }); +}); diff --git a/subgraph/matchstick.yaml b/subgraph/matchstick.yaml new file mode 100644 index 000000000..4623e448a --- /dev/null +++ b/subgraph/matchstick.yaml @@ -0,0 +1,3 @@ +testsFolder: ./core/tests +libsFolder: ../node_modules +manifestPath: ./core/subgraph.yaml diff --git a/subgraph/package.json b/subgraph/package.json index ffe8758f7..dd6da3a42 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -10,7 +10,7 @@ "update:core:local": "./scripts/update.sh localhost mainnet core/subgraph.yaml", "codegen:core": "graph codegen --output-dir core/generated/ core/subgraph.yaml", "build:core": "graph build --output-dir core/build/ core/subgraph.yaml", - "test:core": "cd core && graph test", + "test: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 kleros-v2-core-devnet -l v$npm_package_version core/subgraph.yaml", "deploy:core:arbitrum-sepolia": "graph deploy kleros-v2-core-testnet -l v$npm_package_version core/subgraph.yaml", diff --git a/yarn.lock b/yarn.lock index 862714530..82306f7fa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4294,7 +4294,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/address@npm:5.8.0, @ethersproject/address@npm:^5.8.0": +"@ethersproject/address@npm:5.8.0, @ethersproject/address@npm:^5.0.2, @ethersproject/address@npm:^5.8.0": version: 5.8.0 resolution: "@ethersproject/address@npm:5.8.0" dependencies: @@ -6605,6 +6605,7 @@ __metadata: "@logtail/pino": "npm:^0.5.0" "@nomicfoundation/hardhat-chai-matchers": "npm:^2.1.2" "@nomicfoundation/hardhat-ethers": "npm:^3.1.2" + "@nomicfoundation/hardhat-verify": "npm:^2.0.14" "@nomiclabs/hardhat-solhint": "npm:^4.1.2" "@openzeppelin/contracts": "npm:^5.5.0" "@openzeppelin/upgrades-core": "npm:^1.44.2" @@ -8669,6 +8670,25 @@ __metadata: languageName: node linkType: hard +"@nomicfoundation/hardhat-verify@npm:^2.0.14": + version: 2.1.3 + resolution: "@nomicfoundation/hardhat-verify@npm:2.1.3" + dependencies: + "@ethersproject/abi": "npm:^5.1.2" + "@ethersproject/address": "npm:^5.0.2" + cbor: "npm:^8.1.0" + debug: "npm:^4.1.1" + lodash.clonedeep: "npm:^4.5.0" + picocolors: "npm:^1.1.0" + semver: "npm:^6.3.0" + table: "npm:^6.8.0" + undici: "npm:^5.14.0" + peerDependencies: + hardhat: ^2.26.0 + checksum: 10/ae3f0a55edae5ffd852dca6f71f50e47cc3f9653832717fca875e181068155c9f5990aa3b1b1d11b448af9a343adc4e8a2256223ba4ddf88d07aad65887e676d + languageName: node + linkType: hard + "@nomicfoundation/slang@npm:^0.18.3": version: 0.18.3 resolution: "@nomicfoundation/slang@npm:0.18.3" @@ -15769,6 +15789,15 @@ __metadata: languageName: node linkType: hard +"cbor@npm:^8.1.0": + version: 8.1.0 + resolution: "cbor@npm:8.1.0" + dependencies: + nofilter: "npm:^3.1.0" + checksum: 10/fc6c6d4f8d14def3a0f2ef111f4fc14b3b0bc91d22ed8fd0eb005095c4699c723a45721e515d713571148d0d965ceeb771f4ad422953cb4e9658b379991b52c9 + languageName: node + linkType: hard + "cborg@npm:^2.0.1": version: 2.0.1 resolution: "cborg@npm:2.0.1" @@ -25767,6 +25796,13 @@ __metadata: languageName: node linkType: hard +"lodash.clonedeep@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.clonedeep@npm:4.5.0" + checksum: 10/957ed243f84ba6791d4992d5c222ffffca339a3b79dbe81d2eaf0c90504160b500641c5a0f56e27630030b18b8e971ea10b44f928a977d5ced3c8948841b555f + languageName: node + linkType: hard + "lodash.debounce@npm:^4.0.8": version: 4.0.8 resolution: "lodash.debounce@npm:4.0.8" @@ -28527,7 +28563,7 @@ __metadata: languageName: node linkType: hard -"nofilter@npm:^3.0.2": +"nofilter@npm:^3.0.2, nofilter@npm:^3.1.0": version: 3.1.0 resolution: "nofilter@npm:3.1.0" checksum: 10/f63d87231dfda4b783db17d75b15aac948f78e65f4f1043096ef441147f6667ff74cd4b3f57ada5dbe240be282d3e9838558ac863a66cb04ef25fff7b2b4be4e @@ -35056,6 +35092,19 @@ __metadata: languageName: node linkType: hard +"table@npm:^6.8.0": + version: 6.9.0 + resolution: "table@npm:6.9.0" + dependencies: + ajv: "npm:^8.0.1" + lodash.truncate: "npm:^4.4.2" + slice-ansi: "npm:^4.0.0" + string-width: "npm:^4.2.3" + strip-ansi: "npm:^6.0.1" + checksum: 10/976da6d89841566e39628d1ba107ffab126964c9390a0a877a7c54ebb08820bf388d28fe9f8dcf354b538f19634a572a506c38a3762081640013a149cc862af9 + languageName: node + linkType: hard + "table@npm:^6.8.1": version: 6.8.1 resolution: "table@npm:6.8.1"