diff --git a/.gitignore b/.gitignore index ee1eb0dae..4db596eef 100644 --- a/.gitignore +++ b/.gitignore @@ -173,6 +173,9 @@ tags .history .ionide +# Ignore the .DS_Store file +.DS_Store + # Support for Project snippet scope !.vscode/*.code-snippets diff --git a/contracts/deploy/01-foreign-chain.ts b/contracts/deploy/01-foreign-chain.ts index 370b851ac..c7a38726b 100644 --- a/contracts/deploy/01-foreign-chain.ts +++ b/contracts/deploy/01-foreign-chain.ts @@ -15,16 +15,19 @@ const paramsByChainId = { claimDeposit: parseEther("0.1"), challengeDuration: 86400, // 1 day homeChainId: 42161, + arbitrumInbox: "0x4Dbd4fc535Ac27206064B68FfCf827b0A60BAB3f", }, 4: { claimDeposit: parseEther("0.1"), challengeDuration: 120, // 2 min homeChainId: 421611, + arbitrumInbox: "0x578BAde599406A8fE3d24Fd7f7211c0911F5B29e", }, 31337: { claimDeposit: parseEther("0.1"), challengeDuration: 120, // 2 min homeChainId: 31337, + arbitrumInbox: "0x00", }, }; @@ -50,26 +53,35 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme let nonce; if (chainId === ForeignChains.HARDHAT) { nonce = await ethers.provider.getTransactionCount(deployer); - nonce += 4; // HomeGatewayToEthereum deploy tx will be the 6th after this, same network for both home/foreign. + nonce += 5; // HomeGatewayToEthereum deploy tx will be the 6th after this, same network for both home/foreign. } else { const homeChainProvider = new providers.JsonRpcProvider(homeNetworks[chainId].url); nonce = await homeChainProvider.getTransactionCount(deployer); nonce += 1; // HomeGatewayToEthereum deploy tx will the third tx after this on its home network, so we add two to the current nonce. } - const { claimDeposit, challengeDuration, homeChainId } = paramsByChainId[chainId]; + const { claimDeposit, challengeDuration, homeChainId, arbitrumInbox } = paramsByChainId[chainId]; const challengeDeposit = claimDeposit; const bridgeAlpha = 5000; const homeChainIdAsBytes32 = hexZeroPad(homeChainId, 32); const homeGatewayAddress = getContractAddress(deployer, nonce); console.log("calculated future HomeGatewayToEthereum address for nonce %d: %s", nonce, homeGatewayAddress); + nonce -= 1; + + const fastBridgeSenderAddress = getContractAddress(deployer, nonce); + console.log("calculated future FastSender for nonce %d: %s", nonce, fastBridgeSenderAddress); + + nonce += 5; + + const inboxAddress = chainId === ForeignChains.HARDHAT ? getContractAddress(deployer, nonce) : arbitrumInbox; + console.log("calculated future inboxAddress for nonce %d: %s", nonce, inboxAddress); const fastBridgeReceiver = await deploy("FastBridgeReceiverOnEthereum", { from: deployer, args: [ deployer, - ethers.constants.AddressZero, // should be safeBridgeSender - ethers.constants.AddressZero, // should be Arbitrum Inbox + fastBridgeSenderAddress, + inboxAddress, claimDeposit, challengeDeposit, challengeDuration, @@ -92,7 +104,8 @@ const deployForeignGateway: DeployFunction = async (hre: HardhatRuntimeEnvironme const metaEvidenceUri = "https://raw.githubusercontent.com/kleros/kleros-v2/master/contracts/deployments/rinkeby/MetaEvidence_ArbitrableExample.json"; - const arbitrable = await deploy("ArbitrableExample", { + + await deploy("ArbitrableExample", { from: deployer, args: [foreignGateway.address, metaEvidenceUri], log: true, diff --git a/contracts/deploy/02-home-chain.ts b/contracts/deploy/02-home-chain.ts index 87cef5270..1e1a31bb4 100644 --- a/contracts/deploy/02-home-chain.ts +++ b/contracts/deploy/02-home-chain.ts @@ -1,10 +1,11 @@ import { HardhatRuntimeEnvironment } from "hardhat/types"; import { DeployFunction } from "hardhat-deploy/types"; -import { Address } from "ethereumjs-util"; import { ethers } from "hardhat"; const HOME_CHAIN_IDS = [42161, 421611, 31337]; // ArbOne, ArbRinkeby, Hardhat +// TODO: use deterministic deployments + const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) => { const { deployments, getNamedAccounts, getChainId } = hre; const { deploy, execute } = deployments; @@ -14,35 +15,100 @@ const deployHomeGateway: DeployFunction = async (hre: HardhatRuntimeEnvironment) const deployer = (await getNamedAccounts()).deployer ?? (await hre.ethers.getSigners())[0].address; console.log("deployer: %s", deployer); - // The object below is not available when launching the hardhat node. - // TODO: use deterministic deployments - const fastBridgeReceiver = - chainId === 31337 - ? await deployments.get("FastBridgeReceiverOnEthereum") - : await hre.companionNetworks.foreign.deployments.get("FastBridgeReceiverOnEthereum"); - const fastBridgeSender = await deploy("FastBridgeSenderToEthereum", { - from: deployer, - args: [deployer, fastBridgeReceiver.address, ethers.constants.AddressZero], - log: true, - }); // nonce+0 - - const klerosCore = await deployments.get("KlerosCore"); - const foreignGateway = - chainId === 31337 - ? await deployments.get("ForeignGatewayOnEthereum") - : await hre.companionNetworks.foreign.deployments.get("ForeignGatewayOnEthereum"); - const foreignChainId = chainId === 31337 ? 31337 : Number(await hre.companionNetworks.foreign.getChainId()); - const homeGateway = await deploy("HomeGatewayToEthereum", { - from: deployer, - args: [klerosCore.address, fastBridgeSender.address, foreignGateway.address, foreignChainId], - log: true, - }); // nonce+1 - - const fastSender = await hre.ethers - .getContractAt("FastBridgeSenderToEthereum", fastBridgeSender.address) - .then((contract) => contract.fastBridgeSender()); - if (fastSender === ethers.constants.AddressZero) { - await execute("FastBridgeSenderToEthereum", { from: deployer, log: true }, "changeFastSender", homeGateway.address); + // ---------------------------------------------------------------------------------------------- + const hardhatDeployer = async () => { + const fastBridgeReceiver = await deployments.get("FastBridgeReceiverOnEthereum"); + const arbSysMock = await deploy("ArbSysMock", { from: deployer, log: true }); + + const fastBridgeSender = await deploy("FastBridgeSenderToEthereumMock", { + from: deployer, + args: [deployer, fastBridgeReceiver.address, ethers.constants.AddressZero, arbSysMock.address], + log: true, + }); // nonce+0 + + const klerosCore = await deployments.get("KlerosCore"); + const foreignGateway = await deployments.get("ForeignGatewayOnEthereum"); + const foreignChainId = 31337; + + const homeGateway = await deploy("HomeGatewayToEthereum", { + from: deployer, + args: [klerosCore.address, fastBridgeSender.address, foreignGateway.address, foreignChainId], + log: true, + }); // nonce+1 + + const fastSender = await hre.ethers + .getContractAt("FastBridgeSenderToEthereumMock", fastBridgeSender.address) + .then((contract) => contract.fastBridgeSender()); + + if (fastSender === ethers.constants.AddressZero) { + await execute( + "FastBridgeSenderToEthereumMock", + { + from: deployer, + log: true, + }, + "changeFastSender", + homeGateway.address + ); + + const outbox = await deploy("OutboxMock", { + from: deployer, + args: [fastBridgeSender.address], + log: true, + }); + + const bridge = await deploy("BridgeMock", { + from: deployer, + args: [outbox.address], + log: true, + }); + + await deploy("InboxMock", { + from: deployer, + args: [bridge.address], + log: true, + }); + } + }; + + // ---------------------------------------------------------------------------------------------- + const liveDeployer = async () => { + const fastBridgeReceiver = await hre.companionNetworks.foreign.deployments.get("FastBridgeReceiverOnEthereum"); + + const fastBridgeSender = await deploy("FastBridgeSenderToEthereum", { + from: deployer, + args: [deployer, fastBridgeReceiver.address, ethers.constants.AddressZero], + log: true, + }); // nonce+0 + + const klerosCore = await deployments.get("KlerosCore"); + const foreignGateway = await hre.companionNetworks.foreign.deployments.get("ForeignGatewayOnEthereum"); + const foreignChainId = Number(await hre.companionNetworks.foreign.getChainId()); + const homeGateway = await deploy("HomeGatewayToEthereum", { + from: deployer, + args: [klerosCore.address, fastBridgeSender.address, foreignGateway.address, foreignChainId], + log: true, + }); // nonce+1 + + const fastSender = await hre.ethers + .getContractAt("FastBridgeSenderToEthereum", fastBridgeSender.address) + .then((contract) => contract.fastBridgeSender()); + + if (fastSender === ethers.constants.AddressZero) { + await execute( + "FastBridgeSenderToEthereum", + { from: deployer, log: true }, + "changeFastSender", + homeGateway.address + ); + } + }; + + // ---------------------------------------------------------------------------------------------- + if (chainId === 31337) { + await hardhatDeployer(); + } else { + await liveDeployer(); } }; diff --git a/contracts/src/bridge/mock/ArbSysMock.sol b/contracts/src/bridge/mock/ArbSysMock.sol new file mode 100644 index 000000000..1b657a6cc --- /dev/null +++ b/contracts/src/bridge/mock/ArbSysMock.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@hrishibhat] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../interfaces/arbitrum/IArbSys.sol"; + +contract ArbSysMock { + function sendTxToL1(address destination, bytes calldata calldataForL1) + external + payable + returns (uint256 _withdrawal_ID) + { + (bool success, ) = address(destination).call(calldataForL1); + require(success, "Failed TxToL1"); + return _withdrawal_ID; + } +} diff --git a/contracts/src/bridge/mock/BridgeMock.sol b/contracts/src/bridge/mock/BridgeMock.sol new file mode 100644 index 000000000..89cb34cc9 --- /dev/null +++ b/contracts/src/bridge/mock/BridgeMock.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@hrishibhat] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../interfaces/arbitrum/IInbox.sol"; + +contract BridgeMock is IBridge { + address public outbox; + + constructor(address _outbox) { + outbox = _outbox; + } + + function activeOutbox() external view returns (address _outbox) { + return address(outbox); + } + + function deliverMessageToInbox( + uint8 kind, + address sender, + bytes32 messageDataHash + ) external payable returns (uint256) {} + + function executeCall( + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (bool success, bytes memory returnData) {} + + // These are only callable by the admin + function setInbox(address inbox, bool enabled) external {} + + function setOutbox(address inbox, bool enabled) external {} + + // View functions + + function allowedInboxes(address inbox) external view returns (bool) {} + + function allowedOutboxes(address outbox) external view returns (bool) {} + + function inboxAccs(uint256 index) external view returns (bytes32) {} + + function messageCount() external view returns (uint256) {} +} diff --git a/contracts/src/bridge/mock/FastBridgeSenderToEthereumMock.sol b/contracts/src/bridge/mock/FastBridgeSenderToEthereumMock.sol new file mode 100644 index 000000000..fdd9a44f2 --- /dev/null +++ b/contracts/src/bridge/mock/FastBridgeSenderToEthereumMock.sol @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@jaybuidl, @shalzz, @hrishibhat, @shotaronowhere] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../FastBridgeSenderToEthereum.sol"; + +/** + * Fast Bridge Sender to Ethereum from Arbitrum + * Counterpart of `FastBridgeReceiverOnEthereum` + */ +contract FastBridgeSenderToEthereumMock is FastBridgeSenderToEthereum { + IArbSys public arbsys; + + // ************************************* // + // * Enums / Structs * // + // ************************************* // + + // struct Ticket { + // bytes32 messageHash; + // uint256 blockNumber; + // bool sentSafe; + // } + + // ************************************* // + // * Storage * // + // ************************************* // + + // address public override governor; // The governor of the contract. + // IFastBridgeReceiver public override fastBridgeReceiver; // The address of the Fast Bridge on Ethereum. + // address public fastBridgeSender; // The address of the Fast Bridge sender on Arbitrum, generally the Home Gateway. + // uint256 public currentTicketID = 1; // Zero means not set, start at 1. + // mapping(uint256 => Ticket) public tickets; // The tickets by ticketID. + + // ************************************* // + // * Function Modifiers * // + // ************************************* // + + // modifier onlyByGovernor() { + // require(governor == msg.sender, "Access not allowed: Governor only."); + // _; + // } + + /** + * @dev Constructor. + * @param _governor The governor's address. + * @param _fastBridgeReceiver The address of the Fast Bridge on Ethereum. + * @param _fastBridgeSender The address of the Fast Bridge sender on Arbitrum, generally the Home Gateway. + */ + constructor( + address _governor, + IFastBridgeReceiver _fastBridgeReceiver, + address _fastBridgeSender, + address _arbsys + ) FastBridgeSenderToEthereum(_governor, _fastBridgeReceiver, _fastBridgeSender) { + arbsys = IArbSys(address(_arbsys)); + } + + // ************************************* // + // * State Modifiers * // + // ************************************* // + function sendSafeFallbackMock( + uint256 _ticketID, + address _receiver, + bytes memory _calldata + ) external payable { + // TODO: check if keeping _calldata in storage in sendFast() is cheaper than passing it again as a parameter here + Ticket storage ticket = tickets[_ticketID]; + require(ticket.messageHash != 0, "Ticket does not exist."); + require(ticket.sentSafe == false, "Ticket already sent safely."); + + (bytes32 messageHash, bytes memory messageData) = _encode(_ticketID, ticket.blockNumber, _receiver, _calldata); + require(ticket.messageHash == messageHash, "Invalid message for ticketID."); + + // Safe Bridge message envelope + bytes4 methodSelector = IFastBridgeReceiver.verifyAndRelaySafe.selector; + bytes memory safeMessageData = abi.encodeWithSelector( + methodSelector, + _ticketID, + ticket.blockNumber, + messageData + ); + + // TODO: how much ETH should be provided for bridging? add an ISafeBridgeSender.bridgingCost() if needed + _sendSafeMock(address(fastBridgeReceiver), safeMessageData); + } + + // ************************ // + // * Governance * // + // ************************ // + + // ************************ // + // * Internal * // + // ************************ // + function _sendSafeMock(address _receiver, bytes memory _calldata) internal returns (uint256) { + uint256 withdrawalId = arbsys.sendTxToL1(_receiver, _calldata); + emit L2ToL1TxCreated(withdrawalId); + return withdrawalId; + } +} diff --git a/contracts/src/bridge/mock/InboxMock.sol b/contracts/src/bridge/mock/InboxMock.sol new file mode 100644 index 000000000..5d7b91edc --- /dev/null +++ b/contracts/src/bridge/mock/InboxMock.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@hrishibhat] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../interfaces/arbitrum/IInbox.sol"; + +contract InboxMock is IInbox { + IBridge public arbBridge; + + constructor(address _bridge) { + arbBridge = IBridge(_bridge); + } + + function bridge() external view returns (IBridge) { + return arbBridge; + } + + function sendL2Message(bytes calldata messageData) external returns (uint256) {} + + function sendUnsignedTransaction( + uint256 maxGas, + uint256 gasPriceBid, + uint256 nonce, + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (uint256) {} + + function sendContractTransaction( + uint256 maxGas, + uint256 gasPriceBid, + address destAddr, + uint256 amount, + bytes calldata data + ) external returns (uint256) {} + + function sendL1FundedUnsignedTransaction( + uint256 maxGas, + uint256 gasPriceBid, + uint256 nonce, + address destAddr, + bytes calldata data + ) external payable returns (uint256) {} + + function sendL1FundedContractTransaction( + uint256 maxGas, + uint256 gasPriceBid, + address destAddr, + bytes calldata data + ) external payable returns (uint256) {} + + function createRetryableTicket( + address destAddr, + uint256 arbTxCallValue, + uint256 maxSubmissionCost, + address submissionRefundAddress, + address valueRefundAddress, + uint256 maxGas, + uint256 gasPriceBid, + bytes calldata data + ) external payable returns (uint256) {} + + function depositEth(uint256 maxSubmissionCost) external payable returns (uint256) {} +} diff --git a/contracts/src/bridge/mock/OutboxMock.sol b/contracts/src/bridge/mock/OutboxMock.sol new file mode 100644 index 000000000..ed23d9709 --- /dev/null +++ b/contracts/src/bridge/mock/OutboxMock.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT + +/** + * @authors: [@hrishibhat] + * @reviewers: [] + * @auditors: [] + * @bounties: [] + * @deployments: [] + */ + +pragma solidity ^0.8.0; + +import "../interfaces/arbitrum/IOutbox.sol"; + +contract OutboxMock is IOutbox { + address public safeBridgeSender; + + constructor(address _safeBridgeSender) { + safeBridgeSender = _safeBridgeSender; + } + + function l2ToL1Sender() external view returns (address) { + return address(safeBridgeSender); + } + + function l2ToL1Block() external view returns (uint256) {} + + function l2ToL1EthBlock() external view returns (uint256) {} + + function l2ToL1Timestamp() external view returns (uint256) {} + + function processOutgoingMessages(bytes calldata sendsData, uint256[] calldata sendLengths) external {} +} diff --git a/contracts/test/pre-alpha1/index.ts b/contracts/test/pre-alpha1/index.ts index a86d4548b..395a89ab9 100644 --- a/contracts/test/pre-alpha1/index.ts +++ b/contracts/test/pre-alpha1/index.ts @@ -1,6 +1,6 @@ import { expect } from "chai"; import { deployments, ethers, getNamedAccounts, network } from "hardhat"; -import { BigNumber } from "ethers"; +import { BigNumber, utils } from "ethers"; import { IncrementalNG, PNK, @@ -10,7 +10,9 @@ import { ArbitrableExample, FastBridgeSenderToEthereum, HomeGatewayToEthereum, + InboxMock, } from "../../typechain-types"; +import { OutgoingMessage } from "http"; /* eslint-disable no-unused-vars */ @@ -29,17 +31,19 @@ describe("Demo pre-alpha1", function () { } let deployer, relayer, bridger, challenger, innocentBystander; - let ng, disputeKit, pnk, core, fastBridgeReceiver, foreignGateway, arbitrable, fastBridgeSender, homeGateway; + let ng, disputeKit, pnk, core, fastBridgeReceiver, foreignGateway, arbitrable, fastBridgeSender, homeGateway, inbox; before("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: true, + keepExistingDeployments: false, }); ng = await ethers.getContract("IncrementalNG"); disputeKit = await ethers.getContract("DisputeKitClassic"); @@ -48,8 +52,9 @@ describe("Demo pre-alpha1", function () { fastBridgeReceiver = await ethers.getContract("FastBridgeReceiverOnEthereum"); foreignGateway = await ethers.getContract("ForeignGatewayOnEthereum"); arbitrable = await ethers.getContract("ArbitrableExample"); - fastBridgeSender = await ethers.getContract("FastBridgeSenderToEthereum"); + fastBridgeSender = await ethers.getContract("FastBridgeSenderToEthereumMock"); homeGateway = await ethers.getContract("HomeGatewayToEthereum"); + inbox = await ethers.getContract("InboxMock"); }); it("RNG", async () => { @@ -65,8 +70,9 @@ describe("Demo pre-alpha1", function () { expect(rn).to.equal(rnOld.add(1)); }); - it("Demo", async () => { + it("Demo - Honest Claim - No Challenge - Bridger paid", async () => { const arbitrationCost = ONE_TENTH_ETH.mul(3); + const [bridger, challenger] = await ethers.getSigners(); await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); @@ -97,19 +103,134 @@ describe("Demo pre-alpha1", function () { expect(result.locked).to.equal(0); logJurorBalance(result); }); - - const tx = await foreignGateway.createDispute(2, "0x00", { value: arbitrationCost }); + 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}`); + console.log("Dispute Created"); expect(tx).to.emit(foreignGateway, "DisputeCreation"); //.withArgs(disputeId, deployer.address); expect(tx).to.emit(foreignGateway, "OutgoingDispute"); //.withArgs(disputeId, deployer.address); console.log(`disputeId: ${disputeId}`); + const lastBlock = await ethers.provider.getBlock(tx.blockNumber - 1); + const disputeHash = ethers.utils.solidityKeccak256( + ["uint", "bytes", "bytes", "uint", "uint", "bytes", "address"], + [31337, lastBlock.hash, ethers.utils.toUtf8Bytes("createDispute"), disputeId, 2, "0x00", arbitrable.address] + ); + const events = (await tx.wait()).events; + + // Relayer tx + const tx2 = await homeGateway + .connect(await ethers.getSigner(relayer)) + .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", arbitrable.address, { + value: arbitrationCost, + }); + expect(tx2).to.emit(homeGateway, "Dispute"); + const events2 = (await tx2.wait()).events; + + const tx3 = await core.draw(0, 1000); + const events3 = (await tx3.wait()).events; + + const roundInfo = await core.getRoundInfo(0, 0); + expect(roundInfo.drawnJurors).deep.equal([deployer, deployer, deployer]); + expect(roundInfo.tokensAtStakePerJuror).to.equal(ONE_HUNDRED_PNK.mul(2)); + expect(roundInfo.totalFeesForJurors).to.equal(arbitrationCost); + + 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 core.passPeriod(0); + expect((await core.disputes(0)).period).to.equal(Period.execution); + await core.execute(0, 0, 1000); + const ticket1 = await fastBridgeSender.currentTicketID(); + expect(ticket1).to.equal(1); + + const tx4 = await core.executeRuling(0); + expect(tx4).to.emit(fastBridgeSender, "OutgoingMessage"); + + const event5 = await fastBridgeSender.queryFilter(OutgoingMessage); + console.log("Executed ruling"); + + const ticket2 = await fastBridgeSender.currentTicketID(); + expect(ticket2).to.equal(2); + + const ticketID = event5[0].args.ticketID; + const messageHash = event5[0].args.messageHash; + const blockNumber = event5[0].args.blockNumber; + const messageData = event5[0].args.message; + + const bridgerBalance = await ethers.provider.getBalance(bridger.address); + // bridger tx starts - Honest Bridger + const tx5 = await fastBridgeReceiver.connect(bridger).claim(ticketID, messageHash, { value: ONE_TENTH_ETH }); + const blockNumBefore = await ethers.provider.getBlockNumber(); + const blockBefore = await ethers.provider.getBlock(blockNumBefore); + const timestampBefore = blockBefore.timestamp; + expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(ticketID, messageHash, timestampBefore); + + // wait for challenge period to pass + await network.provider.send("evm_increaseTime", [300]); + await network.provider.send("evm_mine"); + + const tx7 = await fastBridgeReceiver.connect(bridger).verifyAndRelay(ticketID, blockNumber, messageData); + expect(tx7).to.emit(arbitrable, "Ruling"); + + const tx8 = await fastBridgeReceiver.withdrawClaimDeposit(ticketID); + }); + + it("Demo - Honest Claim - Challenged - Bridger Paid, Challenger deposit forfeited", async () => { + const arbitrationCost = ONE_TENTH_ETH.mul(3); + const [bridger, challenger] = await ethers.getSigners(); + + await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); + + await core.setStake(0, ONE_THOUSAND_PNK); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_THOUSAND_PNK); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, ONE_HUNDRED_PNK.mul(5)); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_HUNDRED_PNK.mul(5)); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, 0); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(0); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, ONE_THOUSAND_PNK.mul(4)); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_THOUSAND_PNK.mul(4)); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + 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}`); + console.log("Dispute Created"); + expect(tx).to.emit(foreignGateway, "DisputeCreation"); //.withArgs(disputeId, deployer.address); + expect(tx).to.emit(foreignGateway, "OutgoingDispute"); //.withArgs(disputeId, deployer.address); + console.log(`disputeId: ${disputeId}`); + + const eventOutgoingDispute = foreignGateway.filters.OutgoingDispute(); + const events = await foreignGateway.queryFilter(eventOutgoingDispute, "latest"); + const eventDisputeCreation = foreignGateway.filters.DisputeCreation(); + const events2 = await foreignGateway.queryFilter(eventDisputeCreation, "latest"); + + const lastBlock = await ethers.provider.getBlock(tx.blockNumber - 1); const disputeHash = ethers.utils.solidityKeccak256( ["uint", "bytes", "bytes", "uint", "uint", "bytes", "address"], - [31337, lastBlock.hash, ethers.utils.toUtf8Bytes("createDispute"), disputeId, 2, "0x00", deployer] + [31337, lastBlock.hash, ethers.utils.toUtf8Bytes("createDispute"), disputeId, 2, "0x00", arbitrable.address] ); expect(events[0].event).to.equal("OutgoingDispute"); @@ -118,36 +239,256 @@ describe("Demo pre-alpha1", function () { expect(events[0].args.localDisputeID).to.equal(disputeId); expect(events[0].args._choices).to.equal(2); expect(events[0].args._extraData).to.equal("0x00"); - expect(events[0].args.arbitrable).to.equal(deployer); + expect(events[0].args.arbitrable).to.equal(arbitrable.address); + + expect(events2[0].event).to.equal("DisputeCreation"); + expect(events2[0].args._arbitrable).to.equal(arbitrable.address); + expect(events2[0].args._disputeID).to.equal(disputeId); + // Relayer tx + const tx2 = await homeGateway + .connect(await ethers.getSigner(relayer)) + .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", arbitrable.address, { + value: arbitrationCost, + }); + + expect(tx2).to.emit(homeGateway, "Dispute"); + + const tx3 = await core.draw(1, 1000); + const events3 = (await tx3.wait()).events; + + const roundInfo = await core.getRoundInfo(1, 0); + expect(roundInfo.drawnJurors).deep.equal([deployer, deployer, deployer]); + expect(roundInfo.tokensAtStakePerJuror).to.equal(ONE_HUNDRED_PNK.mul(2)); + expect(roundInfo.totalFeesForJurors).to.equal(arbitrationCost); + + expect((await core.disputes(1)).period).to.equal(Period.evidence); + await core.passPeriod(1); + expect((await core.disputes(1)).period).to.equal(Period.vote); + + await disputeKit.connect(await ethers.getSigner(deployer)).castVote(1, [0, 1, 2], 0, 0); + await core.passPeriod(1); + await core.passPeriod(1); + expect((await core.disputes(1)).period).to.equal(Period.execution); + await core.execute(1, 0, 1000); + const ticket1 = await fastBridgeSender.currentTicketID(); + expect(ticket1).to.equal(2); + + const tx4 = await core.executeRuling(1); + + expect(tx4).to.emit(fastBridgeSender, "OutgoingMessage"); + + console.log("Executed ruling"); + + const ticket2 = await fastBridgeSender.currentTicketID(); + expect(ticket2).to.equal(3); + const eventFilter = fastBridgeSender.filters.OutgoingMessage(); + const event5 = await fastBridgeSender.queryFilter(eventFilter, "latest"); + const event6 = await ethers.provider.getLogs(eventFilter); + + const ticketID = event5[0].args.ticketID.toNumber(); + const messageHash = event5[0].args.messageHash; + const blockNumber = event5[0].args.blockNumber; + const messageData = event5[0].args.message; + console.log("TicketID: %d", ticketID); + console.log("Block: %d", blockNumber); + console.log("Message Data: %s", messageData); + console.log("Message Hash: %s", messageHash); + const expectedHash = utils.keccak256( + utils.defaultAbiCoder.encode(["uint256", "uint256", "bytes"], [ticketID, blockNumber, messageData]) + ); + expect(messageHash).to.equal(expectedHash); + + const currentID = await fastBridgeSender.currentTicketID(); + expect(currentID).to.equal(3); + + // bridger tx starts + const tx5 = await fastBridgeReceiver.connect(bridger).claim(ticketID, messageHash, { value: ONE_TENTH_ETH }); + let blockNumBefore = await ethers.provider.getBlockNumber(); + let blockBefore = await ethers.provider.getBlock(blockNumBefore); + let timestampBefore = blockBefore.timestamp; + expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(ticketID, messageHash, timestampBefore); + + // Challenger tx starts + const tx6 = await fastBridgeReceiver.connect(challenger).challenge(ticketID, { value: ONE_TENTH_ETH }); + blockNumBefore = await ethers.provider.getBlockNumber(); + blockBefore = await ethers.provider.getBlock(blockNumBefore); + timestampBefore = blockBefore.timestamp; + console.log("Block: %d", blockNumBefore); + expect(tx6).to.emit(fastBridgeReceiver, "ClaimChallenged").withArgs(ticketID, timestampBefore); + + // wait for challenge period to pass + await network.provider.send("evm_increaseTime", [300]); + await network.provider.send("evm_mine"); + + await expect( + fastBridgeReceiver.connect(bridger).verifyAndRelay(ticketID, blockNumber, messageData) + ).to.be.revertedWith("Claim is challenged"); + + const data = await ethers.utils.defaultAbiCoder.decode(["address", "bytes"], messageData); + const tx7 = await fastBridgeSender + .connect(bridger) + .sendSafeFallbackMock(ticketID, foreignGateway.address, data[1], { gasLimit: 1000000 }); + expect(tx7).to.emit(fastBridgeSender, "L2ToL1TxCreated"); + expect(tx7).to.emit(arbitrable, "Ruling"); + + await expect(fastBridgeReceiver.withdrawChallengeDeposit(ticketID)).to.be.revertedWith( + "Claim verified: deposit forfeited" + ); + }); + + it("Demo - Dishonest Claim - Challenged - Bridger deposit forfeited, Challenger paid", async () => { + const arbitrationCost = ONE_TENTH_ETH.mul(3); + const [bridger, challenger] = await ethers.getSigners(); + + await pnk.approve(core.address, ONE_THOUSAND_PNK.mul(100)); + + await core.setStake(0, ONE_THOUSAND_PNK); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_THOUSAND_PNK); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, ONE_HUNDRED_PNK.mul(5)); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_HUNDRED_PNK.mul(5)); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, 0); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(0); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + + await core.setStake(0, ONE_THOUSAND_PNK.mul(4)); + await core.getJurorBalance(deployer, 0).then((result) => { + expect(result.staked).to.equal(ONE_THOUSAND_PNK.mul(4)); + expect(result.locked).to.equal(0); + logJurorBalance(result); + }); + 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}`); + console.log("Dispute Created"); + expect(tx).to.emit(foreignGateway, "DisputeCreation"); //.withArgs(disputeId, deployer.address); + expect(tx).to.emit(foreignGateway, "OutgoingDispute"); //.withArgs(disputeId, deployer.address); + console.log(`disputeId: ${disputeId}`); + const coreId = disputeId - 1; + // let events = await foreignGateway.queryFilter(OutgoingMessage); - expect(events[1].event).to.equal("DisputeCreation"); - expect(events[1].args._arbitrable).to.equal(deployer); - expect(events[1].args._disputeID).to.equal(disputeId); + const lastBlock = await ethers.provider.getBlock(tx.blockNumber - 1); + const disputeHash = ethers.utils.solidityKeccak256( + ["uint", "bytes", "bytes", "uint", "uint", "bytes", "address"], + [31337, lastBlock.hash, ethers.utils.toUtf8Bytes("createDispute"), disputeId, 2, "0x00", arbitrable.address] + ); // Relayer tx const tx2 = await homeGateway .connect(await ethers.getSigner(relayer)) - .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", deployer, { + .relayCreateDispute(31337, lastBlock.hash, disputeId, 2, "0x00", arbitrable.address, { value: arbitrationCost, }); expect(tx2).to.emit(homeGateway, "Dispute"); - const events2 = (await tx2.wait()).events; - // console.log("event=%O", events2); - const tx3 = await core.draw(0, 1000); + const tx3 = await core.draw(coreId, 1000); const events3 = (await tx3.wait()).events; - console.log("event=%O", events3[0].args); - console.log("event=%O", events3[1].args); - console.log("event=%O", events3[2].args); - const roundInfo = await core.getRoundInfo(0, 0); + const roundInfo = await core.getRoundInfo(coreId, 0); expect(roundInfo.drawnJurors).deep.equal([deployer, deployer, deployer]); expect(roundInfo.tokensAtStakePerJuror).to.equal(ONE_HUNDRED_PNK.mul(2)); expect(roundInfo.totalFeesForJurors).to.equal(arbitrationCost); - expect((await core.disputes(0)).period).to.equal(Period.evidence); - await core.passPeriod(0); - expect((await core.disputes(0)).period).to.equal(Period.vote); + expect((await core.disputes(coreId)).period).to.equal(Period.evidence); + await core.passPeriod(coreId); + expect((await core.disputes(coreId)).period).to.equal(Period.vote); + + await disputeKit.connect(await ethers.getSigner(deployer)).castVote(coreId, [0, 1, 2], 0, 0); + await core.passPeriod(coreId); + await core.passPeriod(coreId); + expect((await core.disputes(coreId)).period).to.equal(Period.execution); + await core.execute(coreId, 0, 1000); + const ticket1 = await fastBridgeSender.currentTicketID(); + expect(ticket1).to.equal(3); + + const tx4 = await core.executeRuling(coreId); + + expect(tx4).to.emit(fastBridgeSender, "OutgoingMessage"); + + console.log("Executed ruling"); + + const ticket2 = await fastBridgeSender.currentTicketID(); + expect(ticket2).to.equal(4); + const eventFilter = fastBridgeSender.filters.OutgoingMessage(); + const event5 = await fastBridgeSender.queryFilter(eventFilter, "latest"); + const event6 = await ethers.provider.getLogs(eventFilter); + + const ticketID = event5[0].args.ticketID.toNumber(); + const messageHash = event5[0].args.messageHash; + const blockNumber = event5[0].args.blockNumber; + const messageData = event5[0].args.message; + console.log("TicketID: %d", ticketID); + console.log("Block: %d", blockNumber); + console.log("Message Data: %s", messageData); + console.log("Message Hash: %s", messageHash); + const expectedHash = utils.keccak256( + utils.defaultAbiCoder.encode(["uint256", "uint256", "bytes"], [ticketID, blockNumber, messageData]) + ); + expect(messageHash).to.equal(expectedHash); + + const currentID = await fastBridgeSender.currentTicketID(); + expect(currentID).to.equal(4); + + // bridger tx starts - bridger creates fakeData & fakeHash for dishonest ruling + const fakeData = "0x0000000000000000000000009a9f2ccfde556a7e9ff0848998aa4a0cfd8863ae000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000643496987923bd6a8aa2bdce6c5b15551665079e7acfb1b4d2149ac7e2f72260417d541b7f000000000000000000000000000000000000000000000000000000000000000100000000000000000000000070997970c51812dc3a010c7d01b50e0d17dc79c800000000000000000000000000000000000000000000000000000000"; + const fakeHash = utils.keccak256( + utils.defaultAbiCoder.encode(["uint256", "uint256", "bytes"], [ticketID, blockNumber, fakeData]) + ); + + const tx5 = await fastBridgeReceiver.connect(bridger).claim(ticketID, fakeHash, { value: ONE_TENTH_ETH }); + let blockNumBefore = await ethers.provider.getBlockNumber(); + let blockBefore = await ethers.provider.getBlock(blockNumBefore); + let timestampBefore = blockBefore.timestamp; + console.log("Block: %d", blockNumBefore); + expect(tx5).to.emit(fastBridgeReceiver, "ClaimReceived").withArgs(ticketID, fakeHash, timestampBefore); + + // Challenger tx starts + const tx6 = await fastBridgeReceiver.connect(challenger).challenge(ticketID, { value: ONE_TENTH_ETH }); + blockNumBefore = await ethers.provider.getBlockNumber(); + blockBefore = await ethers.provider.getBlock(blockNumBefore); + timestampBefore = blockBefore.timestamp; + console.log("Block: %d", blockNumBefore); + expect(tx6).to.emit(fastBridgeReceiver, "ClaimChallenged").withArgs(ticketID, timestampBefore); + + // wait for challenge period to pass + await network.provider.send("evm_increaseTime", [300]); + await network.provider.send("evm_mine"); + + await expect( + fastBridgeReceiver.connect(bridger).verifyAndRelay(ticketID, blockNumber, fakeData) + ).to.be.revertedWith("Claim is challenged"); + + let data = await ethers.utils.defaultAbiCoder.decode(["address", "bytes"], fakeData); + + await expect( + fastBridgeSender + .connect(bridger) + .sendSafeFallbackMock(ticketID, foreignGateway.address, data[1], { gasLimit: 1000000 }) + ).to.be.revertedWith("Invalid message for ticketID."); + + data = await ethers.utils.defaultAbiCoder.decode(["address", "bytes"], messageData); + const tx8 = await fastBridgeSender + .connect(bridger) + .sendSafeFallbackMock(ticketID, foreignGateway.address, data[1], { gasLimit: 1000000 }); + expect(tx8).to.emit(fastBridgeSender, "L2ToL1TxCreated"); + expect(tx8).to.emit(arbitrable, "Ruling"); + + await expect(fastBridgeReceiver.withdrawClaimDeposit(ticketID)).to.be.revertedWith( + "Claim not verified: deposit forfeited" + ); + await fastBridgeReceiver.withdrawChallengeDeposit(ticketID); }); });