From ad3f1947b38ed7747a4c1f5d2a6b97668e196538 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 6 Jul 2023 18:02:51 +0100 Subject: [PATCH 01/11] feat: dispute relayer bot, wip --- contracts/package.json | 1 + contracts/scripts/disputeRelayerBot.ts | 74 ++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 contracts/scripts/disputeRelayerBot.ts diff --git a/contracts/package.json b/contracts/package.json index 2daa33f77..23ec4bddc 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -26,6 +26,7 @@ "simulate": "hardhat simulate:all", "simulate-local": "hardhat simulate:all --network localhost", "bot:keeper": "NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-fetch hardhat run ./scripts/keeperBot.ts", + "bot:relayer": "NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-fetch hardhat run ./scripts/disputeRelayerBot.ts", "etherscan-verify": "hardhat etherscan-verify", "sourcify": "hardhat sourcify --write-failing-metadata", "size": "hardhat size-contracts --no-compile", diff --git a/contracts/scripts/disputeRelayerBot.ts b/contracts/scripts/disputeRelayerBot.ts new file mode 100644 index 000000000..38de9043b --- /dev/null +++ b/contracts/scripts/disputeRelayerBot.ts @@ -0,0 +1,74 @@ +import { ethers } from "hardhat"; +import hre = require("hardhat"); +import { KlerosCore, PolicyRegistry, ForeignGateway__factory, HomeGateway, TestERC20 } from "../typechain-types"; + +async function main() { + const core = (await ethers.getContract("KlerosCore")) as KlerosCore; + const policyRegistry = (await ethers.getContract("PolicyRegistry")) as PolicyRegistry; + const homeGateway = (await ethers.getContract("HomeGatewayToGnosis")) as HomeGateway; + const dai = (await ethers.getContract("DAI")) as TestERC20; + + const foreignChainProvider = new ethers.providers.JsonRpcProvider(hre.config.networks.chiado.url); + const foreignGatewayDeployment = await hre.companionNetworks.foreignChiado.deployments.get("ForeignGatewayOnGnosis"); + const foreignGateway = await ForeignGateway__factory.connect(foreignGatewayDeployment.address, foreignChainProvider); + + foreignGateway.on( + "CrossChainDisputeOutgoing", + async (foreignBlockHash, foreignArbitrable, foreignDisputeID, choices, extraData) => { + console.log( + "CrossChainDisputeOutgoing: %s %s %s %s %s", + foreignBlockHash, + foreignArbitrable, + foreignDisputeID, + choices, + extraData + ); + + const cost = (await core.functions["arbitrationCost(bytes,address)"](extraData, dai.address)).cost; + + await dai.approve(homeGateway.address, cost); + + await homeGateway.functions[ + "relayCreateDispute((bytes32,uint256,address,uint256,uint256,uint256,string,uint256,bytes),uint256)" + ]( + { + foreignBlockHash: foreignBlockHash, + foreignChainID: 10200, + foreignArbitrable: foreignArbitrable, + foreignDisputeID: foreignDisputeID, + externalDisputeID: ethers.utils.keccak256(ethers.utils.toUtf8Bytes("future of france")), // FIXME + templateId: 1, // FIXME + templateUri: "", // FIXME + choices: choices, + extraData: extraData, + }, + cost + ); + } + ); + + // type Listener = [ eventArg1, ...eventArgN, transactionReceipt ] + + // policyRegistry.on("PolicyUpdate", (courtId, courtName, policy, receipt) => { + // console.log("PolicyUpdate: %d %s %s %O", courtId, courtName, policy, receipt); + // // WARNING: This callback might run more than once if the script is restarted in the same block + // }); + + // core.on("DisputeCreation", (disputeID, arbitrable) => { + // console.log("DisputeCreation: %d %s %s %d %s %s", disputeID, arbitrable); + // // WARNING: This callback might run more than once if the script is restarted in the same block + + // // if phase is staking and minStakingTime has passed, then passPhase() + // }); + + console.log("Listening for events..."); + const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); + await delay(1000000); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); From 2f379295f338bfe941e1fc7d8ee3d54e5fd6ebbb Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 6 Jul 2023 20:59:30 +0100 Subject: [PATCH 02/11] feat: retrieval of the DisputeRequest event, refactored to accommodate other foreign chains --- contracts/hardhat.config.ts | 1 + contracts/package.json | 4 +- contracts/scripts/disputeRelayerBot.ts | 113 ++++++++++-------- .../scripts/disputeRelayerBotFromChiado.ts | 20 ++++ .../scripts/disputeRelayerBotFromGoerli.ts | 19 +++ .../scripts/disputeRelayerBotFromHardhat.ts | 19 +++ 6 files changed, 128 insertions(+), 48 deletions(-) create mode 100644 contracts/scripts/disputeRelayerBotFromChiado.ts create mode 100644 contracts/scripts/disputeRelayerBotFromGoerli.ts create mode 100644 contracts/scripts/disputeRelayerBotFromHardhat.ts diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index b7c6f2cd4..4e11f1b81 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -48,6 +48,7 @@ const config: HardhatUserConfig = { saveDeployments: true, tags: ["test", "local"], companionNetworks: { + home: "localhost", foreign: "localhost", }, }, diff --git a/contracts/package.json b/contracts/package.json index 23ec4bddc..9b75233d4 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -26,7 +26,9 @@ "simulate": "hardhat simulate:all", "simulate-local": "hardhat simulate:all --network localhost", "bot:keeper": "NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-fetch hardhat run ./scripts/keeperBot.ts", - "bot:relayer": "NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-fetch hardhat run ./scripts/disputeRelayerBot.ts", + "bot:relayer-from-chiado": "NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-fetch hardhat run ./scripts/disputeRelayerBotFromChiado.ts", + "bot:relayer-from-goerli": "NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-fetch hardhat run ./scripts/disputeRelayerBotFromGoerli.ts", + "bot:relayer-from-hardhat": "NODE_NO_WARNINGS=1 NODE_OPTIONS=--experimental-fetch hardhat run ./scripts/disputeRelayerBotFromHardhat.ts", "etherscan-verify": "hardhat etherscan-verify", "sourcify": "hardhat sourcify --write-failing-metadata", "size": "hardhat size-contracts --no-compile", diff --git a/contracts/scripts/disputeRelayerBot.ts b/contracts/scripts/disputeRelayerBot.ts index 38de9043b..53f90867e 100644 --- a/contracts/scripts/disputeRelayerBot.ts +++ b/contracts/scripts/disputeRelayerBot.ts @@ -1,20 +1,39 @@ import { ethers } from "hardhat"; import hre = require("hardhat"); -import { KlerosCore, PolicyRegistry, ForeignGateway__factory, HomeGateway, TestERC20 } from "../typechain-types"; +import { + KlerosCore, + ForeignGateway__factory, + HomeGateway, + TestERC20, + IArbitrableV2__factory, +} from "../typechain-types"; +import { DisputeRequestEventObject } from "../typechain-types/src/arbitration/interfaces/IArbitrableV2"; +import { HttpNetworkConfig } from "hardhat/types"; +import { DeploymentsExtension } from "hardhat-deploy/types"; -async function main() { +export default async function main( + foreignNetwork: HttpNetworkConfig, + foreignDeployments: DeploymentsExtension, + foreignGatewayArtifact: string, + homeGatewayArtifact: string, + feeTokenArtifact?: string +) { const core = (await ethers.getContract("KlerosCore")) as KlerosCore; - const policyRegistry = (await ethers.getContract("PolicyRegistry")) as PolicyRegistry; - const homeGateway = (await ethers.getContract("HomeGatewayToGnosis")) as HomeGateway; - const dai = (await ethers.getContract("DAI")) as TestERC20; + const homeGateway = (await ethers.getContract(homeGatewayArtifact)) as HomeGateway; + const feeToken = feeTokenArtifact ? ((await ethers.getContract(feeTokenArtifact)) as TestERC20) : undefined; - const foreignChainProvider = new ethers.providers.JsonRpcProvider(hre.config.networks.chiado.url); - const foreignGatewayDeployment = await hre.companionNetworks.foreignChiado.deployments.get("ForeignGatewayOnGnosis"); + const foreignChainProvider = new ethers.providers.JsonRpcProvider(foreignNetwork.url); + const foreignGatewayDeployment = await foreignDeployments.get(foreignGatewayArtifact); const foreignGateway = await ForeignGateway__factory.connect(foreignGatewayDeployment.address, foreignChainProvider); + const foreignChainId = await foreignChainProvider.getNetwork().then((network) => network.chainId); + const arbitrableInterface = IArbitrableV2__factory.createInterface(); + // Event subscription + // WARNING: The callback might run more than once if the script is restarted in the same block + // type Listener = [ eventArg1, ...eventArgN, transactionReceipt ] foreignGateway.on( "CrossChainDisputeOutgoing", - async (foreignBlockHash, foreignArbitrable, foreignDisputeID, choices, extraData) => { + async (foreignBlockHash, foreignArbitrable, foreignDisputeID, choices, extraData, txReceipt) => { console.log( "CrossChainDisputeOutgoing: %s %s %s %s %s", foreignBlockHash, @@ -23,52 +42,52 @@ async function main() { choices, extraData ); + // console.log("tx receipt: %O", txReceipt); - const cost = (await core.functions["arbitrationCost(bytes,address)"](extraData, dai.address)).cost; + // txReceipt is missing the full logs for this tx so we need to request it here + const fullTxReceipt = await foreignChainProvider.getTransactionReceipt(txReceipt.transactionHash); - await dai.approve(homeGateway.address, cost); + // Retrieve the DisputeRequest event + const disputeRequest = fullTxReceipt.logs + .filter((log) => log.topics[0] === arbitrableInterface.getEventTopic("DisputeRequest")) + .map((log) => arbitrableInterface.parseLog(log).args as unknown as DisputeRequestEventObject)[0]; + console.log("tx events DisputeRequest: %O", disputeRequest); + // TODO: log a warning if there are multiple DisputeRequest events - await homeGateway.functions[ - "relayCreateDispute((bytes32,uint256,address,uint256,uint256,uint256,string,uint256,bytes),uint256)" - ]( - { - foreignBlockHash: foreignBlockHash, - foreignChainID: 10200, - foreignArbitrable: foreignArbitrable, - foreignDisputeID: foreignDisputeID, - externalDisputeID: ethers.utils.keccak256(ethers.utils.toUtf8Bytes("future of france")), // FIXME - templateId: 1, // FIXME - templateUri: "", // FIXME - choices: choices, - extraData: extraData, - }, - cost - ); + const relayCreateDisputeParams = { + foreignBlockHash: foreignBlockHash, + foreignChainID: foreignChainId, + foreignArbitrable: foreignArbitrable, + foreignDisputeID: foreignDisputeID, + externalDisputeID: disputeRequest._externalDisputeID, + templateId: disputeRequest._templateId, + templateUri: disputeRequest._templateUri, + choices: choices, + extraData: extraData, + }; + console.log("Relaying dispute to home chain... %O", relayCreateDisputeParams); + + let tx; + if (feeToken === undefined) { + // Paying in native Arbitrum ETH + const cost = (await core.functions["arbitrationCost(bytes)"](extraData)).cost; + tx = await homeGateway.functions[ + "relayCreateDispute((bytes32,uint256,address,uint256,uint256,uint256,string,uint256,bytes))" + ](relayCreateDisputeParams, { value: cost }); + } else { + // Paying in ERC20 + const cost = (await core.functions["arbitrationCost(bytes,address)"](extraData, feeToken.address)).cost; + await (await feeToken.approve(homeGateway.address, cost)).wait(); + tx = await homeGateway.functions[ + "relayCreateDispute((bytes32,uint256,address,uint256,uint256,uint256,string,uint256,bytes),uint256)" + ](relayCreateDisputeParams, cost); + } + tx = tx.wait(); + console.log("relayCreateDispute txId: %O", tx.transactionHash); } ); - // type Listener = [ eventArg1, ...eventArgN, transactionReceipt ] - - // policyRegistry.on("PolicyUpdate", (courtId, courtName, policy, receipt) => { - // console.log("PolicyUpdate: %d %s %s %O", courtId, courtName, policy, receipt); - // // WARNING: This callback might run more than once if the script is restarted in the same block - // }); - - // core.on("DisputeCreation", (disputeID, arbitrable) => { - // console.log("DisputeCreation: %d %s %s %d %s %s", disputeID, arbitrable); - // // WARNING: This callback might run more than once if the script is restarted in the same block - - // // if phase is staking and minStakingTime has passed, then passPhase() - // }); - console.log("Listening for events..."); const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); await delay(1000000); } - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); diff --git a/contracts/scripts/disputeRelayerBotFromChiado.ts b/contracts/scripts/disputeRelayerBotFromChiado.ts new file mode 100644 index 000000000..a4c46dc81 --- /dev/null +++ b/contracts/scripts/disputeRelayerBotFromChiado.ts @@ -0,0 +1,20 @@ +import hre = require("hardhat"); +import relayer from "./disputeRelayerBot"; +import { HttpNetworkConfig } from "hardhat/types"; + +async function main() { + await relayer( + hre.config.networks.chiado as HttpNetworkConfig, + hre.companionNetworks.foreignChiado.deployments, + "ForeignGatewayOnGnosis", + "HomeGatewayToGnosis", + "DAI" + ); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/contracts/scripts/disputeRelayerBotFromGoerli.ts b/contracts/scripts/disputeRelayerBotFromGoerli.ts new file mode 100644 index 000000000..b9191a578 --- /dev/null +++ b/contracts/scripts/disputeRelayerBotFromGoerli.ts @@ -0,0 +1,19 @@ +import hre = require("hardhat"); +import relayer from "./disputeRelayerBot"; +import { HttpNetworkConfig } from "hardhat/types"; + +async function main() { + await relayer( + hre.config.networks.goerli as HttpNetworkConfig, + hre.companionNetworks.foreignGoerli.deployments, + "ForeignGatewayOnEthereum", + "HomeGatewayToEthereum" + ); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); diff --git a/contracts/scripts/disputeRelayerBotFromHardhat.ts b/contracts/scripts/disputeRelayerBotFromHardhat.ts new file mode 100644 index 000000000..6031f8baf --- /dev/null +++ b/contracts/scripts/disputeRelayerBotFromHardhat.ts @@ -0,0 +1,19 @@ +import hre = require("hardhat"); +import relayer from "./disputeRelayerBot"; +import { HttpNetworkConfig } from "hardhat/types"; + +async function main() { + await relayer( + hre.config.networks.localhost as HttpNetworkConfig, + hre.companionNetworks.foreign.deployments, + "ForeignGatewayOnEthereum", + "HomeGatewayToEthereum" + ); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); From fdaf6091baa410e8fad0fe8edfeefecb343a679c Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 7 Jul 2023 17:47:32 +0100 Subject: [PATCH 03/11] feat: dockerized the bots and set up the restart policy with pm2-runtime --- .vscode/settings.json | 5 +- .../@yarnpkg/plugin-workspace-tools.cjs | 28 ++++++++ .yarnrc.yml | 2 + contracts/.dockerignore | 9 +++ contracts/config/DisputeTemplate.simple.json | 23 ++++++ contracts/deploy/00-home-chain-arbitrable.ts | 2 +- contracts/deploy/03-vea-mock.ts | 2 +- contracts/deploy/04-foreign-arbitrable.ts | 2 +- .../deploy/04-klerosliquid-to-v2-gnosis.ts | 2 +- contracts/hardhat.config.ts | 9 +++ contracts/package.json | 1 + contracts/scripts/disputeRelayerBot.ts | 4 +- contracts/scripts/keeperBot.ts | 3 + cspell.json | 1 + services/keeper-bot/Dockerfile | 26 +++++++ services/keeper-bot/common.yml | 8 +++ services/keeper-bot/docker-compose.yml | 70 +++++++++++++++++++ services/keeper-bot/ecosystem.config.js | 28 ++++++++ yarn.lock | 1 + 19 files changed, 219 insertions(+), 7 deletions(-) create mode 100644 .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs create mode 100644 contracts/.dockerignore create mode 100644 contracts/config/DisputeTemplate.simple.json create mode 100644 services/keeper-bot/Dockerfile create mode 100644 services/keeper-bot/common.yml create mode 100644 services/keeper-bot/docker-compose.yml create mode 100644 services/keeper-bot/ecosystem.config.js diff --git a/.vscode/settings.json b/.vscode/settings.json index 0386cc80f..22896e46e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,8 @@ "solidity-language-server.trace.server.verbosity": "message", "eslint.packageManager": "yarn", "prettier.useEditorConfig": true, - "prettier.configPath": "prettier-config/.prettierrc.js" + "prettier.configPath": "prettier-config/.prettierrc.js", + "cSpell.words": [ + "autorestart" + ] } diff --git a/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs b/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs new file mode 100644 index 000000000..fc27b01d3 --- /dev/null +++ b/.yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs @@ -0,0 +1,28 @@ +/* eslint-disable */ +//prettier-ignore +module.exports = { +name: "@yarnpkg/plugin-workspace-tools", +factory: function (require) { +var plugin=(()=>{var _r=Object.create;var we=Object.defineProperty;var Er=Object.getOwnPropertyDescriptor;var br=Object.getOwnPropertyNames;var xr=Object.getPrototypeOf,Cr=Object.prototype.hasOwnProperty;var W=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(r,t)=>(typeof require<"u"?require:r)[t]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+e+'" is not supported')});var q=(e,r)=>()=>(r||e((r={exports:{}}).exports,r),r.exports),wr=(e,r)=>{for(var t in r)we(e,t,{get:r[t],enumerable:!0})},Je=(e,r,t,n)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of br(r))!Cr.call(e,s)&&s!==t&&we(e,s,{get:()=>r[s],enumerable:!(n=Er(r,s))||n.enumerable});return e};var Be=(e,r,t)=>(t=e!=null?_r(xr(e)):{},Je(r||!e||!e.__esModule?we(t,"default",{value:e,enumerable:!0}):t,e)),Sr=e=>Je(we({},"__esModule",{value:!0}),e);var ve=q(ee=>{"use strict";ee.isInteger=e=>typeof e=="number"?Number.isInteger(e):typeof e=="string"&&e.trim()!==""?Number.isInteger(Number(e)):!1;ee.find=(e,r)=>e.nodes.find(t=>t.type===r);ee.exceedsLimit=(e,r,t=1,n)=>n===!1||!ee.isInteger(e)||!ee.isInteger(r)?!1:(Number(r)-Number(e))/Number(t)>=n;ee.escapeNode=(e,r=0,t)=>{let n=e.nodes[r];!n||(t&&n.type===t||n.type==="open"||n.type==="close")&&n.escaped!==!0&&(n.value="\\"+n.value,n.escaped=!0)};ee.encloseBrace=e=>e.type!=="brace"?!1:e.commas>>0+e.ranges>>0===0?(e.invalid=!0,!0):!1;ee.isInvalidBrace=e=>e.type!=="brace"?!1:e.invalid===!0||e.dollar?!0:e.commas>>0+e.ranges>>0===0||e.open!==!0||e.close!==!0?(e.invalid=!0,!0):!1;ee.isOpenOrClose=e=>e.type==="open"||e.type==="close"?!0:e.open===!0||e.close===!0;ee.reduce=e=>e.reduce((r,t)=>(t.type==="text"&&r.push(t.value),t.type==="range"&&(t.type="text"),r),[]);ee.flatten=(...e)=>{let r=[],t=n=>{for(let s=0;s{"use strict";var tt=ve();rt.exports=(e,r={})=>{let t=(n,s={})=>{let i=r.escapeInvalid&&tt.isInvalidBrace(s),a=n.invalid===!0&&r.escapeInvalid===!0,c="";if(n.value)return(i||a)&&tt.isOpenOrClose(n)?"\\"+n.value:n.value;if(n.value)return n.value;if(n.nodes)for(let p of n.nodes)c+=t(p);return c};return t(e)}});var st=q((Jn,nt)=>{"use strict";nt.exports=function(e){return typeof e=="number"?e-e===0:typeof e=="string"&&e.trim()!==""?Number.isFinite?Number.isFinite(+e):isFinite(+e):!1}});var ht=q((es,pt)=>{"use strict";var at=st(),le=(e,r,t)=>{if(at(e)===!1)throw new TypeError("toRegexRange: expected the first argument to be a number");if(r===void 0||e===r)return String(e);if(at(r)===!1)throw new TypeError("toRegexRange: expected the second argument to be a number.");let n={relaxZeros:!0,...t};typeof n.strictZeros=="boolean"&&(n.relaxZeros=n.strictZeros===!1);let s=String(n.relaxZeros),i=String(n.shorthand),a=String(n.capture),c=String(n.wrap),p=e+":"+r+"="+s+i+a+c;if(le.cache.hasOwnProperty(p))return le.cache[p].result;let m=Math.min(e,r),h=Math.max(e,r);if(Math.abs(m-h)===1){let y=e+"|"+r;return n.capture?`(${y})`:n.wrap===!1?y:`(?:${y})`}let R=ft(e)||ft(r),f={min:e,max:r,a:m,b:h},$=[],_=[];if(R&&(f.isPadded=R,f.maxLen=String(f.max).length),m<0){let y=h<0?Math.abs(h):1;_=it(y,Math.abs(m),f,n),m=f.a=0}return h>=0&&($=it(m,h,f,n)),f.negatives=_,f.positives=$,f.result=vr(_,$,n),n.capture===!0?f.result=`(${f.result})`:n.wrap!==!1&&$.length+_.length>1&&(f.result=`(?:${f.result})`),le.cache[p]=f,f.result};function vr(e,r,t){let n=Me(e,r,"-",!1,t)||[],s=Me(r,e,"",!1,t)||[],i=Me(e,r,"-?",!0,t)||[];return n.concat(i).concat(s).join("|")}function Hr(e,r){let t=1,n=1,s=ut(e,t),i=new Set([r]);for(;e<=s&&s<=r;)i.add(s),t+=1,s=ut(e,t);for(s=ct(r+1,n)-1;e1&&c.count.pop(),c.count.push(h.count[0]),c.string=c.pattern+lt(c.count),a=m+1;continue}t.isPadded&&(R=Or(m,t,n)),h.string=R+h.pattern+lt(h.count),i.push(h),a=m+1,c=h}return i}function Me(e,r,t,n,s){let i=[];for(let a of e){let{string:c}=a;!n&&!ot(r,"string",c)&&i.push(t+c),n&&ot(r,"string",c)&&i.push(t+c)}return i}function Tr(e,r){let t=[];for(let n=0;nr?1:r>e?-1:0}function ot(e,r,t){return e.some(n=>n[r]===t)}function ut(e,r){return Number(String(e).slice(0,-r)+"9".repeat(r))}function ct(e,r){return e-e%Math.pow(10,r)}function lt(e){let[r=0,t=""]=e;return t||r>1?`{${r+(t?","+t:"")}}`:""}function Lr(e,r,t){return`[${e}${r-e===1?"":"-"}${r}]`}function ft(e){return/^-?(0+)\d/.test(e)}function Or(e,r,t){if(!r.isPadded)return e;let n=Math.abs(r.maxLen-String(e).length),s=t.relaxZeros!==!1;switch(n){case 0:return"";case 1:return s?"0?":"0";case 2:return s?"0{0,2}":"00";default:return s?`0{0,${n}}`:`0{${n}}`}}le.cache={};le.clearCache=()=>le.cache={};pt.exports=le});var Ue=q((ts,Et)=>{"use strict";var Nr=W("util"),At=ht(),dt=e=>e!==null&&typeof e=="object"&&!Array.isArray(e),Ir=e=>r=>e===!0?Number(r):String(r),Pe=e=>typeof e=="number"||typeof e=="string"&&e!=="",Ae=e=>Number.isInteger(+e),De=e=>{let r=`${e}`,t=-1;if(r[0]==="-"&&(r=r.slice(1)),r==="0")return!1;for(;r[++t]==="0";);return t>0},Br=(e,r,t)=>typeof e=="string"||typeof r=="string"?!0:t.stringify===!0,Mr=(e,r,t)=>{if(r>0){let n=e[0]==="-"?"-":"";n&&(e=e.slice(1)),e=n+e.padStart(n?r-1:r,"0")}return t===!1?String(e):e},gt=(e,r)=>{let t=e[0]==="-"?"-":"";for(t&&(e=e.slice(1),r--);e.length{e.negatives.sort((a,c)=>ac?1:0),e.positives.sort((a,c)=>ac?1:0);let t=r.capture?"":"?:",n="",s="",i;return e.positives.length&&(n=e.positives.join("|")),e.negatives.length&&(s=`-(${t}${e.negatives.join("|")})`),n&&s?i=`${n}|${s}`:i=n||s,r.wrap?`(${t}${i})`:i},mt=(e,r,t,n)=>{if(t)return At(e,r,{wrap:!1,...n});let s=String.fromCharCode(e);if(e===r)return s;let i=String.fromCharCode(r);return`[${s}-${i}]`},Rt=(e,r,t)=>{if(Array.isArray(e)){let n=t.wrap===!0,s=t.capture?"":"?:";return n?`(${s}${e.join("|")})`:e.join("|")}return At(e,r,t)},yt=(...e)=>new RangeError("Invalid range arguments: "+Nr.inspect(...e)),_t=(e,r,t)=>{if(t.strictRanges===!0)throw yt([e,r]);return[]},Dr=(e,r)=>{if(r.strictRanges===!0)throw new TypeError(`Expected step "${e}" to be a number`);return[]},Ur=(e,r,t=1,n={})=>{let s=Number(e),i=Number(r);if(!Number.isInteger(s)||!Number.isInteger(i)){if(n.strictRanges===!0)throw yt([e,r]);return[]}s===0&&(s=0),i===0&&(i=0);let a=s>i,c=String(e),p=String(r),m=String(t);t=Math.max(Math.abs(t),1);let h=De(c)||De(p)||De(m),R=h?Math.max(c.length,p.length,m.length):0,f=h===!1&&Br(e,r,n)===!1,$=n.transform||Ir(f);if(n.toRegex&&t===1)return mt(gt(e,R),gt(r,R),!0,n);let _={negatives:[],positives:[]},y=T=>_[T<0?"negatives":"positives"].push(Math.abs(T)),E=[],S=0;for(;a?s>=i:s<=i;)n.toRegex===!0&&t>1?y(s):E.push(Mr($(s,S),R,f)),s=a?s-t:s+t,S++;return n.toRegex===!0?t>1?Pr(_,n):Rt(E,null,{wrap:!1,...n}):E},Gr=(e,r,t=1,n={})=>{if(!Ae(e)&&e.length>1||!Ae(r)&&r.length>1)return _t(e,r,n);let s=n.transform||(f=>String.fromCharCode(f)),i=`${e}`.charCodeAt(0),a=`${r}`.charCodeAt(0),c=i>a,p=Math.min(i,a),m=Math.max(i,a);if(n.toRegex&&t===1)return mt(p,m,!1,n);let h=[],R=0;for(;c?i>=a:i<=a;)h.push(s(i,R)),i=c?i-t:i+t,R++;return n.toRegex===!0?Rt(h,null,{wrap:!1,options:n}):h},$e=(e,r,t,n={})=>{if(r==null&&Pe(e))return[e];if(!Pe(e)||!Pe(r))return _t(e,r,n);if(typeof t=="function")return $e(e,r,1,{transform:t});if(dt(t))return $e(e,r,0,t);let s={...n};return s.capture===!0&&(s.wrap=!0),t=t||s.step||1,Ae(t)?Ae(e)&&Ae(r)?Ur(e,r,t,s):Gr(e,r,Math.max(Math.abs(t),1),s):t!=null&&!dt(t)?Dr(t,s):$e(e,r,1,t)};Et.exports=$e});var Ct=q((rs,xt)=>{"use strict";var qr=Ue(),bt=ve(),Kr=(e,r={})=>{let t=(n,s={})=>{let i=bt.isInvalidBrace(s),a=n.invalid===!0&&r.escapeInvalid===!0,c=i===!0||a===!0,p=r.escapeInvalid===!0?"\\":"",m="";if(n.isOpen===!0||n.isClose===!0)return p+n.value;if(n.type==="open")return c?p+n.value:"(";if(n.type==="close")return c?p+n.value:")";if(n.type==="comma")return n.prev.type==="comma"?"":c?n.value:"|";if(n.value)return n.value;if(n.nodes&&n.ranges>0){let h=bt.reduce(n.nodes),R=qr(...h,{...r,wrap:!1,toRegex:!0});if(R.length!==0)return h.length>1&&R.length>1?`(${R})`:R}if(n.nodes)for(let h of n.nodes)m+=t(h,n);return m};return t(e)};xt.exports=Kr});var vt=q((ns,St)=>{"use strict";var Wr=Ue(),wt=He(),he=ve(),fe=(e="",r="",t=!1)=>{let n=[];if(e=[].concat(e),r=[].concat(r),!r.length)return e;if(!e.length)return t?he.flatten(r).map(s=>`{${s}}`):r;for(let s of e)if(Array.isArray(s))for(let i of s)n.push(fe(i,r,t));else for(let i of r)t===!0&&typeof i=="string"&&(i=`{${i}}`),n.push(Array.isArray(i)?fe(s,i,t):s+i);return he.flatten(n)},jr=(e,r={})=>{let t=r.rangeLimit===void 0?1e3:r.rangeLimit,n=(s,i={})=>{s.queue=[];let a=i,c=i.queue;for(;a.type!=="brace"&&a.type!=="root"&&a.parent;)a=a.parent,c=a.queue;if(s.invalid||s.dollar){c.push(fe(c.pop(),wt(s,r)));return}if(s.type==="brace"&&s.invalid!==!0&&s.nodes.length===2){c.push(fe(c.pop(),["{}"]));return}if(s.nodes&&s.ranges>0){let R=he.reduce(s.nodes);if(he.exceedsLimit(...R,r.step,t))throw new RangeError("expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.");let f=Wr(...R,r);f.length===0&&(f=wt(s,r)),c.push(fe(c.pop(),f)),s.nodes=[];return}let p=he.encloseBrace(s),m=s.queue,h=s;for(;h.type!=="brace"&&h.type!=="root"&&h.parent;)h=h.parent,m=h.queue;for(let R=0;R{"use strict";Ht.exports={MAX_LENGTH:1024*64,CHAR_0:"0",CHAR_9:"9",CHAR_UPPERCASE_A:"A",CHAR_LOWERCASE_A:"a",CHAR_UPPERCASE_Z:"Z",CHAR_LOWERCASE_Z:"z",CHAR_LEFT_PARENTHESES:"(",CHAR_RIGHT_PARENTHESES:")",CHAR_ASTERISK:"*",CHAR_AMPERSAND:"&",CHAR_AT:"@",CHAR_BACKSLASH:"\\",CHAR_BACKTICK:"`",CHAR_CARRIAGE_RETURN:"\r",CHAR_CIRCUMFLEX_ACCENT:"^",CHAR_COLON:":",CHAR_COMMA:",",CHAR_DOLLAR:"$",CHAR_DOT:".",CHAR_DOUBLE_QUOTE:'"',CHAR_EQUAL:"=",CHAR_EXCLAMATION_MARK:"!",CHAR_FORM_FEED:"\f",CHAR_FORWARD_SLASH:"/",CHAR_HASH:"#",CHAR_HYPHEN_MINUS:"-",CHAR_LEFT_ANGLE_BRACKET:"<",CHAR_LEFT_CURLY_BRACE:"{",CHAR_LEFT_SQUARE_BRACKET:"[",CHAR_LINE_FEED:` +`,CHAR_NO_BREAK_SPACE:"\xA0",CHAR_PERCENT:"%",CHAR_PLUS:"+",CHAR_QUESTION_MARK:"?",CHAR_RIGHT_ANGLE_BRACKET:">",CHAR_RIGHT_CURLY_BRACE:"}",CHAR_RIGHT_SQUARE_BRACKET:"]",CHAR_SEMICOLON:";",CHAR_SINGLE_QUOTE:"'",CHAR_SPACE:" ",CHAR_TAB:" ",CHAR_UNDERSCORE:"_",CHAR_VERTICAL_LINE:"|",CHAR_ZERO_WIDTH_NOBREAK_SPACE:"\uFEFF"}});var Nt=q((as,Ot)=>{"use strict";var Fr=He(),{MAX_LENGTH:Tt,CHAR_BACKSLASH:Ge,CHAR_BACKTICK:Qr,CHAR_COMMA:Xr,CHAR_DOT:Zr,CHAR_LEFT_PARENTHESES:Yr,CHAR_RIGHT_PARENTHESES:zr,CHAR_LEFT_CURLY_BRACE:Vr,CHAR_RIGHT_CURLY_BRACE:Jr,CHAR_LEFT_SQUARE_BRACKET:kt,CHAR_RIGHT_SQUARE_BRACKET:Lt,CHAR_DOUBLE_QUOTE:en,CHAR_SINGLE_QUOTE:tn,CHAR_NO_BREAK_SPACE:rn,CHAR_ZERO_WIDTH_NOBREAK_SPACE:nn}=$t(),sn=(e,r={})=>{if(typeof e!="string")throw new TypeError("Expected a string");let t=r||{},n=typeof t.maxLength=="number"?Math.min(Tt,t.maxLength):Tt;if(e.length>n)throw new SyntaxError(`Input length (${e.length}), exceeds max characters (${n})`);let s={type:"root",input:e,nodes:[]},i=[s],a=s,c=s,p=0,m=e.length,h=0,R=0,f,$={},_=()=>e[h++],y=E=>{if(E.type==="text"&&c.type==="dot"&&(c.type="text"),c&&c.type==="text"&&E.type==="text"){c.value+=E.value;return}return a.nodes.push(E),E.parent=a,E.prev=c,c=E,E};for(y({type:"bos"});h0){if(a.ranges>0){a.ranges=0;let E=a.nodes.shift();a.nodes=[E,{type:"text",value:Fr(a)}]}y({type:"comma",value:f}),a.commas++;continue}if(f===Zr&&R>0&&a.commas===0){let E=a.nodes;if(R===0||E.length===0){y({type:"text",value:f});continue}if(c.type==="dot"){if(a.range=[],c.value+=f,c.type="range",a.nodes.length!==3&&a.nodes.length!==5){a.invalid=!0,a.ranges=0,c.type="text";continue}a.ranges++,a.args=[];continue}if(c.type==="range"){E.pop();let S=E[E.length-1];S.value+=c.value+f,c=S,a.ranges--;continue}y({type:"dot",value:f});continue}y({type:"text",value:f})}do if(a=i.pop(),a.type!=="root"){a.nodes.forEach(T=>{T.nodes||(T.type==="open"&&(T.isOpen=!0),T.type==="close"&&(T.isClose=!0),T.nodes||(T.type="text"),T.invalid=!0)});let E=i[i.length-1],S=E.nodes.indexOf(a);E.nodes.splice(S,1,...a.nodes)}while(i.length>0);return y({type:"eos"}),s};Ot.exports=sn});var Mt=q((is,Bt)=>{"use strict";var It=He(),an=Ct(),on=vt(),un=Nt(),X=(e,r={})=>{let t=[];if(Array.isArray(e))for(let n of e){let s=X.create(n,r);Array.isArray(s)?t.push(...s):t.push(s)}else t=[].concat(X.create(e,r));return r&&r.expand===!0&&r.nodupes===!0&&(t=[...new Set(t)]),t};X.parse=(e,r={})=>un(e,r);X.stringify=(e,r={})=>It(typeof e=="string"?X.parse(e,r):e,r);X.compile=(e,r={})=>(typeof e=="string"&&(e=X.parse(e,r)),an(e,r));X.expand=(e,r={})=>{typeof e=="string"&&(e=X.parse(e,r));let t=on(e,r);return r.noempty===!0&&(t=t.filter(Boolean)),r.nodupes===!0&&(t=[...new Set(t)]),t};X.create=(e,r={})=>e===""||e.length<3?[e]:r.expand!==!0?X.compile(e,r):X.expand(e,r);Bt.exports=X});var me=q((os,qt)=>{"use strict";var cn=W("path"),se="\\\\/",Pt=`[^${se}]`,ie="\\.",ln="\\+",fn="\\?",Te="\\/",pn="(?=.)",Dt="[^/]",qe=`(?:${Te}|$)`,Ut=`(?:^|${Te})`,Ke=`${ie}{1,2}${qe}`,hn=`(?!${ie})`,dn=`(?!${Ut}${Ke})`,gn=`(?!${ie}{0,1}${qe})`,An=`(?!${Ke})`,mn=`[^.${Te}]`,Rn=`${Dt}*?`,Gt={DOT_LITERAL:ie,PLUS_LITERAL:ln,QMARK_LITERAL:fn,SLASH_LITERAL:Te,ONE_CHAR:pn,QMARK:Dt,END_ANCHOR:qe,DOTS_SLASH:Ke,NO_DOT:hn,NO_DOTS:dn,NO_DOT_SLASH:gn,NO_DOTS_SLASH:An,QMARK_NO_DOT:mn,STAR:Rn,START_ANCHOR:Ut},yn={...Gt,SLASH_LITERAL:`[${se}]`,QMARK:Pt,STAR:`${Pt}*?`,DOTS_SLASH:`${ie}{1,2}(?:[${se}]|$)`,NO_DOT:`(?!${ie})`,NO_DOTS:`(?!(?:^|[${se}])${ie}{1,2}(?:[${se}]|$))`,NO_DOT_SLASH:`(?!${ie}{0,1}(?:[${se}]|$))`,NO_DOTS_SLASH:`(?!${ie}{1,2}(?:[${se}]|$))`,QMARK_NO_DOT:`[^.${se}]`,START_ANCHOR:`(?:^|[${se}])`,END_ANCHOR:`(?:[${se}]|$)`},_n={alnum:"a-zA-Z0-9",alpha:"a-zA-Z",ascii:"\\x00-\\x7F",blank:" \\t",cntrl:"\\x00-\\x1F\\x7F",digit:"0-9",graph:"\\x21-\\x7E",lower:"a-z",print:"\\x20-\\x7E ",punct:"\\-!\"#$%&'()\\*+,./:;<=>?@[\\]^_`{|}~",space:" \\t\\r\\n\\v\\f",upper:"A-Z",word:"A-Za-z0-9_",xdigit:"A-Fa-f0-9"};qt.exports={MAX_LENGTH:1024*64,POSIX_REGEX_SOURCE:_n,REGEX_BACKSLASH:/\\(?![*+?^${}(|)[\]])/g,REGEX_NON_SPECIAL_CHARS:/^[^@![\].,$*+?^{}()|\\/]+/,REGEX_SPECIAL_CHARS:/[-*+?.^${}(|)[\]]/,REGEX_SPECIAL_CHARS_BACKREF:/(\\?)((\W)(\3*))/g,REGEX_SPECIAL_CHARS_GLOBAL:/([-*+?.^${}(|)[\]])/g,REGEX_REMOVE_BACKSLASH:/(?:\[.*?[^\\]\]|\\(?=.))/g,REPLACEMENTS:{"***":"*","**/**":"**","**/**/**":"**"},CHAR_0:48,CHAR_9:57,CHAR_UPPERCASE_A:65,CHAR_LOWERCASE_A:97,CHAR_UPPERCASE_Z:90,CHAR_LOWERCASE_Z:122,CHAR_LEFT_PARENTHESES:40,CHAR_RIGHT_PARENTHESES:41,CHAR_ASTERISK:42,CHAR_AMPERSAND:38,CHAR_AT:64,CHAR_BACKWARD_SLASH:92,CHAR_CARRIAGE_RETURN:13,CHAR_CIRCUMFLEX_ACCENT:94,CHAR_COLON:58,CHAR_COMMA:44,CHAR_DOT:46,CHAR_DOUBLE_QUOTE:34,CHAR_EQUAL:61,CHAR_EXCLAMATION_MARK:33,CHAR_FORM_FEED:12,CHAR_FORWARD_SLASH:47,CHAR_GRAVE_ACCENT:96,CHAR_HASH:35,CHAR_HYPHEN_MINUS:45,CHAR_LEFT_ANGLE_BRACKET:60,CHAR_LEFT_CURLY_BRACE:123,CHAR_LEFT_SQUARE_BRACKET:91,CHAR_LINE_FEED:10,CHAR_NO_BREAK_SPACE:160,CHAR_PERCENT:37,CHAR_PLUS:43,CHAR_QUESTION_MARK:63,CHAR_RIGHT_ANGLE_BRACKET:62,CHAR_RIGHT_CURLY_BRACE:125,CHAR_RIGHT_SQUARE_BRACKET:93,CHAR_SEMICOLON:59,CHAR_SINGLE_QUOTE:39,CHAR_SPACE:32,CHAR_TAB:9,CHAR_UNDERSCORE:95,CHAR_VERTICAL_LINE:124,CHAR_ZERO_WIDTH_NOBREAK_SPACE:65279,SEP:cn.sep,extglobChars(e){return{"!":{type:"negate",open:"(?:(?!(?:",close:`))${e.STAR})`},"?":{type:"qmark",open:"(?:",close:")?"},"+":{type:"plus",open:"(?:",close:")+"},"*":{type:"star",open:"(?:",close:")*"},"@":{type:"at",open:"(?:",close:")"}}},globChars(e){return e===!0?yn:Gt}}});var Re=q(F=>{"use strict";var En=W("path"),bn=process.platform==="win32",{REGEX_BACKSLASH:xn,REGEX_REMOVE_BACKSLASH:Cn,REGEX_SPECIAL_CHARS:wn,REGEX_SPECIAL_CHARS_GLOBAL:Sn}=me();F.isObject=e=>e!==null&&typeof e=="object"&&!Array.isArray(e);F.hasRegexChars=e=>wn.test(e);F.isRegexChar=e=>e.length===1&&F.hasRegexChars(e);F.escapeRegex=e=>e.replace(Sn,"\\$1");F.toPosixSlashes=e=>e.replace(xn,"/");F.removeBackslashes=e=>e.replace(Cn,r=>r==="\\"?"":r);F.supportsLookbehinds=()=>{let e=process.version.slice(1).split(".").map(Number);return e.length===3&&e[0]>=9||e[0]===8&&e[1]>=10};F.isWindows=e=>e&&typeof e.windows=="boolean"?e.windows:bn===!0||En.sep==="\\";F.escapeLast=(e,r,t)=>{let n=e.lastIndexOf(r,t);return n===-1?e:e[n-1]==="\\"?F.escapeLast(e,r,n-1):`${e.slice(0,n)}\\${e.slice(n)}`};F.removePrefix=(e,r={})=>{let t=e;return t.startsWith("./")&&(t=t.slice(2),r.prefix="./"),t};F.wrapOutput=(e,r={},t={})=>{let n=t.contains?"":"^",s=t.contains?"":"$",i=`${n}(?:${e})${s}`;return r.negated===!0&&(i=`(?:^(?!${i}).*$)`),i}});var Yt=q((cs,Zt)=>{"use strict";var Kt=Re(),{CHAR_ASTERISK:We,CHAR_AT:vn,CHAR_BACKWARD_SLASH:ye,CHAR_COMMA:Hn,CHAR_DOT:je,CHAR_EXCLAMATION_MARK:Fe,CHAR_FORWARD_SLASH:Xt,CHAR_LEFT_CURLY_BRACE:Qe,CHAR_LEFT_PARENTHESES:Xe,CHAR_LEFT_SQUARE_BRACKET:$n,CHAR_PLUS:Tn,CHAR_QUESTION_MARK:Wt,CHAR_RIGHT_CURLY_BRACE:kn,CHAR_RIGHT_PARENTHESES:jt,CHAR_RIGHT_SQUARE_BRACKET:Ln}=me(),Ft=e=>e===Xt||e===ye,Qt=e=>{e.isPrefix!==!0&&(e.depth=e.isGlobstar?1/0:1)},On=(e,r)=>{let t=r||{},n=e.length-1,s=t.parts===!0||t.scanToEnd===!0,i=[],a=[],c=[],p=e,m=-1,h=0,R=0,f=!1,$=!1,_=!1,y=!1,E=!1,S=!1,T=!1,L=!1,z=!1,I=!1,re=0,K,g,v={value:"",depth:0,isGlob:!1},k=()=>m>=n,l=()=>p.charCodeAt(m+1),H=()=>(K=g,p.charCodeAt(++m));for(;m0&&(B=p.slice(0,h),p=p.slice(h),R-=h),w&&_===!0&&R>0?(w=p.slice(0,R),o=p.slice(R)):_===!0?(w="",o=p):w=p,w&&w!==""&&w!=="/"&&w!==p&&Ft(w.charCodeAt(w.length-1))&&(w=w.slice(0,-1)),t.unescape===!0&&(o&&(o=Kt.removeBackslashes(o)),w&&T===!0&&(w=Kt.removeBackslashes(w)));let u={prefix:B,input:e,start:h,base:w,glob:o,isBrace:f,isBracket:$,isGlob:_,isExtglob:y,isGlobstar:E,negated:L,negatedExtglob:z};if(t.tokens===!0&&(u.maxDepth=0,Ft(g)||a.push(v),u.tokens=a),t.parts===!0||t.tokens===!0){let M;for(let b=0;b{"use strict";var ke=me(),Z=Re(),{MAX_LENGTH:Le,POSIX_REGEX_SOURCE:Nn,REGEX_NON_SPECIAL_CHARS:In,REGEX_SPECIAL_CHARS_BACKREF:Bn,REPLACEMENTS:zt}=ke,Mn=(e,r)=>{if(typeof r.expandRange=="function")return r.expandRange(...e,r);e.sort();let t=`[${e.join("-")}]`;try{new RegExp(t)}catch{return e.map(s=>Z.escapeRegex(s)).join("..")}return t},de=(e,r)=>`Missing ${e}: "${r}" - use "\\\\${r}" to match literal characters`,Vt=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");e=zt[e]||e;let t={...r},n=typeof t.maxLength=="number"?Math.min(Le,t.maxLength):Le,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);let i={type:"bos",value:"",output:t.prepend||""},a=[i],c=t.capture?"":"?:",p=Z.isWindows(r),m=ke.globChars(p),h=ke.extglobChars(m),{DOT_LITERAL:R,PLUS_LITERAL:f,SLASH_LITERAL:$,ONE_CHAR:_,DOTS_SLASH:y,NO_DOT:E,NO_DOT_SLASH:S,NO_DOTS_SLASH:T,QMARK:L,QMARK_NO_DOT:z,STAR:I,START_ANCHOR:re}=m,K=A=>`(${c}(?:(?!${re}${A.dot?y:R}).)*?)`,g=t.dot?"":E,v=t.dot?L:z,k=t.bash===!0?K(t):I;t.capture&&(k=`(${k})`),typeof t.noext=="boolean"&&(t.noextglob=t.noext);let l={input:e,index:-1,start:0,dot:t.dot===!0,consumed:"",output:"",prefix:"",backtrack:!1,negated:!1,brackets:0,braces:0,parens:0,quotes:0,globstar:!1,tokens:a};e=Z.removePrefix(e,l),s=e.length;let H=[],w=[],B=[],o=i,u,M=()=>l.index===s-1,b=l.peek=(A=1)=>e[l.index+A],V=l.advance=()=>e[++l.index]||"",J=()=>e.slice(l.index+1),Q=(A="",O=0)=>{l.consumed+=A,l.index+=O},Ee=A=>{l.output+=A.output!=null?A.output:A.value,Q(A.value)},Rr=()=>{let A=1;for(;b()==="!"&&(b(2)!=="("||b(3)==="?");)V(),l.start++,A++;return A%2===0?!1:(l.negated=!0,l.start++,!0)},be=A=>{l[A]++,B.push(A)},oe=A=>{l[A]--,B.pop()},C=A=>{if(o.type==="globstar"){let O=l.braces>0&&(A.type==="comma"||A.type==="brace"),d=A.extglob===!0||H.length&&(A.type==="pipe"||A.type==="paren");A.type!=="slash"&&A.type!=="paren"&&!O&&!d&&(l.output=l.output.slice(0,-o.output.length),o.type="star",o.value="*",o.output=k,l.output+=o.output)}if(H.length&&A.type!=="paren"&&(H[H.length-1].inner+=A.value),(A.value||A.output)&&Ee(A),o&&o.type==="text"&&A.type==="text"){o.value+=A.value,o.output=(o.output||"")+A.value;return}A.prev=o,a.push(A),o=A},xe=(A,O)=>{let d={...h[O],conditions:1,inner:""};d.prev=o,d.parens=l.parens,d.output=l.output;let x=(t.capture?"(":"")+d.open;be("parens"),C({type:A,value:O,output:l.output?"":_}),C({type:"paren",extglob:!0,value:V(),output:x}),H.push(d)},yr=A=>{let O=A.close+(t.capture?")":""),d;if(A.type==="negate"){let x=k;A.inner&&A.inner.length>1&&A.inner.includes("/")&&(x=K(t)),(x!==k||M()||/^\)+$/.test(J()))&&(O=A.close=`)$))${x}`),A.inner.includes("*")&&(d=J())&&/^\.[^\\/.]+$/.test(d)&&(O=A.close=`)${d})${x})`),A.prev.type==="bos"&&(l.negatedExtglob=!0)}C({type:"paren",extglob:!0,value:u,output:O}),oe("parens")};if(t.fastpaths!==!1&&!/(^[*!]|[/()[\]{}"])/.test(e)){let A=!1,O=e.replace(Bn,(d,x,P,j,G,Ie)=>j==="\\"?(A=!0,d):j==="?"?x?x+j+(G?L.repeat(G.length):""):Ie===0?v+(G?L.repeat(G.length):""):L.repeat(P.length):j==="."?R.repeat(P.length):j==="*"?x?x+j+(G?k:""):k:x?d:`\\${d}`);return A===!0&&(t.unescape===!0?O=O.replace(/\\/g,""):O=O.replace(/\\+/g,d=>d.length%2===0?"\\\\":d?"\\":"")),O===e&&t.contains===!0?(l.output=e,l):(l.output=Z.wrapOutput(O,l,r),l)}for(;!M();){if(u=V(),u==="\0")continue;if(u==="\\"){let d=b();if(d==="/"&&t.bash!==!0||d==="."||d===";")continue;if(!d){u+="\\",C({type:"text",value:u});continue}let x=/^\\+/.exec(J()),P=0;if(x&&x[0].length>2&&(P=x[0].length,l.index+=P,P%2!==0&&(u+="\\")),t.unescape===!0?u=V():u+=V(),l.brackets===0){C({type:"text",value:u});continue}}if(l.brackets>0&&(u!=="]"||o.value==="["||o.value==="[^")){if(t.posix!==!1&&u===":"){let d=o.value.slice(1);if(d.includes("[")&&(o.posix=!0,d.includes(":"))){let x=o.value.lastIndexOf("["),P=o.value.slice(0,x),j=o.value.slice(x+2),G=Nn[j];if(G){o.value=P+G,l.backtrack=!0,V(),!i.output&&a.indexOf(o)===1&&(i.output=_);continue}}}(u==="["&&b()!==":"||u==="-"&&b()==="]")&&(u=`\\${u}`),u==="]"&&(o.value==="["||o.value==="[^")&&(u=`\\${u}`),t.posix===!0&&u==="!"&&o.value==="["&&(u="^"),o.value+=u,Ee({value:u});continue}if(l.quotes===1&&u!=='"'){u=Z.escapeRegex(u),o.value+=u,Ee({value:u});continue}if(u==='"'){l.quotes=l.quotes===1?0:1,t.keepQuotes===!0&&C({type:"text",value:u});continue}if(u==="("){be("parens"),C({type:"paren",value:u});continue}if(u===")"){if(l.parens===0&&t.strictBrackets===!0)throw new SyntaxError(de("opening","("));let d=H[H.length-1];if(d&&l.parens===d.parens+1){yr(H.pop());continue}C({type:"paren",value:u,output:l.parens?")":"\\)"}),oe("parens");continue}if(u==="["){if(t.nobracket===!0||!J().includes("]")){if(t.nobracket!==!0&&t.strictBrackets===!0)throw new SyntaxError(de("closing","]"));u=`\\${u}`}else be("brackets");C({type:"bracket",value:u});continue}if(u==="]"){if(t.nobracket===!0||o&&o.type==="bracket"&&o.value.length===1){C({type:"text",value:u,output:`\\${u}`});continue}if(l.brackets===0){if(t.strictBrackets===!0)throw new SyntaxError(de("opening","["));C({type:"text",value:u,output:`\\${u}`});continue}oe("brackets");let d=o.value.slice(1);if(o.posix!==!0&&d[0]==="^"&&!d.includes("/")&&(u=`/${u}`),o.value+=u,Ee({value:u}),t.literalBrackets===!1||Z.hasRegexChars(d))continue;let x=Z.escapeRegex(o.value);if(l.output=l.output.slice(0,-o.value.length),t.literalBrackets===!0){l.output+=x,o.value=x;continue}o.value=`(${c}${x}|${o.value})`,l.output+=o.value;continue}if(u==="{"&&t.nobrace!==!0){be("braces");let d={type:"brace",value:u,output:"(",outputIndex:l.output.length,tokensIndex:l.tokens.length};w.push(d),C(d);continue}if(u==="}"){let d=w[w.length-1];if(t.nobrace===!0||!d){C({type:"text",value:u,output:u});continue}let x=")";if(d.dots===!0){let P=a.slice(),j=[];for(let G=P.length-1;G>=0&&(a.pop(),P[G].type!=="brace");G--)P[G].type!=="dots"&&j.unshift(P[G].value);x=Mn(j,t),l.backtrack=!0}if(d.comma!==!0&&d.dots!==!0){let P=l.output.slice(0,d.outputIndex),j=l.tokens.slice(d.tokensIndex);d.value=d.output="\\{",u=x="\\}",l.output=P;for(let G of j)l.output+=G.output||G.value}C({type:"brace",value:u,output:x}),oe("braces"),w.pop();continue}if(u==="|"){H.length>0&&H[H.length-1].conditions++,C({type:"text",value:u});continue}if(u===","){let d=u,x=w[w.length-1];x&&B[B.length-1]==="braces"&&(x.comma=!0,d="|"),C({type:"comma",value:u,output:d});continue}if(u==="/"){if(o.type==="dot"&&l.index===l.start+1){l.start=l.index+1,l.consumed="",l.output="",a.pop(),o=i;continue}C({type:"slash",value:u,output:$});continue}if(u==="."){if(l.braces>0&&o.type==="dot"){o.value==="."&&(o.output=R);let d=w[w.length-1];o.type="dots",o.output+=u,o.value+=u,d.dots=!0;continue}if(l.braces+l.parens===0&&o.type!=="bos"&&o.type!=="slash"){C({type:"text",value:u,output:R});continue}C({type:"dot",value:u,output:R});continue}if(u==="?"){if(!(o&&o.value==="(")&&t.noextglob!==!0&&b()==="("&&b(2)!=="?"){xe("qmark",u);continue}if(o&&o.type==="paren"){let x=b(),P=u;if(x==="<"&&!Z.supportsLookbehinds())throw new Error("Node.js v10 or higher is required for regex lookbehinds");(o.value==="("&&!/[!=<:]/.test(x)||x==="<"&&!/<([!=]|\w+>)/.test(J()))&&(P=`\\${u}`),C({type:"text",value:u,output:P});continue}if(t.dot!==!0&&(o.type==="slash"||o.type==="bos")){C({type:"qmark",value:u,output:z});continue}C({type:"qmark",value:u,output:L});continue}if(u==="!"){if(t.noextglob!==!0&&b()==="("&&(b(2)!=="?"||!/[!=<:]/.test(b(3)))){xe("negate",u);continue}if(t.nonegate!==!0&&l.index===0){Rr();continue}}if(u==="+"){if(t.noextglob!==!0&&b()==="("&&b(2)!=="?"){xe("plus",u);continue}if(o&&o.value==="("||t.regex===!1){C({type:"plus",value:u,output:f});continue}if(o&&(o.type==="bracket"||o.type==="paren"||o.type==="brace")||l.parens>0){C({type:"plus",value:u});continue}C({type:"plus",value:f});continue}if(u==="@"){if(t.noextglob!==!0&&b()==="("&&b(2)!=="?"){C({type:"at",extglob:!0,value:u,output:""});continue}C({type:"text",value:u});continue}if(u!=="*"){(u==="$"||u==="^")&&(u=`\\${u}`);let d=In.exec(J());d&&(u+=d[0],l.index+=d[0].length),C({type:"text",value:u});continue}if(o&&(o.type==="globstar"||o.star===!0)){o.type="star",o.star=!0,o.value+=u,o.output=k,l.backtrack=!0,l.globstar=!0,Q(u);continue}let A=J();if(t.noextglob!==!0&&/^\([^?]/.test(A)){xe("star",u);continue}if(o.type==="star"){if(t.noglobstar===!0){Q(u);continue}let d=o.prev,x=d.prev,P=d.type==="slash"||d.type==="bos",j=x&&(x.type==="star"||x.type==="globstar");if(t.bash===!0&&(!P||A[0]&&A[0]!=="/")){C({type:"star",value:u,output:""});continue}let G=l.braces>0&&(d.type==="comma"||d.type==="brace"),Ie=H.length&&(d.type==="pipe"||d.type==="paren");if(!P&&d.type!=="paren"&&!G&&!Ie){C({type:"star",value:u,output:""});continue}for(;A.slice(0,3)==="/**";){let Ce=e[l.index+4];if(Ce&&Ce!=="/")break;A=A.slice(3),Q("/**",3)}if(d.type==="bos"&&M()){o.type="globstar",o.value+=u,o.output=K(t),l.output=o.output,l.globstar=!0,Q(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&!j&&M()){l.output=l.output.slice(0,-(d.output+o.output).length),d.output=`(?:${d.output}`,o.type="globstar",o.output=K(t)+(t.strictSlashes?")":"|$)"),o.value+=u,l.globstar=!0,l.output+=d.output+o.output,Q(u);continue}if(d.type==="slash"&&d.prev.type!=="bos"&&A[0]==="/"){let Ce=A[1]!==void 0?"|$":"";l.output=l.output.slice(0,-(d.output+o.output).length),d.output=`(?:${d.output}`,o.type="globstar",o.output=`${K(t)}${$}|${$}${Ce})`,o.value+=u,l.output+=d.output+o.output,l.globstar=!0,Q(u+V()),C({type:"slash",value:"/",output:""});continue}if(d.type==="bos"&&A[0]==="/"){o.type="globstar",o.value+=u,o.output=`(?:^|${$}|${K(t)}${$})`,l.output=o.output,l.globstar=!0,Q(u+V()),C({type:"slash",value:"/",output:""});continue}l.output=l.output.slice(0,-o.output.length),o.type="globstar",o.output=K(t),o.value+=u,l.output+=o.output,l.globstar=!0,Q(u);continue}let O={type:"star",value:u,output:k};if(t.bash===!0){O.output=".*?",(o.type==="bos"||o.type==="slash")&&(O.output=g+O.output),C(O);continue}if(o&&(o.type==="bracket"||o.type==="paren")&&t.regex===!0){O.output=u,C(O);continue}(l.index===l.start||o.type==="slash"||o.type==="dot")&&(o.type==="dot"?(l.output+=S,o.output+=S):t.dot===!0?(l.output+=T,o.output+=T):(l.output+=g,o.output+=g),b()!=="*"&&(l.output+=_,o.output+=_)),C(O)}for(;l.brackets>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing","]"));l.output=Z.escapeLast(l.output,"["),oe("brackets")}for(;l.parens>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing",")"));l.output=Z.escapeLast(l.output,"("),oe("parens")}for(;l.braces>0;){if(t.strictBrackets===!0)throw new SyntaxError(de("closing","}"));l.output=Z.escapeLast(l.output,"{"),oe("braces")}if(t.strictSlashes!==!0&&(o.type==="star"||o.type==="bracket")&&C({type:"maybe_slash",value:"",output:`${$}?`}),l.backtrack===!0){l.output="";for(let A of l.tokens)l.output+=A.output!=null?A.output:A.value,A.suffix&&(l.output+=A.suffix)}return l};Vt.fastpaths=(e,r)=>{let t={...r},n=typeof t.maxLength=="number"?Math.min(Le,t.maxLength):Le,s=e.length;if(s>n)throw new SyntaxError(`Input length: ${s}, exceeds maximum allowed length: ${n}`);e=zt[e]||e;let i=Z.isWindows(r),{DOT_LITERAL:a,SLASH_LITERAL:c,ONE_CHAR:p,DOTS_SLASH:m,NO_DOT:h,NO_DOTS:R,NO_DOTS_SLASH:f,STAR:$,START_ANCHOR:_}=ke.globChars(i),y=t.dot?R:h,E=t.dot?f:h,S=t.capture?"":"?:",T={negated:!1,prefix:""},L=t.bash===!0?".*?":$;t.capture&&(L=`(${L})`);let z=g=>g.noglobstar===!0?L:`(${S}(?:(?!${_}${g.dot?m:a}).)*?)`,I=g=>{switch(g){case"*":return`${y}${p}${L}`;case".*":return`${a}${p}${L}`;case"*.*":return`${y}${L}${a}${p}${L}`;case"*/*":return`${y}${L}${c}${p}${E}${L}`;case"**":return y+z(t);case"**/*":return`(?:${y}${z(t)}${c})?${E}${p}${L}`;case"**/*.*":return`(?:${y}${z(t)}${c})?${E}${L}${a}${p}${L}`;case"**/.*":return`(?:${y}${z(t)}${c})?${a}${p}${L}`;default:{let v=/^(.*?)\.(\w+)$/.exec(g);if(!v)return;let k=I(v[1]);return k?k+a+v[2]:void 0}}},re=Z.removePrefix(e,T),K=I(re);return K&&t.strictSlashes!==!0&&(K+=`${c}?`),K};Jt.exports=Vt});var rr=q((fs,tr)=>{"use strict";var Pn=W("path"),Dn=Yt(),Ze=er(),Ye=Re(),Un=me(),Gn=e=>e&&typeof e=="object"&&!Array.isArray(e),D=(e,r,t=!1)=>{if(Array.isArray(e)){let h=e.map(f=>D(f,r,t));return f=>{for(let $ of h){let _=$(f);if(_)return _}return!1}}let n=Gn(e)&&e.tokens&&e.input;if(e===""||typeof e!="string"&&!n)throw new TypeError("Expected pattern to be a non-empty string");let s=r||{},i=Ye.isWindows(r),a=n?D.compileRe(e,r):D.makeRe(e,r,!1,!0),c=a.state;delete a.state;let p=()=>!1;if(s.ignore){let h={...r,ignore:null,onMatch:null,onResult:null};p=D(s.ignore,h,t)}let m=(h,R=!1)=>{let{isMatch:f,match:$,output:_}=D.test(h,a,r,{glob:e,posix:i}),y={glob:e,state:c,regex:a,posix:i,input:h,output:_,match:$,isMatch:f};return typeof s.onResult=="function"&&s.onResult(y),f===!1?(y.isMatch=!1,R?y:!1):p(h)?(typeof s.onIgnore=="function"&&s.onIgnore(y),y.isMatch=!1,R?y:!1):(typeof s.onMatch=="function"&&s.onMatch(y),R?y:!0)};return t&&(m.state=c),m};D.test=(e,r,t,{glob:n,posix:s}={})=>{if(typeof e!="string")throw new TypeError("Expected input to be a string");if(e==="")return{isMatch:!1,output:""};let i=t||{},a=i.format||(s?Ye.toPosixSlashes:null),c=e===n,p=c&&a?a(e):e;return c===!1&&(p=a?a(e):e,c=p===n),(c===!1||i.capture===!0)&&(i.matchBase===!0||i.basename===!0?c=D.matchBase(e,r,t,s):c=r.exec(p)),{isMatch:Boolean(c),match:c,output:p}};D.matchBase=(e,r,t,n=Ye.isWindows(t))=>(r instanceof RegExp?r:D.makeRe(r,t)).test(Pn.basename(e));D.isMatch=(e,r,t)=>D(r,t)(e);D.parse=(e,r)=>Array.isArray(e)?e.map(t=>D.parse(t,r)):Ze(e,{...r,fastpaths:!1});D.scan=(e,r)=>Dn(e,r);D.compileRe=(e,r,t=!1,n=!1)=>{if(t===!0)return e.output;let s=r||{},i=s.contains?"":"^",a=s.contains?"":"$",c=`${i}(?:${e.output})${a}`;e&&e.negated===!0&&(c=`^(?!${c}).*$`);let p=D.toRegex(c,r);return n===!0&&(p.state=e),p};D.makeRe=(e,r={},t=!1,n=!1)=>{if(!e||typeof e!="string")throw new TypeError("Expected a non-empty string");let s={negated:!1,fastpaths:!0};return r.fastpaths!==!1&&(e[0]==="."||e[0]==="*")&&(s.output=Ze.fastpaths(e,r)),s.output||(s=Ze(e,r)),D.compileRe(s,r,t,n)};D.toRegex=(e,r)=>{try{let t=r||{};return new RegExp(e,t.flags||(t.nocase?"i":""))}catch(t){if(r&&r.debug===!0)throw t;return/$^/}};D.constants=Un;tr.exports=D});var sr=q((ps,nr)=>{"use strict";nr.exports=rr()});var cr=q((hs,ur)=>{"use strict";var ir=W("util"),or=Mt(),ae=sr(),ze=Re(),ar=e=>e===""||e==="./",N=(e,r,t)=>{r=[].concat(r),e=[].concat(e);let n=new Set,s=new Set,i=new Set,a=0,c=h=>{i.add(h.output),t&&t.onResult&&t.onResult(h)};for(let h=0;h!n.has(h));if(t&&m.length===0){if(t.failglob===!0)throw new Error(`No matches found for "${r.join(", ")}"`);if(t.nonull===!0||t.nullglob===!0)return t.unescape?r.map(h=>h.replace(/\\/g,"")):r}return m};N.match=N;N.matcher=(e,r)=>ae(e,r);N.isMatch=(e,r,t)=>ae(r,t)(e);N.any=N.isMatch;N.not=(e,r,t={})=>{r=[].concat(r).map(String);let n=new Set,s=[],a=N(e,r,{...t,onResult:c=>{t.onResult&&t.onResult(c),s.push(c.output)}});for(let c of s)a.includes(c)||n.add(c);return[...n]};N.contains=(e,r,t)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${ir.inspect(e)}"`);if(Array.isArray(r))return r.some(n=>N.contains(e,n,t));if(typeof r=="string"){if(ar(e)||ar(r))return!1;if(e.includes(r)||e.startsWith("./")&&e.slice(2).includes(r))return!0}return N.isMatch(e,r,{...t,contains:!0})};N.matchKeys=(e,r,t)=>{if(!ze.isObject(e))throw new TypeError("Expected the first argument to be an object");let n=N(Object.keys(e),r,t),s={};for(let i of n)s[i]=e[i];return s};N.some=(e,r,t)=>{let n=[].concat(e);for(let s of[].concat(r)){let i=ae(String(s),t);if(n.some(a=>i(a)))return!0}return!1};N.every=(e,r,t)=>{let n=[].concat(e);for(let s of[].concat(r)){let i=ae(String(s),t);if(!n.every(a=>i(a)))return!1}return!0};N.all=(e,r,t)=>{if(typeof e!="string")throw new TypeError(`Expected a string: "${ir.inspect(e)}"`);return[].concat(r).every(n=>ae(n,t)(e))};N.capture=(e,r,t)=>{let n=ze.isWindows(t),i=ae.makeRe(String(e),{...t,capture:!0}).exec(n?ze.toPosixSlashes(r):r);if(i)return i.slice(1).map(a=>a===void 0?"":a)};N.makeRe=(...e)=>ae.makeRe(...e);N.scan=(...e)=>ae.scan(...e);N.parse=(e,r)=>{let t=[];for(let n of[].concat(e||[]))for(let s of or(String(n),r))t.push(ae.parse(s,r));return t};N.braces=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");return r&&r.nobrace===!0||!/\{.*\}/.test(e)?[e]:or(e,r)};N.braceExpand=(e,r)=>{if(typeof e!="string")throw new TypeError("Expected a string");return N.braces(e,{...r,expand:!0})};ur.exports=N});var fr=q((ds,lr)=>{"use strict";lr.exports=(e,...r)=>new Promise(t=>{t(e(...r))})});var hr=q((gs,Ve)=>{"use strict";var qn=fr(),pr=e=>{if(e<1)throw new TypeError("Expected `concurrency` to be a number from 1 and up");let r=[],t=0,n=()=>{t--,r.length>0&&r.shift()()},s=(c,p,...m)=>{t++;let h=qn(c,...m);p(h),h.then(n,n)},i=(c,p,...m)=>{tnew Promise(m=>i(c,m,...p));return Object.defineProperties(a,{activeCount:{get:()=>t},pendingCount:{get:()=>r.length}}),a};Ve.exports=pr;Ve.exports.default=pr});var Fn={};wr(Fn,{default:()=>jn});var Se=W("@yarnpkg/cli"),ne=W("@yarnpkg/core"),et=W("@yarnpkg/core"),ue=W("clipanion"),ce=class extends Se.BaseCommand{constructor(){super(...arguments);this.json=ue.Option.Boolean("--json",!1,{description:"Format the output as an NDJSON stream"});this.production=ue.Option.Boolean("--production",!1,{description:"Only install regular dependencies by omitting dev dependencies"});this.all=ue.Option.Boolean("-A,--all",!1,{description:"Install the entire project"});this.workspaces=ue.Option.Rest()}async execute(){let t=await ne.Configuration.find(this.context.cwd,this.context.plugins),{project:n,workspace:s}=await ne.Project.find(t,this.context.cwd),i=await ne.Cache.find(t);await n.restoreInstallState({restoreResolutions:!1});let a;if(this.all)a=new Set(n.workspaces);else if(this.workspaces.length===0){if(!s)throw new Se.WorkspaceRequiredError(n.cwd,this.context.cwd);a=new Set([s])}else a=new Set(this.workspaces.map(p=>n.getWorkspaceByIdent(et.structUtils.parseIdent(p))));for(let p of a)for(let m of this.production?["dependencies"]:ne.Manifest.hardDependencies)for(let h of p.manifest.getForScope(m).values()){let R=n.tryWorkspaceByDescriptor(h);R!==null&&a.add(R)}for(let p of n.workspaces)a.has(p)?this.production&&p.manifest.devDependencies.clear():(p.manifest.installConfig=p.manifest.installConfig||{},p.manifest.installConfig.selfReferences=!1,p.manifest.dependencies.clear(),p.manifest.devDependencies.clear(),p.manifest.peerDependencies.clear(),p.manifest.scripts.clear());return(await ne.StreamReport.start({configuration:t,json:this.json,stdout:this.context.stdout,includeLogs:!0},async p=>{await n.install({cache:i,report:p,persistProject:!1})})).exitCode()}};ce.paths=[["workspaces","focus"]],ce.usage=ue.Command.Usage({category:"Workspace-related commands",description:"install a single workspace and its dependencies",details:"\n This command will run an install as if the specified workspaces (and all other workspaces they depend on) were the only ones in the project. If no workspaces are explicitly listed, the active one will be assumed.\n\n Note that this command is only very moderately useful when using zero-installs, since the cache will contain all the packages anyway - meaning that the only difference between a full install and a focused install would just be a few extra lines in the `.pnp.cjs` file, at the cost of introducing an extra complexity.\n\n If the `-A,--all` flag is set, the entire project will be installed. Combine with `--production` to replicate the old `yarn install --production`.\n "});var Ne=W("@yarnpkg/cli"),ge=W("@yarnpkg/core"),_e=W("@yarnpkg/core"),Y=W("@yarnpkg/core"),gr=W("@yarnpkg/plugin-git"),U=W("clipanion"),Oe=Be(cr()),Ar=W("os"),mr=Be(hr()),te=Be(W("typanion")),pe=class extends Ne.BaseCommand{constructor(){super(...arguments);this.recursive=U.Option.Boolean("-R,--recursive",!1,{description:"Find packages via dependencies/devDependencies instead of using the workspaces field"});this.from=U.Option.Array("--from",[],{description:"An array of glob pattern idents from which to base any recursion"});this.all=U.Option.Boolean("-A,--all",!1,{description:"Run the command on all workspaces of a project"});this.verbose=U.Option.Boolean("-v,--verbose",!1,{description:"Prefix each output line with the name of the originating workspace"});this.parallel=U.Option.Boolean("-p,--parallel",!1,{description:"Run the commands in parallel"});this.interlaced=U.Option.Boolean("-i,--interlaced",!1,{description:"Print the output of commands in real-time instead of buffering it"});this.jobs=U.Option.String("-j,--jobs",{description:"The maximum number of parallel tasks that the execution will be limited to; or `unlimited`",validator:te.isOneOf([te.isEnum(["unlimited"]),te.applyCascade(te.isNumber(),[te.isInteger(),te.isAtLeast(1)])])});this.topological=U.Option.Boolean("-t,--topological",!1,{description:"Run the command after all workspaces it depends on (regular) have finished"});this.topologicalDev=U.Option.Boolean("--topological-dev",!1,{description:"Run the command after all workspaces it depends on (regular + dev) have finished"});this.include=U.Option.Array("--include",[],{description:"An array of glob pattern idents; only matching workspaces will be traversed"});this.exclude=U.Option.Array("--exclude",[],{description:"An array of glob pattern idents; matching workspaces won't be traversed"});this.publicOnly=U.Option.Boolean("--no-private",{description:"Avoid running the command on private workspaces"});this.since=U.Option.String("--since",{description:"Only include workspaces that have been changed since the specified ref.",tolerateBoolean:!0});this.commandName=U.Option.String();this.args=U.Option.Proxy()}async execute(){let t=await ge.Configuration.find(this.context.cwd,this.context.plugins),{project:n,workspace:s}=await ge.Project.find(t,this.context.cwd);if(!this.all&&!s)throw new Ne.WorkspaceRequiredError(n.cwd,this.context.cwd);await n.restoreInstallState();let i=this.cli.process([this.commandName,...this.args]),a=i.path.length===1&&i.path[0]==="run"&&typeof i.scriptName<"u"?i.scriptName:null;if(i.path.length===0)throw new U.UsageError("Invalid subcommand name for iteration - use the 'run' keyword if you wish to execute a script");let c=this.all?n.topLevelWorkspace:s,p=this.since?Array.from(await gr.gitUtils.fetchChangedWorkspaces({ref:this.since,project:n})):[c,...this.from.length>0?c.getRecursiveWorkspaceChildren():[]],m=g=>Oe.default.isMatch(Y.structUtils.stringifyIdent(g.locator),this.from),h=this.from.length>0?p.filter(m):p,R=new Set([...h,...h.map(g=>[...this.recursive?this.since?g.getRecursiveWorkspaceDependents():g.getRecursiveWorkspaceDependencies():g.getRecursiveWorkspaceChildren()]).flat()]),f=[],$=!1;if(a!=null&&a.includes(":")){for(let g of n.workspaces)if(g.manifest.scripts.has(a)&&($=!$,$===!1))break}for(let g of R)a&&!g.manifest.scripts.has(a)&&!$&&!(await ge.scriptUtils.getWorkspaceAccessibleBinaries(g)).has(a)||a===process.env.npm_lifecycle_event&&g.cwd===s.cwd||this.include.length>0&&!Oe.default.isMatch(Y.structUtils.stringifyIdent(g.locator),this.include)||this.exclude.length>0&&Oe.default.isMatch(Y.structUtils.stringifyIdent(g.locator),this.exclude)||this.publicOnly&&g.manifest.private===!0||f.push(g);let _=this.parallel?this.jobs==="unlimited"?1/0:Number(this.jobs)||Math.max(1,(0,Ar.cpus)().length/2):1,y=_===1?!1:this.parallel,E=y?this.interlaced:!0,S=(0,mr.default)(_),T=new Map,L=new Set,z=0,I=null,re=!1,K=await _e.StreamReport.start({configuration:t,stdout:this.context.stdout},async g=>{let v=async(k,{commandIndex:l})=>{if(re)return-1;!y&&this.verbose&&l>1&&g.reportSeparator();let H=Kn(k,{configuration:t,verbose:this.verbose,commandIndex:l}),[w,B]=dr(g,{prefix:H,interlaced:E}),[o,u]=dr(g,{prefix:H,interlaced:E});try{this.verbose&&g.reportInfo(null,`${H} Process started`);let M=Date.now(),b=await this.cli.run([this.commandName,...this.args],{cwd:k.cwd,stdout:w,stderr:o})||0;w.end(),o.end(),await B,await u;let V=Date.now();if(this.verbose){let J=t.get("enableTimers")?`, completed in ${Y.formatUtils.pretty(t,V-M,Y.formatUtils.Type.DURATION)}`:"";g.reportInfo(null,`${H} Process exited (exit code ${b})${J}`)}return b===130&&(re=!0,I=b),b}catch(M){throw w.end(),o.end(),await B,await u,M}};for(let k of f)T.set(k.anchoredLocator.locatorHash,k);for(;T.size>0&&!g.hasErrors();){let k=[];for(let[w,B]of T){if(L.has(B.anchoredDescriptor.descriptorHash))continue;let o=!0;if(this.topological||this.topologicalDev){let u=this.topologicalDev?new Map([...B.manifest.dependencies,...B.manifest.devDependencies]):B.manifest.dependencies;for(let M of u.values()){let b=n.tryWorkspaceByDescriptor(M);if(o=b===null||!T.has(b.anchoredLocator.locatorHash),!o)break}}if(!!o&&(L.add(B.anchoredDescriptor.descriptorHash),k.push(S(async()=>{let u=await v(B,{commandIndex:++z});return T.delete(w),L.delete(B.anchoredDescriptor.descriptorHash),u})),!y))break}if(k.length===0){let w=Array.from(T.values()).map(B=>Y.structUtils.prettyLocator(t,B.anchoredLocator)).join(", ");g.reportError(_e.MessageName.CYCLIC_DEPENDENCIES,`Dependency cycle detected (${w})`);return}let H=(await Promise.all(k)).find(w=>w!==0);I===null&&(I=typeof H<"u"?1:I),(this.topological||this.topologicalDev)&&typeof H<"u"&&g.reportError(_e.MessageName.UNNAMED,"The command failed for workspaces that are depended upon by other workspaces; can't satisfy the dependency graph")}});return I!==null?I:K.exitCode()}};pe.paths=[["workspaces","foreach"]],pe.usage=U.Command.Usage({category:"Workspace-related commands",description:"run a command on all workspaces",details:"\n This command will run a given sub-command on current and all its descendant workspaces. Various flags can alter the exact behavior of the command:\n\n - If `-p,--parallel` is set, the commands will be ran in parallel; they'll by default be limited to a number of parallel tasks roughly equal to half your core number, but that can be overridden via `-j,--jobs`, or disabled by setting `-j unlimited`.\n\n - If `-p,--parallel` and `-i,--interlaced` are both set, Yarn will print the lines from the output as it receives them. If `-i,--interlaced` wasn't set, it would instead buffer the output from each process and print the resulting buffers only after their source processes have exited.\n\n - If `-t,--topological` is set, Yarn will only run the command after all workspaces that it depends on through the `dependencies` field have successfully finished executing. If `--topological-dev` is set, both the `dependencies` and `devDependencies` fields will be considered when figuring out the wait points.\n\n - If `-A,--all` is set, Yarn will run the command on all the workspaces of a project. By default yarn runs the command only on current and all its descendant workspaces.\n\n - If `-R,--recursive` is set, Yarn will find workspaces to run the command on by recursively evaluating `dependencies` and `devDependencies` fields, instead of looking at the `workspaces` fields.\n\n - If `--from` is set, Yarn will use the packages matching the 'from' glob as the starting point for any recursive search.\n\n - If `--since` is set, Yarn will only run the command on workspaces that have been modified since the specified ref. By default Yarn will use the refs specified by the `changesetBaseRefs` configuration option.\n\n - The command may apply to only some workspaces through the use of `--include` which acts as a whitelist. The `--exclude` flag will do the opposite and will be a list of packages that mustn't execute the script. Both flags accept glob patterns (if valid Idents and supported by [micromatch](https://github.com/micromatch/micromatch)). Make sure to escape the patterns, to prevent your own shell from trying to expand them.\n\n Adding the `-v,--verbose` flag will cause Yarn to print more information; in particular the name of the workspace that generated the output will be printed at the front of each line.\n\n If the command is `run` and the script being run does not exist the child workspace will be skipped without error.\n ",examples:[["Publish current and all descendant packages","yarn workspaces foreach npm publish --tolerate-republish"],["Run build script on current and all descendant packages","yarn workspaces foreach run build"],["Run build script on current and all descendant packages in parallel, building package dependencies first","yarn workspaces foreach -pt run build"],["Run build script on several packages and all their dependencies, building dependencies first","yarn workspaces foreach -ptR --from '{workspace-a,workspace-b}' run build"]]});function dr(e,{prefix:r,interlaced:t}){let n=e.createStreamReporter(r),s=new Y.miscUtils.DefaultStream;s.pipe(n,{end:!1}),s.on("finish",()=>{n.end()});let i=new Promise(c=>{n.on("finish",()=>{c(s.active)})});if(t)return[s,i];let a=new Y.miscUtils.BufferStream;return a.pipe(s,{end:!1}),a.on("finish",()=>{s.end()}),[a,i]}function Kn(e,{configuration:r,commandIndex:t,verbose:n}){if(!n)return null;let i=`[${Y.structUtils.stringifyIdent(e.locator)}]:`,a=["#2E86AB","#A23B72","#F18F01","#C73E1D","#CCE2A3"],c=a[t%a.length];return Y.formatUtils.pretty(r,i,c)}var Wn={commands:[ce,pe]},jn=Wn;return Sr(Fn);})(); +/*! + * fill-range + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. + */ +/*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + */ +/*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + */ +return plugin; +} +}; diff --git a/.yarnrc.yml b/.yarnrc.yml index 333a8cf26..fc7f280f8 100644 --- a/.yarnrc.yml +++ b/.yarnrc.yml @@ -7,5 +7,7 @@ plugins: spec: "@yarnpkg/plugin-stage" - path: .yarn/plugins/@yarnpkg/plugin-version.cjs spec: "@yarnpkg/plugin-version" + - path: .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs + spec: "@yarnpkg/plugin-workspace-tools" yarnPath: .yarn/releases/yarn-3.3.1.cjs diff --git a/contracts/.dockerignore b/contracts/.dockerignore new file mode 100644 index 000000000..80d7bffec --- /dev/null +++ b/contracts/.dockerignore @@ -0,0 +1,9 @@ +.env +test +lib +cache +cache_hardhat +config +tenderly.yaml +**/.DS_Store +**/*.log \ No newline at end of file diff --git a/contracts/config/DisputeTemplate.simple.json b/contracts/config/DisputeTemplate.simple.json new file mode 100644 index 000000000..50b3760dc --- /dev/null +++ b/contracts/config/DisputeTemplate.simple.json @@ -0,0 +1,23 @@ +{ + "$schema": "../NewDisputeTemplate.schema.json", + "title": "Let's do this", + "description": "We want to do this: %s", + "question": "Does it comply with the policy?", + "answers": [ + { + "title": "Yes", + "description": "Select this if you agree that it must be done." + }, + { + "title": "No", + "description": "Select this if you do not agree that it must be done." + } + ], + "policyURI": "/ipfs/Qmdvk...rSD6cE/policy.pdf", + "frontendUrl": "https://kleros-v2.netlify.app/#/cases/%s/overview", + "arbitratorChainID": "421613", + "arbitratorAddress": "0xD08Ab99480d02bf9C092828043f611BcDFEA917b", + "category": "Others", + "specification": "KIP001", + "lang": "en_US" +} diff --git a/contracts/deploy/00-home-chain-arbitrable.ts b/contracts/deploy/00-home-chain-arbitrable.ts index 84827f520..62f731fdf 100644 --- a/contracts/deploy/00-home-chain-arbitrable.ts +++ b/contracts/deploy/00-home-chain-arbitrable.ts @@ -1,6 +1,6 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; -import disputeTemplate from "../../kleros-sdk/config/v2-disputetemplate/simple/NewDisputeTemplate.simple.json"; +import disputeTemplate from "../config/DisputeTemplate.simple.json"; enum HomeChains { ARBITRUM_ONE = 42161, diff --git a/contracts/deploy/03-vea-mock.ts b/contracts/deploy/03-vea-mock.ts index c0c51897b..6bf753ce5 100644 --- a/contracts/deploy/03-vea-mock.ts +++ b/contracts/deploy/03-vea-mock.ts @@ -2,7 +2,7 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; import getContractAddress from "../deploy-helpers/getContractAddress"; import { KlerosCore__factory } from "../typechain-types"; -import disputeTemplate from "../../kleros-sdk/config/v2-disputetemplate/simple/NewDisputeTemplate.simple.json"; +import disputeTemplate from "../config/DisputeTemplate.simple.json"; const HARDHAT_NETWORK = 31337; diff --git a/contracts/deploy/04-foreign-arbitrable.ts b/contracts/deploy/04-foreign-arbitrable.ts index 3623aca43..725c5c03c 100644 --- a/contracts/deploy/04-foreign-arbitrable.ts +++ b/contracts/deploy/04-foreign-arbitrable.ts @@ -1,6 +1,6 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; -import disputeTemplate from "../../kleros-sdk/config/v2-disputetemplate/simple/NewDisputeTemplate.simple.json"; +import disputeTemplate from "../config/DisputeTemplate.simple.json"; enum ForeignChains { ETHEREUM_MAINNET = 1, diff --git a/contracts/deploy/04-klerosliquid-to-v2-gnosis.ts b/contracts/deploy/04-klerosliquid-to-v2-gnosis.ts index 49760a685..c96bbf326 100644 --- a/contracts/deploy/04-klerosliquid-to-v2-gnosis.ts +++ b/contracts/deploy/04-klerosliquid-to-v2-gnosis.ts @@ -1,7 +1,7 @@ import { parseUnits, parseEther } from "ethers/lib/utils"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; -import disputeTemplate from "../../kleros-sdk/config/v2-disputetemplate/simple/NewDisputeTemplate.simple.json"; +import disputeTemplate from "../config/DisputeTemplate.simple.json"; enum ForeignChains { GNOSIS_MAINNET = 100, diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 4e11f1b81..47741a36b 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -52,6 +52,15 @@ const config: HardhatUserConfig = { foreign: "localhost", }, }, + dockerhost: { + url: `http://host.docker.internal:8545`, + chainId: 31337, + saveDeployments: true, + tags: ["test", "local"], + companionNetworks: { + foreign: "localhost", + }, + }, mainnetFork: { chainId: 1, url: `http://127.0.0.1:8545`, diff --git a/contracts/package.json b/contracts/package.json index 9b75233d4..0d6016a53 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -57,6 +57,7 @@ "dotenv": "^16.3.1", "ethereumjs-util": "^7.1.5", "ethers": "^5.7.2", + "graphql": "^16.7.1", "graphql-request": "^6.1.0", "hardhat": "^2.15.0", "hardhat-contract-sizer": "^2.10.0", diff --git a/contracts/scripts/disputeRelayerBot.ts b/contracts/scripts/disputeRelayerBot.ts index 53f90867e..fe91235a0 100644 --- a/contracts/scripts/disputeRelayerBot.ts +++ b/contracts/scripts/disputeRelayerBot.ts @@ -88,6 +88,6 @@ export default async function main( ); console.log("Listening for events..."); - const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); - await delay(1000000); + const delay = (duration: string) => new Promise((x) => setTimeout(x, duration)); + await delay("7 days"); } diff --git a/contracts/scripts/keeperBot.ts b/contracts/scripts/keeperBot.ts index 3360c7f4b..d68b44f28 100644 --- a/contracts/scripts/keeperBot.ts +++ b/contracts/scripts/keeperBot.ts @@ -381,6 +381,8 @@ async function main() { return PHASES[await sortition.phase()] === Phase.DRAWING; }; + logger.info("Starting up"); + await sendHeartbeat(); logger.info(`Current phase: ${PHASES[await sortition.phase()]}`); @@ -566,6 +568,7 @@ async function main() { await sendHeartbeat(); + logger.info("Shutting down"); await delay(2000); // Some log messages may be lost otherwise } diff --git a/cspell.json b/cspell.json index 7e9e2a494..8bbcf759d 100644 --- a/cspell.json +++ b/cspell.json @@ -13,6 +13,7 @@ "commitlint", "COOLDOWN", "datetime", + "dockerhost", "Ethfinex", "hearbeat", "IERC", diff --git a/services/keeper-bot/Dockerfile b/services/keeper-bot/Dockerfile new file mode 100644 index 000000000..879a0151b --- /dev/null +++ b/services/keeper-bot/Dockerfile @@ -0,0 +1,26 @@ +FROM --platform=arm64 node:16-bookworm + +WORKDIR /usr/src/app + +RUN npm install pm2 -g + +USER node + +COPY --chown=node:node "./node_modules" "./node_modules" +COPY --chown=node:node "./contracts" "./contracts" +COPY --chown=node:node "./eslint-config" "./eslint-config" +COPY --chown=node:node "./prettier-config" "./prettier-config" +COPY --chown=node:node "./tsconfig" "./tsconfig" +COPY --chown=node:node "./.yarn" "./.yarn" +COPY --chown=node:node [ ".yarnrc.yml", ".nvmrc", ".eslintignore", "package.json", "yarn.lock", "./" ] + +RUN yarn set version 3.3.1 && \ + yarn --version && \ + node --version + +WORKDIR /usr/src/app/contracts + +RUN yarn workspaces focus && \ + yarn build + +ENTRYPOINT [ "yarn" ] diff --git a/services/keeper-bot/common.yml b/services/keeper-bot/common.yml new file mode 100644 index 000000000..1b75154be --- /dev/null +++ b/services/keeper-bot/common.yml @@ -0,0 +1,8 @@ +version: '3.3' +services: + bot-base: + build: + context: ../../ + dockerfile: ./services/keeper-bot/Dockerfile + image: kleros-v2-bots + command: --help \ No newline at end of file diff --git a/services/keeper-bot/docker-compose.yml b/services/keeper-bot/docker-compose.yml new file mode 100644 index 000000000..c52c7ee0e --- /dev/null +++ b/services/keeper-bot/docker-compose.yml @@ -0,0 +1,70 @@ +version: '3' +services: + keeper-bot-testnet: + extends: + file: common.yml + service: bot-base + # command: bot:keeper --network arbitrumGoerli + entrypoint: + [ + "/bin/bash", + "-c", + "pm2-runtime ecosystem.config.js --only keeper-bot-testnet --no-auto-exit" + ] + # entrypoint: + # [ + # "/bin/bash", + # "-c", + # "pm2-runtime ecosystem.config.js --only foo --no-auto-exit" + # ] + env_file: .env.testnet + volumes: + - type: bind + source: ./ecosystem.config.js + target: /usr/src/app/contracts/ecosystem.config.js + + relayer-bot-from-chiado-testnet: + extends: + file: common.yml + service: bot-base + # command: bot:relayer-from-chiado --network arbitrumGoerli + entrypoint: + [ + "/bin/bash", + "-c", + "pm2-runtime ecosystem.config.js --only relayer-bot-from-chiado-testnet --no-auto-exit" + ] + volumes: + - type: bind + source: ./ecosystem.config.js + target: /usr/src/app/contracts/ecosystem.config.js + profiles: + - chiado + env_file: + - .env.testnet + + relayer-bot-from-goerli-testnet: + extends: + file: common.yml + service: bot-base + command: bot:relayer-from-goerli --network arbitrumGoerli + profiles: + - goerli + env_file: + - .env.testnet + + relayer-bot-from-hardhat-host: + extends: + file: common.yml + service: bot-base + command: bot:relayer-from-hardhat --network dockerhost + extra_hosts: + - host.docker.internal:host-gateway + volumes: + - type: bind + source: ../../contracts/deployments/localhost + target: /usr/src/app/contracts/deployments/localhost + profiles: + - hardhat + env_file: + - .env.testnet diff --git a/services/keeper-bot/ecosystem.config.js b/services/keeper-bot/ecosystem.config.js new file mode 100644 index 000000000..bc5d3f4d0 --- /dev/null +++ b/services/keeper-bot/ecosystem.config.js @@ -0,0 +1,28 @@ +module.exports = { + apps: [ + { + name: "foo", + interpreter: "bash", + script: "yarn", + args: "foo", + restart_delay: 3000, + autorestart: true, + }, + { + name: "keeper-bot-testnet", + interpreter: "bash", + script: "yarn", + args: "bot:keeper --network arbitrumGoerli", + restart_delay: 10000, + autorestart: true, + }, + { + name: "relayer-bot-from-chiado-testnet", + interpreter: "bash", + script: "yarn", + args: "bot:relayer-from-chiado --network arbitrumGoerli", + restart_delay: 2000, + autorestart: true, + }, + ], +}; diff --git a/yarn.lock b/yarn.lock index dd8b2bb91..ba43df44d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5170,6 +5170,7 @@ __metadata: dotenv: ^16.3.1 ethereumjs-util: ^7.1.5 ethers: ^5.7.2 + graphql: ^16.7.1 graphql-request: ^6.1.0 hardhat: ^2.15.0 hardhat-contract-sizer: ^2.10.0 From 73b37b7bc66daf79129a3522cf7b53f65f31a1ef Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 7 Jul 2023 18:01:11 +0100 Subject: [PATCH 04/11] refactor: bot folder rename --- contracts/scripts/disputeRelayerBot.ts | 4 ++-- services/{keeper-bot => bots}/Dockerfile | 0 services/{keeper-bot => bots}/common.yml | 2 +- services/{keeper-bot => bots}/docker-compose.yml | 0 services/{keeper-bot => bots}/ecosystem.config.js | 0 5 files changed, 3 insertions(+), 3 deletions(-) rename services/{keeper-bot => bots}/Dockerfile (100%) rename services/{keeper-bot => bots}/common.yml (70%) rename services/{keeper-bot => bots}/docker-compose.yml (100%) rename services/{keeper-bot => bots}/ecosystem.config.js (100%) diff --git a/contracts/scripts/disputeRelayerBot.ts b/contracts/scripts/disputeRelayerBot.ts index fe91235a0..2d94d0269 100644 --- a/contracts/scripts/disputeRelayerBot.ts +++ b/contracts/scripts/disputeRelayerBot.ts @@ -88,6 +88,6 @@ export default async function main( ); console.log("Listening for events..."); - const delay = (duration: string) => new Promise((x) => setTimeout(x, duration)); - await delay("7 days"); + const delay = (ms) => new Promise((x) => setTimeout(x, ms)); + await delay(24 * 60 * 60 * 1000); // 24 hours } diff --git a/services/keeper-bot/Dockerfile b/services/bots/Dockerfile similarity index 100% rename from services/keeper-bot/Dockerfile rename to services/bots/Dockerfile diff --git a/services/keeper-bot/common.yml b/services/bots/common.yml similarity index 70% rename from services/keeper-bot/common.yml rename to services/bots/common.yml index 1b75154be..5762b42ca 100644 --- a/services/keeper-bot/common.yml +++ b/services/bots/common.yml @@ -3,6 +3,6 @@ services: bot-base: build: context: ../../ - dockerfile: ./services/keeper-bot/Dockerfile + dockerfile: ./services/bots/Dockerfile image: kleros-v2-bots command: --help \ No newline at end of file diff --git a/services/keeper-bot/docker-compose.yml b/services/bots/docker-compose.yml similarity index 100% rename from services/keeper-bot/docker-compose.yml rename to services/bots/docker-compose.yml diff --git a/services/keeper-bot/ecosystem.config.js b/services/bots/ecosystem.config.js similarity index 100% rename from services/keeper-bot/ecosystem.config.js rename to services/bots/ecosystem.config.js From 0470843530b384a137d9fab67bc5199a4cf356cd Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Fri, 7 Jul 2023 18:13:58 +0100 Subject: [PATCH 05/11] fix: build size and performance --- services/bots/Dockerfile | 16 +++++++--------- services/bots/ecosystem.config.js | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/services/bots/Dockerfile b/services/bots/Dockerfile index 879a0151b..f21e85d08 100644 --- a/services/bots/Dockerfile +++ b/services/bots/Dockerfile @@ -2,11 +2,8 @@ FROM --platform=arm64 node:16-bookworm WORKDIR /usr/src/app -RUN npm install pm2 -g +RUN npm install -g pm2 -USER node - -COPY --chown=node:node "./node_modules" "./node_modules" COPY --chown=node:node "./contracts" "./contracts" COPY --chown=node:node "./eslint-config" "./eslint-config" COPY --chown=node:node "./prettier-config" "./prettier-config" @@ -16,11 +13,12 @@ COPY --chown=node:node [ ".yarnrc.yml", ".nvmrc", ".eslintignore", "package.json RUN yarn set version 3.3.1 && \ yarn --version && \ - node --version + node --version && \ + cd contracts && \ + yarn workspaces focus && \ + yarn build && \ + chown -R node:node . +USER node WORKDIR /usr/src/app/contracts - -RUN yarn workspaces focus && \ - yarn build - ENTRYPOINT [ "yarn" ] diff --git a/services/bots/ecosystem.config.js b/services/bots/ecosystem.config.js index bc5d3f4d0..cb5937727 100644 --- a/services/bots/ecosystem.config.js +++ b/services/bots/ecosystem.config.js @@ -13,7 +13,7 @@ module.exports = { interpreter: "bash", script: "yarn", args: "bot:keeper --network arbitrumGoerli", - restart_delay: 10000, + restart_delay: 600000, autorestart: true, }, { From 0ecfd813d35aa5413d1f5d59fcfb9fa6ae88f4f9 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Sat, 8 Jul 2023 13:26:42 +0100 Subject: [PATCH 06/11] feat: smaller docker images using alpine, published to github container registry --- .dockerignore | 13 +++++++++++++ contracts/.dockerignore | 9 --------- services/bots/.env.testnet.example | 12 ++++++++++++ services/bots/.gitignore | 2 ++ services/bots/Dockerfile | 7 ++++++- services/bots/common.yml | 5 ++++- services/bots/docker-compose.yml | 6 +++--- services/bots/ecosystem.config.js | 6 +++--- 8 files changed, 43 insertions(+), 17 deletions(-) create mode 100644 .dockerignore delete mode 100644 contracts/.dockerignore create mode 100644 services/bots/.env.testnet.example create mode 100644 services/bots/.gitignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..db0964493 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +.yarn/cache +.yarn/install-state.gz + +contracts/.env +contracts/test +contracts/lib +contracts/cache +contracts/cache_hardhat +contracts/config +contracts/tenderly.yaml + +*/.DS_Store +*/*.log \ No newline at end of file diff --git a/contracts/.dockerignore b/contracts/.dockerignore deleted file mode 100644 index 80d7bffec..000000000 --- a/contracts/.dockerignore +++ /dev/null @@ -1,9 +0,0 @@ -.env -test -lib -cache -cache_hardhat -config -tenderly.yaml -**/.DS_Store -**/*.log \ No newline at end of file diff --git a/services/bots/.env.testnet.example b/services/bots/.env.testnet.example new file mode 100644 index 000000000..d8e4a1178 --- /dev/null +++ b/services/bots/.env.testnet.example @@ -0,0 +1,12 @@ +# Bot account +PRIVATE_KEY=0x000000.....00000 + +# Bot subgraph +SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/alcercu/kleroscoretest + +# Logging +LOG_LEVEL=debug +LOGTAIL_TOKEN= + +# Heartbeat +HEARTBEAT_URL_KEEPER_BOT= diff --git a/services/bots/.gitignore b/services/bots/.gitignore new file mode 100644 index 000000000..16974fb72 --- /dev/null +++ b/services/bots/.gitignore @@ -0,0 +1,2 @@ +.env.testnet +.env.mainnet diff --git a/services/bots/Dockerfile b/services/bots/Dockerfile index f21e85d08..81b459325 100644 --- a/services/bots/Dockerfile +++ b/services/bots/Dockerfile @@ -1,4 +1,4 @@ -FROM --platform=arm64 node:16-bookworm +FROM node:16-alpine WORKDIR /usr/src/app @@ -22,3 +22,8 @@ RUN yarn set version 3.3.1 && \ USER node WORKDIR /usr/src/app/contracts ENTRYPOINT [ "yarn" ] + +LABEL org.opencontainers.image.source=https://github.com/kleros/kleros-v2 +LABEL org.opencontainers.image.title="Kleros v2 Bots" +LABEL org.opencontainers.image.description="Bots for the Kleros v2 arbitration protocol." +LABEL org.opencontainers.image.licenses=MIT diff --git a/services/bots/common.yml b/services/bots/common.yml index 5762b42ca..41268f7f4 100644 --- a/services/bots/common.yml +++ b/services/bots/common.yml @@ -4,5 +4,8 @@ services: build: context: ../../ dockerfile: ./services/bots/Dockerfile - image: kleros-v2-bots + tags: + - kleros-v2-bots + image: ghcr.io/kleros/kleros-v2-bots + pull_policy: missing command: --help \ No newline at end of file diff --git a/services/bots/docker-compose.yml b/services/bots/docker-compose.yml index c52c7ee0e..b07c942d6 100644 --- a/services/bots/docker-compose.yml +++ b/services/bots/docker-compose.yml @@ -7,13 +7,13 @@ services: # command: bot:keeper --network arbitrumGoerli entrypoint: [ - "/bin/bash", + "/bin/sh", "-c", "pm2-runtime ecosystem.config.js --only keeper-bot-testnet --no-auto-exit" ] # entrypoint: # [ - # "/bin/bash", + # "/bin/sh", # "-c", # "pm2-runtime ecosystem.config.js --only foo --no-auto-exit" # ] @@ -30,7 +30,7 @@ services: # command: bot:relayer-from-chiado --network arbitrumGoerli entrypoint: [ - "/bin/bash", + "/bin/sh", "-c", "pm2-runtime ecosystem.config.js --only relayer-bot-from-chiado-testnet --no-auto-exit" ] diff --git a/services/bots/ecosystem.config.js b/services/bots/ecosystem.config.js index cb5937727..3082e41fc 100644 --- a/services/bots/ecosystem.config.js +++ b/services/bots/ecosystem.config.js @@ -2,7 +2,7 @@ module.exports = { apps: [ { name: "foo", - interpreter: "bash", + interpreter: "sh", script: "yarn", args: "foo", restart_delay: 3000, @@ -10,7 +10,7 @@ module.exports = { }, { name: "keeper-bot-testnet", - interpreter: "bash", + interpreter: "sh", script: "yarn", args: "bot:keeper --network arbitrumGoerli", restart_delay: 600000, @@ -18,7 +18,7 @@ module.exports = { }, { name: "relayer-bot-from-chiado-testnet", - interpreter: "bash", + interpreter: "sh", script: "yarn", args: "bot:relayer-from-chiado --network arbitrumGoerli", restart_delay: 2000, From 94eae6f2468075fd034b9e2d735120ab8bddc827 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 1 Aug 2023 20:01:43 +0100 Subject: [PATCH 07/11] feat: skip some disputeIds based on config or if too many jurors, improved withdrawals --- contracts/scripts/keeperBot.ts | 101 +++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 29 deletions(-) diff --git a/contracts/scripts/keeperBot.ts b/contracts/scripts/keeperBot.ts index d68b44f28..3c4cc7181 100644 --- a/contracts/scripts/keeperBot.ts +++ b/contracts/scripts/keeperBot.ts @@ -2,10 +2,10 @@ import { DisputeKitClassic, KlerosCore, PNK, RandomizerRNG, SortitionModule } fr import request from "graphql-request"; import env from "./utils/env"; import loggerFactory from "./utils/logger"; +import { BigNumber } from "ethers"; import hre = require("hardhat"); const { ethers } = hre; -const { BigNumber } = ethers; const MAX_DRAW_ITERATIONS = 30; const MAX_EXECUTE_ITERATIONS = 20; const WAIT_FOR_RNG_DURATION = 5 * 1000; // 5 seconds @@ -13,12 +13,17 @@ const ITERATIONS_COOLDOWN_PERIOD = 20 * 1000; // 20 seconds const HIGH_GAS_LIMIT = { gasLimit: 50000000 }; // 50M gas const HEARTBEAT_URL = env.optionalNoDefault("HEARTBEAT_URL_KEEPER_BOT"); const SUBGRAPH_URL = env.require("SUBGRAPH_URL"); +const MAX_JURORS_PER_DISPUTE = 1000; // Skip disputes with more than this number of jurors +const DISPUTES_TO_SKIP = env + .optional("DISPUTES_TO_SKIP", "") + .split(",") + .map((s) => s.trim()); -const loggerOptions = env.optionalNoDefault("LOGTAIL_TOKEN") +const loggerOptions = env.optionalNoDefault("LOGTAIL_TOKEN_KEEPER_BOT") ? { transportTargetOptions: { target: "@logtail/pino", - options: { sourceToken: env.require("LOGTAIL_TOKEN") }, + options: { sourceToken: env.require("LOGTAIL_TOKEN_KEEPER_BOT") }, level: env.optional("LOG_LEVEL", "info"), }, level: env.optional("LOG_LEVEL", "info"), // for pino-pretty @@ -269,30 +274,48 @@ const executeRuling = async (dispute: { id: string }) => { return success; }; -const withdrawAppealContribution = async (dispute: Dispute, contribution: Contribution): Promise => { +const withdrawAppealContribution = async ( + disputeId: string, + roundId: string, + contribution: Contribution +): Promise => { const { disputeKitClassic: kit } = await getContracts(); let success = false; + let amountWithdrawn = BigNumber.from(0); try { + amountWithdrawn = await kit.callStatic.withdrawFeesAndRewards( + disputeId, + contribution.contributor.id, + roundId, + contribution.choice + ); + } catch (e) { logger.info( - `Withdrawing appeal contribution for dispute #${dispute.id}, round #${dispute.currentRoundIndex}, choice ${contribution.choice} and beneficiary ${contribution.contributor.id} ` + `WithdrawFeesAndRewards: will fail for dispute #${disputeId}, round #${roundId}, choice ${contribution.choice} and beneficiary ${contribution.contributor.id}, skipping` + ); + return success; + } + if (amountWithdrawn.isZero()) { + logger.debug( + `WithdrawFeesAndRewards: no fees or rewards to withdraw for dispute #${disputeId}, round #${roundId}, choice ${contribution.choice} and beneficiary ${contribution.contributor.id}, skipping` + ); + return success; + } + try { + logger.info( + `WithdrawFeesAndRewards: appeal contribution for dispute #${disputeId}, round #${roundId}, choice ${contribution.choice} and beneficiary ${contribution.contributor.id}` ); const gas = ( - await kit.estimateGas.withdrawFeesAndRewards( - dispute.id, - contribution.contributor.id, - dispute.currentRoundIndex, - contribution.choice - ) + await kit.estimateGas.withdrawFeesAndRewards(disputeId, contribution.contributor.id, roundId, contribution.choice) ) .mul(150) .div(100); // 50% extra gas - await kit.withdrawFeesAndRewards( - dispute.id, - contribution.contributor.id, - dispute.currentRoundIndex, - contribution.choice, - { gasLimit: gas } - ); + const tx = await ( + await kit.withdrawFeesAndRewards(disputeId, contribution.contributor.id, roundId, contribution.choice, { + gasLimit: gas, + }) + ).wait(); + logger.info(`WithdrawFeesAndRewards txID: ${tx?.transactionHash}`); success = true; } catch (e) { handleError(e); @@ -321,6 +344,15 @@ const getNumberOfMissingRepartitions = async ( : 2 * drawnJurors.length - repartitions.toNumber(); }; +const filterDisputesToSkip = (disputes: Dispute[]) => { + logger.debug( + `Skipping disputes: ${disputes + .filter((dispute) => DISPUTES_TO_SKIP.includes(dispute.id)) + .map((dispute) => dispute.id)}` + ); + return disputes.filter((dispute) => !DISPUTES_TO_SKIP.includes(dispute.id)); +}; + const mapAsync = (array: T[], callbackfn: (value: T, index: number, array: T[]) => Promise): Promise => { return Promise.all(array.map(callbackfn)); }; @@ -387,18 +419,14 @@ async function main() { logger.info(`Current phase: ${PHASES[await sortition.phase()]}`); - const disputes = await getNonFinalDisputes().catch((e) => handleError(e)); + // Retrieve the disputes which are in a non-final period + let disputes = await getNonFinalDisputes().catch((e) => handleError(e)); if (!disputes) { return; } - for (var dispute of disputes) { - logger.info(`Dispute #${dispute.id}, round #${dispute.currentRoundIndex}, ${dispute.period} period`); - } - - const disputesWithoutJurors = await filterAsync(disputes, async (dispute) => { + let disputesWithoutJurors = await filterAsync(disputes, async (dispute) => { return !(await isDisputeFullyDrawn(dispute)); }); - logger.info(`Disputes needing more jurors: ${disputesWithoutJurors.map((dispute) => dispute.id)}`); // Just a sanity check const numberOfDisputesWithoutJurors = await sortition.disputesWithoutJurors(); @@ -408,6 +436,14 @@ async function main() { logger.error(`SortitionModule.disputesWithoutJurors = ${numberOfDisputesWithoutJurors}`); } + // Skip some disputes + disputes = filterDisputesToSkip(disputes); + disputesWithoutJurors = filterDisputesToSkip(disputesWithoutJurors); + for (var dispute of disputes) { + logger.info(`Dispute #${dispute.id}, round #${dispute.currentRoundIndex}, ${dispute.period} period`); + } + logger.info(`Disputes needing more jurors: ${disputesWithoutJurors.map((dispute) => dispute.id)}`); + if ((await hasMinStakingTimePassed()) && disputesWithoutJurors.length > 0) { // ----------------------------------------------- // // DRAWING ATTEMPT // @@ -433,6 +469,10 @@ async function main() { break; } let numberOfMissingJurors = await getMissingJurors(dispute); + if (numberOfMissingJurors.gt(MAX_JURORS_PER_DISPUTE)) { + logger.warn(`Skipping dispute #${dispute.id} with too many jurors to draw`); + continue; + } do { const drawIterations = Math.min(MAX_DRAW_ITERATIONS, numberOfMissingJurors.toNumber()); logger.info( @@ -483,8 +523,8 @@ async function main() { ); // Disputes union and deduplicate - const unprocessedDisputesInExecution = getUniqueDisputes( - unexecutedDisputes.concat(disputesWithContributionsNotYetWithdrawn).concat(disputes) + let unprocessedDisputesInExecution = filterDisputesToSkip( + getUniqueDisputes(unexecutedDisputes.concat(disputesWithContributionsNotYetWithdrawn).concat(disputes)) ); logger.info(`Disputes not fully executed: ${unprocessedDisputesInExecution.map((dispute) => dispute.id)}`); @@ -542,8 +582,11 @@ async function main() { // Remove duplicates which may have a different contribution amount for the same round, choice and beneficiary contributions = [...new Set(contributions)]; for (var contribution of contributions) { - await withdrawAppealContribution(dispute, contribution); - await delay(ITERATIONS_COOLDOWN_PERIOD); // To avoid spiking the gas price + // Could be improved by pinpointing exactly which round requires a withdrawal, just try all of them for now. + for (var round = BigNumber.from(dispute.currentRoundIndex); round.gte(0); round = round.sub(1)) { + await withdrawAppealContribution(dispute.id, round.toString(), contribution); + await delay(ITERATIONS_COOLDOWN_PERIOD); // To avoid spiking the gas price + } } } From a8e6f537851779f5b5cc642056a22461a281b240 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 1 Aug 2023 22:01:03 +0100 Subject: [PATCH 08/11] feat: added instrumentation for heartbeat and logging to the dispute relayer bot --- contracts/scripts/disputeRelayerBot.ts | 48 ++++++++++++++++++-------- services/bots/.env.testnet.example | 6 +++- services/bots/ecosystem.config.js | 2 +- 3 files changed, 40 insertions(+), 16 deletions(-) diff --git a/contracts/scripts/disputeRelayerBot.ts b/contracts/scripts/disputeRelayerBot.ts index 2d94d0269..40d449184 100644 --- a/contracts/scripts/disputeRelayerBot.ts +++ b/contracts/scripts/disputeRelayerBot.ts @@ -1,4 +1,6 @@ -import { ethers } from "hardhat"; +import env from "./utils/env"; +import loggerFactory from "./utils/logger"; +import { BigNumber } from "ethers"; import hre = require("hardhat"); import { KlerosCore, @@ -11,6 +13,20 @@ import { DisputeRequestEventObject } from "../typechain-types/src/arbitration/in import { HttpNetworkConfig } from "hardhat/types"; import { DeploymentsExtension } from "hardhat-deploy/types"; +const { ethers } = hre; +const HEARTBEAT_URL = env.optionalNoDefault("HEARTBEAT_URL_RELAYER_BOT"); + +const loggerOptions = env.optionalNoDefault("LOGTAIL_TOKEN_RELAYER_BOT") + ? { + transportTargetOptions: { + target: "@logtail/pino", + options: { sourceToken: env.require("LOGTAIL_TOKEN_RELAYER_BOT") }, + level: env.optional("LOG_LEVEL", "info"), + }, + level: env.optional("LOG_LEVEL", "info"), // for pino-pretty + } + : {}; + export default async function main( foreignNetwork: HttpNetworkConfig, foreignDeployments: DeploymentsExtension, @@ -28,21 +44,26 @@ export default async function main( const foreignChainId = await foreignChainProvider.getNetwork().then((network) => network.chainId); const arbitrableInterface = IArbitrableV2__factory.createInterface(); + const logger = loggerFactory.createLogger(loggerOptions).child({ foreignChainId: foreignChainId }); + logger.info(`Listening for events from ${foreignGatewayArtifact}...`); + + if (HEARTBEAT_URL) { + logger.debug("Sending heartbeat"); + fetch(HEARTBEAT_URL); + } else { + logger.debug("Heartbeat not set up, skipping"); + } + // Event subscription // WARNING: The callback might run more than once if the script is restarted in the same block // type Listener = [ eventArg1, ...eventArgN, transactionReceipt ] foreignGateway.on( "CrossChainDisputeOutgoing", async (foreignBlockHash, foreignArbitrable, foreignDisputeID, choices, extraData, txReceipt) => { - console.log( - "CrossChainDisputeOutgoing: %s %s %s %s %s", - foreignBlockHash, - foreignArbitrable, - foreignDisputeID, - choices, - extraData + logger.info( + `CrossChainDisputeOutgoing: ${foreignBlockHash} ${foreignArbitrable} ${foreignDisputeID} ${choices} ${extraData}` ); - // console.log("tx receipt: %O", txReceipt); + // logger.info(`tx receipt: ${JSON.stringify(txReceipt)}`); // txReceipt is missing the full logs for this tx so we need to request it here const fullTxReceipt = await foreignChainProvider.getTransactionReceipt(txReceipt.transactionHash); @@ -51,7 +72,7 @@ export default async function main( const disputeRequest = fullTxReceipt.logs .filter((log) => log.topics[0] === arbitrableInterface.getEventTopic("DisputeRequest")) .map((log) => arbitrableInterface.parseLog(log).args as unknown as DisputeRequestEventObject)[0]; - console.log("tx events DisputeRequest: %O", disputeRequest); + logger.info(`tx events DisputeRequest: ${JSON.stringify(disputeRequest)}`); // TODO: log a warning if there are multiple DisputeRequest events const relayCreateDisputeParams = { @@ -65,7 +86,7 @@ export default async function main( choices: choices, extraData: extraData, }; - console.log("Relaying dispute to home chain... %O", relayCreateDisputeParams); + logger.info(`Relaying dispute to home chain... ${JSON.stringify(relayCreateDisputeParams)}`); let tx; if (feeToken === undefined) { @@ -83,11 +104,10 @@ export default async function main( ](relayCreateDisputeParams, cost); } tx = tx.wait(); - console.log("relayCreateDispute txId: %O", tx.transactionHash); + logger.info(`relayCreateDispute txId: ${tx.transactionHash}`); } ); - console.log("Listening for events..."); const delay = (ms) => new Promise((x) => setTimeout(x, ms)); - await delay(24 * 60 * 60 * 1000); // 24 hours + await delay(60 * 60 * 1000); // 1 hour } diff --git a/services/bots/.env.testnet.example b/services/bots/.env.testnet.example index d8e4a1178..7ac85da62 100644 --- a/services/bots/.env.testnet.example +++ b/services/bots/.env.testnet.example @@ -6,7 +6,11 @@ SUBGRAPH_URL=https://api.thegraph.com/subgraphs/name/alcercu/kleroscoretest # Logging LOG_LEVEL=debug -LOGTAIL_TOKEN= +LOGTAIL_TOKEN_KEEPER_BOT= +LOGTAIL_TOKEN_RELAYER_BOT= # Heartbeat HEARTBEAT_URL_KEEPER_BOT= +HEARTBEAT_URL_RELAYER_BOT= + +DISPUTES_TO_SKIP= \ No newline at end of file diff --git a/services/bots/ecosystem.config.js b/services/bots/ecosystem.config.js index 3082e41fc..7ee79d398 100644 --- a/services/bots/ecosystem.config.js +++ b/services/bots/ecosystem.config.js @@ -21,7 +21,7 @@ module.exports = { interpreter: "sh", script: "yarn", args: "bot:relayer-from-chiado --network arbitrumGoerli", - restart_delay: 2000, + restart_delay: 5000, autorestart: true, }, ], From c69efcc4804f5222a4f7425ed531212d45974143 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 3 Aug 2023 14:43:25 +0100 Subject: [PATCH 09/11] chore: cspell --- .vscode/settings.json | 5 +---- cspell.json | 3 ++- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 22896e46e..0386cc80f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,8 +7,5 @@ "solidity-language-server.trace.server.verbosity": "message", "eslint.packageManager": "yarn", "prettier.useEditorConfig": true, - "prettier.configPath": "prettier-config/.prettierrc.js", - "cSpell.words": [ - "autorestart" - ] + "prettier.configPath": "prettier-config/.prettierrc.js" } diff --git a/cspell.json b/cspell.json index 8bbcf759d..f94489bbf 100644 --- a/cspell.json +++ b/cspell.json @@ -24,7 +24,8 @@ "repartitions", "solhint", "typechain", - "Unslashed" + "Unslashed", + "autorestart" ], "ignoreWords": [], "import": [] From 568e2913588f59d69d24a12b729443ff21181169 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 3 Aug 2023 15:06:02 +0100 Subject: [PATCH 10/11] fix: linter --- contracts/scripts/disputeRelayerBot.ts | 11 ++++++----- contracts/scripts/keeperBot.ts | 4 ++-- services/bots/Dockerfile | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/contracts/scripts/disputeRelayerBot.ts b/contracts/scripts/disputeRelayerBot.ts index 40d449184..ecfdf668c 100644 --- a/contracts/scripts/disputeRelayerBot.ts +++ b/contracts/scripts/disputeRelayerBot.ts @@ -1,6 +1,5 @@ import env from "./utils/env"; import loggerFactory from "./utils/logger"; -import { BigNumber } from "ethers"; import hre = require("hardhat"); import { KlerosCore, @@ -63,17 +62,19 @@ export default async function main( logger.info( `CrossChainDisputeOutgoing: ${foreignBlockHash} ${foreignArbitrable} ${foreignDisputeID} ${choices} ${extraData}` ); - // logger.info(`tx receipt: ${JSON.stringify(txReceipt)}`); + logger.debug(`tx receipt: ${JSON.stringify(txReceipt)}`); // txReceipt is missing the full logs for this tx so we need to request it here const fullTxReceipt = await foreignChainProvider.getTransactionReceipt(txReceipt.transactionHash); // Retrieve the DisputeRequest event - const disputeRequest = fullTxReceipt.logs + const disputeRequests: DisputeRequestEventObject[] = fullTxReceipt.logs .filter((log) => log.topics[0] === arbitrableInterface.getEventTopic("DisputeRequest")) - .map((log) => arbitrableInterface.parseLog(log).args as unknown as DisputeRequestEventObject)[0]; + .map((log) => arbitrableInterface.parseLog(log).args as unknown as DisputeRequestEventObject); + logger.warn(`More than 1 DisputeRequest event: not supported yet, skipping the others events.`); + + const disputeRequest = disputeRequests[0]; logger.info(`tx events DisputeRequest: ${JSON.stringify(disputeRequest)}`); - // TODO: log a warning if there are multiple DisputeRequest events const relayCreateDisputeParams = { foreignBlockHash: foreignBlockHash, diff --git a/contracts/scripts/keeperBot.ts b/contracts/scripts/keeperBot.ts index 3c4cc7181..f1d11f8b7 100644 --- a/contracts/scripts/keeperBot.ts +++ b/contracts/scripts/keeperBot.ts @@ -581,9 +581,9 @@ async function main() { }); // Remove duplicates which may have a different contribution amount for the same round, choice and beneficiary contributions = [...new Set(contributions)]; - for (var contribution of contributions) { + for (let contribution of contributions) { // Could be improved by pinpointing exactly which round requires a withdrawal, just try all of them for now. - for (var round = BigNumber.from(dispute.currentRoundIndex); round.gte(0); round = round.sub(1)) { + for (let round = BigNumber.from(dispute.currentRoundIndex); round.gte(0); round = round.sub(1)) { await withdrawAppealContribution(dispute.id, round.toString(), contribution); await delay(ITERATIONS_COOLDOWN_PERIOD); // To avoid spiking the gas price } diff --git a/services/bots/Dockerfile b/services/bots/Dockerfile index 81b459325..54056ad73 100644 --- a/services/bots/Dockerfile +++ b/services/bots/Dockerfile @@ -2,7 +2,7 @@ FROM node:16-alpine WORKDIR /usr/src/app -RUN npm install -g pm2 +RUN npm install --ignore-scripts -g pm2 COPY --chown=node:node "./contracts" "./contracts" COPY --chown=node:node "./eslint-config" "./eslint-config" From 058069ef55f5543a1bf285e27c40c4adf27cfdac Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Thu, 3 Aug 2023 15:18:38 +0100 Subject: [PATCH 11/11] fix: linter about property shorthand in object literal --- contracts/scripts/disputeRelayerBot.ts | 16 ++++++++-------- contracts/scripts/keeperBot.ts | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/scripts/disputeRelayerBot.ts b/contracts/scripts/disputeRelayerBot.ts index ecfdf668c..9437bdf52 100644 --- a/contracts/scripts/disputeRelayerBot.ts +++ b/contracts/scripts/disputeRelayerBot.ts @@ -40,10 +40,10 @@ export default async function main( const foreignChainProvider = new ethers.providers.JsonRpcProvider(foreignNetwork.url); const foreignGatewayDeployment = await foreignDeployments.get(foreignGatewayArtifact); const foreignGateway = await ForeignGateway__factory.connect(foreignGatewayDeployment.address, foreignChainProvider); - const foreignChainId = await foreignChainProvider.getNetwork().then((network) => network.chainId); + const foreignChainID = await foreignChainProvider.getNetwork().then((network) => network.chainId); const arbitrableInterface = IArbitrableV2__factory.createInterface(); - const logger = loggerFactory.createLogger(loggerOptions).child({ foreignChainId: foreignChainId }); + const logger = loggerFactory.createLogger(loggerOptions).child({ foreignChainId: foreignChainID }); logger.info(`Listening for events from ${foreignGatewayArtifact}...`); if (HEARTBEAT_URL) { @@ -77,15 +77,15 @@ export default async function main( logger.info(`tx events DisputeRequest: ${JSON.stringify(disputeRequest)}`); const relayCreateDisputeParams = { - foreignBlockHash: foreignBlockHash, - foreignChainID: foreignChainId, - foreignArbitrable: foreignArbitrable, - foreignDisputeID: foreignDisputeID, + foreignBlockHash, + foreignChainID, + foreignArbitrable, + foreignDisputeID, externalDisputeID: disputeRequest._externalDisputeID, templateId: disputeRequest._templateId, templateUri: disputeRequest._templateUri, - choices: choices, - extraData: extraData, + choices, + extraData, }; logger.info(`Relaying dispute to home chain... ${JSON.stringify(relayCreateDisputeParams)}`); diff --git a/contracts/scripts/keeperBot.ts b/contracts/scripts/keeperBot.ts index f1d11f8b7..9cf8d0453 100644 --- a/contracts/scripts/keeperBot.ts +++ b/contracts/scripts/keeperBot.ts @@ -523,7 +523,7 @@ async function main() { ); // Disputes union and deduplicate - let unprocessedDisputesInExecution = filterDisputesToSkip( + const unprocessedDisputesInExecution = filterDisputesToSkip( getUniqueDisputes(unexecutedDisputes.concat(disputesWithContributionsNotYetWithdrawn).concat(disputes)) ); logger.info(`Disputes not fully executed: ${unprocessedDisputesInExecution.map((dispute) => dispute.id)}`);