kleros/kleros-v2

View on GitHub
contracts/test/integration/index.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
import { expect } from "chai";
import { deployments, ethers, getNamedAccounts, network } from "hardhat";
import { BigNumber } from "ethers";
import {
  PNK,
  KlerosCore,
  ForeignGateway,
  ArbitrableExample,
  HomeGateway,
  VeaMock,
  DisputeKitClassic,
  RandomizerRNG,
  RandomizerMock,
  SortitionModule,
} from "../../typechain-types";

/* eslint-disable no-unused-vars */
/* eslint-disable no-unused-expressions */ // https://github.com/standard/standard/issues/690#issuecomment-278533482

describe("Integration tests", async () => {
  const ONE_TENTH_ETH = BigNumber.from(10).pow(17);
  const ONE_ETH = BigNumber.from(10).pow(18);
  const ONE_HUNDRED_PNK = BigNumber.from(10).pow(20);
  const ONE_THOUSAND_PNK = BigNumber.from(10).pow(21);

  const enum Period {
    evidence, // Evidence can be submitted. This is also when drawing has to take place.
    commit, // Jurors commit a hashed vote. This is skipped for courts without hidden votes.
    vote, // Jurors reveal/cast their vote depending on whether the court has hidden votes or not.
    appeal, // The dispute can be appealed.
    execution, // Tokens are redistributed and the ruling is executed.
  }

  const enum Phase {
    staking, // No disputes that need drawing.
    generating, // Waiting for a random number. Pass as soon as it is ready.
    drawing, // Jurors can be drawn.
  }

  let deployer;
  let rng, randomizer, disputeKit, pnk, core, vea, foreignGateway, arbitrable, homeGateway, sortitionModule;

  beforeEach("Setup", async () => {
    ({ deployer } = await getNamedAccounts());
    await deployments.fixture(["Arbitration", "VeaMock"], {
      fallbackToGlobal: true,
      keepExistingDeployments: false,
    });
    rng = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
    randomizer = (await ethers.getContract("RandomizerOracle")) as RandomizerMock;
    disputeKit = (await ethers.getContract("DisputeKitClassic")) as DisputeKitClassic;
    pnk = (await ethers.getContract("PNK")) as PNK;
    core = (await ethers.getContract("KlerosCore")) as KlerosCore;
    vea = (await ethers.getContract("VeaMock")) as VeaMock;
    foreignGateway = (await ethers.getContract("ForeignGatewayOnEthereum")) as ForeignGateway;
    arbitrable = (await ethers.getContract("ArbitrableExample")) as ArbitrableExample;
    homeGateway = (await ethers.getContract("HomeGatewayToEthereum")) as HomeGateway;
    sortitionModule = (await ethers.getContract("SortitionModule")) as SortitionModule;
  });

  it("Resolves a dispute on the home chain with no appeal", async () => {
    const arbitrationCost = ONE_TENTH_ETH.mul(3);
    const [, , relayer] = await ethers.getSigners();

    await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100));

    await core.setStake(1, ONE_THOUSAND_PNK);
    await sortitionModule.getJurorBalance(deployer, 1).then((result) => {
      expect(result.totalStaked).to.equal(ONE_THOUSAND_PNK);
      expect(result.totalLocked).to.equal(0);
      logJurorBalance(result);
    });

    await core.setStake(1, ONE_HUNDRED_PNK.mul(5));
    await sortitionModule.getJurorBalance(deployer, 1).then((result) => {
      expect(result.totalStaked).to.equal(ONE_HUNDRED_PNK.mul(5));
      expect(result.totalLocked).to.equal(0);
      logJurorBalance(result);
    });

    await core.setStake(1, 0);
    await sortitionModule.getJurorBalance(deployer, 1).then((result) => {
      expect(result.totalStaked).to.equal(0);
      expect(result.totalLocked).to.equal(0);
      logJurorBalance(result);
    });

    await core.setStake(1, ONE_THOUSAND_PNK.mul(4));
    await sortitionModule.getJurorBalance(deployer, 1).then((result) => {
      expect(result.totalStaked).to.equal(ONE_THOUSAND_PNK.mul(4));
      expect(result.totalLocked).to.equal(0);
      logJurorBalance(result);
    });
    const tx = await arbitrable.functions["createDispute(string)"]("future of france", {
      value: arbitrationCost,
    });
    const trace = await network.provider.send("debug_traceTransaction", [tx.hash]);
    const [disputeId] = ethers.utils.defaultAbiCoder.decode(["uint"], `0x${trace.returnValue}`); // get returned value from createDispute()
    console.log("Dispute Created with disputeId: %d", disputeId);
    await expect(tx)
      .to.emit(foreignGateway, "CrossChainDisputeOutgoing")
      .withArgs(anyValue, arbitrable.address, 1, 2, "0x00");
    await expect(tx)
      .to.emit(arbitrable, "DisputeRequest")
      .withArgs(
        foreignGateway.address,
        1,
        BigNumber.from("46619385602526556702049273755915206310773794210139929511467397410441395547901"),
        0,
        ""
      );

    const lastBlock = await ethers.provider.getBlock(tx.blockNumber - 1);
    const disputeHash = ethers.utils.solidityKeccak256(
      ["bytes", "bytes32", "uint256", "address", "uint256", "uint256", "bytes"],
      [ethers.utils.toUtf8Bytes("createDispute"), lastBlock.hash, 31337, arbitrable.address, disputeId, 2, "0x00"]
    );
    console.log("dispute hash: ", disputeHash);

    // Relayer tx
    const tx2 = await homeGateway
      .connect(relayer)
      .functions["relayCreateDispute((bytes32,uint256,address,uint256,uint256,uint256,string,uint256,bytes))"](
        {
          foreignBlockHash: lastBlock.hash,
          foreignChainID: 31337,
          foreignArbitrable: arbitrable.address,
          foreignDisputeID: disputeId,
          externalDisputeID: ethers.utils.keccak256(ethers.utils.toUtf8Bytes("future of france")),
          templateId: 0,
          templateUri: "",
          choices: 2,
          extraData: "0x00",
        },
        { value: arbitrationCost }
      );
    expect(tx2).to.emit(homeGateway, "Dispute");
    const events2 = (await tx2.wait()).events;

    await network.provider.send("evm_increaseTime", [2000]); // Wait for minStakingTime
    await network.provider.send("evm_mine");

    expect(await sortitionModule.phase()).to.equal(Phase.staking);
    expect(await sortitionModule.disputesWithoutJurors()).to.equal(1);
    console.log("KC phase: %d", await sortitionModule.phase());

    await sortitionModule.passPhase(); // Staking -> Generating
    await mineBlocks(await sortitionModule.rngLookahead()); // Wait for finality
    expect(await sortitionModule.phase()).to.equal(Phase.generating);
    console.log("KC phase: %d", await sortitionModule.phase());
    await randomizer.relay(rng.address, 0, ethers.utils.randomBytes(32));
    await sortitionModule.passPhase(); // Generating -> Drawing
    expect(await sortitionModule.phase()).to.equal(Phase.drawing);
    console.log("KC phase: %d", await sortitionModule.phase());

    const tx3 = await core.draw(0, 1000);
    console.log("draw successful");
    const events3 = (await tx3.wait()).events;

    const roundInfo = await core.getRoundInfo(0, 0);
    expect(roundInfo.drawnJurors).deep.equal([deployer, deployer, deployer]);
    expect(roundInfo.pnkAtStakePerJuror).to.equal(ONE_HUNDRED_PNK.mul(2));
    expect(roundInfo.totalFeesForJurors).to.equal(arbitrationCost);
    expect(roundInfo.feeToken).to.equal(ethers.constants.AddressZero);

    expect((await core.disputes(0)).period).to.equal(Period.evidence);

    await core.passPeriod(0);
    expect((await core.disputes(0)).period).to.equal(Period.vote);
    await disputeKit.connect(await ethers.getSigner(deployer)).castVote(0, [0, 1, 2], 0, 0, "");
    await core.passPeriod(0);

    await network.provider.send("evm_increaseTime", [100]); // Wait for the appeal period
    await network.provider.send("evm_mine");

    await core.passPeriod(0);
    expect((await core.disputes(0)).period).to.equal(Period.execution);
    expect(await core.execute(0, 0, 1000)).to.emit(core, "TokenAndETHShift");

    const tx4 = await core.executeRuling(0, { gasLimit: 10000000, gasPrice: 5000000000 });
    console.log("Ruling executed on KlerosCore");
    expect(tx4).to.emit(core, "Ruling").withArgs(homeGateway.address, 0, 0);
    expect(tx4).to.emit(arbitrable, "Ruling").withArgs(foreignGateway.address, 1, 0); // The ForeignGateway starts counting disputeID from 1.
  });

  const mineBlocks = async (n: number) => {
    for (let index = 0; index < n; index++) {
      await network.provider.send("evm_mine");
    }
  };
});

const logJurorBalance = async (result) => {
  console.log(
    "staked=%s, locked=%s",
    ethers.utils.formatUnits(result.totalStaked),
    ethers.utils.formatUnits(result.totalLocked)
  );
};