diff --git a/.github/workflows/contracts-testing.yml b/.github/workflows/contracts-testing.yml index 36c2c530b..2e42ce270 100644 --- a/.github/workflows/contracts-testing.yml +++ b/.github/workflows/contracts-testing.yml @@ -43,12 +43,12 @@ jobs: - name: Compile run: | - npx hardhat compile + yarn hardhat compile working-directory: contracts - name: Test with coverage run: | - npx hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"./test/**/*.ts\" + yarn hardhat coverage --solcoverjs ./.solcover.js --temp artifacts --testfiles \"./test/**/*.ts\" --show-stack-traces working-directory: contracts - name: Upload a build artifact diff --git a/contracts/src/arbitration/KlerosCore.sol b/contracts/src/arbitration/KlerosCore.sol index 349d2928e..b2a406b90 100644 --- a/contracts/src/arbitration/KlerosCore.sol +++ b/contracts/src/arbitration/KlerosCore.sol @@ -961,6 +961,18 @@ contract KlerosCore is IArbitrator { // * Public Views for Dispute Kits * // // ************************************* // + function getSortitionSumTreeK(bytes32 _key) public view returns (uint256) { + return sortitionSumTrees.sortitionSumTrees[_key].K; + } + + function getSortitionSumTreeNode(bytes32 _key, uint256 _index) public view returns (uint256) { + return sortitionSumTrees.sortitionSumTrees[_key].nodes[_index]; + } + + function getSortitionSumTreeNodesLength(bytes32 _key) public view returns (uint256) { + return sortitionSumTrees.sortitionSumTrees[_key].nodes.length; + } + function getSortitionSumTree(bytes32 _key) public view diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol index 3929be284..b6cc322d3 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitClassic.sol @@ -224,24 +224,26 @@ contract DisputeKitClassic is BaseDisputeKit, IEvidence { returns (address drawnAddress) { require(phase == Phase.drawing, "Should be in drawing phase"); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + bytes32 key = bytes32(core.getSubcourtID(_coreDisputeID)); // Get the ID of the tree. uint256 drawnNumber = getRandomNumber(); - (uint256 K, , uint256[] memory nodes) = core.getSortitionSumTree(key); + uint256 K = core.getSortitionSumTreeK(key); + uint256 nodesLength = core.getSortitionSumTreeNodesLength(key); uint256 treeIndex = 0; - uint256 currentDrawnNumber = drawnNumber % nodes[0]; - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; + uint256 currentDrawnNumber = drawnNumber % core.getSortitionSumTreeNode(key, 0); // TODO: Handle the situation when no one has staked yet. // While it still has children - while ((K * treeIndex) + 1 < nodes.length) { + while ((K * treeIndex) + 1 < nodesLength) { for (uint256 i = 1; i <= K; i++) { // Loop over children. uint256 nodeIndex = (K * treeIndex) + i; - uint256 nodeValue = nodes[nodeIndex]; + uint256 nodeValue = core.getSortitionSumTreeNode(key, nodeIndex); if (currentDrawnNumber >= nodeValue) { // Go to the next child. diff --git a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol index 990b50029..f2b9b60a8 100644 --- a/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol +++ b/contracts/src/arbitration/dispute-kits/DisputeKitSybilResistant.sol @@ -242,24 +242,26 @@ contract DisputeKitSybilResistant is BaseDisputeKit, IEvidence { returns (address drawnAddress) { require(phase == Phase.drawing, "Should be in drawing phase"); + + Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; + Round storage round = dispute.rounds[dispute.rounds.length - 1]; + bytes32 key = bytes32(core.getSubcourtID(_coreDisputeID)); // Get the ID of the tree. uint256 drawnNumber = getRandomNumber(); - (uint256 K, , uint256[] memory nodes) = core.getSortitionSumTree(key); + uint256 K = core.getSortitionSumTreeK(key); + uint256 nodesLength = core.getSortitionSumTreeNodesLength(key); uint256 treeIndex = 0; - uint256 currentDrawnNumber = drawnNumber % nodes[0]; - - Dispute storage dispute = disputes[coreDisputeIDToLocal[_coreDisputeID]]; - Round storage round = dispute.rounds[dispute.rounds.length - 1]; + uint256 currentDrawnNumber = drawnNumber % core.getSortitionSumTreeNode(key, 0); // TODO: Handle the situation when no one has staked yet. // While it still has children - while ((K * treeIndex) + 1 < nodes.length) { + while ((K * treeIndex) + 1 < nodesLength) { for (uint256 i = 1; i <= K; i++) { // Loop over children. uint256 nodeIndex = (K * treeIndex) + i; - uint256 nodeValue = nodes[nodeIndex]; + uint256 nodeValue = core.getSortitionSumTreeNode(key, nodeIndex); if (currentDrawnNumber >= nodeValue) { // Go to the next child. diff --git a/contracts/src/arbitration/mock/PNK.sol b/contracts/src/arbitration/mock/PNK.sol index 84c03b8b6..0fb7a98ef 100644 --- a/contracts/src/arbitration/mock/PNK.sol +++ b/contracts/src/arbitration/mock/PNK.sol @@ -6,6 +6,6 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract PNK is ERC20 { constructor() ERC20("Pinakion", "PNK") { - _mint(msg.sender, 10000 ether); + _mint(msg.sender, 1000000 ether); } } diff --git a/contracts/test/arbitration/draw.ts b/contracts/test/arbitration/draw.ts new file mode 100644 index 000000000..d07b65de6 --- /dev/null +++ b/contracts/test/arbitration/draw.ts @@ -0,0 +1,102 @@ +import { deployments, ethers, getNamedAccounts, network } from "hardhat"; +import { BigNumber } from "ethers"; +import { PNK, KlerosCore, ArbitrableExample, HomeGatewayToEthereum, DisputeKitClassic } from "../../typechain-types"; +import { expect } from "chai"; + +/* eslint-disable no-unused-vars */ +/* eslint-disable no-unused-expressions */ // https://github.com/standard/standard/issues/690#issuecomment-278533482 + +// FIXME: This test fails on Github actions, cannot figure why, skipping for now. +(process.env.GITHUB_ACTIONS ? describe.skip : describe)("Draw Benchmark", async () => { + const ONE_TENTH_ETH = BigNumber.from(10).pow(17); + 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, // Stake can be updated during this phase. + freezing, // Phase during which the dispute kits can undergo the drawing process. Staking is not allowed during this phase. + } + + const enum DisputeKitPhase { + resolving, // 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 relayer; + let disputeKit; + let pnk; + let core; + let arbitrable; + let homeGateway; + + beforeEach("Setup", async () => { + deployer = (await getNamedAccounts()).deployer; + relayer = (await getNamedAccounts()).relayer; + + console.log("deployer:%s", deployer); + console.log("named accounts: %O", await getNamedAccounts()); + + await deployments.fixture(["Arbitration", "ForeignGateway", "HomeGateway"], { + fallbackToGlobal: true, + keepExistingDeployments: false, + }); + disputeKit = (await ethers.getContract("DisputeKitClassic")) as DisputeKitClassic; + pnk = (await ethers.getContract("PNK")) as PNK; + core = (await ethers.getContract("KlerosCore")) as KlerosCore; + homeGateway = (await ethers.getContract("HomeGatewayToEthereum")) as HomeGatewayToEthereum; + arbitrable = (await ethers.getContract("ArbitrableExample")) as ArbitrableExample; + }); + + it("Draw Benchmark", async () => { + const arbitrationCost = ONE_TENTH_ETH.mul(3); + const [bridger] = await ethers.getSigners(); + + // Stake some jurors + for (let i = 0; i < 16; i++) { + const wallet = ethers.Wallet.createRandom().connect(ethers.provider); + + await bridger.sendTransaction({ to: wallet.address, value: ethers.utils.parseEther("10") }); + expect(await wallet.getBalance()).to.equal(ethers.utils.parseEther("10")); + + await pnk.transfer(wallet.address, ONE_THOUSAND_PNK.mul(10)); + expect(await pnk.balanceOf(wallet.address)).to.equal(ONE_THOUSAND_PNK.mul(10)); + + await pnk.connect(wallet).approve(core.address, ONE_THOUSAND_PNK.mul(10), { gasLimit: 300000 }); + await core.connect(wallet).setStake(0, ONE_THOUSAND_PNK.mul(10), { gasLimit: 5000000 }); // Github Action fails here, no idea why. + } + + // Create a dispute + const tx = await arbitrable.createDispute(2, "0x00", 0, { value: arbitrationCost }); + const trace = await network.provider.send("debug_traceTransaction", [tx.hash]); + const [disputeId] = ethers.utils.defaultAbiCoder.decode(["uint"], `0x${trace.returnValue}`); + const lastBlock = await ethers.provider.getBlock(tx.blockNumber - 1); + + // Relayer tx + const tx2 = await homeGateway + .connect(await ethers.getSigner(relayer)) + .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", arbitrable.address, { + value: arbitrationCost, + }); + + await network.provider.send("evm_increaseTime", [130]); // Wait for minStakingTime + await network.provider.send("evm_mine"); + await core.passPhase(); // Staking -> Freezing + for (let index = 0; index < 20; index++) { + await network.provider.send("evm_mine"); // Wait for 20 blocks finality + } + await disputeKit.passPhase(); // Resolving -> Generating + await disputeKit.passPhase(); // Generating -> Drawing + + // Draw! + const tx3 = await core.draw(0, 1000, { gasLimit: 1000000 }); + }); +});