diff --git a/contracts/deploy/002_foreign_proxy.js b/contracts/deploy/002_foreign_proxy.js index 278a13e..c56de86 100644 --- a/contracts/deploy/002_foreign_proxy.js +++ b/contracts/deploy/002_foreign_proxy.js @@ -13,7 +13,7 @@ const paramsByChainId = { arbitratorExtraData: "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", zkAddress: "0x9A6DE0f62Aa270A8bCB1e2610078650D539B1Ef9", - metaEvidence: "/ipfs/QmUWbqopqmdJhabN1WpUMXse6TzTdUiM6TF95FQnP4AonY/realitio.json", + metaEvidence: "/ipfs/QmZjtv9jTbykD39z8U7ZAmZ3mvRw7J6etKohgJBS5Nmxm5/metaevidence.json", }, 1: { arbitrator: "0x988b3a538b618c7a603e1c11ab82cd16dbe28069", // KlerosLiquid address @@ -24,21 +24,32 @@ const paramsByChainId = { }, }; -const surplus = ethers.utils.parseUnits("0.01", "ether"); +const surplus = ethers.utils.parseUnits("0.05", "ether"); const winnerMultiplier = 3000; const loserMultiplier = 7000; const loserAppealPeriodMultiplier = 5000; +const l2GasLimit = 1500000; +const l2GasPerPubdataByteLimit = 800; async function main() { console.log("Starting foreign proxy deployment.."); const chainId = hre.network.config.chainId; const { arbitrator, arbitratorExtraData, zkAddress, metaEvidence } = paramsByChainId[chainId]; + let governor; + if (chainId === 1) { + governor = "TODO"; // Determine later + } else { + governor = (await ethers.getSigners())[0].address; + } const ForeignProxy = await ethers.getContractFactory("zkRealitioForeignProxy"); const foreignProxy = await ForeignProxy.deploy( + governor, arbitrator, arbitratorExtraData, zkAddress, + l2GasLimit, + l2GasPerPubdataByteLimit, surplus, metaEvidence, winnerMultiplier, diff --git a/contracts/hardhat.config.js b/contracts/hardhat.config.js index 1b13e01..67086c0 100644 --- a/contracts/hardhat.config.js +++ b/contracts/hardhat.config.js @@ -9,14 +9,6 @@ require("@matterlabs/hardhat-zksync-solc"); require('hardhat-contract-sizer'); require("@matterlabs/hardhat-zksync-verify"); -const getProof = require("./tasks/get_proof.js"); - -task("getProof", "Obtain a proof and send it to L2") - .addParam("txhash", "The transaction hash") - .setAction(async (taskArgs) => { - await getProof(taskArgs.txhash); - }); - module.exports = { solidity: { version: "0.8.19", @@ -37,6 +29,8 @@ module.exports = { networks: { hardhat: { blockGasLimit: 100000000000, + // Enable zkSync only if not in a test environment. We need this flag to create zk artifacts. For tests the ordinary artifacts will be used for both proxies. + zksync: !process.env.TEST_ENV, }, zkSyncGoerli: { url: "https://testnet.era.zksync.dev", diff --git a/contracts/package.json b/contracts/package.json index d3a13df..074ab08 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -9,9 +9,9 @@ "size": "hardhat size-contracts", "deploy:staging": "hardhat compile && hardhat deploy-zksync --script \\deploy\\001_home_proxy.js --network zkSyncSepolia && hardhat run .\\deploy\\002_foreign_proxy.js --network sepolia", "deploy-production": "hardhat compile && hardhat deploy-zksync --script \\deploy\\001_home_proxy.js --network zkSyncMainnet && hardhat run .\\deploy\\002_foreign_proxy.js --network mainnet", - "proof:staging": "hardhat getProof --network zkSyncSepolia", - "proof:production": "hardhat getProof --network zkSyncMainnet", - "test": "hardhat test" + "proof:staging": "hardhat run .\\scripts\\execute_proof.js --network zkSyncSepolia", + "proof:production": "hardhat run .\\scripts\\execute_proof.js --network zkSyncMainnet", + "test": "set TEST_ENV=true && hardhat test" }, "dependencies": { "@kleros/dispute-resolver-interface-contract": "^8.0.0", diff --git a/contracts/scripts/execute_proof.js b/contracts/scripts/execute_proof.js new file mode 100644 index 0000000..6496ab7 --- /dev/null +++ b/contracts/scripts/execute_proof.js @@ -0,0 +1,109 @@ +const hre = require("hardhat"); +const { Provider, utils } = require("zksync-web3"); +const { getL1MessageSentEvent, getCalldata } = require("../helpers/get_event_properties"); +const RealitioForeignArbitrationProxy = require("@kleros/cross-chain-realitio-contracts/artifacts-zk/src/zkRealitioForeignProxy.sol/zkRealitioForeignProxy.json"); +const RealitioHomeArbitrationProxy = require("@kleros/cross-chain-realitio-contracts/artifacts-zk/src/zkRealitioHomeProxy.sol/zkRealitioHomeProxy.json"); + +async function executeProof() { + // https://era.zksync.io/docs/dev/how-to/send-message-l2-l1.html + const txHash = ""; + + const { providers } = ethers; + const foreignNetworks = { + 280: hre.config.networks.goerli, + 324: hre.config.networks.mainnet, + 300: hre.config.networks.sepolia, + }; + const chainId = hre.network.config.chainId; + const url = foreignNetworks[chainId]; + + const l1Provider = new Provider(hre.network.config.url); + const l2Provider = new providers.JsonRpcProvider(url); + + const l1MessageSentEvent = await getL1MessageSentEvent(txHash, utils.L1_MESSENGER, l1Provider); + + if (!l1MessageSentEvent) { + throw new Error("No L1MessageSent event found in the transaction."); + } + + const blockNumber = l1MessageSentEvent.blockNumber; + const homeProxy = "0x" + BigInt(l1MessageSentEvent.address).toString(16); + const msgHash = l1MessageSentEvent.msgHash; + const eventData = await getCalldata(txHash, l1Provider); + const homeProxyContract = new ethers.Contract(homeProxy, RealitioHomeArbitrationProxy.abi, l1Provider); + console.log(await homeProxyContract.foreignProxy()); + const foreignProxyContract = new ethers.Contract( + await homeProxyContract.foreignProxy(), + RealitioForeignArbitrationProxy.abi, + l2Provider + ); + + console.log(`Event: ${l1MessageSentEvent.name}`); + console.log("Block Number:", blockNumber); + console.log("Smart Contract Address:", homeProxy); + console.log("Hash:", msgHash); + console.log("Message:", eventData); + + const proof = await getL1MessageProof(blockNumber, l1Provider, homeProxy, msgHash); + console.log(`Proof is: `, proof); + const { l1BatchNumber, l1BatchTxIndex } = await l1Provider.getTransactionReceipt(txHash); + + console.log("L1 Index for Tx in block :>> ", l1BatchTxIndex); + console.log("L1 Batch for block :>> ", l1BatchNumber); + + const result = await proveL1MessageInclusion( + l1BatchNumber, + proof, + l1BatchTxIndex, + l1Provider, + l2Provider, + homeProxy, + eventData + ); + + console.log("Result is :>> ", result); + + if (result) { + const signer = new ethers.Wallet(process.env.PRIVATE_KEY, l2Provider); + try { + await foreignProxyContract + .connect(signer) + .consumeMessageFromL2(l1BatchNumber, proof.id, l1BatchTxIndex, eventData, proof.proof); + console.log("Message successfully consumed on L2"); + } catch (error) { + console.error("Error calling consumeMessageFromL2:", error.message); + } + } else { + console.error("The result is false. Skipping the call"); + } + process.exit(); +} + +async function getL1MessageProof(blockNumber, l1Provider, homeProxy, msgHash) { + console.log(`Getting L1 message proof for block ${blockNumber}`); + return await l1Provider.getMessageProof(blockNumber, homeProxy, msgHash); +} + +async function proveL1MessageInclusion(l1BatchNumber, proof, trxIndex, l1Provider, l2Provider, homeProxy, message) { + const zkAddress = await l1Provider.getMainContractAddress(); + + const mailboxL1Contract = new ethers.Contract(zkAddress, utils.ZKSYNC_MAIN_ABI, l2Provider); + const messageInfo = { + txNumberInBlock: trxIndex, + sender: homeProxy, + data: message, + }; + + console.log(`Retrieving proof for batch ${l1BatchNumber}, transaction index ${trxIndex} and proof id ${proof.id}`); + const res = await mailboxL1Contract.proveL2MessageInclusion(l1BatchNumber, proof.id, messageInfo, proof.proof); + + return res; +} + +executeProof() + .then(() => { + console.log("Executed successfully!"); + }) + .catch((error) => { + console.error("Error:", error); + }); diff --git a/contracts/src/zkRealitioForeignProxy.sol b/contracts/src/zkRealitioForeignProxy.sol index c533000..458913a 100644 --- a/contracts/src/zkRealitioForeignProxy.sol +++ b/contracts/src/zkRealitioForeignProxy.sol @@ -25,9 +25,7 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { uint256 public constant NUMBER_OF_CHOICES_FOR_ARBITRATOR = type(uint256).max; uint256 public constant REFUSE_TO_ARBITRATE_REALITIO = type(uint256).max; // Constant that represents "Refuse to rule" in realitio format. uint256 public constant MULTIPLIER_DIVISOR = 10000; // Divisor parameter for multipliers. - uint256 public constant META_EVIDENCE_ID = 0; // The ID of the MetaEvidence for disputes. - uint256 public constant L2_GAS_LIMIT = 1500000; // Gas limit of the transaction call. Note some L2 operations consume up to 700000 gas. - uint256 public constant L2_GAS_PER_PUB_DATA_BYTE_LIMIT = 800; // L2 gas price for each published L1 calldata byte. Current default value is 800 https://era.zksync.io/docs/api/js/utils.html#gas + uint256 public constant L2_CALL_VALUE = 0; // Amount of ETH to pass with the call to L2. Used as msg.value for the transaction. /* Storage */ @@ -46,6 +44,9 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { uint256 disputeID; // The ID of the dispute in arbitrator contract. uint256 answer; // The answer given by the arbitrator. Round[] rounds; // Tracks each appeal round of a dispute. + IArbitrator arbitrator; // The arbitrator trusted to solve disputes for this request. + bytes arbitratorExtraData; // The extra data for the trusted arbitrator of this request. + uint256 metaEvidenceID; // The meta evidence to be used in a dispute for this case. } struct DisputeDetails { @@ -62,19 +63,23 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { uint256[] fundedAnswers; // Stores the answer choices that are fully funded. } - address public deployer = msg.sender; - IArbitrator public immutable arbitrator; // The address of the arbitrator. TRUSTED. + address public deployer = msg.sender; // Deployer address. It will be set to 0 after setting HomeProxy. + address public governor; // Governor of the contract (e.g KlerosGovernor). + IArbitrator public arbitrator; // The address of the arbitrator. TRUSTED. bytes public arbitratorExtraData; // The extra data used to raise a dispute in the arbitrator. - // Note that zkSync address can change in the future. Redeploy the contract in this case. - IZkSync public immutable zkSyncAddress; // The current address of the zkSync L1 bridge. + uint256 public metaEvidenceUpdates; // The number of times the meta evidence has been updated. Used to track the latest meta evidence ID. + + IZkSync public zkSyncAddress; // The current address of the zkSync L1 bridge. address public homeProxy; // Proxy on L2. - uint256 public surplusAmount; // The amount to add to arbitration fees to cover for zkSync fees. The leftover will be reimbursed. This is required for Realtio UI. + uint256 public l2GasLimit; // Gas limit of the transaction call. Note some L2 operations consume up to 700000 gas. Default value is 1500000 + uint256 public l2GasPerPubdataByteLimit; // L2 gas price for each published L1 calldata byte. Current default value is 800 https://era.zksync.io/docs/api/js/utils.html#gas + uint256 public surplusAmount; // The amount to add to arbitration fees to cover for zkSync fees. The leftover will be reimbursed. This is required for Realtio UI. Default value is 0.05 Eth. // Multipliers are in basis points. - uint256 public immutable winnerMultiplier; // Multiplier for calculating the appeal fee that must be paid for the answer that was chosen by the arbitrator in the previous round. - uint256 public immutable loserMultiplier; // Multiplier for calculating the appeal fee that must be paid for the answer that the arbitrator didn't rule for in the previous round. - uint256 public immutable loserAppealPeriodMultiplier; // Multiplier for calculating the duration of the appeal period for the loser, in basis points. + uint256 public winnerMultiplier; // Multiplier for calculating the appeal fee that must be paid for the answer that was chosen by the arbitrator in the previous round. + uint256 public loserMultiplier; // Multiplier for calculating the appeal fee that must be paid for the answer that the arbitrator didn't rule for in the previous round. + uint256 public loserAppealPeriodMultiplier; // Multiplier for calculating the duration of the appeal period for the loser, in basis points. mapping(uint256 => mapping(address => ArbitrationRequest)) public arbitrationRequests; // Maps arbitration ID to its data. arbitrationRequests[uint(questionID)][requester]. mapping(uint256 => DisputeDetails) public disputeIDToDisputeDetails; // Maps external dispute ids to local arbitration ID and requester who was able to complete the arbitration request. @@ -94,11 +99,19 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { _; } + modifier onlyGovernor() { + require(msg.sender == governor, "The caller must be the governor."); + _; + } + /** * @notice Creates an arbitration proxy on the foreign chain (L1). + * @param _governor Governor of the contract. * @param _arbitrator Arbitrator contract address. * @param _arbitratorExtraData The extra data used to raise a dispute in the arbitrator. * @param _zkSyncAddress The current address of the zkSync L1 bridge. + * @param _l2GasLimit Gas limit for L2 tx. + * @param _l2GasPerPubdataByteLimit L2 gas price for each published L1 calldata byte. * @param _surplusAmount The surplus amount to cover zk fees. * @param _metaEvidence The URI of the meta evidence file. * @param _winnerMultiplier Multiplier for calculating the appeal cost of the winning answer. @@ -106,30 +119,40 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { * @param _loserAppealPeriodMultiplier Multiplier for calculating the appeal period for the losing answer. */ constructor( + address _governor, IArbitrator _arbitrator, bytes memory _arbitratorExtraData, IZkSync _zkSyncAddress, + uint256 _l2GasLimit, + uint256 _l2GasPerPubdataByteLimit, uint256 _surplusAmount, string memory _metaEvidence, uint256 _winnerMultiplier, uint256 _loserMultiplier, uint256 _loserAppealPeriodMultiplier ) { + governor = _governor; arbitrator = _arbitrator; arbitratorExtraData = _arbitratorExtraData; zkSyncAddress = _zkSyncAddress; + l2GasLimit = _l2GasLimit; + l2GasPerPubdataByteLimit = _l2GasPerPubdataByteLimit; surplusAmount = _surplusAmount; winnerMultiplier = _winnerMultiplier; loserMultiplier = _loserMultiplier; loserAppealPeriodMultiplier = _loserAppealPeriodMultiplier; - emit MetaEvidence(META_EVIDENCE_ID, _metaEvidence); + emit MetaEvidence(metaEvidenceUpdates, _metaEvidence); } /* External and public */ + // ************************ // + // * Governance * // + // ************************ // + /** - * @notice Set home proxy. + * @notice Set home proxy. Can only be done by deployer. * @param _homeProxy Address of the proxy on L2. */ function setHomeProxy(address _homeProxy) external { @@ -138,6 +161,91 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { deployer = address(0); } + /** + * @notice Changes the governor of the contract. + * @param _governor New governor address. + */ + function changeGovernor(address _governor) external onlyGovernor { + governor = _governor; + } + + /** + * @notice Changes the arbitrator and extradata. The arbitrator is trusted to support appeal period and not reenter. + * Note avoid changing arbitrator if there is an active arbitration request in Requested phase, otherwise evidence submitted during this phase + * will be submitted to the new arbitrator, while arbitration request will be processed by the old one. + * @param _arbitrator New arbitrator address. + * @param _arbitratorExtraData Extradata for the arbitrator + */ + function changeArbitrator(IArbitrator _arbitrator, bytes calldata _arbitratorExtraData) external onlyGovernor { + arbitrator = _arbitrator; + arbitratorExtraData = _arbitratorExtraData; + } + + /** + * @notice Update the meta evidence used for disputes. + * @param _metaEvidence Metaevidence URI. + */ + function changeMetaevidence(string memory _metaEvidence) external onlyGovernor { + metaEvidenceUpdates++; + emit MetaEvidence(metaEvidenceUpdates, _metaEvidence); + } + + /** + * @notice Changes the address of zkSync bridge. + * @param _zkSyncAddress New zkSync bridge address. + */ + function changeZkSyncAddress(IZkSync _zkSyncAddress) external onlyGovernor { + zkSyncAddress = _zkSyncAddress; + } + + /** + * @notice Changes gas limit for L2 tx. + * @param _l2GasLimit New L2 gas limit. + */ + function changeL2GasLimit(uint256 _l2GasLimit) external onlyGovernor { + l2GasLimit = _l2GasLimit; + } + + /** + * @notice Changes L2 gas price for each published L1 calldata byte. + * @param _l2GasPerPubdataByteLimit New L2 gas per pubdata. + */ + function changeL2GasPerPubdata(uint256 _l2GasPerPubdataByteLimit) external onlyGovernor { + l2GasPerPubdataByteLimit = _l2GasPerPubdataByteLimit; + } + + /** + * @notice Changes the surplus amount to cover the zk fees. + * @param _surplusAmount New surplus amount value. + */ + function changeSurplusAmount(uint256 _surplusAmount) external onlyGovernor { + surplusAmount = _surplusAmount; + } + + /** + * @notice Changes winner multiplier value. + * @param _winnerMultiplier New winner multiplier. + */ + function changeWinnerMultiplier(uint256 _winnerMultiplier) external onlyGovernor { + winnerMultiplier = _winnerMultiplier; + } + + /** + * @notice Changes loser multiplier value. + * @param _loserMultiplier New loser multiplier. + */ + function changeLoserMultiplier(uint256 _loserMultiplier) external onlyGovernor { + loserMultiplier = _loserMultiplier; + } + + /** + * @notice Changes loser multiplier for appeal period. + * @param _loserAppealPeriodMultiplier New loser multiplier for appeal perido. + */ + function changeLoserAppealPeriodMultiplier(uint256 _loserAppealPeriodMultiplier) external onlyGovernor { + loserAppealPeriodMultiplier = _loserAppealPeriodMultiplier; + } + // ************************ // // * Realitio logic * // // ************************ // @@ -154,6 +262,10 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { ArbitrationRequest storage arbitration = arbitrationRequests[uint256(_questionID)][msg.sender]; require(arbitration.status == Status.None, "Arbitration already requested"); + arbitration.arbitrator = arbitrator; + arbitration.arbitratorExtraData = arbitratorExtraData; + arbitration.metaEvidenceID = metaEvidenceUpdates; + uint256 arbitrationCost = arbitrator.arbitrationCost(arbitratorExtraData); uint256 zkGasFee = getZkGasFee(); require(msg.value >= arbitrationCost + zkGasFee, "Deposit value too low"); @@ -166,10 +278,10 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { zkSyncAddress.requestL2Transaction{value: zkGasFee}( homeProxy, - 0, + L2_CALL_VALUE, data, - L2_GAS_LIMIT, - L2_GAS_PER_PUB_DATA_BYTE_LIMIT, + l2GasLimit, + l2GasPerPubdataByteLimit, new bytes[](0), msg.sender ); @@ -219,11 +331,11 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { ArbitrationRequest storage arbitration = arbitrationRequests[arbitrationID][_requester]; require(arbitration.status == Status.Requested, "Invalid arbitration status"); - uint256 arbitrationCost = arbitrator.arbitrationCost(arbitratorExtraData); + uint256 arbitrationCost = arbitration.arbitrator.arbitrationCost(arbitration.arbitratorExtraData); if (arbitration.deposit >= arbitrationCost) { try - arbitrator.createDispute{value: arbitrationCost}(NUMBER_OF_CHOICES_FOR_ARBITRATOR, arbitratorExtraData) + arbitration.arbitrator.createDispute{value: arbitrationCost}(NUMBER_OF_CHOICES_FOR_ARBITRATOR, arbitration.arbitratorExtraData) returns (uint256 disputeID) { DisputeDetails storage disputeDetails = disputeIDToDisputeDetails[disputeID]; disputeDetails.arbitrationID = arbitrationID; @@ -245,7 +357,7 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { } emit ArbitrationCreated(_questionID, _requester, disputeID); - emit Dispute(arbitrator, disputeID, META_EVIDENCE_ID, arbitrationID); + emit Dispute(arbitration.arbitrator, disputeID, arbitration.metaEvidenceID, arbitrationID); } catch { arbitration.status = Status.Failed; emit ArbitrationFailed(_questionID, _requester); @@ -295,10 +407,10 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { bytes memory data = abi.encodeWithSelector(methodSelector, _questionID, _requester); zkSyncAddress.requestL2Transaction{value: zkGasFee}( homeProxy, - 0, + L2_CALL_VALUE, data, - L2_GAS_LIMIT, - L2_GAS_PER_PUB_DATA_BYTE_LIMIT, + l2GasLimit, + l2GasPerPubdataByteLimit, new bytes[](0), msg.sender ); @@ -325,12 +437,12 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { require(arbitration.status == Status.Created, "No dispute to appeal."); uint256 disputeID = arbitration.disputeID; - (uint256 appealPeriodStart, uint256 appealPeriodEnd) = arbitrator.appealPeriod(disputeID); + (uint256 appealPeriodStart, uint256 appealPeriodEnd) = arbitration.arbitrator.appealPeriod(disputeID); require(block.timestamp >= appealPeriodStart && block.timestamp < appealPeriodEnd, "Appeal period is over."); uint256 multiplier; { - uint256 winner = arbitrator.currentRuling(disputeID); + uint256 winner = arbitration.arbitrator.currentRuling(disputeID); if (winner == _answer) { multiplier = winnerMultiplier; } else { @@ -346,7 +458,7 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { uint256 lastRoundID = arbitration.rounds.length - 1; Round storage round = arbitration.rounds[lastRoundID]; require(!round.hasPaid[_answer], "Appeal fee is already paid."); - uint256 appealCost = arbitrator.appealCost(disputeID, arbitratorExtraData); + uint256 appealCost = arbitration.arbitrator.appealCost(disputeID, arbitration.arbitratorExtraData); uint256 totalCost = appealCost + ((appealCost * multiplier) / MULTIPLIER_DIVISOR); // Take up to the amount necessary to fund the current round at the current costs. @@ -369,7 +481,7 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { arbitration.rounds.push(); round.feeRewards = round.feeRewards - appealCost; - arbitrator.appeal{value: appealCost}(disputeID, arbitratorExtraData); + arbitration.arbitrator.appeal{value: appealCost}(disputeID, arbitration.arbitratorExtraData); } if (msg.value - contribution > 0) payable(msg.sender).send(msg.value - contribution); // Sending extra value back to contributor. It is the user's responsibility to accept ETH. @@ -445,7 +557,14 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { * @param _evidenceURI Link to evidence. */ function submitEvidence(uint256 _arbitrationID, string calldata _evidenceURI) external override { - emit Evidence(arbitrator, _arbitrationID, msg.sender, _evidenceURI); + address requester = arbitrationIDToRequester[_arbitrationID]; + ArbitrationRequest storage arbitration = arbitrationRequests[_arbitrationID][requester]; + if (address(arbitration.arbitrator) == address(0)) { //None or Requested status. + // Note that arbitrator set during requestArbitration might differ from default arbitrator, if default arbitrator was changed during Requested status. + emit Evidence(arbitrator, _arbitrationID, msg.sender, _evidenceURI); + } else { + emit Evidence(arbitration.arbitrator, _arbitrationID, msg.sender, _evidenceURI); + } } /** @@ -460,7 +579,7 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { address requester = disputeDetails.requester; ArbitrationRequest storage arbitration = arbitrationRequests[arbitrationID][requester]; - require(msg.sender == address(arbitrator), "Only arbitrator allowed"); + require(msg.sender == address(arbitration.arbitrator), "Only arbitrator allowed"); require(arbitration.status == Status.Created, "Invalid arbitration status"); uint256 finalRuling = _ruling; @@ -470,7 +589,7 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { arbitration.answer = finalRuling; arbitration.status = Status.Ruled; - emit Ruling(arbitrator, _disputeID, finalRuling); + emit Ruling(IArbitrator(msg.sender), _disputeID, finalRuling); } /** @@ -495,10 +614,10 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { zkSyncAddress.requestL2Transaction{value: zkGasFee}( homeProxy, - 0, + L2_CALL_VALUE, data, - L2_GAS_LIMIT, - L2_GAS_PER_PUB_DATA_BYTE_LIMIT, + l2GasLimit, + l2GasPerPubdataByteLimit, new bytes[](0), msg.sender ); @@ -691,6 +810,6 @@ contract zkRealitioForeignProxy is IForeignArbitrationProxy, IDisputeResolver { */ function getZkGasFee() private view returns (uint256 zkGasFee) { uint256 gasPrice = tx.gasprice; - zkGasFee = zkSyncAddress.l2TransactionBaseCost(gasPrice, L2_GAS_LIMIT, L2_GAS_PER_PUB_DATA_BYTE_LIMIT); + zkGasFee = zkSyncAddress.l2TransactionBaseCost(gasPrice, l2GasLimit, l2GasPerPubdataByteLimit); } } diff --git a/contracts/src/zkRealitioHomeProxy.sol b/contracts/src/zkRealitioHomeProxy.sol index 925d590..be0ed1b 100644 --- a/contracts/src/zkRealitioHomeProxy.sol +++ b/contracts/src/zkRealitioHomeProxy.sol @@ -133,7 +133,7 @@ contract zkRealitioHomeProxy is IHomeArbitrationProxy { bytes4 selector = IForeignArbitrationProxy.receiveArbitrationAcknowledgement.selector; bytes memory data = abi.encodeWithSelector(selector, _questionID, _requester); - sendToL1((data)); + sendToL1(data); emit RequestAcknowledged(_questionID, _requester); } diff --git a/contracts/tasks/get_proof.js b/contracts/tasks/get_proof.js deleted file mode 100644 index 8ae6e15..0000000 --- a/contracts/tasks/get_proof.js +++ /dev/null @@ -1,93 +0,0 @@ -const { Provider, utils } = require("zksync-web3"); -const { getL1MessageSentEvent, getCalldata } = require("../helpers/get_event_properties"); -const RealitioForeignArbitrationProxy = require("@kleros/cross-chain-realitio-contracts/artifacts-zk/src/zkRealitioForeignProxy.sol/zkRealitioForeignProxy.json"); -const RealitioHomeArbitrationProxy = require("@kleros/cross-chain-realitio-contracts/artifacts-zk/src/zkRealitioHomeProxy.sol/zkRealitioHomeProxy.json"); - -async function getProof(txHash) { - // https://era.zksync.io/docs/dev/how-to/send-message-l2-l1.html - const { providers } = ethers; - const foreignNetworks = { - 280: hre.config.networks.goerli, - 324: hre.config.networks.mainnet, - 300: hre.config.networks.sepolia, - }; - const chainId = hre.network.config.chainId; - const url = foreignNetworks[chainId]; - - const l1Provider = new Provider(hre.network.config.url); - const l2Provider = new providers.JsonRpcProvider(url); - - const l1MessageSentEvent = await getL1MessageSentEvent(txHash, utils.L1_MESSENGER, l1Provider); - - if (!l1MessageSentEvent) { - throw new Error("No L1MessageSent event found in the transaction."); - } - - const blockNumber = l1MessageSentEvent.blockNumber; - const homeProxy = "0x" + BigInt(l1MessageSentEvent.address).toString(16); - const msgHash = l1MessageSentEvent.msgHash; - const eventData = await getCalldata(txHash, l1Provider); - const homeProxyContract = new ethers.Contract(homeProxy, RealitioHomeArbitrationProxy.abi, l1Provider); - console.log(await homeProxyContract.foreignProxy()); - const foreignProxyContract = new ethers.Contract(await homeProxyContract.foreignProxy(), RealitioForeignArbitrationProxy.abi, l2Provider); - - console.log(`Event: ${l1MessageSentEvent.name}`); - console.log("Block Number:", blockNumber); - console.log("Smart Contract Address:", homeProxy); - console.log("Hash:", msgHash); - console.log("Message:", eventData); - - const proof = await getL1MessageProof(blockNumber, l1Provider, homeProxy, msgHash); - console.log(`Proof is: `, proof); - const { l1BatchNumber, l1BatchTxIndex } = await l1Provider.getTransactionReceipt(txHash); - - console.log("L1 Index for Tx in block :>> ", l1BatchTxIndex); - console.log("L1 Batch for block :>> ", l1BatchNumber); - - const result = await proveL1MessageInclusion( - l1BatchNumber, - proof, - l1BatchTxIndex, - l1Provider, - l2Provider, - homeProxy, - eventData - ); - - console.log("Result is :>> ", result); - - if (result) { - const signer = new ethers.Wallet(process.env.PRIVATE_KEY, l2Provider); - try { - await foreignProxyContract.connect(signer).consumeMessageFromL2(l1BatchNumber, proof.id, l1BatchTxIndex, eventData, proof.proof); - console.log("Message successfully consumed on L2"); - } catch (error) { - console.error("Error calling consumeMessageFromL2:", error.message); - } - } else { - console.error("The result is false. Skipping the call"); - } - process.exit(); - } - - async function getL1MessageProof(blockNumber, l1Provider, homeProxy, msgHash) { - console.log(`Getting L1 message proof for block ${blockNumber}`); - return await l1Provider.getMessageProof(blockNumber, homeProxy, msgHash); - } - - async function proveL1MessageInclusion(l1BatchNumber, proof, trxIndex, l1Provider, l2Provider, homeProxy, message) { - const zkAddress = await l1Provider.getMainContractAddress(); - - const mailboxL1Contract = new ethers.Contract(zkAddress, utils.ZKSYNC_MAIN_ABI, l2Provider); - const messageInfo = { - txNumberInBlock: trxIndex, - sender: homeProxy, - data: message, - }; - - console.log(`Retrieving proof for batch ${l1BatchNumber}, transaction index ${trxIndex} and proof id ${proof.id}`); - const res = await mailboxL1Contract.proveL2MessageInclusion(l1BatchNumber, proof.id, messageInfo, proof.proof); - - return res; - } - module.exports = getProof; diff --git a/contracts/test/foreign_proxy_with_appeals.js b/contracts/test/foreign_proxy_with_appeals.js index 7e232a6..9d4ed9f 100644 --- a/contracts/test/foreign_proxy_with_appeals.js +++ b/contracts/test/foreign_proxy_with_appeals.js @@ -70,10 +70,14 @@ describe("Cross-chain arbitration with appeals", () => { it("Should correctly set the initial values", async () => { await foreignProxy.setHomeProxy(homeProxy.address); + expect(await foreignProxy.governor()).to.equal(governor.address); expect(await foreignProxy.arbitrator()).to.equal(arbitrator.address); expect(await foreignProxy.arbitratorExtraData()).to.equal(arbitratorExtraData); expect(await foreignProxy.zkSyncAddress()).to.equal(mockZkSync.address); + expect(await foreignProxy.l2GasLimit()).to.equal(L2_GAS_LIMIT); + expect(await foreignProxy.l2GasPerPubdataByteLimit()).to.equal(L2_GAS_PER_PUB_DATA_BYTE_LIMIT); expect(await foreignProxy.surplusAmount()).to.equal(200); + expect(await foreignProxy.metaEvidenceUpdates()).to.equal(0); expect(await foreignProxy.homeProxy()).to.equal(homeProxy.address); expect(await foreignProxy.deployer()).to.equal(ADDRESS_ZERO); @@ -99,6 +103,51 @@ describe("Cross-chain arbitration with appeals", () => { await expect(foreignProxy.setHomeProxy(mockZkSync.address)).to.be.revertedWith("Only deployer can"); // Deployer is nullified so no one can re-set the proxy }); + it("Check governance requires", async () => { + await expect(foreignProxy.connect(other).changeArbitrator(homeProxy.address, "0xff")).to.be.revertedWith("The caller must be the governor."); + await foreignProxy.connect(governor).changeArbitrator(homeProxy.address, "0xff"); + expect(await foreignProxy.arbitrator()).to.equal(homeProxy.address); + expect(await foreignProxy.arbitratorExtraData()).to.equal("0xff"); + + await expect(foreignProxy.connect(other).changeMetaevidence("ME2.0")).to.be.revertedWith("The caller must be the governor."); + await foreignProxy.connect(governor).changeMetaevidence("ME2.0"); + expect(await foreignProxy.metaEvidenceUpdates()).to.equal(1); + + await expect(foreignProxy.connect(other).changeZkSyncAddress(other.address)).to.be.revertedWith("The caller must be the governor."); + await foreignProxy.connect(governor).changeZkSyncAddress(other.address); + expect(await foreignProxy.zkSyncAddress()).to.equal(other.address); + + await expect(foreignProxy.connect(other).changeL2GasLimit(1111)).to.be.revertedWith("The caller must be the governor."); + await foreignProxy.connect(governor).changeL2GasLimit(1111); + expect(await foreignProxy.l2GasLimit()).to.equal(1111); + + await expect(foreignProxy.connect(other).changeL2GasPerPubdata(2222)).to.be.revertedWith("The caller must be the governor."); + await foreignProxy.connect(governor).changeL2GasPerPubdata(2222); + expect(await foreignProxy.l2GasPerPubdataByteLimit()).to.equal(2222); + + await expect(foreignProxy.connect(other).changeSurplusAmount(3333)).to.be.revertedWith("The caller must be the governor."); + await foreignProxy.connect(governor).changeSurplusAmount(3333); + expect(await foreignProxy.surplusAmount()).to.equal(3333); + + await expect(foreignProxy.connect(other).changeWinnerMultiplier(51)).to.be.revertedWith("The caller must be the governor."); + await foreignProxy.connect(governor).changeWinnerMultiplier(51); + expect(await foreignProxy.winnerMultiplier()).to.equal(51); + + await expect(foreignProxy.connect(other).changeGovernor(other.address)).to.be.revertedWith("The caller must be the governor."); + await foreignProxy.connect(governor).changeGovernor(other.address); + expect(await foreignProxy.governor()).to.equal(other.address); + + // Governor is change from now on + + await expect(foreignProxy.connect(governor).changeLoserMultiplier(25)).to.be.revertedWith("The caller must be the governor."); + await foreignProxy.connect(other).changeLoserMultiplier(25); + expect(await foreignProxy.loserMultiplier()).to.equal(25); + + await expect(foreignProxy.connect(governor).changeLoserAppealPeriodMultiplier(777)).to.be.revertedWith("The caller must be the governor."); + await foreignProxy.connect(other).changeLoserAppealPeriodMultiplier(777); + expect(await foreignProxy.loserAppealPeriodMultiplier()).to.equal(777); + }); + it("Should not allow to request arbitration if home proxy is not set", async () => { await expect( foreignProxy.connect(requester).requestArbitration(questionID, maxPrevious, { value: totalCost }) @@ -1073,9 +1122,12 @@ describe("Cross-chain arbitration with appeals", () => { const HomeProxy = await ethers.getContractFactory("MockZkHomeProxy", signer); const foreignProxy = await ForeignProxy.deploy( + signer.address, arbitrator.address, arbitratorExtraData, mockZkSync.address, + L2_GAS_LIMIT, + L2_GAS_PER_PUB_DATA_BYTE_LIMIT, surplusAmount, metaEvidence, winnerMultiplier, diff --git a/dynamic-script/package.json b/dynamic-script/package.json index f5a4212..3483313 100644 --- a/dynamic-script/package.json +++ b/dynamic-script/package.json @@ -23,5 +23,8 @@ "dotenv-webpack": "^4.0.0", "webpack-cli": "^3.3.12", "webpack-node-externals": "^2.5.2" + }, + "volta": { + "node": "16.20.2" } } diff --git a/evidence-display/package.json b/evidence-display/package.json index a663289..0a6cbfa 100644 --- a/evidence-display/package.json +++ b/evidence-display/package.json @@ -46,5 +46,8 @@ "babel-eslint": "^10.1.0", "babel-loader": "^8.1.0", "babel-plugin-ramda": "^2.0.0" + }, + "volta": { + "node": "16.20.2" } } diff --git a/package.json b/package.json index 39825c2..34d6783 100644 --- a/package.json +++ b/package.json @@ -30,5 +30,8 @@ "prettier-plugin-solidity": "^1.0.0-beta.15", "solhint": "^3.3.6", "solhint-plugin-prettier": "^0.0.5" + }, + "volta": { + "node": "16.20.2" } }