Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dispute relayer bot and dockerization #1024

Merged
merged 11 commits into from
Aug 3, 2023
Merged
13 changes: 13 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions .yarn/plugins/@yarnpkg/plugin-workspace-tools.cjs

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
23 changes: 23 additions & 0 deletions contracts/config/DisputeTemplate.simple.json
Original file line number Diff line number Diff line change
@@ -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"
}
2 changes: 1 addition & 1 deletion contracts/deploy/00-home-chain-arbitrable.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
2 changes: 1 addition & 1 deletion contracts/deploy/03-vea-mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
2 changes: 1 addition & 1 deletion contracts/deploy/04-foreign-arbitrable.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
2 changes: 1 addition & 1 deletion contracts/deploy/04-klerosliquid-to-v2-gnosis.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
10 changes: 10 additions & 0 deletions contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ const config: HardhatUserConfig = {
chainId: 31337,
saveDeployments: true,
tags: ["test", "local"],
companionNetworks: {
home: "localhost",
foreign: "localhost",
},
},
dockerhost: {
url: `http://host.docker.internal:8545`,
chainId: 31337,
saveDeployments: true,
tags: ["test", "local"],
companionNetworks: {
foreign: "localhost",
},
Expand Down
4 changes: 4 additions & 0 deletions contracts/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +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-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",
Expand Down Expand Up @@ -54,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",
Expand Down
114 changes: 114 additions & 0 deletions contracts/scripts/disputeRelayerBot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import env from "./utils/env";
import loggerFactory from "./utils/logger";
import hre = require("hardhat");
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";

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,
foreignGatewayArtifact: string,
homeGatewayArtifact: string,
feeTokenArtifact?: string
) {
const core = (await ethers.getContract("KlerosCore")) as KlerosCore;
const homeGateway = (await ethers.getContract(homeGatewayArtifact)) as HomeGateway;
const feeToken = feeTokenArtifact ? ((await ethers.getContract(feeTokenArtifact)) as TestERC20) : undefined;

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();

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) => {
logger.info(
`CrossChainDisputeOutgoing: ${foreignBlockHash} ${foreignArbitrable} ${foreignDisputeID} ${choices} ${extraData}`
);
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 disputeRequests: DisputeRequestEventObject[] = fullTxReceipt.logs
.filter((log) => log.topics[0] === arbitrableInterface.getEventTopic("DisputeRequest"))
.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)}`);

const relayCreateDisputeParams = {
foreignBlockHash,
foreignChainID,
foreignArbitrable,
foreignDisputeID,
externalDisputeID: disputeRequest._externalDisputeID,
templateId: disputeRequest._templateId,
templateUri: disputeRequest._templateUri,
choices,
extraData,
};
logger.info(`Relaying dispute to home chain... ${JSON.stringify(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();
logger.info(`relayCreateDispute txId: ${tx.transactionHash}`);
}
);

const delay = (ms) => new Promise((x) => setTimeout(x, ms));
await delay(60 * 60 * 1000); // 1 hour
}
20 changes: 20 additions & 0 deletions contracts/scripts/disputeRelayerBotFromChiado.ts
Original file line number Diff line number Diff line change
@@ -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);
});
19 changes: 19 additions & 0 deletions contracts/scripts/disputeRelayerBotFromGoerli.ts
Original file line number Diff line number Diff line change
@@ -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);
});
19 changes: 19 additions & 0 deletions contracts/scripts/disputeRelayerBotFromHardhat.ts
Original file line number Diff line number Diff line change
@@ -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);
});
Loading