diff --git a/contracts/deployments/arbitrumSepolia/VeaInboxArbToEthTestnet.json b/contracts/deployments/arbitrumSepolia/VeaInboxArbToEthTestnet.json new file mode 100644 index 00000000..d9e1cf72 --- /dev/null +++ b/contracts/deployments/arbitrumSepolia/VeaInboxArbToEthTestnet.json @@ -0,0 +1,443 @@ +{ + "address": "0xE12daFE59Bc3A996362d54b37DFd2BA9279cAd06", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epochPeriod", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_veaOutboxArbToEth", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes", + "name": "_nodeData", + "type": "bytes" + } + ], + "name": "MessageSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bytes32", + "name": "_snapshot", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint64", + "name": "_count", + "type": "uint64" + } + ], + "name": "SnapshotSaved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "_epochSent", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "_ticketId", + "type": "bytes32" + } + ], + "name": "SnapshotSent", + "type": "event" + }, + { + "inputs": [], + "name": "count", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_timestamp", + "type": "uint256" + } + ], + "name": "epochAt", + "outputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochFinalized", + "outputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochNow", + "outputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "saveSnapshot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "bytes4", + "name": "_fnSelector", + "type": "bytes4" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "sendMessage", + "outputs": [ + { + "internalType": "uint64", + "name": "", + "type": "uint64" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "sendSnapshot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "snapshots", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "veaOutboxArbToEth", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } + ], + "transactionHash": "0x6e499d37ad3867970cef46cfd3cf46eda955b36197f9e8703e2ed7ecb546c5f1", + "receipt": { + "to": null, + "from": "0xFa00D29d378EDC57AA1006946F0fc6230a5E3288", + "contractAddress": "0xE12daFE59Bc3A996362d54b37DFd2BA9279cAd06", + "transactionIndex": 3, + "gasUsed": "7438794", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "blockHash": "0x511e80865c4c8bc133e1cb6346531e707601c7c662cd86078006e716b1d1762f", + "transactionHash": "0x6e499d37ad3867970cef46cfd3cf46eda955b36197f9e8703e2ed7ecb546c5f1", + "logs": [], + "blockNumber": 77452741, + "cumulativeGasUsed": "24246782", + "status": 1, + "byzantium": true + }, + "args": [ + 7200, + "0x209BFdC6B7c66b63A8382196Ba3d06619d0F12c9" + ], + "numDeployments": 1, + "solcInputHash": "0d66bd5cfdf493ed8e081e9f7e1bf4fa", + "metadata": "{\"compiler\":{\"version\":\"0.8.24+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epochPeriod\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_veaOutboxArbToEth\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes\",\"name\":\"_nodeData\",\"type\":\"bytes\"}],\"name\":\"MessageSent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"_snapshot\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"_count\",\"type\":\"uint64\"}],\"name\":\"SnapshotSaved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"_epochSent\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"_ticketId\",\"type\":\"bytes32\"}],\"name\":\"SnapshotSent\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"count\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_timestamp\",\"type\":\"uint256\"}],\"name\":\"epochAt\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"epochFinalized\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"epochNow\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"epochPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"saveSnapshot\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"bytes4\",\"name\":\"_fnSelector\",\"type\":\"bytes4\"},{\"internalType\":\"bytes\",\"name\":\"_data\",\"type\":\"bytes\"}],\"name\":\"sendMessage\",\"outputs\":[{\"internalType\":\"uint64\",\"name\":\"\",\"type\":\"uint64\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"sendSnapshot\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"name\":\"snapshots\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"veaOutboxArbToEth\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Vea Inbox From Arbitrum to Ethereum. Note: This contract is deployed on Arbitrum.\",\"events\":{\"MessageSent(bytes)\":{\"details\":\"Relayers watch for these events to construct merkle proofs to execute transactions on Ethereum.\",\"params\":{\"_nodeData\":\"The data to create leaves in the merkle tree. abi.encodePacked(msgId, to, message), outbox relays to.call(message).\"}},\"SnapshotSaved(bytes32,uint256,uint64)\":{\"params\":{\"_count\":\"The count of messages in the merkle tree.\",\"_epoch\":\"The epoch of the snapshot.\",\"_snapshot\":\"The snapshot of the merkle tree state root.\"}},\"SnapshotSent(uint256,bytes32)\":{\"details\":\"The event is emitted when a snapshot is sent through the canonical arbitrum bridge.\",\"params\":{\"_epochSent\":\"The epoch of the snapshot.\",\"_ticketId\":\"The ticketId of the L2->L1 message.\"}}},\"kind\":\"dev\",\"methods\":{\"constructor\":{\"details\":\"Constructor. Note: epochPeriod must match the VeaOutboxArbToEth contract deployment on Ethereum, since it's on a different chain, we can't read it and trust the deployer to set a correct value\",\"params\":{\"_epochPeriod\":\"The duration in seconds between epochs.\",\"_veaOutboxArbToEth\":\"The veaOutbox on ethereum.\"}},\"epochAt(uint256)\":{\"details\":\"Get the epoch from the inbox's point of view using timestamp.\",\"params\":{\"_timestamp\":\"The timestamp to calculate the epoch from.\"},\"returns\":{\"epoch\":\"The calculated epoch.\"}},\"epochFinalized()\":{\"details\":\"Get the most recent epoch for which snapshots are finalized.\",\"returns\":{\"epoch\":\"The epoch associated with the current inbox block.timestamp\"}},\"epochNow()\":{\"details\":\"Get the current epoch from the inbox's point of view using the Arbitrum L2 clock.\",\"returns\":{\"epoch\":\"The epoch associated with the current inbox block.timestamp\"}},\"saveSnapshot()\":{\"details\":\"Saves snapshot of state root. Snapshots can be saved a maximum of once per epoch. `O(log(count))` where count number of messages in the inbox. Note: See merkle tree docs for details how inbox manages state.\"},\"sendMessage(address,bytes4,bytes)\":{\"details\":\"Sends an arbitrary message to Ethereum. `O(log(count))` where count is the number of messages already sent. Amortized cost is constant. Note: See docs for details how inbox manages merkle tree state.\",\"params\":{\"_data\":\"The message calldata, abi.encode(param1, param2, ...)\",\"_fnSelector\":\"The function selector of the receiving contract.\",\"_to\":\"The address of the contract on the receiving chain which receives the calldata.\"},\"returns\":{\"_0\":\"msgId The zero based index of the message in the inbox.\"}},\"sendSnapshot(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"Sends the state root snapshot using Arbitrum's canonical bridge.\",\"params\":{\"_claim\":\"The claim associated with the epoch.\",\"_epoch\":\"The epoch of the snapshot requested to send.\"}}},\"version\":1},\"userdoc\":{\"events\":{\"SnapshotSaved(bytes32,uint256,uint64)\":{\"notice\":\"The bridgers can watch this event to claim the stateRoot on the veaOutbox.\"}},\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/arbitrumToEth/VeaInboxArbToEth.sol\":\"VeaInboxArbToEth\"},\"evmVersion\":\"shanghai\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"src/arbitrumToEth/VeaInboxArbToEth.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\n/// @custom:authors: [@jaybuidl, @shotaronowhere]\\n/// @custom:reviewers: []\\n/// @custom:auditors: []\\n/// @custom:bounties: []\\n/// @custom:deployments: []\\n\\npragma solidity 0.8.24;\\n\\nimport \\\"../canonical/arbitrum/IArbSys.sol\\\";\\nimport \\\"../interfaces/inboxes/IVeaInbox.sol\\\";\\nimport \\\"../interfaces/outboxes/IVeaOutboxOnL1.sol\\\";\\n\\n/// @dev Vea Inbox From Arbitrum to Ethereum.\\n/// Note: This contract is deployed on Arbitrum.\\ncontract VeaInboxArbToEth is IVeaInbox {\\n // ************************************* //\\n // * Storage * //\\n // ************************************* //\\n\\n // Arbitrum precompile ArbSys for L2->L1 messaging: https://developer.arbitrum.io/arbos/precompiles#arbsys\\n IArbSys internal constant ARB_SYS = IArbSys(address(100));\\n\\n uint256 public immutable epochPeriod; // Epochs mark the period between potential snapshots.\\n address public immutable veaOutboxArbToEth; // The vea outbox on ethereum.\\n\\n mapping(uint256 epoch => bytes32) public snapshots; // epoch => state root snapshot\\n\\n // Inbox represents minimum data availability to maintain incremental merkle tree.\\n // Supports a max of 2^64 - 1 messages. See merkle tree docs for details how inbox manages state.\\n\\n bytes32[64] internal inbox; // stores minimal set of complete subtree roots of the merkle tree to increment.\\n uint64 public count; // count of messages in the merkle tree\\n\\n // ************************************* //\\n // * Events * //\\n // ************************************* //\\n\\n /// @dev Relayers watch for these events to construct merkle proofs to execute transactions on Ethereum.\\n /// @param _nodeData The data to create leaves in the merkle tree. abi.encodePacked(msgId, to, message), outbox relays to.call(message).\\n event MessageSent(bytes _nodeData);\\n\\n /// The bridgers can watch this event to claim the stateRoot on the veaOutbox.\\n /// @param _snapshot The snapshot of the merkle tree state root.\\n /// @param _epoch The epoch of the snapshot.\\n /// @param _count The count of messages in the merkle tree.\\n event SnapshotSaved(bytes32 _snapshot, uint256 _epoch, uint64 _count);\\n\\n /// @dev The event is emitted when a snapshot is sent through the canonical arbitrum bridge.\\n /// @param _epochSent The epoch of the snapshot.\\n /// @param _ticketId The ticketId of the L2->L1 message.\\n event SnapshotSent(uint256 indexed _epochSent, bytes32 _ticketId);\\n\\n /// @dev Constructor.\\n /// Note: epochPeriod must match the VeaOutboxArbToEth contract deployment on Ethereum, since it's on a different chain, we can't read it and trust the deployer to set a correct value\\n /// @param _epochPeriod The duration in seconds between epochs.\\n /// @param _veaOutboxArbToEth The veaOutbox on ethereum.\\n constructor(uint256 _epochPeriod, address _veaOutboxArbToEth) {\\n epochPeriod = _epochPeriod;\\n veaOutboxArbToEth = _veaOutboxArbToEth;\\n }\\n\\n // ************************************* //\\n // * State Modifiers * //\\n // ************************************* //\\n\\n /// @dev Sends an arbitrary message to Ethereum.\\n /// `O(log(count))` where count is the number of messages already sent.\\n /// Amortized cost is constant.\\n /// Note: See docs for details how inbox manages merkle tree state.\\n /// @param _to The address of the contract on the receiving chain which receives the calldata.\\n /// @param _fnSelector The function selector of the receiving contract.\\n /// @param _data The message calldata, abi.encode(param1, param2, ...)\\n /// @return msgId The zero based index of the message in the inbox.\\n function sendMessage(address _to, bytes4 _fnSelector, bytes memory _data) external override returns (uint64) {\\n uint64 oldCount = count;\\n\\n // Given arbitrum's speed limit of 7 million gas / second, it would take atleast 8 million years of full blocks to overflow.\\n // It *should* be impossible to overflow, but we check to be safe when appending to the tree.\\n require(oldCount < type(uint64).max, \\\"Inbox is full.\\\");\\n\\n bytes memory nodeData = abi.encodePacked(\\n oldCount,\\n _to,\\n // _data is abi.encode(param1, param2, ...), we need to encode it again to get the correct leaf data\\n abi.encodePacked( // equivalent to abi.encodeWithSelector(fnSelector, msg.sender, param1, param2, ...)\\n _fnSelector,\\n bytes32(uint256(uint160(msg.sender))), // big endian padded encoding of msg.sender, simulating abi.encodeWithSelector\\n _data\\n )\\n );\\n\\n // single hashed leaf\\n bytes32 newInboxNode = keccak256(nodeData);\\n\\n // double hashed leaf\\n // avoids second order preimage attacks\\n // https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack/\\n assembly {\\n // efficient hash using EVM scratch space\\n mstore(0x00, newInboxNode)\\n newInboxNode := keccak256(0x00, 0x20)\\n }\\n\\n // increment merkle tree calculating minimal number of hashes\\n unchecked {\\n uint256 height;\\n\\n // x = oldCount + 1; acts as a bit mask to determine if a hash is needed\\n // note: x is always non-zero, and x is bit shifted to the right each loop\\n // hence this loop will always terminate in a maximum of log_2(oldCount + 1) iterations\\n for (uint64 x = oldCount + 1; x & 1 == 0; x = x >> 1) {\\n // sort sibling hashes as a convention for efficient proof validation\\n newInboxNode = sortConcatAndHash(inbox[height], newInboxNode);\\n height++;\\n }\\n\\n inbox[height] = newInboxNode;\\n\\n // finally increment count\\n count = oldCount + 1;\\n }\\n\\n emit MessageSent(nodeData);\\n\\n // old count is the zero indexed leaf position in the tree, acts as a msgId\\n // gateways should index these msgIds to later relay proofs\\n return oldCount;\\n }\\n\\n /// @dev Saves snapshot of state root. Snapshots can be saved a maximum of once per epoch.\\n /// `O(log(count))` where count number of messages in the inbox.\\n /// Note: See merkle tree docs for details how inbox manages state.\\n function saveSnapshot() external {\\n uint256 epoch;\\n bytes32 stateRoot;\\n\\n unchecked {\\n epoch = block.timestamp / epochPeriod;\\n\\n // calculate the current root of the incremental merkle tree encoded in the inbox\\n\\n uint256 height;\\n\\n // x acts as a bit mask to determine if the hash stored in the inbox contributes to the root\\n uint256 x;\\n\\n // x is bit shifted to the right each loop, hence this loop will always terminate in a maximum of log_2(count) iterations\\n for (x = uint256(count); x > 0; x = x >> 1) {\\n if ((x & 1) == 1) {\\n // first hash is special case\\n // inbox stores the root of complete subtrees\\n // eg if count = 4 = 0b100, then the first complete subtree is inbox[2]\\n // inbox = [H(3), H(1,2), H(1,4)], we read inbox[2] directly\\n\\n stateRoot = inbox[height];\\n break;\\n }\\n height++;\\n }\\n\\n // after the first hash, we can calculate the root incrementally\\n for (x = x >> 1; x > 0; x = x >> 1) {\\n height++;\\n if ((x & 1) == 1) {\\n // sort sibling hashes as a convention for efficient proof validation\\n stateRoot = sortConcatAndHash(inbox[height], stateRoot);\\n }\\n }\\n }\\n\\n snapshots[epoch] = stateRoot;\\n\\n emit SnapshotSaved(stateRoot, epoch, count);\\n }\\n\\n /// @dev Helper function to calculate merkle tree interior nodes by sorting and concatenating and hashing a pair of children nodes, left and right.\\n /// Note: EVM scratch space is used to efficiently calculate hashes.\\n /// @param _left The left hash.\\n /// @param _right The right hash.\\n /// @return parent The parent hash.\\n function sortConcatAndHash(bytes32 _left, bytes32 _right) internal pure returns (bytes32 parent) {\\n // sort sibling hashes as a convention for efficient proof validation\\n if (_left < _right) {\\n // efficient hash using EVM scratch space\\n assembly {\\n mstore(0x00, _left)\\n mstore(0x20, _right)\\n parent := keccak256(0x00, 0x40)\\n }\\n } else {\\n assembly {\\n mstore(0x00, _right)\\n mstore(0x20, _left)\\n parent := keccak256(0x00, 0x40)\\n }\\n }\\n }\\n\\n /// @dev Sends the state root snapshot using Arbitrum's canonical bridge.\\n /// @param _epoch The epoch of the snapshot requested to send.\\n /// @param _claim The claim associated with the epoch.\\n function sendSnapshot(uint256 _epoch, Claim memory _claim) external virtual {\\n unchecked {\\n require(_epoch < block.timestamp / epochPeriod, \\\"Can only send past epoch snapshot.\\\");\\n }\\n\\n bytes memory data = abi.encodeCall(IVeaOutboxOnL1.resolveDisputedClaim, (_epoch, snapshots[_epoch], _claim));\\n\\n // Arbitrum -> Ethereum message with native bridge\\n // docs: https://developer.arbitrum.io/for-devs/cross-chain-messsaging#arbitrum-to-ethereum-messaging\\n // example: https://github.com/OffchainLabs/arbitrum-tutorials/blob/2c1b7d2db8f36efa496e35b561864c0f94123a5f/packages/greeter/contracts/arbitrum/GreeterL2.sol#L25\\n bytes32 ticketID = bytes32(ARB_SYS.sendTxToL1(veaOutboxArbToEth, data));\\n\\n emit SnapshotSent(_epoch, ticketID);\\n }\\n\\n // ************************************* //\\n // * Pure / Views * //\\n // ************************************* //\\n\\n /// @dev Get the current epoch from the inbox's point of view using the Arbitrum L2 clock.\\n /// @return epoch The epoch associated with the current inbox block.timestamp\\n function epochNow() external view returns (uint256 epoch) {\\n epoch = block.timestamp / epochPeriod;\\n }\\n\\n /// @dev Get the most recent epoch for which snapshots are finalized.\\n /// @return epoch The epoch associated with the current inbox block.timestamp\\n function epochFinalized() external view returns (uint256 epoch) {\\n epoch = block.timestamp / epochPeriod - 1;\\n }\\n\\n /// @dev Get the epoch from the inbox's point of view using timestamp.\\n /// @param _timestamp The timestamp to calculate the epoch from.\\n /// @return epoch The calculated epoch.\\n function epochAt(uint256 _timestamp) external view returns (uint256 epoch) {\\n epoch = _timestamp / epochPeriod;\\n }\\n}\\n\",\"keccak256\":\"0xe3fdd0cc51b541482e72b8cf37981499dc301556f77b78666c5b3b22237dd05e\",\"license\":\"MIT\"},\"src/canonical/arbitrum/IArbSys.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\n// https://developer.arbitrum.io/arbos/precompiles#arbsys\\n// https://github.com/OffchainLabs/nitro-contracts/blob/39ea5a163afc637e2706d9be29cf7a289c300d00/src/precompiles/ArbSys.sol\\n// https://arbiscan.io/address/0x0000000000000000000000000000000000000064#code\\n// interface is pruned for relevant function stubs\\n\\npragma solidity 0.8.24;\\n\\n///@title System level functionality\\n///@notice For use by contracts to interact with core L2-specific functionality.\\n///Precompiled contract that exists in every Arbitrum chain at address(100), 0x0000000000000000000000000000000000000064.\\ninterface IArbSys {\\n /// @notice Send a transaction to L1\\n /// @dev it is not possible to execute on the L1 any L2-to-L1 transaction which contains data\\n /// to a contract address without any code (as enforced by the Bridge contract).\\n /// @param destination recipient address on L1\\n /// @param data (optional) calldata for L1 contract call\\n /// @return a unique identifier for this L2-to-L1 transaction.\\n function sendTxToL1(address destination, bytes calldata data) external payable returns (uint256);\\n}\\n\",\"keccak256\":\"0x5ae1fd0267552160821402b9bc50b2551b086904436e5abe838599179b279420\",\"license\":\"BUSL-1.1\"},\"src/interfaces/inboxes/IVeaInbox.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\n/// @custom:authors: [@jaybuidl, @shotaronowhere]\\n/// @custom:reviewers: []\\n/// @custom:auditors: []\\n/// @custom:bounties: []\\n/// @custom:deployments: []\\n\\npragma solidity 0.8.24;\\n\\ninterface IVeaInbox {\\n /// @dev Sends an arbitrary message to receiving chain.\\n /// Note: Calls authenticated by receiving gateway checking the sender argument.\\n /// @param _to The cross-domain contract address which receives the calldata.\\n /// @param _fnSelection The function selector of the receiving contract.\\n /// @param _data The message calldata, abi.encode(...)\\n /// @return msgId The index of the message in the inbox, as a message Id, needed to relay the message.\\n function sendMessage(address _to, bytes4 _fnSelection, bytes memory _data) external returns (uint64 msgId);\\n\\n /// @dev Snapshots can be saved a maximum of once per epoch.\\n /// Saves snapshot of state root.\\n /// `O(log(count))` where count number of messages in the inbox.\\n function saveSnapshot() external;\\n}\\n\",\"keccak256\":\"0xa8e2f65b7596235422f39933af80f02473493d2b15b398d7e34b81c82bd24a29\",\"license\":\"MIT\"},\"src/interfaces/outboxes/IVeaOutboxOnL1.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\n/// @custom:authors: [@jaybuidl, @shotaronowhere]\\n/// @custom:reviewers: []\\n/// @custom:auditors: []\\n/// @custom:bounties: []\\n/// @custom:deployments: []\\n\\npragma solidity 0.8.24;\\n\\nimport \\\"../types/VeaClaim.sol\\\";\\n\\n/// @dev Interface of the Vea Outbox on L1 chains like Ethereum, Gnosis, Polygon POS where storage is expensive.\\ninterface IVeaOutboxOnL1 {\\n /// @dev Verifies and relays the message.\\n /// Note: Gateways expect first argument of message call to be the arbitrum message sender, used for authentication.\\n /// @param _proof The merkle proof to prove the message.\\n /// @param _msgId The zero based index of the message in the inbox.\\n /// @param _to The address to send the message to.\\n /// @param _message The message to relay.\\n function sendMessage(bytes32[] calldata _proof, uint64 _msgId, address _to, bytes calldata _message) external;\\n\\n /// @dev Resolves any challenge of the optimistic claim for 'epoch' using the canonical bridge.\\n /// Note: Access restricted to canonical bridge.\\n /// @param _epoch The epoch to verify.\\n /// @param _stateRoot The true state root for the epoch.\\n /// @param _claim The claim associated with the epoch.\\n function resolveDisputedClaim(uint256 _epoch, bytes32 _stateRoot, Claim memory _claim) external;\\n}\\n\",\"keccak256\":\"0xf1d52e289e790088502b7909f11f47bc33ddd3fc545636b7fb29c01ed00d3ff3\",\"license\":\"MIT\"},\"src/interfaces/types/VeaClaim.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\n/// @custom:authors: [@jaybuidl, @shotaronowhere]\\n/// @custom:reviewers: []\\n/// @custom:auditors: []\\n/// @custom:bounties: []\\n/// @custom:deployments: []\\n\\npragma solidity 0.8.24;\\n\\nenum Party {\\n None,\\n Claimer,\\n Challenger\\n}\\n\\nstruct Claim {\\n bytes32 stateRoot;\\n address claimer;\\n uint32 timestampClaimed;\\n uint32 timestampVerification;\\n uint32 blocknumberVerification;\\n Party honest;\\n address challenger;\\n}\\n\",\"keccak256\":\"0xfef781e359c97aebbe8dbfcb75edb7cb962139fd9ea538b8b89a3f2e13a05bfe\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x60c060405234801561000f575f80fd5b50604051610bed380380610bed83398101604081905261002e91610045565b6080919091526001600160a01b031660a05261007f565b5f8060408385031215610056575f80fd5b825160208401519092506001600160a01b0381168114610074575f80fd5b809150509250929050565b60805160a051610b236100ca5f395f818161012401526105d001525f8181610163015281816101bd015281816101ef015281816102240152818161025301526104df0152610b235ff3fe608060405234801561000f575f80fd5b506004361061009b575f3560e01c80635f85896c116100635780635f85896c1461010c578063744b49bf1461011f578063b5b7a1841461015e578063c705e41214610185578063d6565a2d14610198575f80fd5b806306661abd1461009f578063222ae786146100d15780633ac3b6b6146100e75780634a439cfe146100ef5780635192053514610102575b5f80fd5b6041546100b39067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020015b60405180910390f35b6100d96101b7565b6040519081526020016100c8565b6100d96101e7565b6100d96100fd3660046106a5565b61021e565b61010a61024f565b005b6100b361011a366004610745565b610370565b6101467f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100c8565b6100d97f000000000000000000000000000000000000000000000000000000000000000081565b61010a610193366004610815565b6104dd565b6100d96101a63660046106a5565b5f6020819052908152604090205481565b5f6101e27f0000000000000000000000000000000000000000000000000000000000000000426108d7565b905090565b5f60016102147f0000000000000000000000000000000000000000000000000000000000000000426108d7565b6101e291906108f6565b5f6102497f0000000000000000000000000000000000000000000000000000000000000000836108d7565b92915050565b5f807f00000000000000000000000000000000000000000000000000000000000000004281610280576102806108c3565b60415491900492505f9067ffffffffffffffff165b80156102ce57806001166001036102c257600182604081106102b9576102b9610915565b015492506102ce565b6001918201911c610295565b60011c5b801561031057600191820191818116900361030857610305600183604081106102fd576102fd610915565b015484610678565b92505b60011c6102d2565b50505f8281526020818152604091829020839055604154825184815291820185905267ffffffffffffffff168183015290517f592424eb1d6135501bd20833f15fd127c29d08eed4f03872f6f75182126b1e489181900360600190a15050565b6041545f9067ffffffffffffffff9081169081106103c65760405162461bcd60e51b815260206004820152600e60248201526d24b73137bc1034b990333ab6361760911b60448201526064015b60405180910390fd5b6040515f90829087906103e19088903390899060200161094b565b60408051601f1981840301815290829052610400939291602001610980565b60408051601f1981840301815291905280516020808301919091205f908152908120919250600184015b600181165f036104625761044a600183604081106102fd576102fd610915565b92506001918201911c677fffffffffffffff1661042a565b50816001826040811061047757610477610915565b0155506041805467ffffffffffffffff19166001850167ffffffffffffffff161790556040517f8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036906104ca9084906109f9565b60405180910390a1509095945050505050565b7f0000000000000000000000000000000000000000000000000000000000000000428161050c5761050c6108c3565b0482106105665760405162461bcd60e51b815260206004820152602260248201527f43616e206f6e6c792073656e6420706173742065706f636820736e617073686f6044820152613a1760f11b60648201526084016103bd565b5f828152602081905260408082205490516105879185918590602401610a12565b60408051601f198184030181529181526020820180516001600160e01b0316630f0adca560e01b179052516349460b4d60e11b81529091505f9060649063928c169a906105fa907f0000000000000000000000000000000000000000000000000000000000000000908690600401610aab565b6020604051808303815f875af1158015610616573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061063a9190610ad6565b60405181815290915084907f6fdd49f435101fc7b6ebdec7c8972932a926d18f6cb78a8891dfe950743b6b829060200160405180910390a250505050565b5f8183101561069357825f528160205260405f209050610249565b505f9081526020919091526040902090565b5f602082840312156106b5575f80fd5b5035919050565b80356001600160a01b03811681146106d2575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b60405160e0810167ffffffffffffffff8111828210171561070e5761070e6106d7565b60405290565b604051601f8201601f1916810167ffffffffffffffff8111828210171561073d5761073d6106d7565b604052919050565b5f805f60608486031215610757575f80fd5b610760846106bc565b92506020848101356001600160e01b03198116811461077d575f80fd5b9250604085013567ffffffffffffffff80821115610799575f80fd5b818701915087601f8301126107ac575f80fd5b8135818111156107be576107be6106d7565b6107d0601f8201601f19168501610714565b915080825288848285010111156107e5575f80fd5b80848401858401375f848284010152508093505050509250925092565b803563ffffffff811681146106d2575f80fd5b5f80828403610100811215610828575f80fd5b8335925060e0601f198201121561083d575f80fd5b506108466106eb565b60208401358152610859604085016106bc565b602082015261086a60608501610802565b604082015261087b60808501610802565b606082015261088c60a08501610802565b608082015260c0840135600381106108a2575f80fd5b60a08201526108b360e085016106bc565b60c0820152809150509250929050565b634e487b7160e01b5f52601260045260245ffd5b5f826108f157634e487b7160e01b5f52601260045260245ffd5b500490565b8181038181111561024957634e487b7160e01b5f52601160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f5b8381101561094357818101518382015260200161092b565b50505f910152565b63ffffffff60e01b841681528260048201525f8251610971816024850160208701610929565b91909101602401949350505050565b67ffffffffffffffff60c01b8460c01b1681526bffffffffffffffffffffffff198360601b1660088201525f82516109bf81601c850160208701610929565b91909101601c01949350505050565b5f81518084526109e5816020860160208601610929565b601f01601f19169290920160200192915050565b602081525f610a0b60208301846109ce565b9392505050565b5f610120820190508482528360208301528251604083015260018060a01b036020840151166060830152604083015163ffffffff80821660808501528060608601511660a08501528060808601511660c0850152505060a083015160038110610a8957634e487b7160e01b5f52602160045260245ffd5b60e083015260c092909201516001600160a01b03166101009091015292915050565b6001600160a01b03831681526040602082018190525f90610ace908301846109ce565b949350505050565b5f60208284031215610ae6575f80fd5b505191905056fea2646970667358221220e0d777b7e1a2c210f6fdf79122d58020ed70e6c9bd569102148c7b15915e21a764736f6c63430008180033", + "deployedBytecode": "0x608060405234801561000f575f80fd5b506004361061009b575f3560e01c80635f85896c116100635780635f85896c1461010c578063744b49bf1461011f578063b5b7a1841461015e578063c705e41214610185578063d6565a2d14610198575f80fd5b806306661abd1461009f578063222ae786146100d15780633ac3b6b6146100e75780634a439cfe146100ef5780635192053514610102575b5f80fd5b6041546100b39067ffffffffffffffff1681565b60405167ffffffffffffffff90911681526020015b60405180910390f35b6100d96101b7565b6040519081526020016100c8565b6100d96101e7565b6100d96100fd3660046106a5565b61021e565b61010a61024f565b005b6100b361011a366004610745565b610370565b6101467f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100c8565b6100d97f000000000000000000000000000000000000000000000000000000000000000081565b61010a610193366004610815565b6104dd565b6100d96101a63660046106a5565b5f6020819052908152604090205481565b5f6101e27f0000000000000000000000000000000000000000000000000000000000000000426108d7565b905090565b5f60016102147f0000000000000000000000000000000000000000000000000000000000000000426108d7565b6101e291906108f6565b5f6102497f0000000000000000000000000000000000000000000000000000000000000000836108d7565b92915050565b5f807f00000000000000000000000000000000000000000000000000000000000000004281610280576102806108c3565b60415491900492505f9067ffffffffffffffff165b80156102ce57806001166001036102c257600182604081106102b9576102b9610915565b015492506102ce565b6001918201911c610295565b60011c5b801561031057600191820191818116900361030857610305600183604081106102fd576102fd610915565b015484610678565b92505b60011c6102d2565b50505f8281526020818152604091829020839055604154825184815291820185905267ffffffffffffffff168183015290517f592424eb1d6135501bd20833f15fd127c29d08eed4f03872f6f75182126b1e489181900360600190a15050565b6041545f9067ffffffffffffffff9081169081106103c65760405162461bcd60e51b815260206004820152600e60248201526d24b73137bc1034b990333ab6361760911b60448201526064015b60405180910390fd5b6040515f90829087906103e19088903390899060200161094b565b60408051601f1981840301815290829052610400939291602001610980565b60408051601f1981840301815291905280516020808301919091205f908152908120919250600184015b600181165f036104625761044a600183604081106102fd576102fd610915565b92506001918201911c677fffffffffffffff1661042a565b50816001826040811061047757610477610915565b0155506041805467ffffffffffffffff19166001850167ffffffffffffffff161790556040517f8c5261668696ce22758910d05bab8f186d6eb247ceac2af2e82c7dc17669b036906104ca9084906109f9565b60405180910390a1509095945050505050565b7f0000000000000000000000000000000000000000000000000000000000000000428161050c5761050c6108c3565b0482106105665760405162461bcd60e51b815260206004820152602260248201527f43616e206f6e6c792073656e6420706173742065706f636820736e617073686f6044820152613a1760f11b60648201526084016103bd565b5f828152602081905260408082205490516105879185918590602401610a12565b60408051601f198184030181529181526020820180516001600160e01b0316630f0adca560e01b179052516349460b4d60e11b81529091505f9060649063928c169a906105fa907f0000000000000000000000000000000000000000000000000000000000000000908690600401610aab565b6020604051808303815f875af1158015610616573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061063a9190610ad6565b60405181815290915084907f6fdd49f435101fc7b6ebdec7c8972932a926d18f6cb78a8891dfe950743b6b829060200160405180910390a250505050565b5f8183101561069357825f528160205260405f209050610249565b505f9081526020919091526040902090565b5f602082840312156106b5575f80fd5b5035919050565b80356001600160a01b03811681146106d2575f80fd5b919050565b634e487b7160e01b5f52604160045260245ffd5b60405160e0810167ffffffffffffffff8111828210171561070e5761070e6106d7565b60405290565b604051601f8201601f1916810167ffffffffffffffff8111828210171561073d5761073d6106d7565b604052919050565b5f805f60608486031215610757575f80fd5b610760846106bc565b92506020848101356001600160e01b03198116811461077d575f80fd5b9250604085013567ffffffffffffffff80821115610799575f80fd5b818701915087601f8301126107ac575f80fd5b8135818111156107be576107be6106d7565b6107d0601f8201601f19168501610714565b915080825288848285010111156107e5575f80fd5b80848401858401375f848284010152508093505050509250925092565b803563ffffffff811681146106d2575f80fd5b5f80828403610100811215610828575f80fd5b8335925060e0601f198201121561083d575f80fd5b506108466106eb565b60208401358152610859604085016106bc565b602082015261086a60608501610802565b604082015261087b60808501610802565b606082015261088c60a08501610802565b608082015260c0840135600381106108a2575f80fd5b60a08201526108b360e085016106bc565b60c0820152809150509250929050565b634e487b7160e01b5f52601260045260245ffd5b5f826108f157634e487b7160e01b5f52601260045260245ffd5b500490565b8181038181111561024957634e487b7160e01b5f52601160045260245ffd5b634e487b7160e01b5f52603260045260245ffd5b5f5b8381101561094357818101518382015260200161092b565b50505f910152565b63ffffffff60e01b841681528260048201525f8251610971816024850160208701610929565b91909101602401949350505050565b67ffffffffffffffff60c01b8460c01b1681526bffffffffffffffffffffffff198360601b1660088201525f82516109bf81601c850160208701610929565b91909101601c01949350505050565b5f81518084526109e5816020860160208601610929565b601f01601f19169290920160200192915050565b602081525f610a0b60208301846109ce565b9392505050565b5f610120820190508482528360208301528251604083015260018060a01b036020840151166060830152604083015163ffffffff80821660808501528060608601511660a08501528060808601511660c0850152505060a083015160038110610a8957634e487b7160e01b5f52602160045260245ffd5b60e083015260c092909201516001600160a01b03166101009091015292915050565b6001600160a01b03831681526040602082018190525f90610ace908301846109ce565b949350505050565b5f60208284031215610ae6575f80fd5b505191905056fea2646970667358221220e0d777b7e1a2c210f6fdf79122d58020ed70e6c9bd569102148c7b15915e21a764736f6c63430008180033", + "devdoc": { + "details": "Vea Inbox From Arbitrum to Ethereum. Note: This contract is deployed on Arbitrum.", + "events": { + "MessageSent(bytes)": { + "details": "Relayers watch for these events to construct merkle proofs to execute transactions on Ethereum.", + "params": { + "_nodeData": "The data to create leaves in the merkle tree. abi.encodePacked(msgId, to, message), outbox relays to.call(message)." + } + }, + "SnapshotSaved(bytes32,uint256,uint64)": { + "params": { + "_count": "The count of messages in the merkle tree.", + "_epoch": "The epoch of the snapshot.", + "_snapshot": "The snapshot of the merkle tree state root." + } + }, + "SnapshotSent(uint256,bytes32)": { + "details": "The event is emitted when a snapshot is sent through the canonical arbitrum bridge.", + "params": { + "_epochSent": "The epoch of the snapshot.", + "_ticketId": "The ticketId of the L2->L1 message." + } + } + }, + "kind": "dev", + "methods": { + "constructor": { + "details": "Constructor. Note: epochPeriod must match the VeaOutboxArbToEth contract deployment on Ethereum, since it's on a different chain, we can't read it and trust the deployer to set a correct value", + "params": { + "_epochPeriod": "The duration in seconds between epochs.", + "_veaOutboxArbToEth": "The veaOutbox on ethereum." + } + }, + "epochAt(uint256)": { + "details": "Get the epoch from the inbox's point of view using timestamp.", + "params": { + "_timestamp": "The timestamp to calculate the epoch from." + }, + "returns": { + "epoch": "The calculated epoch." + } + }, + "epochFinalized()": { + "details": "Get the most recent epoch for which snapshots are finalized.", + "returns": { + "epoch": "The epoch associated with the current inbox block.timestamp" + } + }, + "epochNow()": { + "details": "Get the current epoch from the inbox's point of view using the Arbitrum L2 clock.", + "returns": { + "epoch": "The epoch associated with the current inbox block.timestamp" + } + }, + "saveSnapshot()": { + "details": "Saves snapshot of state root. Snapshots can be saved a maximum of once per epoch. `O(log(count))` where count number of messages in the inbox. Note: See merkle tree docs for details how inbox manages state." + }, + "sendMessage(address,bytes4,bytes)": { + "details": "Sends an arbitrary message to Ethereum. `O(log(count))` where count is the number of messages already sent. Amortized cost is constant. Note: See docs for details how inbox manages merkle tree state.", + "params": { + "_data": "The message calldata, abi.encode(param1, param2, ...)", + "_fnSelector": "The function selector of the receiving contract.", + "_to": "The address of the contract on the receiving chain which receives the calldata." + }, + "returns": { + "_0": "msgId The zero based index of the message in the inbox." + } + }, + "sendSnapshot(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "Sends the state root snapshot using Arbitrum's canonical bridge.", + "params": { + "_claim": "The claim associated with the epoch.", + "_epoch": "The epoch of the snapshot requested to send." + } + } + }, + "version": 1 + }, + "userdoc": { + "events": { + "SnapshotSaved(bytes32,uint256,uint64)": { + "notice": "The bridgers can watch this event to claim the stateRoot on the veaOutbox." + } + }, + "kind": "user", + "methods": {}, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 24, + "contract": "src/arbitrumToEth/VeaInboxArbToEth.sol:VeaInboxArbToEth", + "label": "snapshots", + "offset": 0, + "slot": "0", + "type": "t_mapping(t_uint256,t_bytes32)" + }, + { + "astId": 28, + "contract": "src/arbitrumToEth/VeaInboxArbToEth.sol:VeaInboxArbToEth", + "label": "inbox", + "offset": 0, + "slot": "1", + "type": "t_array(t_bytes32)64_storage" + }, + { + "astId": 30, + "contract": "src/arbitrumToEth/VeaInboxArbToEth.sol:VeaInboxArbToEth", + "label": "count", + "offset": 0, + "slot": "65", + "type": "t_uint64" + } + ], + "types": { + "t_array(t_bytes32)64_storage": { + "base": "t_bytes32", + "encoding": "inplace", + "label": "bytes32[64]", + "numberOfBytes": "2048" + }, + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_bytes32)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => bytes32)", + "numberOfBytes": "32", + "value": "t_bytes32" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint64": { + "encoding": "inplace", + "label": "uint64", + "numberOfBytes": "8" + } + } + } +} diff --git a/contracts/deployments/sepolia/VeaOutboxArbToEthTestnet.json b/contracts/deployments/sepolia/VeaOutboxArbToEthTestnet.json new file mode 100644 index 00000000..0096b542 --- /dev/null +++ b/contracts/deployments/sepolia/VeaOutboxArbToEthTestnet.json @@ -0,0 +1,1426 @@ +{ + "address": "0x209BFdC6B7c66b63A8382196Ba3d06619d0F12c9", + "abi": [ + { + "inputs": [ + { + "internalType": "uint256", + "name": "_deposit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_epochPeriod", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_minChallengePeriod", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "_timeoutEpochs", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_veaInboxArbToEth", + "type": "address" + }, + { + "internalType": "address", + "name": "_bridge", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_maxMissingBlocks", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "_challenger", + "type": "address" + } + ], + "name": "Challenged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "_claimer", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "_stateRoot", + "type": "bytes32" + } + ], + "name": "Claimed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "_msgId", + "type": "uint64" + } + ], + "name": "MessageRelayed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + } + ], + "name": "VerificationStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + } + ], + "name": "Verified", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_requestedSequencerDelayLimit", + "type": "uint256" + } + ], + "name": "sequencerDelayLimitDecreaseRequested", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "_newSequencerDelayLimit", + "type": "uint256" + } + ], + "name": "sequencerDelayLimitUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "BURN_ADDRESS", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bridge", + "outputs": [ + { + "internalType": "contract IBridge", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "censorshipTestStatus", + "outputs": [ + { + "internalType": "enum VeaOutboxArbToEth.CensorshipTestStatus", + "name": "status", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "challenge", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_withdrawalAddress", + "type": "address" + } + ], + "name": "challenge", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_stateRoot", + "type": "bytes32" + } + ], + "name": "claim", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "name": "claimHashes", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "depositPlusReward", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "epochAt", + "outputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochNow", + "outputs": [ + { + "internalType": "uint256", + "name": "epoch", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "epochPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "executeSequencerDelayLimitDecreaseRequest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "hashClaim", + "outputs": [ + { + "internalType": "bytes32", + "name": "hashedClaim", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_msgId", + "type": "uint256" + } + ], + "name": "isMsgRelayed", + "outputs": [ + { + "internalType": "bool", + "name": "isRelayed", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "latestVerifiedEpoch", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxMissingBlocks", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minChallengePeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "_stateRoot", + "type": "bytes32" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "resolveDisputedClaim", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32[]", + "name": "_proof", + "type": "bytes32[]" + }, + { + "internalType": "uint64", + "name": "_msgId", + "type": "uint64" + }, + { + "internalType": "address", + "name": "_to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + } + ], + "name": "sendMessage", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sequencerDelayLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sequencerDelayLimitDecreaseRequest", + "outputs": [ + { + "internalType": "uint256", + "name": "requestedsequencerDelayLimit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "startVerification", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "stateRoot", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "timeoutEpochs", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "updateSequencerDelayLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "veaInboxArbToEth", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "verifySnapshot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "withdrawChallengeDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "withdrawChallengerEscapeHatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "withdrawClaimDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_epoch", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "claimer", + "type": "address" + }, + { + "internalType": "uint32", + "name": "timestampClaimed", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "timestampVerification", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "blocknumberVerification", + "type": "uint32" + }, + { + "internalType": "enum Party", + "name": "honest", + "type": "uint8" + }, + { + "internalType": "address", + "name": "challenger", + "type": "address" + } + ], + "internalType": "struct Claim", + "name": "_claim", + "type": "tuple" + } + ], + "name": "withdrawClaimerEscapeHatch", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "transactionHash": "0x7d95d7544e7d675987599a7b8753e10d6789daa13082167c3d467c32003f1323", + "receipt": { + "to": null, + "from": "0xFa00D29d378EDC57AA1006946F0fc6230a5E3288", + "contractAddress": "0x209BFdC6B7c66b63A8382196Ba3d06619d0F12c9", + "transactionIndex": 46, + "gasUsed": "2274424", + "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000800000000000000000000000000000000000000001000000000000000000000200000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000", + "blockHash": "0x5058fe0097d076cb3028b75fcb24414d3c9026bae3d25f95634596e442fb1c58", + "transactionHash": "0x7d95d7544e7d675987599a7b8753e10d6789daa13082167c3d467c32003f1323", + "logs": [ + { + "transactionIndex": 46, + "blockNumber": 6636625, + "transactionHash": "0x7d95d7544e7d675987599a7b8753e10d6789daa13082167c3d467c32003f1323", + "address": "0x209BFdC6B7c66b63A8382196Ba3d06619d0F12c9", + "topics": [ + "0x611c2e4a78552f908fb0eb2cc503efc1f947cde8574277ab3b0f10fdd510258b" + ], + "data": "0x0000000000000000000000000000000000000000000000000000000000015180", + "logIndex": 105, + "blockHash": "0x5058fe0097d076cb3028b75fcb24414d3c9026bae3d25f95634596e442fb1c58" + } + ], + "blockNumber": 6636625, + "cumulativeGasUsed": "9590104", + "status": 1, + "byzantium": true + }, + "args": [ + "1000000000000000000", + 7200, + 10800, + 1000000, + "0xE12daFE59Bc3A996362d54b37DFd2BA9279cAd06", + "0x38f918D0E9F1b721EDaA41302E399fa1B79333a9", + 1000000 + ], + "numDeployments": 1, + "solcInputHash": "0d66bd5cfdf493ed8e081e9f7e1bf4fa", + "metadata": "{\"compiler\":{\"version\":\"0.8.24+commit.e11b9ed9\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_deposit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_epochPeriod\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_minChallengePeriod\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_timeoutEpochs\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_veaInboxArbToEth\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_bridge\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_maxMissingBlocks\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_challenger\",\"type\":\"address\"}],\"name\":\"Challenged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"_claimer\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"_stateRoot\",\"type\":\"bytes32\"}],\"name\":\"Claimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"_msgId\",\"type\":\"uint64\"}],\"name\":\"MessageRelayed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"}],\"name\":\"VerificationStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"}],\"name\":\"Verified\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_requestedSequencerDelayLimit\",\"type\":\"uint256\"}],\"name\":\"sequencerDelayLimitDecreaseRequested\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_newSequencerDelayLimit\",\"type\":\"uint256\"}],\"name\":\"sequencerDelayLimitUpdated\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"BURN_ADDRESS\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"bridge\",\"outputs\":[{\"internalType\":\"contract IBridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"burn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"censorshipTestStatus\",\"outputs\":[{\"internalType\":\"enum VeaOutboxArbToEth.CensorshipTestStatus\",\"name\":\"status\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"challenge\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"},{\"internalType\":\"address\",\"name\":\"_withdrawalAddress\",\"type\":\"address\"}],\"name\":\"challenge\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_stateRoot\",\"type\":\"bytes32\"}],\"name\":\"claim\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"name\":\"claimHashes\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"deposit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"depositPlusReward\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"name\":\"epochAt\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"epochNow\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"epoch\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"epochPeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"executeSequencerDelayLimitDecreaseRequest\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"hashClaim\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"hashedClaim\",\"type\":\"bytes32\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_msgId\",\"type\":\"uint256\"}],\"name\":\"isMsgRelayed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isRelayed\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"latestVerifiedEpoch\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"maxMissingBlocks\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"minChallengePeriod\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_stateRoot\",\"type\":\"bytes32\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"resolveDisputedClaim\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"_proof\",\"type\":\"bytes32[]\"},{\"internalType\":\"uint64\",\"name\":\"_msgId\",\"type\":\"uint64\"},{\"internalType\":\"address\",\"name\":\"_to\",\"type\":\"address\"},{\"internalType\":\"bytes\",\"name\":\"_message\",\"type\":\"bytes\"}],\"name\":\"sendMessage\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sequencerDelayLimit\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sequencerDelayLimitDecreaseRequest\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"requestedsequencerDelayLimit\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"startVerification\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"stateRoot\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"timeoutEpochs\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"updateSequencerDelayLimit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"veaInboxArbToEth\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"verifySnapshot\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"withdrawChallengeDeposit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"withdrawChallengerEscapeHatch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"withdrawClaimDeposit\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_epoch\",\"type\":\"uint256\"},{\"components\":[{\"internalType\":\"bytes32\",\"name\":\"stateRoot\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"claimer\",\"type\":\"address\"},{\"internalType\":\"uint32\",\"name\":\"timestampClaimed\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"timestampVerification\",\"type\":\"uint32\"},{\"internalType\":\"uint32\",\"name\":\"blocknumberVerification\",\"type\":\"uint32\"},{\"internalType\":\"enum Party\",\"name\":\"honest\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"challenger\",\"type\":\"address\"}],\"internalType\":\"struct Claim\",\"name\":\"_claim\",\"type\":\"tuple\"}],\"name\":\"withdrawClaimerEscapeHatch\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"Vea Outbox From Arbitrum to Ethereum. Note: This contract is deployed on Ethereum.\",\"events\":{\"Challenged(uint256,address)\":{\"details\":\"This event indicates that `sendSnapshot(epoch)` should be called in the inbox.\",\"params\":{\"_challenger\":\"The address of the challenger.\",\"_epoch\":\"The epoch associated with the challenged claim.\"}},\"Claimed(address,uint256,bytes32)\":{\"details\":\"Watchers check this event to challenge fraud.\",\"params\":{\"_claimer\":\"The address of the claimer.\",\"_epoch\":\"The epoch associated with the claim.\",\"_stateRoot\":\"The state root of the claim.\"}},\"MessageRelayed(uint64)\":{\"details\":\"This event indicates that a message has been relayed.\",\"params\":{\"_msgId\":\"The msgId of the message that was relayed.\"}},\"VerificationStarted(uint256)\":{\"details\":\"This event indicates that the censorship test started and all challengers are ready even in the worst case scenario of a malicious sequencer.\",\"params\":{\"_epoch\":\"The epoch that started verification.\"}},\"Verified(uint256)\":{\"details\":\"This events indicates that verification has succeeded. The messages are ready to be relayed.\",\"params\":{\"_epoch\":\"The epoch that was verified.\"}},\"sequencerDelayLimitDecreaseRequested(uint256)\":{\"details\":\"This event indicates that a request to decrease the sequencer limit has been made.\",\"params\":{\"_requestedSequencerDelayLimit\":\"The new sequencer delay limit requested.\"}},\"sequencerDelayLimitUpdated(uint256)\":{\"details\":\"This event indicates the sequencer limit updated.\",\"params\":{\"_newSequencerDelayLimit\":\"The new sequencer delay limit.\"}}},\"kind\":\"dev\",\"methods\":{\"censorshipTestStatus((bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"Gets the status of the censorship test for claim.\",\"params\":{\"_claim\":\"The claim to test.\"},\"returns\":{\"status\":\"True if the claim passed the censorship test.\"}},\"challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"Submit a challenge for the claim of the inbox state root snapshot taken at 'epoch'.\",\"params\":{\"_claim\":\"The claim associated with the epoch.\",\"_epoch\":\"The epoch of the claim to challenge.\"}},\"challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address),address)\":{\"details\":\"Submit a challenge for the claim of the inbox state root snapshot taken at 'epoch'.Allows proxy contracts to batch challenges.\",\"params\":{\"_claim\":\"The claim associated with the epoch.\",\"_epoch\":\"The epoch of the claim to challenge.\",\"_withdrawalAddress\":\"The address to withdraw the deposit + reward to.\"}},\"claim(uint256,bytes32)\":{\"details\":\"Submit a claim about the _stateRoot at _epoch and submit a deposit.\",\"params\":{\"_epoch\":\"The epoch for which the claim is made.\",\"_stateRoot\":\"The state root to claim.\"}},\"constructor\":{\"details\":\"Constructor. Note: epochPeriod must match the VeaInboxArbToEth contract deployment on Arbitrum, since it's on a different chain, we can't read it and trust the deployer to set a correct value\",\"params\":{\"_deposit\":\"The deposit amount to submit a claim in wei.\",\"_epochPeriod\":\"The duration of each epoch.\",\"_maxMissingBlocks\":\"The maximum number of blocks that can be missing in a challenge period.\",\"_minChallengePeriod\":\"The minimum time window to challenge a claim.\",\"_timeoutEpochs\":\"The epochs before the bridge is considered shutdown.\",\"_veaInboxArbToEth\":\"The address of the inbox contract on Arbitrum.\"}},\"epochAt(uint256)\":{\"details\":\"Get the current epoch from the outbox's point of view using the Ethereum L1 clock.\",\"returns\":{\"epoch\":\"The hash of the claim.\"}},\"epochNow()\":{\"details\":\"Get the current epoch from the outbox's point of view using the Ethereum L1 clock.\",\"returns\":{\"epoch\":\"The hash of the claim.\"}},\"executeSequencerDelayLimitDecreaseRequest()\":{\"details\":\"execute sequencerDelayLimitDecreaseRequest\"},\"hashClaim((bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"Hashes the claim.\",\"params\":{\"_claim\":\"The claim to hash.\"},\"returns\":{\"hashedClaim\":\"The hash of the claim.\"}},\"isMsgRelayed(uint256)\":{\"details\":\"Get the msg relayed status.\",\"params\":{\"_msgId\":\"The msgId to check.\"},\"returns\":{\"isRelayed\":\"True if the msg was relayed.\"}},\"resolveDisputedClaim(uint256,bytes32,(bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"Resolves any challenge of the optimistic claim for '_epoch'.\",\"params\":{\"_claim\":\"The claim associated with the epoch.\",\"_epoch\":\"The epoch to verify.\",\"_stateRoot\":\"The true state root for the epoch.\"}},\"sendMessage(bytes32[],uint64,address,bytes)\":{\"details\":\"Verifies and relays the message. UNTRUSTED.\",\"params\":{\"_message\":\"The message encoded in the vea inbox as abi.encodeWithSelector(fnSelector, msg.sender, param1, param2, ...)\",\"_msgId\":\"The zero based index of the message in the inbox.\",\"_proof\":\"The merkle proof to prove the message inclusion in the inbox state root.\",\"_to\":\"The address of the contract on Ethereum to call.\"}},\"startVerification(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"Start verification for claim for 'epoch'.\",\"params\":{\"_claim\":\"The claim associated with the epoch.\",\"_epoch\":\"The epoch of the claim to challenge.\"}},\"updateSequencerDelayLimit()\":{\"details\":\"Request to decrease the sequencerDelayLimit.\"},\"verifySnapshot(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"Resolves the optimistic claim for '_epoch'.\",\"params\":{\"_claim\":\"The claim associated with the epoch.\",\"_epoch\":\"The epoch of the optimistic claim.\"}},\"withdrawChallengeDeposit(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"Sends the deposit back to the Challenger if successful. Includes a portion of the Bridger's deposit.\",\"params\":{\"_claim\":\"The claim associated with the epoch.\",\"_epoch\":\"The epoch associated with the challenge deposit to withraw.\"}},\"withdrawChallengerEscapeHatch(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"When bridge is shutdown, no claim disputes can be resolved. This allows the challenger to withdraw their deposit.\",\"params\":{\"_claim\":\"The claim associated with the epoch.\",\"_epoch\":\"The epoch associated with the claim deposit to withraw.\"}},\"withdrawClaimDeposit(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"Sends the deposit back to the Claimer if successful. Includes a portion of the Challenger's deposit if unsuccessfully challenged.\",\"params\":{\"_claim\":\"The claim associated with the epoch.\",\"_epoch\":\"The epoch associated with the claim deposit to withraw.\"}},\"withdrawClaimerEscapeHatch(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"details\":\"When bridge is shutdown, no claim disputes can be resolved. This allows the claimer to withdraw their deposit.\",\"params\":{\"_claim\":\"The claim associated with the epoch.\",\"_epoch\":\"The epoch associated with the claim deposit to withraw.\"}}},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"resolveDisputedClaim(uint256,bytes32,(bytes32,address,uint32,uint32,uint32,uint8,address))\":{\"notice\":\"Note: Access restricted to arbitrum bridge.\"}},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/arbitrumToEth/VeaOutboxArbToEth.sol\":\"VeaOutboxArbToEth\"},\"evmVersion\":\"shanghai\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"src/arbitrumToEth/VeaOutboxArbToEth.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\n/// @custom:authors: [@jaybuidl, @shotaronowhere]\\n/// @custom:reviewers: []\\n/// @custom:auditors: []\\n/// @custom:bounties: []\\n/// @custom:deployments: []\\n\\npragma solidity 0.8.24;\\n\\nimport \\\"../canonical/arbitrum/ISequencerInbox.sol\\\";\\nimport \\\"../canonical/arbitrum/IBridge.sol\\\";\\nimport \\\"../canonical/arbitrum/IOutbox.sol\\\";\\nimport \\\"../interfaces/outboxes/IVeaOutboxOnL1.sol\\\";\\n\\n/// @dev Vea Outbox From Arbitrum to Ethereum.\\n/// Note: This contract is deployed on Ethereum.\\ncontract VeaOutboxArbToEth is IVeaOutboxOnL1 {\\n // ************************************* //\\n // * Storage * //\\n // ************************************* //\\n\\n IBridge public immutable bridge; // The address of the Arbitrum bridge contract.\\n address public immutable veaInboxArbToEth; // The address of the vea inbox on arbitrum.\\n\\n uint256 public immutable deposit; // The deposit in wei required to submit a claim or challenge\\n uint256 public immutable burn; // The amount of wei to burn. deposit / 2\\n uint256 public immutable depositPlusReward; // 2 * deposit - burn\\n\\n address public constant BURN_ADDRESS = address(0); // Address to send burned eth\\n uint256 internal constant SLOT_TIME = 12; // Ethereum 12 second slot time\\n\\n uint256 public immutable epochPeriod; // Epochs mark the period between potential snapshots.\\n uint256 public immutable minChallengePeriod; // Minimum time window to challenge a claim, even with a malicious sequencer.\\n\\n uint256 public immutable timeoutEpochs; // The number of epochs without forward progress before the bridge is considered shutdown.\\n uint256 public immutable maxMissingBlocks; // The maximum number of blocks that can be missing in a challenge period.\\n\\n bytes32 public stateRoot; // merkle root of the outbox state\\n uint256 public latestVerifiedEpoch; // The latest epoch that has been verified.\\n\\n mapping(uint256 epoch => bytes32) public claimHashes; // epoch => claim\\n mapping(uint256 messageId => bytes32) internal relayed; // msgId/256 => packed replay bitmap, preferred over a simple boolean mapping to save 15k gas per message\\n\\n uint256 public sequencerDelayLimit; // This is MaxTimeVariation.delaySeconds from the arbitrum sequencer inbox, it is the maximum seconds the sequencer can backdate L2 txns relative to the L1 clock.\\n SequencerDelayLimitDecreaseRequest public sequencerDelayLimitDecreaseRequest; // Decreasing the sequencerDelayLimit requires a delay to avoid griefing by sequencer, so we keep track of the request here.\\n\\n struct SequencerDelayLimitDecreaseRequest {\\n uint256 requestedsequencerDelayLimit;\\n uint256 timestamp;\\n }\\n\\n enum CensorshipTestStatus {\\n Failed,\\n Passed,\\n NotStarted,\\n InProgress\\n }\\n\\n // ************************************* //\\n // * Events * //\\n // ************************************* //\\n\\n /// @dev Watchers check this event to challenge fraud.\\n /// @param _claimer The address of the claimer.\\n /// @param _epoch The epoch associated with the claim.\\n /// @param _stateRoot The state root of the claim.\\n event Claimed(address indexed _claimer, uint256 indexed _epoch, bytes32 _stateRoot);\\n\\n /// @dev This event indicates that `sendSnapshot(epoch)` should be called in the inbox.\\n /// @param _epoch The epoch associated with the challenged claim.\\n /// @param _challenger The address of the challenger.\\n event Challenged(uint256 indexed _epoch, address indexed _challenger);\\n\\n /// @dev This event indicates that a message has been relayed.\\n /// @param _msgId The msgId of the message that was relayed.\\n event MessageRelayed(uint64 _msgId);\\n\\n /// @dev This event indicates that the censorship test started and all challengers are ready even in the worst case scenario of a malicious sequencer.\\n /// @param _epoch The epoch that started verification.\\n event VerificationStarted(uint256 indexed _epoch);\\n\\n /// @dev This events indicates that verification has succeeded. The messages are ready to be relayed.\\n /// @param _epoch The epoch that was verified.\\n event Verified(uint256 _epoch);\\n\\n /// @dev This event indicates the sequencer limit updated.\\n /// @param _newSequencerDelayLimit The new sequencer delay limit.\\n event sequencerDelayLimitUpdated(uint256 _newSequencerDelayLimit);\\n\\n /// @dev This event indicates that a request to decrease the sequencer limit has been made.\\n /// @param _requestedSequencerDelayLimit The new sequencer delay limit requested.\\n event sequencerDelayLimitDecreaseRequested(uint256 _requestedSequencerDelayLimit);\\n\\n // ************************************* //\\n // * Function Modifiers * //\\n // ************************************* //\\n\\n modifier OnlyBridgeRunning() {\\n unchecked {\\n require(block.timestamp / epochPeriod - latestVerifiedEpoch <= timeoutEpochs, \\\"Bridge Shutdown.\\\");\\n }\\n _;\\n }\\n\\n modifier OnlyBridgeShutdown() {\\n unchecked {\\n require(block.timestamp / epochPeriod - latestVerifiedEpoch > timeoutEpochs, \\\"Bridge Running.\\\");\\n }\\n _;\\n }\\n\\n /// @dev Constructor.\\n /// Note: epochPeriod must match the VeaInboxArbToEth contract deployment on Arbitrum, since it's on a different chain, we can't read it and trust the deployer to set a correct value\\n /// @param _deposit The deposit amount to submit a claim in wei.\\n /// @param _epochPeriod The duration of each epoch.\\n /// @param _minChallengePeriod The minimum time window to challenge a claim.\\n /// @param _timeoutEpochs The epochs before the bridge is considered shutdown.\\n /// @param _veaInboxArbToEth The address of the inbox contract on Arbitrum.\\n /// @param _maxMissingBlocks The maximum number of blocks that can be missing in a challenge period.\\n constructor(\\n uint256 _deposit,\\n uint256 _epochPeriod,\\n uint256 _minChallengePeriod,\\n uint256 _timeoutEpochs,\\n address _veaInboxArbToEth,\\n address _bridge,\\n uint256 _maxMissingBlocks\\n ) {\\n deposit = _deposit;\\n // epochPeriod must match the VeaInboxArbToEth contract deployment epochPeriod value.\\n epochPeriod = _epochPeriod;\\n minChallengePeriod = _minChallengePeriod;\\n timeoutEpochs = _timeoutEpochs;\\n veaInboxArbToEth = _veaInboxArbToEth;\\n bridge = IBridge(_bridge);\\n maxMissingBlocks = _maxMissingBlocks;\\n\\n updateSequencerDelayLimit();\\n\\n // claimant and challenger are not sybil resistant\\n // must burn half deposit to prevent zero cost griefing\\n burn = _deposit / 2;\\n depositPlusReward = 2 * _deposit - burn;\\n\\n latestVerifiedEpoch = block.timestamp / epochPeriod - 1;\\n }\\n\\n // ************************************* //\\n // * Parameter Updates * //\\n // ************************************* //\\n\\n /// @dev Request to decrease the sequencerDelayLimit.\\n function updateSequencerDelayLimit() public {\\n // the maximum asynchronous lag between the L2 and L1 clocks\\n (, , uint256 newSequencerDelayLimit, ) = ISequencerInbox(bridge.sequencerInbox()).maxTimeVariation();\\n\\n if (newSequencerDelayLimit > sequencerDelayLimit) {\\n // For sequencerDelayLimit / epochPeriod > timeoutEpochs, claims cannot be verified by the timeout period and the bridge will shutdown.\\n sequencerDelayLimit = newSequencerDelayLimit;\\n emit sequencerDelayLimitUpdated(newSequencerDelayLimit);\\n } else if (newSequencerDelayLimit < sequencerDelayLimit) {\\n require(\\n sequencerDelayLimitDecreaseRequest.timestamp == 0,\\n \\\"Sequencer limit decrease request already pending.\\\"\\n );\\n\\n sequencerDelayLimitDecreaseRequest = SequencerDelayLimitDecreaseRequest({\\n requestedsequencerDelayLimit: newSequencerDelayLimit,\\n timestamp: block.timestamp\\n });\\n\\n emit sequencerDelayLimitDecreaseRequested(newSequencerDelayLimit);\\n }\\n }\\n\\n /// @dev execute sequencerDelayLimitDecreaseRequest\\n function executeSequencerDelayLimitDecreaseRequest() external {\\n require(sequencerDelayLimitDecreaseRequest.timestamp != 0, \\\"No pending sequencer limit decrease request.\\\");\\n require(\\n block.timestamp > sequencerDelayLimitDecreaseRequest.timestamp + sequencerDelayLimit,\\n \\\"Sequencer limit decrease request is still pending.\\\"\\n );\\n\\n uint256 requestedsequencerDelayLimit = sequencerDelayLimitDecreaseRequest.requestedsequencerDelayLimit;\\n delete sequencerDelayLimitDecreaseRequest;\\n\\n (, , uint256 currentsequencerDelayLimit, ) = ISequencerInbox(bridge.sequencerInbox()).maxTimeVariation();\\n\\n // check the request is still consistent with the arbiturm bridge\\n if (currentsequencerDelayLimit == requestedsequencerDelayLimit) {\\n sequencerDelayLimit = requestedsequencerDelayLimit;\\n emit sequencerDelayLimitUpdated(requestedsequencerDelayLimit);\\n }\\n }\\n\\n // ************************************* //\\n // * State Modifiers * //\\n // ************************************* //\\n\\n /// @dev Submit a claim about the _stateRoot at _epoch and submit a deposit.\\n /// @param _epoch The epoch for which the claim is made.\\n /// @param _stateRoot The state root to claim.\\n function claim(uint256 _epoch, bytes32 _stateRoot) external payable virtual {\\n require(msg.value >= deposit, \\\"Insufficient claim deposit.\\\");\\n unchecked {\\n require(_epoch == block.timestamp / epochPeriod - 1, \\\"Invalid epoch.\\\");\\n }\\n require(_stateRoot != bytes32(0), \\\"Invalid claim.\\\");\\n require(claimHashes[_epoch] == bytes32(0), \\\"Claim already made.\\\");\\n\\n claimHashes[_epoch] = hashClaim(\\n Claim({\\n stateRoot: _stateRoot,\\n claimer: msg.sender,\\n timestampClaimed: uint32(block.timestamp),\\n timestampVerification: uint32(0),\\n blocknumberVerification: uint32(0),\\n honest: Party.None,\\n challenger: address(0)\\n })\\n );\\n\\n emit Claimed(msg.sender, _epoch, _stateRoot);\\n\\n // Refund overpayment.\\n if (msg.value > deposit) {\\n uint256 refund = msg.value - deposit;\\n payable(msg.sender).send(refund); // User is responsible for accepting ETH.\\n }\\n }\\n\\n /// @dev Submit a challenge for the claim of the inbox state root snapshot taken at 'epoch'.\\n /// @param _epoch The epoch of the claim to challenge.\\n /// @param _claim The claim associated with the epoch.\\n function challenge(uint256 _epoch, Claim memory _claim) external payable {\\n _challenge(_epoch, _claim, msg.sender);\\n }\\n\\n /// @dev Submit a challenge for the claim of the inbox state root snapshot taken at 'epoch'.\\n /// @dev Allows proxy contracts to batch challenges.\\n /// @param _epoch The epoch of the claim to challenge.\\n /// @param _claim The claim associated with the epoch.\\n /// @param _withdrawalAddress The address to withdraw the deposit + reward to.\\n function challenge(uint256 _epoch, Claim memory _claim, address _withdrawalAddress) external payable {\\n _challenge(_epoch, _claim, _withdrawalAddress);\\n }\\n\\n /// @dev Submit a challenge for the claim of the inbox state root snapshot taken at 'epoch'.\\n /// @param _epoch The epoch of the claim to challenge.\\n /// @param _claim The claim associated with the epoch.\\n /// @param _withdrawAddress The address to withdraw the deposit + reward to.\\n function _challenge(uint256 _epoch, Claim memory _claim, address _withdrawAddress) internal {\\n require(claimHashes[_epoch] == hashClaim(_claim), \\\"Invalid claim.\\\");\\n require(msg.value >= deposit, \\\"Insufficient challenge deposit.\\\");\\n require(_claim.challenger == address(0), \\\"Claim already challenged.\\\");\\n require(_claim.honest == Party.None, \\\"Claim already verified.\\\");\\n\\n _claim.challenger = _withdrawAddress;\\n claimHashes[_epoch] = hashClaim(_claim);\\n\\n emit Challenged(_epoch, _withdrawAddress);\\n\\n // Refund overpayment.\\n if (msg.value > deposit) {\\n uint256 refund = msg.value - deposit;\\n payable(msg.sender).send(refund); // User is responsible for accepting ETH.\\n }\\n }\\n\\n /// @dev Start verification for claim for 'epoch'.\\n /// @param _epoch The epoch of the claim to challenge.\\n /// @param _claim The claim associated with the epoch.\\n function startVerification(uint256 _epoch, Claim memory _claim) external virtual {\\n require(claimHashes[_epoch] == hashClaim(_claim), \\\"Invalid claim.\\\");\\n\\n // sequencerDelayLimit + epochPeriod is the worst case time to sync the L2 state compared to L1 clock.\\n // using checked arithmetic incase arbitrum governance sets sequencerDelayLimit to a large value\\n require(\\n block.timestamp - uint256(_claim.timestampClaimed) >= sequencerDelayLimit + epochPeriod,\\n \\\"Claim must wait atleast maxL2StateSyncDelay.\\\"\\n );\\n\\n CensorshipTestStatus _censorshipTestStatus = censorshipTestStatus(_claim);\\n require(\\n _censorshipTestStatus == CensorshipTestStatus.NotStarted ||\\n _censorshipTestStatus == CensorshipTestStatus.Failed,\\n \\\"Claim verification in progress or already completed.\\\"\\n );\\n\\n _claim.timestampVerification = uint32(block.timestamp);\\n _claim.blocknumberVerification = uint32(block.number);\\n\\n claimHashes[_epoch] = hashClaim(_claim);\\n\\n emit VerificationStarted(_epoch);\\n }\\n\\n /// @dev Resolves the optimistic claim for '_epoch'.\\n /// @param _epoch The epoch of the optimistic claim.\\n /// @param _claim The claim associated with the epoch.\\n function verifySnapshot(uint256 _epoch, Claim memory _claim) external virtual OnlyBridgeRunning {\\n require(claimHashes[_epoch] == hashClaim(_claim), \\\"Invalid claim.\\\");\\n require(_claim.challenger == address(0), \\\"Claim is challenged.\\\");\\n require(censorshipTestStatus(_claim) == CensorshipTestStatus.Passed, \\\"Censorship test not passed.\\\");\\n\\n if (_epoch > latestVerifiedEpoch) {\\n latestVerifiedEpoch = _epoch;\\n stateRoot = _claim.stateRoot;\\n emit Verified(_epoch);\\n }\\n\\n _claim.honest = Party.Claimer;\\n claimHashes[_epoch] = hashClaim(_claim);\\n }\\n\\n /// Note: Access restricted to arbitrum bridge.\\n /// @dev Resolves any challenge of the optimistic claim for '_epoch'.\\n /// @param _epoch The epoch to verify.\\n /// @param _stateRoot The true state root for the epoch.\\n /// @param _claim The claim associated with the epoch.\\n function resolveDisputedClaim(\\n uint256 _epoch,\\n bytes32 _stateRoot,\\n Claim memory _claim\\n ) external virtual OnlyBridgeRunning {\\n // Arbitrum -> Ethereum message sender authentication\\n // docs: https://developer.arbitrum.io/arbos/l2-to-l1-messaging/\\n // example: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/dfef6a68ee18dbd2e1f5a099061a3b8a0e404485/contracts/crosschain/arbitrum/LibArbitrumL1.sol#L34\\n // example: https://github.com/OffchainLabs/arbitrum-tutorials/blob/2c1b7d2db8f36efa496e35b561864c0f94123a5f/packages/greeter/contracts/ethereum/GreeterL1.sol#L50\\n // note: we call the bridge for the activeOutbox address\\n\\n require(msg.sender == address(bridge), \\\"Not from native arbitrum bridge.\\\");\\n require(IOutbox(bridge.activeOutbox()).l2ToL1Sender() == veaInboxArbToEth, \\\"veaInboxArbToEth only.\\\");\\n\\n if (_epoch > latestVerifiedEpoch && _stateRoot != bytes32(0)) {\\n latestVerifiedEpoch = _epoch;\\n stateRoot = _stateRoot;\\n emit Verified(_epoch);\\n }\\n\\n if (claimHashes[_epoch] == hashClaim(_claim)) {\\n if (_claim.stateRoot == _stateRoot) {\\n _claim.honest = Party.Claimer;\\n } else if (_claim.challenger != address(0)) {\\n _claim.honest = Party.Challenger;\\n }\\n claimHashes[_epoch] = hashClaim(_claim);\\n }\\n }\\n\\n /// @dev Verifies and relays the message. UNTRUSTED.\\n /// @param _proof The merkle proof to prove the message inclusion in the inbox state root.\\n /// @param _msgId The zero based index of the message in the inbox.\\n /// @param _to The address of the contract on Ethereum to call.\\n /// @param _message The message encoded in the vea inbox as abi.encodeWithSelector(fnSelector, msg.sender, param1, param2, ...)\\n function sendMessage(bytes32[] calldata _proof, uint64 _msgId, address _to, bytes calldata _message) external {\\n require(_proof.length < 64, \\\"Proof too long.\\\");\\n\\n bytes32 nodeHash = keccak256(abi.encodePacked(_msgId, _to, _message));\\n\\n // double hashed leaf\\n // avoids second order preimage attacks\\n // https://flawed.net.nz/2018/02/21/attacking-merkle-trees-with-a-second-preimage-attack/\\n assembly {\\n mstore(0x00, nodeHash)\\n nodeHash := keccak256(0x00, 0x20)\\n }\\n\\n unchecked {\\n for (uint256 i = 0; i < _proof.length; i++) {\\n bytes32 proofElement = _proof[i];\\n // sort sibling hashes as a convention for efficient proof validation\\n if (proofElement > nodeHash)\\n assembly {\\n mstore(0x00, nodeHash)\\n mstore(0x20, proofElement)\\n nodeHash := keccak256(0x00, 0x40)\\n }\\n else\\n assembly {\\n mstore(0x00, proofElement)\\n mstore(0x20, nodeHash)\\n nodeHash := keccak256(0x00, 0x40)\\n }\\n }\\n }\\n\\n require(stateRoot == nodeHash, \\\"Invalid proof.\\\");\\n\\n // msgId is the zero-based index of the message in the inbox.\\n // msgId is also used as an index in the relayed bitmap to prevent replay.\\n // Note: a bitmap is used instead of a simple boolean mapping to save 15k gas per message.\\n\\n uint256 relayIndex = _msgId >> 8;\\n uint256 offset;\\n\\n unchecked {\\n offset = _msgId % 256;\\n }\\n\\n bytes32 replay = relayed[relayIndex];\\n\\n require(((replay >> offset) & bytes32(uint256(1))) == bytes32(0), \\\"Message already relayed\\\");\\n relayed[relayIndex] = replay | bytes32(1 << offset);\\n\\n // UNTRUSTED.\\n (bool success, ) = _to.call(_message);\\n require(success, \\\"Failed to call contract\\\");\\n\\n emit MessageRelayed(_msgId);\\n }\\n\\n /// @dev Sends the deposit back to the Claimer if successful. Includes a portion of the Challenger's deposit if unsuccessfully challenged.\\n /// @param _epoch The epoch associated with the claim deposit to withraw.\\n /// @param _claim The claim associated with the epoch.\\n function withdrawClaimDeposit(uint256 _epoch, Claim calldata _claim) external virtual {\\n require(claimHashes[_epoch] == hashClaim(_claim), \\\"Invalid claim.\\\");\\n require(_claim.honest == Party.Claimer, \\\"Claim failed.\\\");\\n\\n delete claimHashes[_epoch];\\n\\n if (_claim.challenger != address(0)) {\\n payable(BURN_ADDRESS).send(burn);\\n payable(_claim.claimer).send(depositPlusReward); // User is responsible for accepting ETH.\\n } else {\\n payable(_claim.claimer).send(deposit); // User is responsible for accepting ETH.\\n }\\n }\\n\\n /// @dev Sends the deposit back to the Challenger if successful. Includes a portion of the Bridger's deposit.\\n /// @param _epoch The epoch associated with the challenge deposit to withraw.\\n /// @param _claim The claim associated with the epoch.\\n function withdrawChallengeDeposit(uint256 _epoch, Claim calldata _claim) external {\\n require(claimHashes[_epoch] == hashClaim(_claim), \\\"Invalid claim.\\\");\\n require(_claim.honest == Party.Challenger, \\\"Challenge failed.\\\");\\n\\n delete claimHashes[_epoch];\\n\\n payable(BURN_ADDRESS).send(burn); // half burnt\\n payable(_claim.challenger).send(depositPlusReward); // User is responsible for accepting ETH.\\n }\\n\\n /// @dev When bridge is shutdown, no claim disputes can be resolved. This allows the claimer to withdraw their deposit.\\n /// @param _epoch The epoch associated with the claim deposit to withraw.\\n /// @param _claim The claim associated with the epoch.\\n function withdrawClaimerEscapeHatch(uint256 _epoch, Claim memory _claim) external OnlyBridgeShutdown {\\n require(claimHashes[_epoch] == hashClaim(_claim), \\\"Invalid claim.\\\");\\n require(_claim.honest == Party.None, \\\"Claim resolved.\\\");\\n\\n if (_claim.claimer != address(0)) {\\n if (_claim.challenger == address(0)) {\\n delete claimHashes[_epoch];\\n payable(_claim.claimer).send(deposit); // User is responsible for accepting ETH.\\n } else {\\n address claimer = _claim.claimer;\\n _claim.claimer = address(0);\\n claimHashes[_epoch] = hashClaim(_claim);\\n payable(claimer).send(deposit); // User is responsible for accepting ETH.\\n }\\n }\\n }\\n\\n /// @dev When bridge is shutdown, no claim disputes can be resolved. This allows the challenger to withdraw their deposit.\\n /// @param _epoch The epoch associated with the claim deposit to withraw.\\n /// @param _claim The claim associated with the epoch.\\n function withdrawChallengerEscapeHatch(uint256 _epoch, Claim memory _claim) external OnlyBridgeShutdown {\\n require(claimHashes[_epoch] == hashClaim(_claim), \\\"Invalid claim.\\\");\\n require(_claim.honest == Party.None, \\\"Claim resolved.\\\");\\n\\n if (_claim.challenger != address(0)) {\\n if (_claim.claimer == address(0)) {\\n delete claimHashes[_epoch];\\n payable(_claim.challenger).send(deposit); // User is responsible for accepting ETH.\\n } else {\\n address challenger = _claim.challenger;\\n _claim.challenger = address(0);\\n claimHashes[_epoch] == hashClaim(_claim);\\n payable(challenger).send(deposit); // User is responsible for accepting ETH.\\n }\\n }\\n }\\n\\n // ************************************* //\\n // * Pure / Views * //\\n // ************************************* //\\n\\n /// @dev Hashes the claim.\\n /// @param _claim The claim to hash.\\n /// @return hashedClaim The hash of the claim.\\n function hashClaim(Claim memory _claim) public pure returns (bytes32 hashedClaim) {\\n return\\n hashedClaim = keccak256(\\n abi.encodePacked(\\n _claim.stateRoot,\\n _claim.claimer,\\n _claim.timestampClaimed,\\n _claim.timestampVerification,\\n _claim.blocknumberVerification,\\n _claim.honest,\\n _claim.challenger\\n )\\n );\\n }\\n\\n /// @dev Gets the status of the censorship test for claim.\\n /// @param _claim The claim to test.\\n /// @return status True if the claim passed the censorship test.\\n function censorshipTestStatus(Claim memory _claim) public view returns (CensorshipTestStatus status) {\\n unchecked {\\n if (uint256(_claim.timestampVerification) == 0) {\\n status = CensorshipTestStatus.NotStarted;\\n } else if (block.timestamp - uint256(_claim.timestampVerification) < minChallengePeriod) {\\n status = CensorshipTestStatus.InProgress;\\n } else {\\n uint256 expectedBlocks = uint256(_claim.blocknumberVerification) +\\n (block.timestamp - uint256(_claim.timestampVerification)) /\\n SLOT_TIME;\\n uint256 actualBlocks = block.number;\\n if (expectedBlocks - actualBlocks <= maxMissingBlocks) {\\n status = CensorshipTestStatus.Passed;\\n } else {\\n status = CensorshipTestStatus.Failed;\\n }\\n }\\n }\\n }\\n\\n /// @dev Get the current epoch from the outbox's point of view using the Ethereum L1 clock.\\n /// @return epoch The hash of the claim.\\n function epochNow() external view returns (uint256 epoch) {\\n epoch = block.timestamp / epochPeriod;\\n }\\n\\n /// @dev Get the current epoch from the outbox's point of view using the Ethereum L1 clock.\\n /// @return epoch The hash of the claim.\\n function epochAt(uint256 timestamp) external view returns (uint256 epoch) {\\n epoch = timestamp / epochPeriod;\\n }\\n\\n /// @dev Get the msg relayed status.\\n /// @param _msgId The msgId to check.\\n /// @return isRelayed True if the msg was relayed.\\n function isMsgRelayed(uint256 _msgId) external view returns (bool isRelayed) {\\n uint256 relayIndex = _msgId >> 8;\\n uint256 offset;\\n\\n unchecked {\\n offset = _msgId % 256;\\n }\\n\\n bytes32 replay = relayed[relayIndex];\\n\\n isRelayed = (replay >> offset) & bytes32(uint256(1)) == bytes32(uint256(1));\\n }\\n}\\n\",\"keccak256\":\"0xa6739700d8449f0432d0de255bc73dc6f2d9ef10e0a16b800519762a796bc7bc\",\"license\":\"MIT\"},\"src/canonical/arbitrum/IBridge.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\n// https://github.com/OffchainLabs/nitro-contracts/blob/08ac127e966fa87a4d5ba3d23cd3132b57701132/src/bridge/IBridge.sol\\n// proxy: https://etherscan.io/address/0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a\\n// implementation: https://etherscan.io/address/0x1066cecc8880948fe55e427e94f1ff221d626591#code\\n// interface is pruned for relevant function stubs\\n\\npragma solidity 0.8.24;\\n\\ninterface IBridge {\\n function activeOutbox() external view returns (address);\\n\\n function sequencerInbox() external view returns (address);\\n\\n function allowedDelayedInboxList(uint256) external returns (address);\\n}\\n\",\"keccak256\":\"0x0e7981b3e9b179caa0085d1ad900b19e88e29fec65923f41fde0315773fa9a3c\",\"license\":\"BUSL-1.1\"},\"src/canonical/arbitrum/IOutbox.sol\":{\"content\":\"// SPDX-License-Identifier: BUSL-1.1\\n// https://github.com/OffchainLabs/nitro-contracts/blob/08ac127e966fa87a4d5ba3d23cd3132b57701132/src/bridge/IBridge.sol\\n// proxy: https://etherscan.io/address/0x0B9857ae2D4A3DBe74ffE1d7DF045bb7F96E4840#code\\n// implementation: https://etherscan.io/address/0x0ea7372338a589e7f0b00e463a53aa464ef04e17#code\\n// interface is pruned for relevant function stubs\\n\\npragma solidity 0.8.24;\\n\\ninterface IOutbox {\\n /// @notice When l2ToL1Sender returns a nonzero address, the message was originated by an L2 account\\n /// When the return value is zero, that means this is a system message\\n /// @dev the l2ToL1Sender behaves as the tx.origin, the msg.sender should be validated to protect against reentrancies\\n function l2ToL1Sender() external view returns (address);\\n}\\n\",\"keccak256\":\"0x4d815542262727a2a3182332a7c16b0b1d33c031997de91c12e67e87e748b8ca\",\"license\":\"BUSL-1.1\"},\"src/canonical/arbitrum/ISequencerInbox.sol\":{\"content\":\"// Copyright 2021-2022, Offchain Labs, Inc.\\n// For license information, see https://github.com/nitro/blob/master/LICENSE\\n// SPDX-License-Identifier: BUSL-1.1\\n// https://github.com/OffchainLabs/nitro-contracts/blob/08ac127e966fa87a4d5ba3d23cd3132b57701132/src/bridge/ISequencerInbox.sol\\n// proxy: https://etherscan.io/address/0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6#code\\n// implementation: https://etherscan.io/address/0xD03bFe2CE83632F4E618a97299cc91B1335BB2d9#code\\n// interface is pruned for relevant function stubs\\n\\npragma solidity 0.8.24;\\n\\nimport \\\"./IBridge.sol\\\";\\n\\ninterface ISequencerInbox {\\n struct MaxTimeVariation {\\n uint256 delayBlocks;\\n uint256 futureBlocks;\\n uint256 delaySeconds;\\n uint256 futureSeconds;\\n }\\n\\n function maxTimeVariation() external view returns (uint256, uint256, uint256, uint256);\\n}\\n\",\"keccak256\":\"0xf972282dbad5eae92a352e0de6b588000bf4c58f45d90a30ef8863d5878313bc\",\"license\":\"BUSL-1.1\"},\"src/interfaces/outboxes/IVeaOutboxOnL1.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\n/// @custom:authors: [@jaybuidl, @shotaronowhere]\\n/// @custom:reviewers: []\\n/// @custom:auditors: []\\n/// @custom:bounties: []\\n/// @custom:deployments: []\\n\\npragma solidity 0.8.24;\\n\\nimport \\\"../types/VeaClaim.sol\\\";\\n\\n/// @dev Interface of the Vea Outbox on L1 chains like Ethereum, Gnosis, Polygon POS where storage is expensive.\\ninterface IVeaOutboxOnL1 {\\n /// @dev Verifies and relays the message.\\n /// Note: Gateways expect first argument of message call to be the arbitrum message sender, used for authentication.\\n /// @param _proof The merkle proof to prove the message.\\n /// @param _msgId The zero based index of the message in the inbox.\\n /// @param _to The address to send the message to.\\n /// @param _message The message to relay.\\n function sendMessage(bytes32[] calldata _proof, uint64 _msgId, address _to, bytes calldata _message) external;\\n\\n /// @dev Resolves any challenge of the optimistic claim for 'epoch' using the canonical bridge.\\n /// Note: Access restricted to canonical bridge.\\n /// @param _epoch The epoch to verify.\\n /// @param _stateRoot The true state root for the epoch.\\n /// @param _claim The claim associated with the epoch.\\n function resolveDisputedClaim(uint256 _epoch, bytes32 _stateRoot, Claim memory _claim) external;\\n}\\n\",\"keccak256\":\"0xf1d52e289e790088502b7909f11f47bc33ddd3fc545636b7fb29c01ed00d3ff3\",\"license\":\"MIT\"},\"src/interfaces/types/VeaClaim.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\n/// @custom:authors: [@jaybuidl, @shotaronowhere]\\n/// @custom:reviewers: []\\n/// @custom:auditors: []\\n/// @custom:bounties: []\\n/// @custom:deployments: []\\n\\npragma solidity 0.8.24;\\n\\nenum Party {\\n None,\\n Claimer,\\n Challenger\\n}\\n\\nstruct Claim {\\n bytes32 stateRoot;\\n address claimer;\\n uint32 timestampClaimed;\\n uint32 timestampVerification;\\n uint32 blocknumberVerification;\\n Party honest;\\n address challenger;\\n}\\n\",\"keccak256\":\"0xfef781e359c97aebbe8dbfcb75edb7cb962139fd9ea538b8b89a3f2e13a05bfe\",\"license\":\"MIT\"}},\"version\":1}", + "bytecode": "0x6101a060405234801562000011575f80fd5b5060405162002bee38038062002bee8339810160408190526200003491620002c7565b60c08790526101208690526101408590526101608490526001600160a01b0380841660a05282166080526101808190526200006e620000cd565b6200007b6002886200033e565b60e08190526200008d8860026200035e565b6200009991906200037e565b6101005261012051600190620000b090426200033e565b620000bc91906200037e565b60015550620003eb95505050505050565b5f6080516001600160a01b031663ee35f3276040518163ffffffff1660e01b8152600401602060405180830381865afa1580156200010d573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019062000133919062000394565b6001600160a01b031663ebea461d6040518163ffffffff1660e01b8152600401608060405180830381865afa1580156200016f573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190620001959190620003b7565b5092505050600454811115620001e05760048190556040518181527f611c2e4a78552f908fb0eb2cc503efc1f947cde8574277ab3b0f10fdd510258b9060200160405180910390a150565b600454811015620002a857600654156200025a5760405162461bcd60e51b815260206004820152603160248201527f53657175656e636572206c696d697420646563726561736520726571756573746044820152701030b63932b0b23c903832b73234b7339760791b606482015260840160405180910390fd5b604080518082018252828152426020918201819052600584905560065590518281527fa552b382e128c9d0732f01f09502c18999aec5dce0ed78c5af0ea2274ce9bd7d910160405180910390a15b50565b80516001600160a01b0381168114620002c2575f80fd5b919050565b5f805f805f805f60e0888a031215620002de575f80fd5b875196506020880151955060408801519450606088015193506200030560808901620002ab565b92506200031560a08901620002ab565b915060c0880151905092959891949750929550565b634e487b7160e01b5f52601160045260245ffd5b5f826200035957634e487b7160e01b5f52601260045260245ffd5b500490565b80820281158282048414176200037857620003786200032a565b92915050565b818103818111156200037857620003786200032a565b5f60208284031215620003a5575f80fd5b620003b082620002ab565b9392505050565b5f805f8060808587031215620003cb575f80fd5b505082516020840151604085015160609095015191969095509092509050565b60805160a05160c05160e05161010051610120516101405161016051610180516126b6620005385f395f81816104fc0152611eae01525f818161052f015281816108e30152818161182201528181611a130152611c1a01525f81816105b40152611e4301525f81816105620152818161090701528181610c0801528181610ca601528181611400015281816115aa0152818161184601528181611a370152611c3e01525f8181610483015261121d01525f8181610375015281816111ca015261132b01525f81816105e701528181610c3401528181610e1101528181610e3d0152818161138e01528181611b7a01528181611bdf01528181611d8201528181611dee01528181611f20015281816120ac01526120d801525f818161043801526109f101525f8181610665015281816107cd0152818161098201528181610a1b015261162901526126b65ff3fe6080604052600436106101e6575f3560e01c80635f43a47f11610108578063b5b7a1841161009d578063da2b7bc41161006d578063da2b7bc414610609578063df19e6ff14610628578063e78cea9214610654578063e813a75514610687578063fccc28131461069c575f80fd5b8063b5b7a18414610551578063b633b94414610584578063c2114a16146105a3578063d0e30db0146105d6575f80fd5b8063930f28af116100d8578063930f28af146104b85780639588eca2146104d7578063aa22a1c6146104eb578063b044397e1461051e575f80fd5b80635f43a47f1461041357806369cd250d14610427578063836e344b146104725780638830dfbd146104a5575f80fd5b806331ddf7431161017e5780634788cb381161014e5780634788cb381461039757806349b4299e146103b65780634a439cfe146103d5578063541adcca146103f4575f80fd5b806331ddf743146102db5780633ce43cfd146102fa57806343b066d51461031957806344df8e7014610364575f80fd5b8063222ae786116101b9578063222ae786146102665780632639c0601461028857806327ee6bdd146102b357806331d14457146102c8575f80fd5b806301139b68146101ea578063051d1970146101ff5780630c63fa84146102335780630f0adca514610247575b5f80fd5b6101fd6101f8366004612217565b6106af565b005b34801561020a575f80fd5b50600554600654610219919082565b604080519283526020830191909152015b60405180910390f35b34801561023e575f80fd5b506101fd6106be565b348015610252575f80fd5b506101fd610261366004612243565b6108e1565b348015610271575f80fd5b5061027a610c02565b60405190815260200161022a565b348015610293575f80fd5b5061027a6102a2366004612277565b60026020525f908152604090205481565b3480156102be575f80fd5b5061027a60015481565b6101fd6102d636600461228e565b610c32565b3480156102e6575f80fd5b506101fd6102f536600461230a565b610e85565b348015610305575f80fd5b506101fd6103143660046123c3565b611115565b348015610324575f80fd5b50610354610333366004612277565b600881901c5f90815260036020526040902054600160ff9092161c81161490565b604051901515815260200161022a565b34801561036f575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103a2575f80fd5b506101fd6103b13660046123c3565b611259565b3480156103c1575f80fd5b506101fd6103d0366004612217565b6113ca565b3480156103e0575f80fd5b5061027a6103ef366004612277565b6115a4565b3480156103ff575f80fd5b5061027a61040e3660046123f9565b6115d5565b34801561041e575f80fd5b506101fd611626565b348015610432575f80fd5b5061045a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200161022a565b34801561047d575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b6101fd6104b336600461241a565b611815565b3480156104c3575f80fd5b506101fd6104d2366004612217565b611820565b3480156104e2575f80fd5b5061027a5f5481565b3480156104f6575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b348015610529575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b34801561055c575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b34801561058f575f80fd5b506101fd61059e366004612217565b611a11565b3480156105ae575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b3480156105e1575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b348015610614575f80fd5b506101fd610623366004612217565b611c18565b348015610633575f80fd5b506106476106423660046123f9565b611e27565b60405161022a919061246e565b34801561065f575f80fd5b5061045a7f000000000000000000000000000000000000000000000000000000000000000081565b348015610692575f80fd5b5061027a60045481565b3480156106a7575f80fd5b5061045a5f81565b6106ba828233611eea565b5050565b6006545f036107295760405162461bcd60e51b815260206004820152602c60248201527f4e6f2070656e64696e672073657175656e636572206c696d697420646563726560448201526b30b9b2903932b8bab2b9ba1760a11b60648201526084015b60405180910390fd5b600454600654610739919061249c565b42116107a25760405162461bcd60e51b815260206004820152603260248201527f53657175656e636572206c696d697420646563726561736520726571756573746044820152711034b99039ba34b636103832b73234b7339760711b6064820152608401610720565b600580545f9182905560068290556040805163ee35f32760e01b815290519192916001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169163ee35f3279160048083019260209291908290030181865afa158015610816573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061083a91906124af565b6001600160a01b031663ebea461d6040518163ffffffff1660e01b8152600401608060405180830381865afa158015610875573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061089991906124ca565b50925050508181036106ba5760048290556040518281527f611c2e4a78552f908fb0eb2cc503efc1f947cde8574277ab3b0f10fdd510258b9060200160405180910390a15050565b7f00000000000000000000000000000000000000000000000000000000000000006001547f00000000000000000000000000000000000000000000000000000000000000004281610934576109346124fd565b040311156109775760405162461bcd60e51b815260206004820152601060248201526f213934b233b29029b43aba3237bbb71760811b6044820152606401610720565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146109ef5760405162461bcd60e51b815260206004820181905260248201527f4e6f742066726f6d206e617469766520617262697472756d206272696467652e6044820152606401610720565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ab5d89436040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a75573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a9991906124af565b6001600160a01b03166380648b026040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ad4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610af891906124af565b6001600160a01b031614610b475760405162461bcd60e51b81526020600482015260166024820152753b32b0a4b73137bc20b9312a37a2ba341037b7363c9760511b6044820152606401610720565b60015483118015610b5757508115155b15610b995760018390555f8290556040518381527ff786e7f77ede00a02a5464f8f0555798f42ba99a4a920ef2778db8d75e4656f79060200160405180910390a15b610ba2816115d5565b5f8481526002602052604090205403610bfd578051829003610bca57600160a0820152610be5565b60c08101516001600160a01b031615610be557600260a08201525b610bee816115d5565b5f848152600260205260409020555b505050565b5f610c2d7f000000000000000000000000000000000000000000000000000000000000000042612511565b905090565b7f0000000000000000000000000000000000000000000000000000000000000000341015610ca25760405162461bcd60e51b815260206004820152601b60248201527f496e73756666696369656e7420636c61696d206465706f7369742e00000000006044820152606401610720565b60017f00000000000000000000000000000000000000000000000000000000000000004281610cd357610cd36124fd565b04038214610d145760405162461bcd60e51b815260206004820152600e60248201526d24b73b30b634b21032b837b1b41760911b6044820152606401610720565b80610d315760405162461bcd60e51b815260040161072090612530565b5f8281526002602052604090205415610d825760405162461bcd60e51b815260206004820152601360248201527221b630b4b69030b63932b0b23c9036b0b2329760691b6044820152606401610720565b6040805160e0810182528281523360208201524263ffffffff16918101919091525f606082018190526080820181905260a0820181905260c0820152610dc7906115d5565b5f838152600260209081526040918290209290925551828152839133917fd95107f4584744c6c893a04c43058aadd1ce8aac8ca5d64140eaf277de6c1d57910160405180910390a37f00000000000000000000000000000000000000000000000000000000000000003411156106ba575f610e627f000000000000000000000000000000000000000000000000000000000000000034612558565b604051909150339082156108fc029083905f818181858888f15050505050505050565b60408510610ec75760405162461bcd60e51b815260206004820152600f60248201526e283937b7b3103a37b7903637b7339760891b6044820152606401610720565b5f84848484604051602001610edf949392919061256b565b604051602081830303815290604052805190602001209050805f5260205f2090505f5b86811015610f57575f888883818110610f1d57610f1d6125ac565b90506020020135905082811115610f4057825f528060205260405f209250610f4e565b805f528260205260405f2092505b50600101610f02565b50805f5414610f995760405162461bcd60e51b815260206004820152600e60248201526d24b73b30b634b210383937b7b31760911b6044820152606401610720565b600885901c66ffffffffffffff165f8181526003602052604090205460ff87169080821c6001161561100d5760405162461bcd60e51b815260206004820152601760248201527f4d65737361676520616c72656164792072656c617965640000000000000000006044820152606401610720565b5f838152600360205260408082206001851b84179055516001600160a01b0389169061103c90899089906125c0565b5f604051808303815f865af19150503d805f8114611075576040519150601f19603f3d011682016040523d82523d5f602084013e61107a565b606091505b50509050806110cb5760405162461bcd60e51b815260206004820152601760248201527f4661696c656420746f2063616c6c20636f6e74726163740000000000000000006044820152606401610720565b60405167ffffffffffffffff8a1681527f54303fab361bc52c2f1f56ace7351189582264f74ce47a6e7c3f478d64c429439060200160405180910390a15050505050505050505050565b61112761040e368390038301836123f9565b5f83815260026020526040902054146111525760405162461bcd60e51b815260040161072090612530565b600261116460c0830160a084016125cf565b60028111156111755761117561245a565b146111b65760405162461bcd60e51b815260206004820152601160248201527021b430b63632b733b2903330b4b632b21760791b6044820152606401610720565b5f82815260026020526040808220829055517f000000000000000000000000000000000000000000000000000000000000000080156108fc029183818181858288f15061120f93505060e0840191505060c083016125e8565b6001600160a01b03166108fc7f000000000000000000000000000000000000000000000000000000000000000090811502906040515f60405180830381858888f150505050505050565b61126b61040e368390038301836123f9565b5f83815260026020526040902054146112965760405162461bcd60e51b815260040161072090612530565b60016112a860c0830160a084016125cf565b60028111156112b9576112b961245a565b146112f65760405162461bcd60e51b815260206004820152600d60248201526c21b630b4b6903330b4b632b21760991b6044820152606401610720565b5f82815260026020526040812081905561131660e0830160c084016125e8565b6001600160a01b031614611370576040515f907f000000000000000000000000000000000000000000000000000000000000000080156108fc029183818181858288f15061120f93505060408401915050602083016125e8565b61138060408201602083016125e8565b6001600160a01b03166108fc7f000000000000000000000000000000000000000000000000000000000000000090811502906040515f60405180830381858888f150505050505050565b6113d3816115d5565b5f83815260026020526040902054146113fe5760405162461bcd60e51b815260040161072090612530565b7f000000000000000000000000000000000000000000000000000000000000000060045461142c919061249c565b60408201516114419063ffffffff1642612558565b10156114a45760405162461bcd60e51b815260206004820152602c60248201527f436c61696d206d75737420776169742061746c65617374206d61784c3253746160448201526b3a32a9bcb731a232b630bc9760a11b6064820152608401610720565b5f6114ae82611e27565b905060028160038111156114c4576114c461245a565b14806114e057505f8160038111156114de576114de61245a565b145b6115495760405162461bcd60e51b815260206004820152603460248201527f436c61696d20766572696669636174696f6e20696e2070726f6772657373206f604482015273391030b63932b0b23c9031b7b6b83632ba32b21760611b6064820152608401610720565b63ffffffff428116606084015243166080830152611566826115d5565b5f8481526002602052604080822092909255905184917f37b700b61b9b4710dddb0c3316b2be7ef6088ed4b1d7bfe0fb98be8f9a163e1691a2505050565b5f6115cf7f000000000000000000000000000000000000000000000000000000000000000083612511565b92915050565b80516020808301516040808501516060860151608087015160a088015160c089015194515f98611609989097969101612603565b604051602081830303815290604052805190602001209050919050565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ee35f3276040518163ffffffff1660e01b8152600401602060405180830381865afa158015611683573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116a791906124af565b6001600160a01b031663ebea461d6040518163ffffffff1660e01b8152600401608060405180830381865afa1580156116e2573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061170691906124ca565b50925050506004548111156117505760048190556040518181527f611c2e4a78552f908fb0eb2cc503efc1f947cde8574277ab3b0f10fdd510258b9060200160405180910390a150565b60045481101561181257600654156117c45760405162461bcd60e51b815260206004820152603160248201527f53657175656e636572206c696d697420646563726561736520726571756573746044820152701030b63932b0b23c903832b73234b7339760791b6064820152608401610720565b604080518082018252828152426020918201819052600584905560065590518281527fa552b382e128c9d0732f01f09502c18999aec5dce0ed78c5af0ea2274ce9bd7d910160405180910390a15b50565b610bfd838383611eea565b7f00000000000000000000000000000000000000000000000000000000000000006001547f00000000000000000000000000000000000000000000000000000000000000004281611873576118736124fd565b040311156118b65760405162461bcd60e51b815260206004820152601060248201526f213934b233b29029b43aba3237bbb71760811b6044820152606401610720565b6118bf816115d5565b5f83815260026020526040902054146118ea5760405162461bcd60e51b815260040161072090612530565b60c08101516001600160a01b03161561193c5760405162461bcd60e51b815260206004820152601460248201527321b630b4b69034b99031b430b63632b733b2b21760611b6044820152606401610720565b600161194782611e27565b60038111156119585761195861245a565b146119a55760405162461bcd60e51b815260206004820152601b60248201527f43656e736f72736869702074657374206e6f74207061737365642e00000000006044820152606401610720565b6001548211156119ec57600182905580515f556040518281527ff786e7f77ede00a02a5464f8f0555798f42ba99a4a920ef2778db8d75e4656f79060200160405180910390a15b600160a08201526119fc816115d5565b5f928352600260205260409092209190915550565b7f00000000000000000000000000000000000000000000000000000000000000006001547f00000000000000000000000000000000000000000000000000000000000000004281611a6457611a646124fd565b040311611aa55760405162461bcd60e51b815260206004820152600f60248201526e213934b233b290293ab73734b7339760891b6044820152606401610720565b611aae816115d5565b5f8381526002602052604090205414611ad95760405162461bcd60e51b815260040161072090612530565b5f8160a001516002811115611af057611af061245a565b14611b2f5760405162461bcd60e51b815260206004820152600f60248201526e21b630b4b6903932b9b7b63b32b21760891b6044820152606401610720565b60c08101516001600160a01b0316156106ba5760208101516001600160a01b0316611bb2575f8281526002602052604080822082905560c083015190516001600160a01b03909116917f000000000000000000000000000000000000000000000000000000000000000080156108fc02929091818181858888f150505050505050565b60c0810180515f909152611bc5826115d5565b505f83815260026020526040516001600160a01b038316917f000000000000000000000000000000000000000000000000000000000000000080156108fc02929091818181858888f15050505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001547f00000000000000000000000000000000000000000000000000000000000000004281611c6b57611c6b6124fd565b040311611cac5760405162461bcd60e51b815260206004820152600f60248201526e213934b233b290293ab73734b7339760891b6044820152606401610720565b611cb5816115d5565b5f8381526002602052604090205414611ce05760405162461bcd60e51b815260040161072090612530565b5f8160a001516002811115611cf757611cf761245a565b14611d365760405162461bcd60e51b815260206004820152600f60248201526e21b630b4b6903932b9b7b63b32b21760891b6044820152606401610720565b60208101516001600160a01b0316156106ba5760c08101516001600160a01b0316611dba575f8281526002602090815260408083208390559083015190516001600160a01b03909116917f000000000000000000000000000000000000000000000000000000000000000080156108fc02929091818181858888f150505050505050565b6020810180515f909152611dcd826115d5565b5f848152600260205260408082209290925590516001600160a01b038316917f000000000000000000000000000000000000000000000000000000000000000080156108fc02929091818181858888f15050505050505050565b5f816060015163ffffffff165f03611e4157506002919050565b7f0000000000000000000000000000000000000000000000000000000000000000826060015163ffffffff1642031015611e7d57506003919050565b5f600c836060015163ffffffff16420381611e9a57611e9a6124fd565b608085015163ffffffff16919004019050437f000000000000000000000000000000000000000000000000000000000000000081830311611ede5760019250611ee2565b5f92505b50505b919050565b611ef3826115d5565b5f8481526002602052604090205414611f1e5760405162461bcd60e51b815260040161072090612530565b7f0000000000000000000000000000000000000000000000000000000000000000341015611f8e5760405162461bcd60e51b815260206004820152601f60248201527f496e73756666696369656e74206368616c6c656e6765206465706f7369742e006044820152606401610720565b60c08201516001600160a01b031615611fe95760405162461bcd60e51b815260206004820152601960248201527f436c61696d20616c7265616479206368616c6c656e6765642e000000000000006044820152606401610720565b5f8260a0015160028111156120005761200061245a565b1461204d5760405162461bcd60e51b815260206004820152601760248201527f436c61696d20616c72656164792076657269666965642e0000000000000000006044820152606401610720565b6001600160a01b03811660c0830152612065826115d5565b5f848152600260205260408082209290925590516001600160a01b0383169185917fcfe09ca25f55d949baba5e280f5750c9ba4b9048fca5532f916067d433afe4d79190a37f0000000000000000000000000000000000000000000000000000000000000000341115610bfd575f6120fd7f000000000000000000000000000000000000000000000000000000000000000034612558565b604051909150339082156108fc029083905f818181858888f1505050505050505050565b6001600160a01b0381168114611812575f80fd5b8035611ee581612121565b803563ffffffff81168114611ee5575f80fd5b803560038110611ee5575f80fd5b5f60e08284031215612171575f80fd5b60405160e0810181811067ffffffffffffffff821117156121a057634e487b7160e01b5f52604160045260245ffd5b604052823581529050806121b660208401612135565b60208201526121c760408401612140565b60408201526121d860608401612140565b60608201526121e960808401612140565b60808201526121fa60a08401612153565b60a082015261220b60c08401612135565b60c08201525092915050565b5f806101008385031215612229575f80fd5b8235915061223a8460208501612161565b90509250929050565b5f805f6101208486031215612256575f80fd5b833592506020840135915061226e8560408601612161565b90509250925092565b5f60208284031215612287575f80fd5b5035919050565b5f806040838503121561229f575f80fd5b50508035926020909101359150565b803567ffffffffffffffff81168114611ee5575f80fd5b5f8083601f8401126122d5575f80fd5b50813567ffffffffffffffff8111156122ec575f80fd5b602083019150836020828501011115612303575f80fd5b9250929050565b5f805f805f806080878903121561231f575f80fd5b863567ffffffffffffffff80821115612336575f80fd5b818901915089601f830112612349575f80fd5b813581811115612357575f80fd5b8a60208260051b850101111561236b575f80fd5b6020830198508097505061238160208a016122ae565b955061238f60408a01612135565b945060608901359150808211156123a4575f80fd5b506123b189828a016122c5565b979a9699509497509295939492505050565b5f808284036101008112156123d6575f80fd5b8335925060e0601f19820112156123eb575f80fd5b506020830190509250929050565b5f60e08284031215612409575f80fd5b6124138383612161565b9392505050565b5f805f610120848603121561242d575f80fd5b8335925061243e8560208601612161565b915061010084013561244f81612121565b809150509250925092565b634e487b7160e01b5f52602160045260245ffd5b60208101600483106124825761248261245a565b91905290565b634e487b7160e01b5f52601160045260245ffd5b808201808211156115cf576115cf612488565b5f602082840312156124bf575f80fd5b815161241381612121565b5f805f80608085870312156124dd575f80fd5b505082516020840151604085015160609095015191969095509092509050565b634e487b7160e01b5f52601260045260245ffd5b5f8261252b57634e487b7160e01b5f52601260045260245ffd5b500490565b6020808252600e908201526d24b73b30b634b21031b630b4b69760911b604082015260600190565b818103818111156115cf576115cf612488565b60c085901b6001600160c01b0319168152606084901b6bffffffffffffffffffffffff191660088201528183601c8301375f9101601c019081529392505050565b634e487b7160e01b5f52603260045260245ffd5b818382375f9101908152919050565b5f602082840312156125df575f80fd5b61241382612153565b5f602082840312156125f8575f80fd5b813561241381612121565b8781525f6bffffffffffffffffffffffff19808960601b16602084015263ffffffff60e01b808960e01b166034850152808860e01b166038850152808760e01b16603c850152506003851061265a5761265a61245a565b60f89490941b60408301525060609190911b90911660418201526055019594505050505056fea2646970667358221220f30d444842e1028f8e0cd5b5c38c93b8d5e2f20a1e7a6bc4f691d60b1991308264736f6c63430008180033", + "deployedBytecode": "0x6080604052600436106101e6575f3560e01c80635f43a47f11610108578063b5b7a1841161009d578063da2b7bc41161006d578063da2b7bc414610609578063df19e6ff14610628578063e78cea9214610654578063e813a75514610687578063fccc28131461069c575f80fd5b8063b5b7a18414610551578063b633b94414610584578063c2114a16146105a3578063d0e30db0146105d6575f80fd5b8063930f28af116100d8578063930f28af146104b85780639588eca2146104d7578063aa22a1c6146104eb578063b044397e1461051e575f80fd5b80635f43a47f1461041357806369cd250d14610427578063836e344b146104725780638830dfbd146104a5575f80fd5b806331ddf7431161017e5780634788cb381161014e5780634788cb381461039757806349b4299e146103b65780634a439cfe146103d5578063541adcca146103f4575f80fd5b806331ddf743146102db5780633ce43cfd146102fa57806343b066d51461031957806344df8e7014610364575f80fd5b8063222ae786116101b9578063222ae786146102665780632639c0601461028857806327ee6bdd146102b357806331d14457146102c8575f80fd5b806301139b68146101ea578063051d1970146101ff5780630c63fa84146102335780630f0adca514610247575b5f80fd5b6101fd6101f8366004612217565b6106af565b005b34801561020a575f80fd5b50600554600654610219919082565b604080519283526020830191909152015b60405180910390f35b34801561023e575f80fd5b506101fd6106be565b348015610252575f80fd5b506101fd610261366004612243565b6108e1565b348015610271575f80fd5b5061027a610c02565b60405190815260200161022a565b348015610293575f80fd5b5061027a6102a2366004612277565b60026020525f908152604090205481565b3480156102be575f80fd5b5061027a60015481565b6101fd6102d636600461228e565b610c32565b3480156102e6575f80fd5b506101fd6102f536600461230a565b610e85565b348015610305575f80fd5b506101fd6103143660046123c3565b611115565b348015610324575f80fd5b50610354610333366004612277565b600881901c5f90815260036020526040902054600160ff9092161c81161490565b604051901515815260200161022a565b34801561036f575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b3480156103a2575f80fd5b506101fd6103b13660046123c3565b611259565b3480156103c1575f80fd5b506101fd6103d0366004612217565b6113ca565b3480156103e0575f80fd5b5061027a6103ef366004612277565b6115a4565b3480156103ff575f80fd5b5061027a61040e3660046123f9565b6115d5565b34801561041e575f80fd5b506101fd611626565b348015610432575f80fd5b5061045a7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b03909116815260200161022a565b34801561047d575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b6101fd6104b336600461241a565b611815565b3480156104c3575f80fd5b506101fd6104d2366004612217565b611820565b3480156104e2575f80fd5b5061027a5f5481565b3480156104f6575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b348015610529575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b34801561055c575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b34801561058f575f80fd5b506101fd61059e366004612217565b611a11565b3480156105ae575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b3480156105e1575f80fd5b5061027a7f000000000000000000000000000000000000000000000000000000000000000081565b348015610614575f80fd5b506101fd610623366004612217565b611c18565b348015610633575f80fd5b506106476106423660046123f9565b611e27565b60405161022a919061246e565b34801561065f575f80fd5b5061045a7f000000000000000000000000000000000000000000000000000000000000000081565b348015610692575f80fd5b5061027a60045481565b3480156106a7575f80fd5b5061045a5f81565b6106ba828233611eea565b5050565b6006545f036107295760405162461bcd60e51b815260206004820152602c60248201527f4e6f2070656e64696e672073657175656e636572206c696d697420646563726560448201526b30b9b2903932b8bab2b9ba1760a11b60648201526084015b60405180910390fd5b600454600654610739919061249c565b42116107a25760405162461bcd60e51b815260206004820152603260248201527f53657175656e636572206c696d697420646563726561736520726571756573746044820152711034b99039ba34b636103832b73234b7339760711b6064820152608401610720565b600580545f9182905560068290556040805163ee35f32760e01b815290519192916001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169163ee35f3279160048083019260209291908290030181865afa158015610816573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061083a91906124af565b6001600160a01b031663ebea461d6040518163ffffffff1660e01b8152600401608060405180830381865afa158015610875573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061089991906124ca565b50925050508181036106ba5760048290556040518281527f611c2e4a78552f908fb0eb2cc503efc1f947cde8574277ab3b0f10fdd510258b9060200160405180910390a15050565b7f00000000000000000000000000000000000000000000000000000000000000006001547f00000000000000000000000000000000000000000000000000000000000000004281610934576109346124fd565b040311156109775760405162461bcd60e51b815260206004820152601060248201526f213934b233b29029b43aba3237bbb71760811b6044820152606401610720565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146109ef5760405162461bcd60e51b815260206004820181905260248201527f4e6f742066726f6d206e617469766520617262697472756d206272696467652e6044820152606401610720565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ab5d89436040518163ffffffff1660e01b8152600401602060405180830381865afa158015610a75573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a9991906124af565b6001600160a01b03166380648b026040518163ffffffff1660e01b8152600401602060405180830381865afa158015610ad4573d5f803e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610af891906124af565b6001600160a01b031614610b475760405162461bcd60e51b81526020600482015260166024820152753b32b0a4b73137bc20b9312a37a2ba341037b7363c9760511b6044820152606401610720565b60015483118015610b5757508115155b15610b995760018390555f8290556040518381527ff786e7f77ede00a02a5464f8f0555798f42ba99a4a920ef2778db8d75e4656f79060200160405180910390a15b610ba2816115d5565b5f8481526002602052604090205403610bfd578051829003610bca57600160a0820152610be5565b60c08101516001600160a01b031615610be557600260a08201525b610bee816115d5565b5f848152600260205260409020555b505050565b5f610c2d7f000000000000000000000000000000000000000000000000000000000000000042612511565b905090565b7f0000000000000000000000000000000000000000000000000000000000000000341015610ca25760405162461bcd60e51b815260206004820152601b60248201527f496e73756666696369656e7420636c61696d206465706f7369742e00000000006044820152606401610720565b60017f00000000000000000000000000000000000000000000000000000000000000004281610cd357610cd36124fd565b04038214610d145760405162461bcd60e51b815260206004820152600e60248201526d24b73b30b634b21032b837b1b41760911b6044820152606401610720565b80610d315760405162461bcd60e51b815260040161072090612530565b5f8281526002602052604090205415610d825760405162461bcd60e51b815260206004820152601360248201527221b630b4b69030b63932b0b23c9036b0b2329760691b6044820152606401610720565b6040805160e0810182528281523360208201524263ffffffff16918101919091525f606082018190526080820181905260a0820181905260c0820152610dc7906115d5565b5f838152600260209081526040918290209290925551828152839133917fd95107f4584744c6c893a04c43058aadd1ce8aac8ca5d64140eaf277de6c1d57910160405180910390a37f00000000000000000000000000000000000000000000000000000000000000003411156106ba575f610e627f000000000000000000000000000000000000000000000000000000000000000034612558565b604051909150339082156108fc029083905f818181858888f15050505050505050565b60408510610ec75760405162461bcd60e51b815260206004820152600f60248201526e283937b7b3103a37b7903637b7339760891b6044820152606401610720565b5f84848484604051602001610edf949392919061256b565b604051602081830303815290604052805190602001209050805f5260205f2090505f5b86811015610f57575f888883818110610f1d57610f1d6125ac565b90506020020135905082811115610f4057825f528060205260405f209250610f4e565b805f528260205260405f2092505b50600101610f02565b50805f5414610f995760405162461bcd60e51b815260206004820152600e60248201526d24b73b30b634b210383937b7b31760911b6044820152606401610720565b600885901c66ffffffffffffff165f8181526003602052604090205460ff87169080821c6001161561100d5760405162461bcd60e51b815260206004820152601760248201527f4d65737361676520616c72656164792072656c617965640000000000000000006044820152606401610720565b5f838152600360205260408082206001851b84179055516001600160a01b0389169061103c90899089906125c0565b5f604051808303815f865af19150503d805f8114611075576040519150601f19603f3d011682016040523d82523d5f602084013e61107a565b606091505b50509050806110cb5760405162461bcd60e51b815260206004820152601760248201527f4661696c656420746f2063616c6c20636f6e74726163740000000000000000006044820152606401610720565b60405167ffffffffffffffff8a1681527f54303fab361bc52c2f1f56ace7351189582264f74ce47a6e7c3f478d64c429439060200160405180910390a15050505050505050505050565b61112761040e368390038301836123f9565b5f83815260026020526040902054146111525760405162461bcd60e51b815260040161072090612530565b600261116460c0830160a084016125cf565b60028111156111755761117561245a565b146111b65760405162461bcd60e51b815260206004820152601160248201527021b430b63632b733b2903330b4b632b21760791b6044820152606401610720565b5f82815260026020526040808220829055517f000000000000000000000000000000000000000000000000000000000000000080156108fc029183818181858288f15061120f93505060e0840191505060c083016125e8565b6001600160a01b03166108fc7f000000000000000000000000000000000000000000000000000000000000000090811502906040515f60405180830381858888f150505050505050565b61126b61040e368390038301836123f9565b5f83815260026020526040902054146112965760405162461bcd60e51b815260040161072090612530565b60016112a860c0830160a084016125cf565b60028111156112b9576112b961245a565b146112f65760405162461bcd60e51b815260206004820152600d60248201526c21b630b4b6903330b4b632b21760991b6044820152606401610720565b5f82815260026020526040812081905561131660e0830160c084016125e8565b6001600160a01b031614611370576040515f907f000000000000000000000000000000000000000000000000000000000000000080156108fc029183818181858288f15061120f93505060408401915050602083016125e8565b61138060408201602083016125e8565b6001600160a01b03166108fc7f000000000000000000000000000000000000000000000000000000000000000090811502906040515f60405180830381858888f150505050505050565b6113d3816115d5565b5f83815260026020526040902054146113fe5760405162461bcd60e51b815260040161072090612530565b7f000000000000000000000000000000000000000000000000000000000000000060045461142c919061249c565b60408201516114419063ffffffff1642612558565b10156114a45760405162461bcd60e51b815260206004820152602c60248201527f436c61696d206d75737420776169742061746c65617374206d61784c3253746160448201526b3a32a9bcb731a232b630bc9760a11b6064820152608401610720565b5f6114ae82611e27565b905060028160038111156114c4576114c461245a565b14806114e057505f8160038111156114de576114de61245a565b145b6115495760405162461bcd60e51b815260206004820152603460248201527f436c61696d20766572696669636174696f6e20696e2070726f6772657373206f604482015273391030b63932b0b23c9031b7b6b83632ba32b21760611b6064820152608401610720565b63ffffffff428116606084015243166080830152611566826115d5565b5f8481526002602052604080822092909255905184917f37b700b61b9b4710dddb0c3316b2be7ef6088ed4b1d7bfe0fb98be8f9a163e1691a2505050565b5f6115cf7f000000000000000000000000000000000000000000000000000000000000000083612511565b92915050565b80516020808301516040808501516060860151608087015160a088015160c089015194515f98611609989097969101612603565b604051602081830303815290604052805190602001209050919050565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663ee35f3276040518163ffffffff1660e01b8152600401602060405180830381865afa158015611683573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906116a791906124af565b6001600160a01b031663ebea461d6040518163ffffffff1660e01b8152600401608060405180830381865afa1580156116e2573d5f803e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061170691906124ca565b50925050506004548111156117505760048190556040518181527f611c2e4a78552f908fb0eb2cc503efc1f947cde8574277ab3b0f10fdd510258b9060200160405180910390a150565b60045481101561181257600654156117c45760405162461bcd60e51b815260206004820152603160248201527f53657175656e636572206c696d697420646563726561736520726571756573746044820152701030b63932b0b23c903832b73234b7339760791b6064820152608401610720565b604080518082018252828152426020918201819052600584905560065590518281527fa552b382e128c9d0732f01f09502c18999aec5dce0ed78c5af0ea2274ce9bd7d910160405180910390a15b50565b610bfd838383611eea565b7f00000000000000000000000000000000000000000000000000000000000000006001547f00000000000000000000000000000000000000000000000000000000000000004281611873576118736124fd565b040311156118b65760405162461bcd60e51b815260206004820152601060248201526f213934b233b29029b43aba3237bbb71760811b6044820152606401610720565b6118bf816115d5565b5f83815260026020526040902054146118ea5760405162461bcd60e51b815260040161072090612530565b60c08101516001600160a01b03161561193c5760405162461bcd60e51b815260206004820152601460248201527321b630b4b69034b99031b430b63632b733b2b21760611b6044820152606401610720565b600161194782611e27565b60038111156119585761195861245a565b146119a55760405162461bcd60e51b815260206004820152601b60248201527f43656e736f72736869702074657374206e6f74207061737365642e00000000006044820152606401610720565b6001548211156119ec57600182905580515f556040518281527ff786e7f77ede00a02a5464f8f0555798f42ba99a4a920ef2778db8d75e4656f79060200160405180910390a15b600160a08201526119fc816115d5565b5f928352600260205260409092209190915550565b7f00000000000000000000000000000000000000000000000000000000000000006001547f00000000000000000000000000000000000000000000000000000000000000004281611a6457611a646124fd565b040311611aa55760405162461bcd60e51b815260206004820152600f60248201526e213934b233b290293ab73734b7339760891b6044820152606401610720565b611aae816115d5565b5f8381526002602052604090205414611ad95760405162461bcd60e51b815260040161072090612530565b5f8160a001516002811115611af057611af061245a565b14611b2f5760405162461bcd60e51b815260206004820152600f60248201526e21b630b4b6903932b9b7b63b32b21760891b6044820152606401610720565b60c08101516001600160a01b0316156106ba5760208101516001600160a01b0316611bb2575f8281526002602052604080822082905560c083015190516001600160a01b03909116917f000000000000000000000000000000000000000000000000000000000000000080156108fc02929091818181858888f150505050505050565b60c0810180515f909152611bc5826115d5565b505f83815260026020526040516001600160a01b038316917f000000000000000000000000000000000000000000000000000000000000000080156108fc02929091818181858888f15050505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001547f00000000000000000000000000000000000000000000000000000000000000004281611c6b57611c6b6124fd565b040311611cac5760405162461bcd60e51b815260206004820152600f60248201526e213934b233b290293ab73734b7339760891b6044820152606401610720565b611cb5816115d5565b5f8381526002602052604090205414611ce05760405162461bcd60e51b815260040161072090612530565b5f8160a001516002811115611cf757611cf761245a565b14611d365760405162461bcd60e51b815260206004820152600f60248201526e21b630b4b6903932b9b7b63b32b21760891b6044820152606401610720565b60208101516001600160a01b0316156106ba5760c08101516001600160a01b0316611dba575f8281526002602090815260408083208390559083015190516001600160a01b03909116917f000000000000000000000000000000000000000000000000000000000000000080156108fc02929091818181858888f150505050505050565b6020810180515f909152611dcd826115d5565b5f848152600260205260408082209290925590516001600160a01b038316917f000000000000000000000000000000000000000000000000000000000000000080156108fc02929091818181858888f15050505050505050565b5f816060015163ffffffff165f03611e4157506002919050565b7f0000000000000000000000000000000000000000000000000000000000000000826060015163ffffffff1642031015611e7d57506003919050565b5f600c836060015163ffffffff16420381611e9a57611e9a6124fd565b608085015163ffffffff16919004019050437f000000000000000000000000000000000000000000000000000000000000000081830311611ede5760019250611ee2565b5f92505b50505b919050565b611ef3826115d5565b5f8481526002602052604090205414611f1e5760405162461bcd60e51b815260040161072090612530565b7f0000000000000000000000000000000000000000000000000000000000000000341015611f8e5760405162461bcd60e51b815260206004820152601f60248201527f496e73756666696369656e74206368616c6c656e6765206465706f7369742e006044820152606401610720565b60c08201516001600160a01b031615611fe95760405162461bcd60e51b815260206004820152601960248201527f436c61696d20616c7265616479206368616c6c656e6765642e000000000000006044820152606401610720565b5f8260a0015160028111156120005761200061245a565b1461204d5760405162461bcd60e51b815260206004820152601760248201527f436c61696d20616c72656164792076657269666965642e0000000000000000006044820152606401610720565b6001600160a01b03811660c0830152612065826115d5565b5f848152600260205260408082209290925590516001600160a01b0383169185917fcfe09ca25f55d949baba5e280f5750c9ba4b9048fca5532f916067d433afe4d79190a37f0000000000000000000000000000000000000000000000000000000000000000341115610bfd575f6120fd7f000000000000000000000000000000000000000000000000000000000000000034612558565b604051909150339082156108fc029083905f818181858888f1505050505050505050565b6001600160a01b0381168114611812575f80fd5b8035611ee581612121565b803563ffffffff81168114611ee5575f80fd5b803560038110611ee5575f80fd5b5f60e08284031215612171575f80fd5b60405160e0810181811067ffffffffffffffff821117156121a057634e487b7160e01b5f52604160045260245ffd5b604052823581529050806121b660208401612135565b60208201526121c760408401612140565b60408201526121d860608401612140565b60608201526121e960808401612140565b60808201526121fa60a08401612153565b60a082015261220b60c08401612135565b60c08201525092915050565b5f806101008385031215612229575f80fd5b8235915061223a8460208501612161565b90509250929050565b5f805f6101208486031215612256575f80fd5b833592506020840135915061226e8560408601612161565b90509250925092565b5f60208284031215612287575f80fd5b5035919050565b5f806040838503121561229f575f80fd5b50508035926020909101359150565b803567ffffffffffffffff81168114611ee5575f80fd5b5f8083601f8401126122d5575f80fd5b50813567ffffffffffffffff8111156122ec575f80fd5b602083019150836020828501011115612303575f80fd5b9250929050565b5f805f805f806080878903121561231f575f80fd5b863567ffffffffffffffff80821115612336575f80fd5b818901915089601f830112612349575f80fd5b813581811115612357575f80fd5b8a60208260051b850101111561236b575f80fd5b6020830198508097505061238160208a016122ae565b955061238f60408a01612135565b945060608901359150808211156123a4575f80fd5b506123b189828a016122c5565b979a9699509497509295939492505050565b5f808284036101008112156123d6575f80fd5b8335925060e0601f19820112156123eb575f80fd5b506020830190509250929050565b5f60e08284031215612409575f80fd5b6124138383612161565b9392505050565b5f805f610120848603121561242d575f80fd5b8335925061243e8560208601612161565b915061010084013561244f81612121565b809150509250925092565b634e487b7160e01b5f52602160045260245ffd5b60208101600483106124825761248261245a565b91905290565b634e487b7160e01b5f52601160045260245ffd5b808201808211156115cf576115cf612488565b5f602082840312156124bf575f80fd5b815161241381612121565b5f805f80608085870312156124dd575f80fd5b505082516020840151604085015160609095015191969095509092509050565b634e487b7160e01b5f52601260045260245ffd5b5f8261252b57634e487b7160e01b5f52601260045260245ffd5b500490565b6020808252600e908201526d24b73b30b634b21031b630b4b69760911b604082015260600190565b818103818111156115cf576115cf612488565b60c085901b6001600160c01b0319168152606084901b6bffffffffffffffffffffffff191660088201528183601c8301375f9101601c019081529392505050565b634e487b7160e01b5f52603260045260245ffd5b818382375f9101908152919050565b5f602082840312156125df575f80fd5b61241382612153565b5f602082840312156125f8575f80fd5b813561241381612121565b8781525f6bffffffffffffffffffffffff19808960601b16602084015263ffffffff60e01b808960e01b166034850152808860e01b166038850152808760e01b16603c850152506003851061265a5761265a61245a565b60f89490941b60408301525060609190911b90911660418201526055019594505050505056fea2646970667358221220f30d444842e1028f8e0cd5b5c38c93b8d5e2f20a1e7a6bc4f691d60b1991308264736f6c63430008180033", + "devdoc": { + "details": "Vea Outbox From Arbitrum to Ethereum. Note: This contract is deployed on Ethereum.", + "events": { + "Challenged(uint256,address)": { + "details": "This event indicates that `sendSnapshot(epoch)` should be called in the inbox.", + "params": { + "_challenger": "The address of the challenger.", + "_epoch": "The epoch associated with the challenged claim." + } + }, + "Claimed(address,uint256,bytes32)": { + "details": "Watchers check this event to challenge fraud.", + "params": { + "_claimer": "The address of the claimer.", + "_epoch": "The epoch associated with the claim.", + "_stateRoot": "The state root of the claim." + } + }, + "MessageRelayed(uint64)": { + "details": "This event indicates that a message has been relayed.", + "params": { + "_msgId": "The msgId of the message that was relayed." + } + }, + "VerificationStarted(uint256)": { + "details": "This event indicates that the censorship test started and all challengers are ready even in the worst case scenario of a malicious sequencer.", + "params": { + "_epoch": "The epoch that started verification." + } + }, + "Verified(uint256)": { + "details": "This events indicates that verification has succeeded. The messages are ready to be relayed.", + "params": { + "_epoch": "The epoch that was verified." + } + }, + "sequencerDelayLimitDecreaseRequested(uint256)": { + "details": "This event indicates that a request to decrease the sequencer limit has been made.", + "params": { + "_requestedSequencerDelayLimit": "The new sequencer delay limit requested." + } + }, + "sequencerDelayLimitUpdated(uint256)": { + "details": "This event indicates the sequencer limit updated.", + "params": { + "_newSequencerDelayLimit": "The new sequencer delay limit." + } + } + }, + "kind": "dev", + "methods": { + "censorshipTestStatus((bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "Gets the status of the censorship test for claim.", + "params": { + "_claim": "The claim to test." + }, + "returns": { + "status": "True if the claim passed the censorship test." + } + }, + "challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "Submit a challenge for the claim of the inbox state root snapshot taken at 'epoch'.", + "params": { + "_claim": "The claim associated with the epoch.", + "_epoch": "The epoch of the claim to challenge." + } + }, + "challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address),address)": { + "details": "Submit a challenge for the claim of the inbox state root snapshot taken at 'epoch'.Allows proxy contracts to batch challenges.", + "params": { + "_claim": "The claim associated with the epoch.", + "_epoch": "The epoch of the claim to challenge.", + "_withdrawalAddress": "The address to withdraw the deposit + reward to." + } + }, + "claim(uint256,bytes32)": { + "details": "Submit a claim about the _stateRoot at _epoch and submit a deposit.", + "params": { + "_epoch": "The epoch for which the claim is made.", + "_stateRoot": "The state root to claim." + } + }, + "constructor": { + "details": "Constructor. Note: epochPeriod must match the VeaInboxArbToEth contract deployment on Arbitrum, since it's on a different chain, we can't read it and trust the deployer to set a correct value", + "params": { + "_deposit": "The deposit amount to submit a claim in wei.", + "_epochPeriod": "The duration of each epoch.", + "_maxMissingBlocks": "The maximum number of blocks that can be missing in a challenge period.", + "_minChallengePeriod": "The minimum time window to challenge a claim.", + "_timeoutEpochs": "The epochs before the bridge is considered shutdown.", + "_veaInboxArbToEth": "The address of the inbox contract on Arbitrum." + } + }, + "epochAt(uint256)": { + "details": "Get the current epoch from the outbox's point of view using the Ethereum L1 clock.", + "returns": { + "epoch": "The hash of the claim." + } + }, + "epochNow()": { + "details": "Get the current epoch from the outbox's point of view using the Ethereum L1 clock.", + "returns": { + "epoch": "The hash of the claim." + } + }, + "executeSequencerDelayLimitDecreaseRequest()": { + "details": "execute sequencerDelayLimitDecreaseRequest" + }, + "hashClaim((bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "Hashes the claim.", + "params": { + "_claim": "The claim to hash." + }, + "returns": { + "hashedClaim": "The hash of the claim." + } + }, + "isMsgRelayed(uint256)": { + "details": "Get the msg relayed status.", + "params": { + "_msgId": "The msgId to check." + }, + "returns": { + "isRelayed": "True if the msg was relayed." + } + }, + "resolveDisputedClaim(uint256,bytes32,(bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "Resolves any challenge of the optimistic claim for '_epoch'.", + "params": { + "_claim": "The claim associated with the epoch.", + "_epoch": "The epoch to verify.", + "_stateRoot": "The true state root for the epoch." + } + }, + "sendMessage(bytes32[],uint64,address,bytes)": { + "details": "Verifies and relays the message. UNTRUSTED.", + "params": { + "_message": "The message encoded in the vea inbox as abi.encodeWithSelector(fnSelector, msg.sender, param1, param2, ...)", + "_msgId": "The zero based index of the message in the inbox.", + "_proof": "The merkle proof to prove the message inclusion in the inbox state root.", + "_to": "The address of the contract on Ethereum to call." + } + }, + "startVerification(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "Start verification for claim for 'epoch'.", + "params": { + "_claim": "The claim associated with the epoch.", + "_epoch": "The epoch of the claim to challenge." + } + }, + "updateSequencerDelayLimit()": { + "details": "Request to decrease the sequencerDelayLimit." + }, + "verifySnapshot(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "Resolves the optimistic claim for '_epoch'.", + "params": { + "_claim": "The claim associated with the epoch.", + "_epoch": "The epoch of the optimistic claim." + } + }, + "withdrawChallengeDeposit(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "Sends the deposit back to the Challenger if successful. Includes a portion of the Bridger's deposit.", + "params": { + "_claim": "The claim associated with the epoch.", + "_epoch": "The epoch associated with the challenge deposit to withraw." + } + }, + "withdrawChallengerEscapeHatch(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "When bridge is shutdown, no claim disputes can be resolved. This allows the challenger to withdraw their deposit.", + "params": { + "_claim": "The claim associated with the epoch.", + "_epoch": "The epoch associated with the claim deposit to withraw." + } + }, + "withdrawClaimDeposit(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "Sends the deposit back to the Claimer if successful. Includes a portion of the Challenger's deposit if unsuccessfully challenged.", + "params": { + "_claim": "The claim associated with the epoch.", + "_epoch": "The epoch associated with the claim deposit to withraw." + } + }, + "withdrawClaimerEscapeHatch(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))": { + "details": "When bridge is shutdown, no claim disputes can be resolved. This allows the claimer to withdraw their deposit.", + "params": { + "_claim": "The claim associated with the epoch.", + "_epoch": "The epoch associated with the claim deposit to withraw." + } + } + }, + "version": 1 + }, + "userdoc": { + "kind": "user", + "methods": { + "resolveDisputedClaim(uint256,bytes32,(bytes32,address,uint32,uint32,uint32,uint8,address))": { + "notice": "Note: Access restricted to arbitrum bridge." + } + }, + "version": 1 + }, + "storageLayout": { + "storage": [ + { + "astId": 446, + "contract": "src/arbitrumToEth/VeaOutboxArbToEth.sol:VeaOutboxArbToEth", + "label": "stateRoot", + "offset": 0, + "slot": "0", + "type": "t_bytes32" + }, + { + "astId": 448, + "contract": "src/arbitrumToEth/VeaOutboxArbToEth.sol:VeaOutboxArbToEth", + "label": "latestVerifiedEpoch", + "offset": 0, + "slot": "1", + "type": "t_uint256" + }, + { + "astId": 452, + "contract": "src/arbitrumToEth/VeaOutboxArbToEth.sol:VeaOutboxArbToEth", + "label": "claimHashes", + "offset": 0, + "slot": "2", + "type": "t_mapping(t_uint256,t_bytes32)" + }, + { + "astId": 456, + "contract": "src/arbitrumToEth/VeaOutboxArbToEth.sol:VeaOutboxArbToEth", + "label": "relayed", + "offset": 0, + "slot": "3", + "type": "t_mapping(t_uint256,t_bytes32)" + }, + { + "astId": 458, + "contract": "src/arbitrumToEth/VeaOutboxArbToEth.sol:VeaOutboxArbToEth", + "label": "sequencerDelayLimit", + "offset": 0, + "slot": "4", + "type": "t_uint256" + }, + { + "astId": 461, + "contract": "src/arbitrumToEth/VeaOutboxArbToEth.sol:VeaOutboxArbToEth", + "label": "sequencerDelayLimitDecreaseRequest", + "offset": 0, + "slot": "5", + "type": "t_struct(SequencerDelayLimitDecreaseRequest)466_storage" + } + ], + "types": { + "t_bytes32": { + "encoding": "inplace", + "label": "bytes32", + "numberOfBytes": "32" + }, + "t_mapping(t_uint256,t_bytes32)": { + "encoding": "mapping", + "key": "t_uint256", + "label": "mapping(uint256 => bytes32)", + "numberOfBytes": "32", + "value": "t_bytes32" + }, + "t_struct(SequencerDelayLimitDecreaseRequest)466_storage": { + "encoding": "inplace", + "label": "struct VeaOutboxArbToEth.SequencerDelayLimitDecreaseRequest", + "members": [ + { + "astId": 463, + "contract": "src/arbitrumToEth/VeaOutboxArbToEth.sol:VeaOutboxArbToEth", + "label": "requestedsequencerDelayLimit", + "offset": 0, + "slot": "0", + "type": "t_uint256" + }, + { + "astId": 465, + "contract": "src/arbitrumToEth/VeaOutboxArbToEth.sol:VeaOutboxArbToEth", + "label": "timestamp", + "offset": 0, + "slot": "1", + "type": "t_uint256" + } + ], + "numberOfBytes": "64" + }, + "t_uint256": { + "encoding": "inplace", + "label": "uint256", + "numberOfBytes": "32" + } + } + } +} diff --git a/contracts/test/integration/ArbToEth.ts b/contracts/test/integration/ArbToEth.ts index 95b3bf73..7059177d 100644 --- a/contracts/test/integration/ArbToEth.ts +++ b/contracts/test/integration/ArbToEth.ts @@ -26,8 +26,8 @@ const ONE_TENTH_ETH = BigNumber.from(10).pow(17); const ONE_ETH = BigNumber.from(10).pow(18); const TEN_ETH = BigNumber.from(10).pow(19); const HARDHAT_CHAIN_ID = 31337; -const EPOCH_PERIOD = 1800; -const CHALLENGE_PERIOD = 1800; +const EPOCH_PERIOD = 600; // 10 minutes for Hardhat +const CHALLENGE_PERIOD = 600; // 10 minutes for Hardhat describe("Integration tests", async () => { let [deployer, bridger, challenger, relayer]: SignerWithAddress[] = []; @@ -133,7 +133,7 @@ describe("Integration tests", async () => { await expect( veaOutbox.connect(bridger).claim(invalidEpoch, batchMerkleRoot, { value: TEN_ETH }) - ).to.be.revertedWith("Epoch has not yet passed."); + ).to.be.revertedWith("Invalid epoch."); const bridgerClaimTx = await veaOutbox.connect(bridger).claim(epoch, batchMerkleRoot, { value: TEN_ETH }); @@ -506,7 +506,6 @@ describe("Integration tests", async () => { it("should be able to challenge", async () => { const data = 1121; - const sendMessagetx = await senderGateway.sendMessage(data); const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); @@ -523,19 +522,22 @@ describe("Integration tests", async () => { const bridgerClaimTx = await veaOutbox.connect(bridger).claim(epoch, batchMerkleRoot, { value: TEN_ETH }); const block = await ethers.provider.getBlock(bridgerClaimTx.blockNumber!); - const challengeTx = await veaOutbox.connect(challenger).challenge( - epoch, - { - stateRoot: batchMerkleRoot, - claimer: bridger.address, - timestampClaimed: block.timestamp, - timestampVerification: 0, - blocknumberVerification: 0, - honest: 0, - challenger: ethers.constants.AddressZero, - }, - { value: TEN_ETH } - ); + const challengeTx = await veaOutbox + .connect(challenger) + ["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + epoch, + { + stateRoot: batchMerkleRoot, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 0, + challenger: ethers.constants.AddressZero, + }, + { value: TEN_ETH } + ); + await expect(challengeTx).to.emit(veaOutbox, "Challenged").withArgs(epoch, challenger.address); }); @@ -558,19 +560,21 @@ describe("Integration tests", async () => { const bridgerClaimTx = await veaOutbox.connect(bridger).claim(epoch, batchMerkleRoot, { value: TEN_ETH }); const block = await ethers.provider.getBlock(bridgerClaimTx.blockNumber!); - const challengeTx = await veaOutbox.connect(challenger).challenge( - epoch, - { - stateRoot: batchMerkleRoot, - claimer: bridger.address, - timestampClaimed: block.timestamp, - timestampVerification: 0, - blocknumberVerification: 0, - honest: 0, - challenger: ethers.constants.AddressZero, - }, - { value: TEN_ETH } - ); + const challengeTx = await veaOutbox + .connect(challenger) + ["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + epoch, + { + stateRoot: batchMerkleRoot, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 0, + challenger: ethers.constants.AddressZero, + }, + { value: TEN_ETH } + ); const sendSafeFallbackTx = await veaInbox.connect(bridger).sendSnapshot( epoch, @@ -661,19 +665,21 @@ describe("Integration tests", async () => { await mine(blocksToMine); // Challenger tx starts - const challengeTx = await veaOutbox.connect(challenger).challenge( - epoch, - { - stateRoot: batchMerkleRoot, - claimer: bridger.address, - timestampClaimed: block.timestamp, - timestampVerification: blockStartValidation.timestamp!, - blocknumberVerification: startValidationTxn.blockNumber!, - honest: 0, - challenger: ethers.constants.AddressZero, - }, - { value: TEN_ETH } - ); + const challengeTx = await veaOutbox + .connect(challenger) + ["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + epoch, + { + stateRoot: batchMerkleRoot, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: ethers.constants.AddressZero, + }, + { value: TEN_ETH } + ); await expect(challengeTx).to.emit(veaOutbox, "Challenged").withArgs(epoch, challenger.address); await expect( @@ -735,7 +741,6 @@ describe("Integration tests", async () => { const data = 1121; const sendMessagetx = await senderGateway.sendMessage(data); - await expect(sendMessagetx).to.emit(veaInbox, "MessageSent"); const MessageSent = veaInbox.filters.MessageSent(); const MessageSentEvent = await veaInbox.queryFilter(MessageSent); @@ -769,25 +774,11 @@ describe("Integration tests", async () => { const bridgerClaimTx = await veaOutbox.connect(bridger).claim(epoch, fakeHash, { value: TEN_ETH }); const block = await ethers.provider.getBlock(bridgerClaimTx.blockNumber!); - // Challenger tx starts - const challengeTx = await veaOutbox.connect(challenger).challenge( - epoch, - { - stateRoot: fakeHash, - claimer: bridger.address, - timestampClaimed: block.timestamp, - timestampVerification: 0, - blocknumberVerification: 0, - honest: 0, - challenger: ethers.constants.AddressZero, - }, - { value: TEN_ETH } - ); - const maxL2StateSyncDelay = (await veaOutbox.sequencerDelayLimit()).toNumber() + epochPeriod / 2; await network.provider.send("evm_increaseTime", [epochPeriod + maxL2StateSyncDelay]); await network.provider.send("evm_mine"); + // Validation starts const startValidationTxn = await veaOutbox.startVerification(epoch, { stateRoot: fakeHash, claimer: bridger.address, @@ -795,10 +786,9 @@ describe("Integration tests", async () => { timestampVerification: 0, blocknumberVerification: 0, honest: 0, - challenger: challenger.address, + challenger: ethers.constants.AddressZero, }); await expect(startValidationTxn).to.emit(veaOutbox, "VerificationStarted").withArgs(epoch); - const blockStartValidation = await ethers.provider.getBlock(startValidationTxn.blockNumber!); const minChallengePeriod = (await veaOutbox.minChallengePeriod()).toNumber(); @@ -807,6 +797,23 @@ describe("Integration tests", async () => { const blocksToMine = Math.ceil(minChallengePeriod / 12); await mine(blocksToMine); + // Challenger tx starts + const challengeTx = await veaOutbox + .connect(challenger) + ["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + epoch, + { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: ethers.constants.AddressZero, + }, + { value: TEN_ETH } + ); + await expect( veaOutbox.connect(relayer).verifySnapshot(epoch, { stateRoot: fakeHash, @@ -859,5 +866,242 @@ describe("Integration tests", async () => { }) ); }); + + it("should update latest verified epoch and state root correctly after dispute resolution", async () => { + const data = 1121; + + const sendMessagetx = await senderGateway.sendMessage(data); + await expect(sendMessagetx).to.emit(veaInbox, "MessageSent"); + const MessageSent = veaInbox.filters.MessageSent(); + const MessageSentEvent = await veaInbox.queryFilter(MessageSent); + const msg = MessageSentEvent[0].args._nodeData; + const nonce = "0x" + msg.slice(2, 18); + const to = "0x" + msg.slice(18, 58); //18+40 + const msgData = "0x" + msg.slice(58); + + let nodes: string[] = []; + nodes.push(MerkleTree.makeLeafNode(nonce, to, msgData)); + + const mt = new MerkleTree(nodes); + const proof = mt.getHexProof(nodes[nodes.length - 1]); + + const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + + const BatchOutgoing = veaInbox.filters.SnapshotSaved(); + const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); + const epoch = Math.floor( + (await batchOutGoingEvent[0].getBlock()).timestamp / (await veaInbox.epochPeriod()).toNumber() + ); + const epochPeriod = (await veaOutbox.epochPeriod()).toNumber(); + + const batchMerkleRoot = await veaInbox.snapshots(epoch); + await network.provider.send("evm_increaseTime", [epochPeriod]); + await network.provider.send("evm_mine"); + // bridger tx starts - bridger creates fakeData & fakeHash + + const fakeData = "KlerosToTheMoon"; + const fakeHash = utils.keccak256(utils.defaultAbiCoder.encode(["string"], [fakeData])); + const bridgerClaimTx = await veaOutbox.connect(bridger).claim(epoch, fakeHash, { value: TEN_ETH }); + const block = await ethers.provider.getBlock(bridgerClaimTx.blockNumber!); + + const maxL2StateSyncDelay = (await veaOutbox.sequencerDelayLimit()).toNumber() + epochPeriod / 2; + await network.provider.send("evm_increaseTime", [epochPeriod + maxL2StateSyncDelay]); + await network.provider.send("evm_mine"); + + // Validation starts + const startValidationTxn = await veaOutbox.startVerification(epoch, { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 0, + challenger: ethers.constants.AddressZero, + }); + await expect(startValidationTxn).to.emit(veaOutbox, "VerificationStarted").withArgs(epoch); + const blockStartValidation = await ethers.provider.getBlock(startValidationTxn.blockNumber!); + + const minChallengePeriod = (await veaOutbox.minChallengePeriod()).toNumber(); + await network.provider.send("evm_increaseTime", [minChallengePeriod]); + await network.provider.send("evm_mine"); + const blocksToMine = Math.ceil(minChallengePeriod / 12); + await mine(blocksToMine); + + // Challenger tx starts + const challengeTx = await veaOutbox + .connect(challenger) + ["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + epoch, + { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: ethers.constants.AddressZero, + }, + { value: TEN_ETH } + ); + + await expect( + veaOutbox.connect(relayer).verifySnapshot(epoch, { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: challenger.address, + }) + ).to.revertedWith("Claim is challenged."); + + // sendSafeFallback internally calls the verifySafeBatch + const sendSafeFallbackTx = await veaInbox.connect(bridger).sendSnapshot( + epoch, + { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: challenger.address, + }, + { gasLimit: 1000000 } + ); + + const latestVerifiedEpoch = await veaOutbox.latestVerifiedEpoch(); + expect(latestVerifiedEpoch).to.equal(epoch); + + const stateRoot = await veaOutbox.stateRoot(); + expect(stateRoot).to.equal(batchMerkleRoot); + }); + + it("should not update latest verified epoch and state root after dispute resolution", async () => { + const data = 1121; + + const sendMessagetx = await senderGateway.sendMessage(data); + await expect(sendMessagetx).to.emit(veaInbox, "MessageSent"); + const sendBatchTx = await veaInbox.connect(bridger).saveSnapshot(); + + const BatchOutgoing = veaInbox.filters.SnapshotSaved(); + const batchOutGoingEvent = await veaInbox.queryFilter(BatchOutgoing); + const epoch = Math.floor( + (await batchOutGoingEvent[0].getBlock()).timestamp / (await veaInbox.epochPeriod()).toNumber() + ); + const stateRoot1 = await veaInbox.snapshots(epoch); + const epochPeriod = (await veaOutbox.epochPeriod()).toNumber(); + await network.provider.send("evm_increaseTime", [epochPeriod]); + await network.provider.send("evm_mine"); + + // bridger tx starts - bridger creates fakeData & fakeHash + const fakeData = "KlerosToTheMoon"; + const fakeHash = utils.keccak256(utils.defaultAbiCoder.encode(["string"], [fakeData])); + const bridgerClaimTx = await veaOutbox.connect(bridger).claim(epoch, fakeHash, { value: TEN_ETH }); + const block = await ethers.provider.getBlock(bridgerClaimTx.blockNumber!); + + const maxL2StateSyncDelay = (await veaOutbox.sequencerDelayLimit()).toNumber() + epochPeriod / 2; + await network.provider.send("evm_increaseTime", [epochPeriod + maxL2StateSyncDelay]); + await network.provider.send("evm_mine"); + + // Validation starts + const startValidationTxn = await veaOutbox.startVerification(epoch, { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 0, + challenger: ethers.constants.AddressZero, + }); + await expect(startValidationTxn).to.emit(veaOutbox, "VerificationStarted").withArgs(epoch); + + const blockStartValidation = await ethers.provider.getBlock(startValidationTxn.blockNumber!); + const minChallengePeriod = (await veaOutbox.minChallengePeriod()).toNumber(); + + await network.provider.send("evm_increaseTime", [minChallengePeriod]); + await network.provider.send("evm_mine"); + const blocksToMine = Math.ceil(minChallengePeriod / 12); + await mine(blocksToMine); + + // Challenger tx starts + const challengeTx = await veaOutbox + .connect(challenger) + ["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + epoch, + { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: ethers.constants.AddressZero, + }, + { value: TEN_ETH } + ); + + // 2nd message at new epoch + const epoch2 = await veaOutbox.epochNow(); + + await network.provider.send("evm_increaseTime", [epochPeriod]); + await network.provider.send("evm_mine"); + + const stateRoot2 = ethers.utils.keccak256(ethers.utils.keccak256(ethers.utils.toUtf8Bytes("stateRoot2"))); + const claimTxn2 = await veaOutbox.connect(bridger).claim(epoch2, stateRoot2, { value: TEN_ETH }); + const claimTxn2Block = await ethers.provider.getBlock(claimTxn2.blockNumber!); + + await network.provider.send("evm_increaseTime", [maxL2StateSyncDelay + epochPeriod]); + await network.provider.send("evm_mine"); + + const startValidationTxn2 = await veaOutbox.startVerification(epoch2, { + stateRoot: stateRoot2, + claimer: bridger.address, + timestampClaimed: claimTxn2Block.timestamp, + timestampVerification: 0, + blocknumberVerification: 0, + honest: 0, + challenger: ethers.constants.AddressZero, + }); + + const blockStartValidation2 = await ethers.provider.getBlock(startValidationTxn2.blockNumber!); + await network.provider.send("evm_increaseTime", [minChallengePeriod]); + await network.provider.send("evm_mine"); + await mine(blocksToMine); + + const verifySnapshotTxn = await veaOutbox.connect(bridger).verifySnapshot(epoch2, { + stateRoot: stateRoot2, + claimer: bridger.address, + timestampClaimed: claimTxn2Block.timestamp, + timestampVerification: blockStartValidation2.timestamp!, + blocknumberVerification: startValidationTxn2.blockNumber!, + honest: 0, + challenger: ethers.constants.AddressZero, + }); + + // Resolve dispute + const sendSafeFallbackTx = await veaInbox.connect(bridger).sendSnapshot( + epoch, + { + stateRoot: fakeHash, + claimer: bridger.address, + timestampClaimed: block.timestamp, + timestampVerification: blockStartValidation.timestamp!, + blocknumberVerification: startValidationTxn.blockNumber!, + honest: 0, + challenger: challenger.address, + }, + { gasLimit: 1000000 } + ); + + // Verify dispute resolution + const latestStateRoot = await veaOutbox.stateRoot(); + expect(latestStateRoot).not.equal(stateRoot1); + expect(latestStateRoot).to.equal(stateRoot2); + + const latestVerifiedEpoch = await veaOutbox.latestVerifiedEpoch(); + expect(latestVerifiedEpoch).to.equal(epoch2); + }); }); }); diff --git a/relayer-cli/.env.dist b/relayer-cli/.env.dist index 8df04ca2..176553cd 100644 --- a/relayer-cli/.env.dist +++ b/relayer-cli/.env.dist @@ -1,8 +1,14 @@ PRIVATE_KEY= +# Devnet Sender Address +DEVNET_SENDER=0x906dE43dBef27639b1688Ac46532a16dc07Ce410 + RPC_CHIADO=https://rpc.chiadochain.net RPC_SEPOLIA= +VEAOUTBOX_CHAIN_ID=1115111 + + VEAINBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS=0x906dE43dBef27639b1688Ac46532a16dc07Ce410 VEAINBOX_ARBSEPOLIA_TO_CHIADO_ADDRESS=0xAb53e341121448Ae259Da8fa17f216Cb0e21199C VEAOUTBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS=0x906dE43dBef27639b1688Ac46532a16dc07Ce410 diff --git a/relayer-cli/package.json b/relayer-cli/package.json index f3b21963..3944cb86 100644 --- a/relayer-cli/package.json +++ b/relayer-cli/package.json @@ -10,7 +10,8 @@ "yarn": "4.2.2" }, "scripts": { - "start-devnet-relayer": "npx ts-node ./src/devnetRelayExample.ts" + "start-devnet-relayer": "npx ts-node ./src/devnetRelayExample.ts", + "start-testnet-relayer": "npx ts-node ./src/testnetRelayer.ts" }, "dependencies": { "@kleros/vea-contracts": "workspace:^", diff --git a/relayer-cli/src/consts/bridgeRoutes.ts b/relayer-cli/src/consts/bridgeRoutes.ts index 0f8cdf10..6efd3ad5 100644 --- a/relayer-cli/src/consts/bridgeRoutes.ts +++ b/relayer-cli/src/consts/bridgeRoutes.ts @@ -1,36 +1,52 @@ // File for handling contants and configurations require("dotenv").config(); +import veaOutboxArbToEthContract from "@kleros/vea-contracts/deployments/sepolia/VeaOutboxArbToEthTestnet.json"; +import veaOutboxArbToGnosisContract from "@kleros/vea-contracts/deployments/chiado/VeaOutboxArbToGnosisTestnet.json"; interface IBridge { chainId: number; - veaInbox: string; - veaOutbox: string; + chain: string; + epochPeriod: number; + veaInboxAddress: string; + veaOutboxAddress: string; batcher: string; rpcOutbox: string; + veaOutboxContract: any; } // Using destination chainId to get the route configuration. const bridges: { [chainId: number]: IBridge } = { 11155111: { chainId: 11155111, - veaInbox: process.env.VEAINBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS, - veaOutbox: process.env.VEAOUTBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS, + chain: "sepolia", + epochPeriod: 7200, + veaInboxAddress: process.env.VEAINBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS, + veaOutboxAddress: process.env.VEAOUTBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS, batcher: process.env.TRANSACTION_BATCHER_CONTRACT_ADDRESS_SEPOLIA, rpcOutbox: process.env.RPC_SEPOLIA, + veaOutboxContract: veaOutboxArbToEthContract, }, 10200: { chainId: 10200, - veaInbox: process.env.VEAINBOX_ARBSEPOLIA_TO_CHIADO_ADDRESS, - veaOutbox: process.env.VEAOUTBOX_ARBSEPOLIA_TO_CHIADO_ADDRESS, + chain: "chiado", + epochPeriod: 3600, + veaInboxAddress: process.env.VEAINBOX_ARBSEPOLIA_TO_CHIADO_ADDRESS, + veaOutboxAddress: process.env.VEAOUTBOX_ARBSEPOLIA_TO_CHIADO_ADDRESS, batcher: process.env.TRANSACTION_BATCHER_CONTRACT_ADDRESS_CHIADO, rpcOutbox: process.env.RPC_CHIADO, + veaOutboxContract: veaOutboxArbToGnosisContract, }, }; +// Getters const getBridgeConfig = (chainId: number): IBridge | undefined => { return bridges[chainId]; }; +const getEpochPeriod = (chainId: number): number => { + return bridges[chainId].epochPeriod; +}; + const getInboxSubgraph = (chainId: number): string => { switch (chainId) { case 11155111: @@ -38,8 +54,8 @@ const getInboxSubgraph = (chainId: number): string => { case 10200: return process.env.VEAINBOX_ARBSEPOLIA_TO_CHIADO_SUBGRAPH; default: - throw new Error("Invalid chainid"); + throw new Error("Invalid chainId"); } }; -export { getBridgeConfig, getInboxSubgraph }; +export { getBridgeConfig, getInboxSubgraph, getEpochPeriod }; diff --git a/relayer-cli/src/devnetRelayExample.ts b/relayer-cli/src/devnetRelayExample.ts index a11292ed..555cc126 100644 --- a/relayer-cli/src/devnetRelayExample.ts +++ b/relayer-cli/src/devnetRelayExample.ts @@ -1,85 +1,26 @@ import { relayAllFrom } from "./utils/relay"; -import * as fs from "fs"; +import { initialize, ShutdownManager, updateStateFile, setupExitHandlers, delay } from "./utils/relayerHelpers"; + +export async function start(shutdownManager: ShutdownManager = new ShutdownManager()) { + const chainId = parseInt(process.env.VEAOUTBOX_CHAIN_ID); + const epochPeriod = 1800; // 30 min + const network = "devnet"; + await setupExitHandlers(chainId, shutdownManager, network); + while (!shutdownManager.getIsShuttingDown()) { + let nonce = await initialize(chainId, network); + // This is libghtbulb switch address in arbitrum sepolia + const sender = process.env.DEVNET_SENDER; + nonce = await relayAllFrom(chainId, nonce, sender); + if (nonce != null) await updateStateFile(chainId, Math.floor(Date.now() / 1000), nonce, network); -// let chain_ids = [5, 10200]; -let chain_ids = [11155111]; -const epochPeriod = 1800; // 30 min -["SIGINT", "SIGTERM", "SIGQUIT", "EXIT", "MODULE_NOT_FOUND"].forEach((signal) => - process.on(signal, async () => { - console.log("exit"); - for (const chain_id of chain_ids) { - const lock_file_name = "./src/state/" + chain_id + ".pid"; - if (fs.existsSync(lock_file_name)) { - fs.unlinkSync(lock_file_name); - } - } - process.exit(0); - }) -); - -(async () => { - while (1) { - for (const chain_id of chain_ids) { - let nonce = await initialize(chain_id); - // This is libghtbulb switch address in arbitrum sepolia - const sender = "0x28d6D503F4c5734cD926E96b63C61527d975B382"; - nonce = await relayAllFrom(chain_id, nonce, sender); - if (nonce != null) await updateStateFile(chain_id, Math.floor(Date.now() / 1000), nonce); - } const currentTS = Math.floor(Date.now() / 1000); const delayAmount = (epochPeriod - (currentTS % epochPeriod)) * 1000 + 100 * 1000; console.log("waiting for the next epoch. . .", Math.floor(delayAmount / 1000), "seconds"); await delay(delayAmount); } -})(); - -async function initialize(chain_id: number): Promise { - if (chain_id !== 11155111) throw new Error("Invalid chainid"); - - const lock_file_name = "./src/state/" + chain_id + ".pid"; - - if (fs.existsSync(lock_file_name)) { - console.log("Skipping chain with process already running, delete pid file to force", chain_id); - throw new Error("Already running"); - } - fs.writeFileSync(lock_file_name, process.pid.toString(), { encoding: "utf8" }); - - // STATE_DIR is absolute path of the directory where the state files are stored - // STATE_DIR must have trailing slash - const state_file = process.env.STATE_DIR + chain_id + ".json"; - if (!fs.existsSync(state_file)) { - // No state file so initialize starting now - const tsnow = Math.floor(Date.now() / 1000); - await updateStateFile(chain_id, tsnow, 0); - } - - // print pwd for debugging - console.log(process.cwd()); - var chain_state = require(state_file); - - let nonce = 0; - if ("nonce" in chain_state) { - nonce = chain_state["nonce"]; - } - - return nonce; -} - -async function updateStateFile(chain_id: number, createdTimestamp: number, nonceFrom: number) { - const chain_state_file = "./src/state/" + chain_id + ".json"; - const json = { - ts: createdTimestamp, - nonce: nonceFrom, - }; - fs.writeFileSync(chain_state_file, JSON.stringify(json), { encoding: "utf8" }); - for (const chain_id of chain_ids) { - const lock_file_name = "./src/state/" + chain_id + ".pid"; - if (fs.existsSync(lock_file_name)) { - fs.unlinkSync(lock_file_name); - } - } } -function delay(ms: number) { - return new Promise((resolve) => setTimeout(resolve, ms)); +if (require.main === module) { + const shutdownManager = new ShutdownManager(false); + start(shutdownManager); } diff --git a/relayer-cli/src/state/11155111.json b/relayer-cli/src/state/11155111.json deleted file mode 100644 index 387cca5d..00000000 --- a/relayer-cli/src/state/11155111.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "ts": 1725086460, - "nonce": "6" -} diff --git a/relayer-cli/src/testnetRelayer.ts b/relayer-cli/src/testnetRelayer.ts new file mode 100644 index 00000000..247345f9 --- /dev/null +++ b/relayer-cli/src/testnetRelayer.ts @@ -0,0 +1,28 @@ +require("dotenv").config(); +import { relayBatch } from "utils/relay"; +import { initialize, updateStateFile, delay, setupExitHandlers, ShutdownManager } from "utils/relayerHelpers"; +import { getEpochPeriod } from "consts/bridgeRoutes"; + +export async function start(shutdownManager: ShutdownManager = new ShutdownManager()) { + const network = "testnet"; + const chainId = parseInt(process.env.VEAOUTBOX_CHAIN_ID); + const epochPeriod = getEpochPeriod(chainId); + const batchSize = 10; // 10 messages per batch + + await setupExitHandlers(chainId, shutdownManager, network); + + while (!shutdownManager.getIsShuttingDown()) { + let nonce = await initialize(chainId, network); + nonce = await relayBatch(chainId, nonce, batchSize); + if (nonce != null) await updateStateFile(chainId, Math.floor(Date.now() / 1000), nonce, network); + const currentTS = Math.floor(Date.now() / 1000); + const delayAmount = (epochPeriod - (currentTS % epochPeriod)) * 1000 + 100 * 1000; + console.log("waiting for the next epoch. . .", Math.floor(delayAmount / 1000), "seconds"); + await delay(delayAmount); + } +} + +if (require.main === module) { + const shutdownManager = new ShutdownManager(false); + start(shutdownManager); +} diff --git a/relayer-cli/src/utils/ethers.ts b/relayer-cli/src/utils/ethers.ts index 4edae0e3..96a9bff4 100644 --- a/relayer-cli/src/utils/ethers.ts +++ b/relayer-cli/src/utils/ethers.ts @@ -3,9 +3,11 @@ import { JsonRpcProvider } from "@ethersproject/providers"; import { VeaOutboxArbToEth__factory, VeaOutboxArbToEthDevnet__factory, - VeaOutboxArbToGnosisDevnet__factory, VeaInboxArbToEth__factory, + VeaInboxArbToGnosis__factory, + VeaOutboxArbToGnosis__factory, } from "@kleros/vea-contracts/typechain-types"; +import { getBridgeConfig } from "consts/bridgeRoutes"; function getWallet(privateKey: string, web3ProviderURL: string) { return new Wallet(privateKey, new JsonRpcProvider(web3ProviderURL)); @@ -15,20 +17,61 @@ function getWalletRPC(privateKey: string, rpc: JsonRpcProvider) { return new Wallet(privateKey, rpc); } -function getVeaInboxArbToEth(veaInboxAddress: string, privateKey: string, web3ProviderURL: string) { - return VeaInboxArbToEth__factory.connect(veaInboxAddress, getWallet(privateKey, web3ProviderURL)); +// Using destination chainId as identifier, Ex: Arbitrum One (42161) -> Ethereum Mainnet (1): Use "1" as chainId +function getVeaInbox(veaInboxAddress: string, privateKey: string, web3ProviderURL: string, chainId: number) { + const bridge = getBridgeConfig(chainId); + switch (bridge.chain) { + case "sepolia": + case "mainnet": + return VeaInboxArbToEth__factory.connect(veaInboxAddress, getWallet(privateKey, web3ProviderURL)); + case "chiado": + case "gnosis": + return VeaInboxArbToGnosis__factory.connect(veaInboxAddress, getWallet(privateKey, web3ProviderURL)); + default: + throw new Error(`Unsupported chainId: ${chainId}`); + } } -function getVeaInboxArbToEthProvider(veaInboxAddress: string, privateKey: string, rpc: JsonRpcProvider) { - return VeaInboxArbToEth__factory.connect(veaInboxAddress, getWalletRPC(privateKey, rpc)); +function getVeaInboxProvider(veaInboxAddress: string, privateKey: string, rpc: JsonRpcProvider, chainId: number) { + const bridges = getBridgeConfig(chainId); + switch (bridges.chain) { + case "sepolia": + case "mainnet": + return VeaInboxArbToEth__factory.connect(veaInboxAddress, getWalletRPC(privateKey, rpc)); + case "chiado": + case "gnosis": + return VeaInboxArbToGnosis__factory.connect(veaInboxAddress, getWalletRPC(privateKey, rpc)); + default: + throw new Error(`Unsupported chainId: ${chainId}`); + } } -function getVeaOutboxArbToEthProvider(veaOutboxAddress: string, privateKey: string, rpc: JsonRpcProvider) { - return VeaOutboxArbToEth__factory.connect(veaOutboxAddress, getWalletRPC(privateKey, rpc)); +function getVeaOutbox(veaOutboxAddress: string, privateKey: string, web3ProviderURL: string, chainId: number) { + const bridge = getBridgeConfig(chainId); + switch (bridge.chain) { + case "sepolia": + case "mainnet": + return VeaOutboxArbToEth__factory.connect(veaInboxAddress, getWallet(privateKey, web3ProviderURL)); + case "chiado": + case "gnosis": + return VeaOutboxArbToGnosis__factory.connect(veaInboxAddress, getWallet(privateKey, web3ProviderURL)); + default: + throw new Error(`Unsupported chainId: ${chainId}`); + } } -function getVeaOutboxArbToEth(veaOutboxAddress: string, privateKey: string, web3ProviderURL: string) { - return VeaOutboxArbToEth__factory.connect(veaOutboxAddress, getWallet(privateKey, web3ProviderURL)); +function getVeaOutboxProvider(veaOutboxAddress: string, privateKey: string, rpc: JsonRpcProvider, chainId: number) { + const bridges = getBridgeConfig(chainId); + switch (bridges.chain) { + case "sepolia": + case "mainnet": + return VeaOutboxArbToEth__factory.connect(veaOutboxAddress, getWalletRPC(privateKey, rpc)); + case "chiado": + case "gnosis": + return VeaOutboxArbToGnosis__factory.connect(veaOutboxAddress, getWalletRPC(privateKey, rpc)); + default: + throw new Error(`Unsupported chainId: ${chainId}`); + } } function getVeaOutboxArbToEthDevnetProvider(veaOutboxAddress: string, privateKey: string, rpc: JsonRpcProvider) { @@ -39,19 +82,12 @@ function getVeaOutboxArbToEthDevnet(veaOutboxAddress: string, privateKey: string return VeaOutboxArbToEthDevnet__factory.connect(veaOutboxAddress, getWallet(privateKey, web3ProviderURL)); } -function getVeaOutboxArbToGnosisProvider(veaOutboxAddress: string, privateKey: string, rpc: JsonRpcProvider) { - return VeaOutboxArbToGnosisDevnet__factory.connect(veaOutboxAddress, getWalletRPC(privateKey, rpc)); -} - -function getVeaOutboxArbToGnosis(veaOutboxAddress: string, privateKey: string, web3ProviderURL: string) { - return VeaOutboxArbToGnosisDevnet__factory.connect(veaOutboxAddress, getWallet(privateKey, web3ProviderURL)); -} - export { - getVeaOutboxArbToEth, getWalletRPC, getVeaOutboxArbToEthDevnetProvider, - getVeaInboxArbToEth, - getVeaInboxArbToEthProvider, - getVeaOutboxArbToEthProvider, + getVeaOutbox, + getVeaInbox, + getVeaOutboxProvider, + getVeaInboxProvider, + getVeaOutboxArbToEthDevnet, }; diff --git a/relayer-cli/src/utils/relay.ts b/relayer-cli/src/utils/relay.ts index 88e54fe6..eb5271c9 100644 --- a/relayer-cli/src/utils/relay.ts +++ b/relayer-cli/src/utils/relay.ts @@ -1,18 +1,14 @@ -import { getProofAtCount, getMessageDataToRelay } from "./proof"; -import { getVeaOutboxArbToEth } from "./ethers"; +require("dotenv").config(); +import Web3 from "web3"; +import initializeBatchedSend from "web3-batched-send"; import request from "graphql-request"; -import { VeaOutboxArbToEth } from "@kleros/vea-contracts/typechain-types"; +import { VeaOutboxArbToEth, VeaOutboxArbToGnosis } from "@kleros/vea-contracts/typechain-types"; +import { getProofAtCount, getMessageDataToRelay } from "./proof"; +import { getVeaOutbox } from "./ethers"; import { getBridgeConfig, getInboxSubgraph } from "../consts/bridgeRoutes"; -const fs = require("fs"); - -require("dotenv").config(); -const Web3 = require("web3"); -const _batchedSend = require("web3-batched-send"); -const _contract = require("@kleros/vea-contracts/deployments/sepolia/VeaOutboxArbToEthDevnet.json"); - -const getCount = async (veaOutbox: VeaOutboxArbToEth, chainid: number): Promise => { - const subgraph = getInboxSubgraph(chainid); +const getCount = async (veaOutbox: VeaOutboxArbToEth | VeaOutboxArbToGnosis, chainId: number): Promise => { + const subgraph = getInboxSubgraph(chainId); const stateRoot = await veaOutbox.stateRoot(); const result = await request( @@ -29,49 +25,61 @@ const getCount = async (veaOutbox: VeaOutboxArbToEth, chainid: number): Promise< return Number(result["snapshotSaveds"][0].count); }; -const relay = async (chainid: number, nonce: number) => { - const routeParams = getBridgeConfig(chainid); - - const veaOutbox = getVeaOutboxArbToEth(routeParams.veaOutbox, process.env.PRIVATE_KEY, routeParams.rpcOutbox); - const count = await getCount(veaOutbox, chainid); +const relay = async (chainId: number, nonce: number) => { + const routeParams = getBridgeConfig(chainId); + const veaOutbox = getVeaOutbox(routeParams.veaOutboxAddress, process.env.PRIVATE_KEY, routeParams.rpcOutbox, chainId); + const count = await getCount(veaOutbox, chainId); - const proof = await getProofAtCount(chainid, nonce, count); - const [to, data] = await getMessageDataToRelay(chainid, nonce); + const [proof, [to, data]] = await Promise.all([ + getProofAtCount(chainId, nonce, count), + getMessageDataToRelay(chainId, nonce), + ]); const txn = await veaOutbox.sendMessage(proof, nonce, to, data); await txn.wait(); }; -const relayBatch = async (chainid: number, nonce: number, iterations: number) => { - const routeParams = getBridgeConfig(chainid); - +const relayBatch = async (chainId: number, nonce: number, maxBatchSize: number) => { + const routeParams = getBridgeConfig(chainId); const web3 = new Web3(routeParams.rpcOutbox); - const batchedSend = _batchedSend(web3, routeParams.rpcOutbox, process.env.PRIVATE_KEY, 0); - - const contract = new web3.eth.Contract(_contract.abi, routeParams.veaOutbox); - const veaOutbox = getVeaOutboxArbToEth(routeParams.veaOutbox, process.env.PRIVATE_KEY, routeParams.rpcOutbox); - const count = await getCount(veaOutbox, chainid); - - let txns = []; - - for (let i = 0; i < iterations; i++) { - const proof = await getProofAtCount(chainid, nonce + i, count); - const [to, data] = await getMessageDataToRelay(chainid, nonce + i); - txns.push({ - args: [proof, nonce + i, to, data], - method: contract.methods.sendMessage, - to: contract.options.address, - }); + const batchedSend = initializeBatchedSend(web3, routeParams.batcher, process.env.PRIVATE_KEY, 0); + const veaOutboxInstance = new web3.eth.Contract(routeParams.veaOutboxContract.abi, routeParams.veaOutboxAddress); + const veaOutbox = getVeaOutbox(routeParams.veaOutboxAddress, process.env.PRIVATE_KEY, routeParams.rpcOutbox, chainId); + const count = await getCount(veaOutbox, chainId); + + while (nonce < count) { + let batchMessages = 0; + let txns = []; + while (batchMessages < maxBatchSize && nonce < count) { + const isMsgRelayed = await veaOutbox.isMsgRelayed(nonce); + if (isMsgRelayed) { + nonce++; + continue; + } + const [proof, [to, data]] = await Promise.all([ + getProofAtCount(chainId, nonce, count), + getMessageDataToRelay(chainId, nonce), + ]); + txns.push({ + args: [proof, nonce, to, data], + method: veaOutboxInstance.methods.sendMessage, + to: veaOutboxInstance.options.address, + }); + batchMessages += 1; + nonce++; + } + if (batchMessages > 0) { + await batchedSend(txns); + } } - - await batchedSend(txns); + return nonce; }; -const relayAllFrom = async (chainid: number, nonce: number, msgSender: string): Promise => { - const routeParams = getBridgeConfig(chainid); +const relayAllFrom = async (chainId: number, nonce: number, msgSender: string): Promise => { + const routeParams = getBridgeConfig(chainId); const web3 = new Web3(routeParams.rpcOutbox); - const batchedSend = _batchedSend( + const batchedSend = initializeBatchedSend( web3, // Your web3 object. // The address of the transaction batcher contract you wish to use. The addresses for the different networks are listed below. If the one you need is missing, feel free to deploy it yourself and make a PR to save the address here for others to use. routeParams.batcher, @@ -79,19 +87,21 @@ const relayAllFrom = async (chainid: number, nonce: number, msgSender: string): 0 // The debounce timeout period in milliseconds in which transactions are batched. ); - const contract = new web3.eth.Contract(_contract.abi, routeParams.veaOutbox); - const veaOutbox = getVeaOutboxArbToEth(routeParams.veaOutbox, process.env.PRIVATE_KEY, routeParams.rpcOutbox); - const count = await getCount(veaOutbox, chainid); + const contract = new web3.eth.Contract(routeParams.veaOutboxContract.abi, routeParams.veaOutboxAddress); + const veaOutbox = getVeaOutbox(routeParams.veaOutboxAddress, process.env.PRIVATE_KEY, routeParams.rpcOutbox, chainId); + const count = await getCount(veaOutbox, chainId); if (!count) return null; let txns = []; - const nonces = await getNonceFrom(chainid, nonce, msgSender); + const nonces = await getNonceFrom(chainId, nonce, msgSender); for (const x of nonces) { - const proof = await getProofAtCount(chainid, x, count); - const [to, data] = await getMessageDataToRelay(chainid, x); + const [proof, [to, data]] = await Promise.all([ + getProofAtCount(chainId, x, count), + getMessageDataToRelay(chainId, x), + ]); txns.push({ args: [proof, x, to, data], method: contract.methods.sendMessage, @@ -104,9 +114,9 @@ const relayAllFrom = async (chainid: number, nonce: number, msgSender: string): return nonces[nonces.length - 1]; }; -const getNonceFrom = async (chainid: number, nonce: number, msgSender: string) => { +const getNonceFrom = async (chainId: number, nonce: number, msgSender: string) => { try { - const subgraph = getInboxSubgraph(chainid); + const subgraph = getInboxSubgraph(chainId); const result = await request( `https://api.studio.thegraph.com/query/${subgraph}`, diff --git a/relayer-cli/src/utils/relayerHelpers.ts b/relayer-cli/src/utils/relayerHelpers.ts new file mode 100644 index 00000000..7499c1a2 --- /dev/null +++ b/relayer-cli/src/utils/relayerHelpers.ts @@ -0,0 +1,103 @@ +import * as fs from "fs"; + +class ShutdownManager { + private isShuttingDown: boolean; + + constructor(initialState: boolean = false) { + this.isShuttingDown = initialState; + } + + public getIsShuttingDown(): boolean { + return this.isShuttingDown; + } + + public triggerShutdown() { + this.isShuttingDown = true; + } +} + +async function initialize(chainId: number, network: string): Promise { + const lockFileName = "./state/" + network + "_" + chainId + ".pid"; + + if (fs.existsSync(lockFileName)) { + console.log("Skipping chain with process already running, delete pid file to force", chainId); + throw new Error("Already running"); + } + fs.writeFileSync(lockFileName, process.pid.toString(), { encoding: "utf8" }); + + // STATE_DIR is absolute path of the directory where the state files are stored + // STATE_DIR must have trailing slash + const state_file = process.env.STATE_DIR + network + "_" + chainId + ".json"; + if (!fs.existsSync(state_file)) { + // No state file so initialize starting now + const tsnow = Math.floor(Date.now() / 1000); + await updateStateFile(chainId, tsnow, 0, network); + } + + // print pwd for debugging + console.log(process.cwd()); + const chain_state_raw = fs.readFileSync(state_file, { encoding: "utf8" }); + const chain_state = JSON.parse(chain_state_raw); + let nonce = 0; + if ("nonce" in chain_state) { + nonce = chain_state["nonce"]; + } + + return nonce; +} + +async function updateStateFile(chainId: number, createdTimestamp: number, nonceFrom: number, network: string) { + const chain_state_file = "./state/" + network + "_" + chainId + ".json"; + const json = { + ts: createdTimestamp, + nonce: nonceFrom, + }; + fs.writeFileSync(chain_state_file, JSON.stringify(json), { encoding: "utf8" }); + + const lockFileName = "./state/" + network + "_" + chainId + ".pid"; + if (fs.existsSync(lockFileName)) { + fs.unlinkSync(lockFileName); + } +} + +async function setupExitHandlers(chainId: number, shutdownManager: ShutdownManager, network: string) { + const cleanup = async () => { + console.log("exit"); + const lockFileName = "./state/" + network + "_" + chainId + ".pid"; + if (fs.existsSync(lockFileName)) { + await fs.promises.unlink(lockFileName); + } + }; + const handleExit = async (exitCode: number = 0) => { + shutdownManager.triggerShutdown(); + await cleanup(); + process.exit(0); + }; + + ["SIGINT", "SIGTERM", "SIGQUIT"].forEach((signal) => + process.on(signal, async () => { + await handleExit(0); + process.exit(0); + }) + ); + + process.on("exit", async () => { + await handleExit(); + }); + + process.on("uncaughtException", async (err) => { + console.error("Uncaught exception:", err); + await handleExit(1); + }); + + process.on("unhandledRejection", async (reason, promise) => { + console.error("Unhandled promise rejection:", reason, "at", promise); + await handleExit(1); + }); +} + +function delay(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export { initialize, updateStateFile, setupExitHandlers, delay, ShutdownManager }; diff --git a/validator-cli/.env.dist b/validator-cli/.env.dist index d648f585..1f909c5b 100644 --- a/validator-cli/.env.dist +++ b/validator-cli/.env.dist @@ -1,9 +1,20 @@ PRIVATE_KEY= +# Devnet RPCs RPC_CHIADO=https://rpc.chiadochain.net RPC_ARB_SEPOLIA=https://sepolia-rollup.arbitrum.io/rpc RPC_SEPOLIA= +# Testnet or Mainnet RPCs +RPC_ARB= +RPC_ETH= + +# Testnet or Mainnet Addresses +VEAINBOX_ARB_TO_ETH_ADDRESS=0xE12daFE59Bc3A996362d54b37DFd2BA9279cAd06 +VEAOUTBOX_ARB_TO_ETH_ADDRESS=0x209BFdC6B7c66b63A8382196Ba3d06619d0F12c9 + + +# Devnet Addresses VEAINBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS=0x906dE43dBef27639b1688Ac46532a16dc07Ce410 VEAINBOX_ARBSEPOLIA_TO_CHIADO_ADDRESS=0xAb53e341121448Ae259Da8fa17f216Cb0e21199C VEAOUTBOX_ARBSEPOLIA_TO_SEPOLIA_ADDRESS=0x906dE43dBef27639b1688Ac46532a16dc07Ce410 diff --git a/validator-cli/package.json b/validator-cli/package.json index cfbf24ef..664e339a 100644 --- a/validator-cli/package.json +++ b/validator-cli/package.json @@ -12,10 +12,11 @@ "scripts": { "start": "npx ts-node ./src/ArbToEth/watcher.ts", "start-chiado-devnet": "npx ts-node ./src/devnet/arbToChiado/happyPath.ts", - "start-sepolia-devnet": "npx ts-node ./src/devnet/arbToSepolia/happyPath.ts" + "start-sepolia-devnet": "npx ts-node ./src/devnet/arbToSepolia/happyPath.ts", + "start-sepolia-testnet": "npx ts-node ./src/ArbToEth/watcherArbToEth.ts" }, "dependencies": { - "@arbitrum/sdk": "^3.1.2", + "@arbitrum/sdk": "4.0.1", "@flashbots/ethers-provider-bundle": "^0.6.2", "@kleros/vea-contracts": "workspace:^", "@typechain/ethers-v5": "^10.2.0", diff --git a/validator-cli/src/ArbToEth/watcherArbToEth.ts b/validator-cli/src/ArbToEth/watcherArbToEth.ts index 2cba6e2f..81175eee 100644 --- a/validator-cli/src/ArbToEth/watcherArbToEth.ts +++ b/validator-cli/src/ArbToEth/watcherArbToEth.ts @@ -1,12 +1,14 @@ -import { getVeaOutboxArbToEthProvider, getVeaInboxArbToEthProvider } from "../utils/ethers"; +import { getVeaOutboxArbToEth, getVeaInboxArbToEth } from "../utils/ethers"; import { JsonRpcProvider } from "@ethersproject/providers"; -import { getL2Network } from "@arbitrum/sdk"; +import { getArbitrumNetwork } from "@arbitrum/sdk"; import { NODE_INTERFACE_ADDRESS } from "@arbitrum/sdk/dist/lib/dataEntities/constants"; import { NodeInterface__factory } from "@arbitrum/sdk/dist/lib/abi/factories/NodeInterface__factory"; import { SequencerInbox__factory } from "@arbitrum/sdk/dist/lib/abi/factories/SequencerInbox__factory"; -import { BigNumber, ContractTransaction } from "ethers"; +import { BigNumber, ContractTransaction, ethers } from "ethers"; import { Block, Log, TransactionReceipt } from "@ethersproject/abstract-provider"; import { SequencerInbox } from "@arbitrum/sdk/dist/lib/abi/SequencerInbox"; +import { NodeInterface } from "@arbitrum/sdk/dist/lib/abi/NodeInterface"; +import { getMessageStatus, messageExecutor } from "../utils/arbMsgExecutor"; require("dotenv").config(); @@ -14,25 +16,32 @@ require("dotenv").config(); const slotsPerEpochEth = 32; const secondsPerSlotEth = 12; +// This script monitors claims made on VeaOutbox and initiates challenges if required. +// The core flow includes: +// 1. `challenge(veaOutbox)`: Check claims and challenge if necassary. +// 2. `sendSnapshot(veaInbox)`: Send the snapshot from veaInbox for a challenged epoch. +// 3. `resolveDisputeClaim(arbitrumBridge)`: Execute the sent snapshot to resolve the dispute. +// 4. `withdrawChallengeDeposit(veaOutbox)`: Withdraw the deposit if the challenge is successful. + const watch = async () => { // connect to RPCs const providerEth = new JsonRpcProvider(process.env.RPC_ETH); const providerArb = new JsonRpcProvider(process.env.RPC_ARB); // use typechain generated contract factories for vea outbox and inbox - const veaOutbox = getVeaOutboxArbToEthProvider( + const veaOutbox = getVeaOutboxArbToEth( process.env.VEAOUTBOX_ARB_TO_ETH_ADDRESS, process.env.PRIVATE_KEY, - providerEth + process.env.RPC_ETH ); - const veaInbox = getVeaInboxArbToEthProvider( + const veaInbox = getVeaInboxArbToEth( process.env.VEAINBOX_ARB_TO_ETH_ADDRESS, process.env.PRIVATE_KEY, - providerEth + process.env.RPC_ARB ); // get Arb sequencer params - const l2Network = await getL2Network(providerArb); + const l2Network = await getArbitrumNetwork(providerArb); const sequencer = SequencerInbox__factory.connect(l2Network.ethBridge.sequencerInbox, providerEth); const maxDelaySeconds = ( (await retryOperation(() => sequencer.maxTimeVariation(), 1000, 10))[1] as BigNumber @@ -71,7 +80,6 @@ const watch = async () => { const veaEpochOutboxCheckClaimsRangeArray: number[] = new Array(veaEpochOutboxRange) .fill(veaEpochOutboxWatchLowerBound) .map((el, i) => el + i); - const challengeTxnHashes = new Map(); console.log( "cold start: checking past claim history from epoch " + @@ -80,6 +88,8 @@ const watch = async () => { veaEpochOutboxCheckClaimsRangeArray[veaEpochOutboxCheckClaimsRangeArray.length - 1] ); + const challengeTxnHashes = new Map(); + while (true) { // returns the most recent finalized arbBlock found on Ethereum and info about finality issues on Eth. // if L1 is experiencing finalization problems, returns the latest arbBlock found in the latest L1 block @@ -108,14 +118,16 @@ const watch = async () => { // the latest epoch that is finalized from the L2 POV // this depends on the L2 clock const veaEpochInboxFinalized = Math.floor(l2Time / epochPeriod) - 1; - const veaEpochOutboxClaimableNowOld = veaEpochOutboxClaimableNow; veaEpochOutboxClaimableNow = Math.floor(timeEth / epochPeriod) - 1; - const veaEpochsOutboxClaimableNew: number[] = new Array(veaEpochOutboxClaimableNow - veaEpochOutboxClaimableNowOld) - .fill(veaEpochOutboxClaimableNowOld + 1) - .map((el, i) => el + i); - - veaEpochOutboxCheckClaimsRangeArray.concat(veaEpochsOutboxClaimableNew); + if (veaEpochOutboxClaimableNow > veaEpochOutboxClaimableNowOld) { + const veaEpochsOutboxClaimableNew: number[] = new Array( + veaEpochOutboxClaimableNow - veaEpochOutboxClaimableNowOld + ) + .fill(veaEpochOutboxClaimableNowOld + 1) + .map((el, i) => el + i); + veaEpochOutboxCheckClaimsRangeArray.push(...veaEpochsOutboxClaimableNew); + } if (veaEpochOutboxCheckClaimsRangeArray.length == 0) { console.log("no claims to check"); @@ -125,8 +137,10 @@ const watch = async () => { } for (let index = 0; index < veaEpochOutboxCheckClaimsRangeArray.length; index++) { + console.log("Checking claim for epoch " + veaEpochOutboxCheckClaimsRangeArray[index]); + const challenge = challengeTxnHashes.get(index); const veaEpochOutboxCheck = veaEpochOutboxCheckClaimsRangeArray[index]; - console.log("checking claim for epoch " + veaEpochOutboxCheck); + // if L1 experiences finality failure, we use the latest block const blockTagEth = finalityIssueFlagEth ? "latest" : "finalized"; const claimHash = (await retryOperation( @@ -156,9 +170,6 @@ const watch = async () => { } } else { // claim exists - - console.log("claim exists for epoch " + veaEpochOutboxCheck); - let blockNumberOutboxLowerBound: number; // to query event performantly, we limit the block range with the heuristic that. delta blocknumber <= delta timestamp / secondsPerSlot @@ -184,7 +195,6 @@ const watch = async () => { 10 ) )[0] as Log; - // check the snapshot on the inbox on Arbitrum // only check the state from L1 POV, don't trust the sequencer feed. // arbBlock is a recent (finalized or latest if there are finality problems) block found posted on L1 @@ -196,19 +206,32 @@ const watch = async () => { // claim differs from snapshot if (logClaimed.data != claimSnapshot) { - console.log("claimed merkle root mismatch for epoch " + veaEpochOutboxCheck); + console.log("!! Claimed merkle root mismatch for epoch " + veaEpochOutboxCheck); // if Eth is finalizing but sequencer is malfunctioning, we can wait until the snapshot is considered finalized (L2 time is in the next epoch) if (!finalityIssueFlagEth && veaEpochInboxFinalized < veaEpochOutboxCheck) { // note as long as L1 does not have finalization probelms, sequencer could still be malfunctioning console.log("L2 snapshot is not yet finalized, waiting for finalization to determine challengable status"); } else { - console.log("claim " + veaEpochOutboxCheck + " is challengable"); - const timestampClaimed = ( (await retryOperation(() => providerEth.getBlock(logClaimed.blockNumber), 1000, 10)) as Block ).timestamp; + /* + + we want to constrcut the struct below from events, since only the hash is stored onchain + + struct Claim { + bytes32 stateRoot; + address claimer; + uint32 timestampClaimed; + uint32 timestampVerification; + uint32 blocknumberVerification; + Party honest; + address challenger; + } + + */ var claim = { stateRoot: logClaimed.data, claimer: "0x" + logClaimed.topics[1].substring(26), @@ -219,116 +242,202 @@ const watch = async () => { challenger: "0x0000000000000000000000000000000000000000", }; - const claimHashCalculated = (await retryOperation( - () => veaOutbox.hashClaim(claim, { blockTag: blockTagEth }), + // check if the claim is in verification or verified + const logVerficiationStarted = (await retryOperation( + () => + providerEth.getLogs({ + address: process.env.VEAOUTBOX_ARB_TO_ETH_ADDRESS, + topics: veaOutbox.filters.VerificationStarted(veaEpochOutboxCheck).topics, + fromBlock: blockNumberOutboxLowerBound, + toBlock: blockTagEth, + }), 1000, 10 - )) as string; - if (claimHashCalculated != claimHash) { - // either claim is already challenged - // or claim is in verification or verified + )) as Log[]; - /* - - we want to reconstruct the struct below from events, since only the hash is stored onchain + if (logVerficiationStarted.length > 0) { + const timestampVerification = ( + (await retryOperation( + () => providerEth.getBlock(logVerficiationStarted[logVerficiationStarted.length - 1].blockNumber), + 1000, + 10 + )) as Block + ).timestamp; + + // Update the claim struct with verification details + claim.timestampVerification = timestampVerification; + claim.blocknumberVerification = logVerficiationStarted[logVerficiationStarted.length - 1].blockNumber; + + const claimHashCalculated = hashClaim(claim); + + // The hash should match if there is no challenge made and no honest party yet + if (claimHashCalculated != claimHash) { + // Either challenge is made or honest party is set with or without a challenge + claim.honest = 1; + const claimerHonestHash = hashClaim(claim); + if (claimerHonestHash == claimHash) { + console.log("Claim is honest for epoch " + veaEpochOutboxCheck); + // As the claim is honest, remove the epoch from the local array + veaEpochOutboxCheckClaimsRangeArray.splice(index, 1); + challengeTxnHashes.delete(index); + continue; + } + // The claim is challenged and anyone can be the honest party + } + } - struct Claim { - bytes32 stateRoot; - address claimer; - uint32 timestampClaimed; - uint32 timestampVerification; - uint32 blocknumberVerification; - Party honest; - address challenger; + const logChallenges = (await retryOperation( + () => + providerEth.getLogs({ + address: process.env.VEAOUTBOX_ARB_TO_ETH_ADDRESS, + topics: veaOutbox.filters.Challenged(veaEpochOutboxCheck, null).topics, + fromBlock: blockNumberOutboxLowerBound, + toBlock: blockTagEth, + }), + 1000, + 10 + )) as Log[]; + + // if not challenged, keep checking all claim struct variables + if (logChallenges.length == 0 && challengeTxnHashes[index] == undefined) { + console.log("Claim is challengeable for epoch " + veaEpochOutboxCheck); + } else if (logChallenges.length > 0) { + // Claim is challenged, we check if the snapShot is sent and if the dispute is resolved + console.log("Claim is already challenged for epoch " + veaEpochOutboxCheck); + claim.challenger = "0x" + logChallenges[0].topics[2].substring(26); + + // if claim hash with challenger as winner matches the claimHash, then the challenge is over and challenger won + const challengerWinClaim = { ...claim }; + challengerWinClaim.honest = 2; // challenger wins + + const claimerWinClaim = { ...claim }; + claimerWinClaim.honest = 1; // claimer wins + if (hashClaim(challengerWinClaim) == claimHash) { + // The challenge is over and challenger won + console.log("Challenger won the challenge for epoch " + veaEpochOutboxCheck); + const withdrawChlngDepositTxn = (await retryOperation( + () => veaOutbox.withdrawChallengeDeposit(veaEpochOutboxCheck, challengerWinClaim), + 1000, + 10 + )) as ContractTransaction; + console.log( + "Deposit withdrawn by challenger for " + + veaEpochOutboxCheck + + " with txn hash " + + withdrawChlngDepositTxn.hash + ); + // As the challenge is over, remove the epoch from the local array + veaEpochOutboxCheckClaimsRangeArray.splice(index, 1); + challengeTxnHashes.delete(index); + continue; + } else if (hashClaim(claimerWinClaim) == claimHash) { + // The challenge is over and claimer won + console.log("Claimer won the challenge for epoch " + veaEpochOutboxCheck); + veaEpochOutboxCheckClaimsRangeArray.splice(index, 1); + challengeTxnHashes.delete(index); + continue; } - - */ - const logChallenges = (await retryOperation( - () => - providerEth.getLogs({ - address: process.env.VEAOUTBOX_ARB_TO_ETH_ADDRESS, - topics: veaOutbox.filters.Challenged(veaEpochOutboxCheck, null).topics, - fromBlock: blockNumberOutboxLowerBound, - toBlock: blockTagEth, - }), - 1000, - 10 - )) as Log[]; - // if already challenged, no action needed + // Claim is challenged, no honest party yet + if (logChallenges[0].blockNumber < blockFinalizedEth.number) { + // Send the "stateRoot" snapshot from Arbitrum to the Eth inbox if not sent already + const claimTimestamp = veaEpochOutboxCheckClaimsRangeArray[index] * epochPeriod; - // if not challenged, keep checking all claim struct variables - if (logChallenges.length == 0) { - const logVerficiationStarted = (await retryOperation( + let blockLatestArb = (await retryOperation(() => providerArb.getBlock("latest"), 1000, 10)) as Block; + let blockoldArb = (await retryOperation( + () => providerArb.getBlock(blockLatestArb.number - 100), + 1000, + 10 + )) as Block; + + const arbAverageBlockTime = (blockLatestArb.timestamp - blockoldArb.timestamp) / 100; + + const fromClaimEpochBlock = Math.ceil( + blockLatestArb.number - (blockLatestArb.timestamp - claimTimestamp) / arbAverageBlockTime + ); + const sendSnapshotLogs = (await retryOperation( () => - providerEth.getLogs({ - address: process.env.VEAOUTBOX_ARB_TO_ETH_ADDRESS, - topics: veaOutbox.filters.VerificationStarted(veaEpochOutboxCheck).topics, - fromBlock: blockNumberOutboxLowerBound, + providerArb.getLogs({ + address: process.env.VEAINBOX_ARB_TO_ETH_ADDRESS, + topics: veaInbox.filters.SnapshotSent(veaEpochOutboxCheck, null).topics, + fromBlock: fromClaimEpochBlock, toBlock: blockTagEth, }), 1000, 10 )) as Log[]; - - if (logVerficiationStarted.length > 1) { - const timestampVerification = ( - (await retryOperation( - () => providerEth.getBlock(logVerficiationStarted[logVerficiationStarted.length - 1].blockNumber), + if (sendSnapshotLogs.length == 0) { + // No snapshot sent so, send snapshot + try { + const gasEstimate = (await retryOperation( + () => + veaInbox.estimateGas[ + "sendSnapshot(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))" + ](veaEpochOutboxCheck, claim), 1000, 10 - )) as Block - ).timestamp; - - claim.timestampVerification = timestampVerification; - claim.blocknumberVerification = logVerficiationStarted[logVerficiationStarted.length - 1].blockNumber; - - const claimHashCalculated = (await retryOperation( - () => veaOutbox.hashClaim(claim), - 1000, - 10 - )) as string; - if (claimHashCalculated != claimHash) { - claim.honest = 1; - const claimHashCalculated = (await retryOperation( - () => veaOutbox.hashClaim(claim), + )) as BigNumber; + + const txnSendSnapshot = (await retryOperation( + () => + veaInbox["sendSnapshot(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + veaEpochOutboxCheck, + claim, // the claim struct has to be updated with the correct challenger + { + gasLimit: gasEstimate, + } + ), 1000, 10 - )) as string; - if (claimHashCalculated != claimHash) { - console.error( - "Invalid claim hash calculated for epoch " + - veaEpochOutboxCheck + - " claim " + - claimHashCalculated + - " expected " + - claimHash - ); - continue; + )) as ContractTransaction; + console.log( + "Snapshot message sent for epoch " + + veaEpochOutboxCheck + + " with txn hash " + + txnSendSnapshot.hash + ); + } catch (error) { + console.error("Error sending snapshot for epoch " + veaEpochOutboxCheck + " with error " + error); + } + } else { + // snapshot already sent, check if the snapshot can be relayed to veaOutbox + console.log("Snapshot already sent for epoch " + veaEpochOutboxCheck); + const msgStatus = await getMessageStatus( + sendSnapshotLogs[0].transactionHash, + process.env.RPC_ARB, + process.env.RPC_ETH + ); + if (msgStatus === 1) { + // msg waiting for execution + const msgExecuteTrnx = await messageExecutor( + sendSnapshotLogs[0].transactionHash, + process.env.RPC_ARB, + process.env.RPC_ETH + ); + if (msgExecuteTrnx) { + // msg executed successfully + console.log("Snapshot message relayed to veaOutbox for epoch " + veaEpochOutboxCheck); + veaEpochOutboxCheckClaimsRangeArray.splice(index, 1); + challengeTxnHashes.delete(index); + } else { + // msg failed to execute + console.error("Error sending snapshot to veaOutbox for epoch " + veaEpochOutboxCheck); } } } - } else { - console.log("claim " + veaEpochOutboxCheck + " is already challenged"); - if (logChallenges[0].blockNumber < blockFinalizedEth.number) { - veaEpochOutboxCheckClaimsRangeArray.splice(index, 1); - index--; - // the challenge is finalized, no further action needed - console.log("challenge is finalized"); - continue; - } continue; } + continue; } - if (challengeTxnHashes[index] != "") { + if (challengeTxnHashes[index] != undefined) { const txnReceipt = (await retryOperation( () => providerEth.getTransactionReceipt(challengeTxnHashes[index]), 10, 1000 )) as TransactionReceipt; if (!txnReceipt) { - console.log("challenge txn " + challengeTxnHashes[index] + " not mined yet"); + console.log("challenge txn " + challenge[index] + " not mined yet"); continue; } const blockNumber = txnReceipt.blockNumber; @@ -336,12 +445,12 @@ const watch = async () => { if (challengeBlock.number < blockFinalizedEth.number) { veaEpochOutboxCheckClaimsRangeArray.splice(index, 1); index--; + challengeTxnHashes.delete(index); // the challenge is finalized, no further action needed console.log("challenge is finalized"); continue; } } - const gasEstimate = (await retryOperation( () => veaOutbox.estimateGas["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( @@ -353,34 +462,44 @@ const watch = async () => { 10 )) as BigNumber; - // deposit / 2 is the profit for challengers - // the initial challenge txn is roughly 1/3 of the cost of completing the challenge process. - const maxFeePerGasProfitable = deposit.div(gasEstimate.mul(3 * 2)); + // Adjust the calculation to ensure maxFeePerGas is reasonable + const maxFeePerGasProfitable = deposit.div(gasEstimate.mul(6)); - // priority fee must be higher than MEV to be competitive - // https://boost-relay.flashbots.net/?order_by=-value - // eg there's never been > 100 eth in MEV in a block - // so 100 eth / 15000000 gas per block = 6667 gwei per gas is competitive - // Set this more modestly if you want to be more conservative - const maxPriorityFeePerGasMEV = BigNumber.from("6667000000000"); // 6667 gwei + // Set a reasonable maxPriorityFeePerGas but ensure it's lower than maxFeePerGas + let maxPriorityFeePerGasMEV = BigNumber.from("6667000000000"); // 6667 gwei + console.log("Transaction Challenge Gas Estimate", gasEstimate.toString()); - const txnChallenge = (await retryOperation( - () => - veaOutbox["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( - veaEpochOutboxCheck, - claim, - { - maxFeePerGas: maxFeePerGasProfitable, - maxPriorityFeePerGas: maxPriorityFeePerGasMEV, - value: deposit, - } - ), - 1000, - 10 - )) as ContractTransaction; - - txnChallenge.nonce; - console.log("challenging claim for epoch " + veaEpochOutboxCheck + " with txn hash " + txnChallenge.hash); + // Ensure maxPriorityFeePerGas <= maxFeePerGas + if (maxPriorityFeePerGasMEV.gt(maxFeePerGasProfitable)) { + console.warn( + "maxPriorityFeePerGas is higher than maxFeePerGasProfitable, adjusting maxPriorityFeePerGas" + ); + maxPriorityFeePerGasMEV = maxFeePerGasProfitable; // adjust to be equal or less + } + try { + const txnChallenge = (await retryOperation( + () => + veaOutbox["challenge(uint256,(bytes32,address,uint32,uint32,uint32,uint8,address))"]( + veaEpochOutboxCheck, + claim, + { + maxFeePerGas: maxFeePerGasProfitable, + maxPriorityFeePerGas: maxPriorityFeePerGasMEV, + value: deposit, + gasLimit: gasEstimate, + } + ), + 1000, + 10 + )) as ContractTransaction; + // Make wait for receipt and check if the challenge is finalized + console.log("Transaction Challenge Hash", txnChallenge.hash); + // Update local var with the challenge txn hash + challengeTxnHashes.set(index, txnChallenge.hash); + console.log("challenging claim for epoch " + veaEpochOutboxCheck + " with txn hash " + txnChallenge.hash); + } catch (error) { + console.error("Error challenging claim for epoch " + veaEpochOutboxCheck + " with error " + error); + } } } else { console.log("claim hash matches snapshot for epoch " + veaEpochOutboxCheck); @@ -439,6 +558,10 @@ const getBlocksAndCheckFinality = async ( // check latest arb block to see if there are any sequencer issues let blockLatestArb = (await retryOperation(() => ArbProvider.getBlock("latest"), 1000, 10)) as Block; + const maxDelayInSeconds = 7 * 24 * 60 * 60; // 7 days + let blockoldArb = (await retryOperation(() => ArbProvider.getBlock(blockLatestArb.number - 100), 1000, 10)) as Block; + const arbAverageBlockTime = (blockLatestArb.timestamp - blockoldArb.timestamp) / 100; + const fromBlockArbFinalized = blockFinalizedArb.number - Math.ceil(maxDelayInSeconds / arbAverageBlockTime); // to performantly query the sequencerInbox's SequencerBatchDelivered event on Eth, we limit the block range // we use the heuristic that. delta blocknumber <= delta timestamp / secondsPerSlot // Arb: -----------x <-- Finalized @@ -458,6 +581,7 @@ const getBlocksAndCheckFinality = async ( sequencer, blockFinalizedArb, fromBlockEthFinalized, + fromBlockArbFinalized, false ); @@ -475,6 +599,7 @@ const getBlocksAndCheckFinality = async ( sequencer, blockLatestArb, fromBlockEthFinalized, + fromBlockArbFinalized, true ); @@ -534,6 +659,7 @@ const ArbBlockToL1Block = async ( sequencer: SequencerInbox, L2Block: Block, fromBlockEth: number, + fromArbBlock: number, fallbackLatest: boolean ): Promise<[Block, number] | undefined> => { const nodeInterface = NodeInterface__factory.connect(NODE_INTERFACE_ADDRESS, L2Provider); @@ -543,19 +669,21 @@ const ArbBlockToL1Block = async ( let result = (await nodeInterface.functions .findBatchContainingBlock(L2Block.number, { blockTag: "latest" }) .catch((e) => { - // if L2 block is ahead of latest L2 batch on L1, we get an error - // catch the error and parse it to get the latest L2 batch on L1 - - // https://github.com/OffchainLabs/nitro/blob/af87ba29bc34c27bd4d85b3066a1cc3a759bab66/nodeInterface/NodeInterface.go#L544 - const errMsg = JSON.parse(JSON.parse(JSON.stringify(e)).error.body).error.message; - console.error(errMsg); - if (fallbackLatest) { - latestL2batchOnEth = parseInt(errMsg.split(" published in batch ")[1]); - latestL2BlockNumberOnEth = parseInt(errMsg.split(" is after latest on-chain block ")[1]); - } + // If the L2Block is the latest ArbBlock this will always throw an error + console.log("Error finding batch containing block, searching heuristically..."); })) as [BigNumber] & { batch: BigNumber }; - if (!result && !fallbackLatest) return undefined; + if (!result) { + if (!fallbackLatest) { + return undefined; + } else { + [latestL2batchOnEth, latestL2BlockNumberOnEth] = await findLatestL2BatchAndBlock( + nodeInterface, + fromArbBlock, + L2Block.number + ); + } + } const batch = result?.batch?.toNumber() ?? latestL2batchOnEth; const L2BlockNumberFallback = latestL2BlockNumberOnEth ?? L2Block.number; @@ -578,6 +706,44 @@ const ArbBlockToL1Block = async ( return [L1Block, L2BlockNumberFallback]; }; +const findLatestL2BatchAndBlock = async ( + nodeInterface: NodeInterface, + fromArbBlock: number, + latestBlockNumber: number +): Promise<[number, number]> => { + let low = fromArbBlock; + let high = latestBlockNumber; + + while (low <= high) { + const mid = Math.floor((low + high) / 2); + try { + (await nodeInterface.functions.findBatchContainingBlock(mid, { blockTag: "latest" })) as any; + low = mid + 1; + } catch (e) { + high = mid - 1; + } + } + if (high < low) return [undefined, undefined]; + // high is now the latest L2 block number that has a corresponding batch on L1 + const result = (await nodeInterface.functions.findBatchContainingBlock(high, { blockTag: "latest" })) as any; + return [result.batch.toNumber(), high]; +}; + +const hashClaim = (claim) => { + return ethers.utils.solidityKeccak256( + ["bytes32", "address", "uint32", "uint32", "uint32", "uint8", "address"], + [ + claim.stateRoot, + claim.claimer, + claim.timestampClaimed, + claim.timestampVerification, + claim.blocknumberVerification, + claim.honest, + claim.challenger, + ] + ); +}; + (async () => { await watch(); })(); diff --git a/validator-cli/src/utils/arbMsgExecutor.ts b/validator-cli/src/utils/arbMsgExecutor.ts new file mode 100644 index 00000000..e2d8c8bf --- /dev/null +++ b/validator-cli/src/utils/arbMsgExecutor.ts @@ -0,0 +1,65 @@ +import { + ChildTransactionReceipt, + ArbitrumProvider, + ChildToParentMessageWriter, + ChildToParentMessageStatus, +} from "@arbitrum/sdk"; +import { Wallet } from "@ethersproject/wallet"; +import { JsonRpcProvider, TransactionReceipt } from "@ethersproject/providers"; +import { Signer } from "@ethersproject/abstract-signer"; +import { ContractTransaction } from "@ethersproject/contracts"; + +// Execute the child-to-parent (arbitrum-to-ethereum) message, for reference see: https://docs.arbitrum.io/sdk/reference/message/ChildToParentMessage +async function messageExecutor(trnxHash: string, childRpc: string, parentRpc: string): Promise { + const PRIVATE_KEY = process.env.PRIVATE_KEY; + const childJsonRpc = new JsonRpcProvider(childRpc); + const childProvider = new ArbitrumProvider(childJsonRpc); + const parentProvider = new JsonRpcProvider(parentRpc); + + const childReceipt: TransactionReceipt = await childProvider.getTransactionReceipt(trnxHash); + if (!childReceipt) { + throw new Error(`Transaction receipt not found for hash: ${trnxHash}`); + } + + const messageReceipt = new ChildTransactionReceipt(childReceipt); + const parentSigner: Signer = new Wallet(PRIVATE_KEY, parentProvider); + + const messages = await messageReceipt.getChildToParentMessages(parentSigner); + const childToParentMessage: ChildToParentMessageWriter = messages[0]; + if (!childToParentMessage) { + throw new Error("No child-to-parent messages found"); + } + + // Execute the message + const res = await childToParentMessage.execute(childProvider); + return res; +} + +async function getMessageStatus( + trnxHash: string, + childRpc: string, + parentRpc: string +): Promise { + const PRIVATE_KEY = process.env.PRIVATE_KEY; + const childJsonRpc = new JsonRpcProvider(childRpc); + const childProvider = new ArbitrumProvider(childJsonRpc); + const parentProvider = new JsonRpcProvider(parentRpc); + + let childReceipt: TransactionReceipt | null; + + childReceipt = await childProvider.getTransactionReceipt(trnxHash); + if (!childReceipt) { + throw new Error(`Transaction receipt not found for hash: ${trnxHash}`); + } + const messageReceipt = new ChildTransactionReceipt(childReceipt); + const parentSigner: Signer = new Wallet(PRIVATE_KEY, parentProvider); + const messages = await messageReceipt.getChildToParentMessages(parentSigner); + const childToParentMessage = messages[0]; + if (!childToParentMessage) { + console.error("No child-to-parent messages found"); + } + const status = await childToParentMessage.status(childProvider); + return status; +} + +export { messageExecutor, getMessageStatus }; diff --git a/yarn.lock b/yarn.lock index 2d41b9a2..7d651f49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,15 +22,16 @@ __metadata: languageName: node linkType: hard -"@arbitrum/sdk@npm:^3.1.2": - version: 3.1.3 - resolution: "@arbitrum/sdk@npm:3.1.3" +"@arbitrum/sdk@npm:4.0.1": + version: 4.0.1 + resolution: "@arbitrum/sdk@npm:4.0.1" dependencies: "@ethersproject/address": "npm:^5.0.8" "@ethersproject/bignumber": "npm:^5.1.1" "@ethersproject/bytes": "npm:^5.0.8" + async-mutex: "npm:^0.4.0" ethers: "npm:^5.1.0" - checksum: 10/8c90afc41dffaafaa3826752770ae3bd8160dd78fbbb39b35902b3100641cdc68e9c7f23c6c1d2cdb7abf0095b0c9e3cf8d042e5ba18db9987b37b280310abb8 + checksum: 10/3d81f8645022f723f36dd8711493f8bddd5f4306e7ed2d1a31b34091492f0be972bae03d3abef5dbf6b230e01e1693c826e6e624ae831f3bfe6cf42166f14350 languageName: node linkType: hard @@ -3290,7 +3291,7 @@ __metadata: version: 0.0.0-use.local resolution: "@kleros/vea-validator-cli@workspace:validator-cli" dependencies: - "@arbitrum/sdk": "npm:^3.1.2" + "@arbitrum/sdk": "npm:4.0.1" "@flashbots/ethers-provider-bundle": "npm:^0.6.2" "@kleros/vea-contracts": "workspace:^" "@typechain/ethers-v5": "npm:^10.2.0" @@ -7386,6 +7387,15 @@ __metadata: languageName: node linkType: hard +"async-mutex@npm:^0.4.0": + version: 0.4.1 + resolution: "async-mutex@npm:0.4.1" + dependencies: + tslib: "npm:^2.4.0" + checksum: 10/7e9f77b112b8545beb6612493fae4a8d9d1d6c3f24fc22f4d6d05ce96d1e8d326ac3e743a804cc6d7bf24e7ef0267afb65bb127f99b2e433609684b38933ff1c + languageName: node + linkType: hard + "async@npm:1.x": version: 1.5.2 resolution: "async@npm:1.5.2"