Skip to content

Commit

Permalink
Merge pull request #90 from zkLinkProtocol/arbitratorUpgrade
Browse files Browse the repository at this point in the history
Arbitrator upgrade
  • Loading branch information
zkbenny authored Apr 12, 2024
2 parents a523286 + b8a850f commit 131834e
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 14 deletions.
3 changes: 2 additions & 1 deletion .solhint.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"max-states-count": "off",
"no-inline-assembly": "off",
"not-rely-on-time": "off",
"no-empty-blocks": "off"
"no-empty-blocks": "off",
"avoid-tx-origin": "off"
}
}
103 changes: 95 additions & 8 deletions contracts/Arbitrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import {DoubleEndedQueueUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/structs/DoubleEndedQueueUpgradeable.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {IArbitrator} from "./interfaces/IArbitrator.sol";
import {IL1Gateway} from "./interfaces/IL1Gateway.sol";
import {IAdmin} from "./zksync/l1-contracts/zksync/interfaces/IAdmin.sol";
Expand All @@ -27,12 +28,14 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra
mapping(IL1Gateway => DoubleEndedQueueUpgradeable.Bytes32Deque) public secondaryChainMessageHashQueues;
/// @notice List of permitted relayers
mapping(address relayerAddress => bool isRelayer) public relayers;
/// @dev A transient storage value for forwarding message from source chain to target chains
bytes32 private finalizeMessageHash;
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
uint256[49] private __gap;

/// @notice Primary chain gateway init
event InitPrimaryChain(IL1Gateway indexed gateway);
Expand Down Expand Up @@ -137,29 +140,41 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra
emit NewFeeParams(_gateway, _newFeeParams);
}

function receiveMessage(uint256 _value, bytes calldata _callData) external payable {
function enqueueMessage(uint256 _value, bytes calldata _callData) external payable {
require(msg.value == _value, "Invalid msg value");
// store message hash for forwarding
bytes32 finalizeMessageHash = keccak256(abi.encode(_value, _callData));
bytes32 _finalizeMessageHash = keccak256(abi.encode(_value, _callData));
IL1Gateway gateway = IL1Gateway(msg.sender);
if (gateway == primaryChainGateway) {
primaryChainMessageHashQueue.pushBack(finalizeMessageHash);
primaryChainMessageHashQueue.pushBack(_finalizeMessageHash);
} else {
require(secondaryChainGateways[gateway], "Not secondary chain gateway");
secondaryChainMessageHashQueues[gateway].pushBack(finalizeMessageHash);
secondaryChainMessageHashQueues[gateway].pushBack(_finalizeMessageHash);
}
emit MessageReceived(_value, _callData);
}

/// @dev This function is called within the `claimMessageCallback` of L1 gateway
function receiveMessage(uint256 _value, bytes calldata _callData) external payable {
require(msg.value == _value, "Invalid msg value");
// temporary store message hash for forwarding
IL1Gateway gateway = IL1Gateway(msg.sender);
require(gateway == primaryChainGateway || secondaryChainGateways[gateway], "Invalid gateway");
bytes32 _finalizeMessageHash = keccak256(abi.encode(msg.sender, _value, _callData));
assembly {
tstore(finalizeMessageHash.slot, _finalizeMessageHash)
}
}

function forwardMessage(
IL1Gateway _gateway,
uint256 _value,
bytes calldata _callData,
bytes calldata _adapterParams
) external payable nonReentrant onlyRelayer {
bytes32 finalizeMessageHash = keccak256(abi.encode(_value, _callData));
bytes32 _finalizeMessageHash = keccak256(abi.encode(_value, _callData));
if (_gateway == primaryChainGateway) {
require(finalizeMessageHash == primaryChainMessageHashQueue.popFront(), "Invalid finalize message hash");
require(_finalizeMessageHash == primaryChainMessageHashQueue.popFront(), "Invalid finalize message hash");
// Unpack destination chain and final callData
(IL1Gateway secondaryChainGateway, bytes memory finalCallData) = abi.decode(_callData, (IL1Gateway, bytes));
require(secondaryChainGateways[secondaryChainGateway], "Invalid secondary chain gateway");
Expand All @@ -168,12 +183,84 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra
} else {
require(secondaryChainGateways[_gateway], "Not secondary chain gateway");
require(
finalizeMessageHash == secondaryChainMessageHashQueues[_gateway].popFront(),
_finalizeMessageHash == secondaryChainMessageHashQueues[_gateway].popFront(),
"Invalid finalize message hash"
);
// Forward fee to send message
primaryChainGateway.sendMessage{value: msg.value + _value}(_value, _callData, _adapterParams);
}
emit MessageForwarded(_gateway, _value, _callData);
}

function claimMessage(
address _sourceChainCanonicalMessageService,
bytes calldata _sourceChainClaimCallData,
IL1Gateway _sourceChainL1Gateway,
uint256 _receiveValue,
bytes calldata _receiveCallData,
bytes calldata _forwardParams
) external payable nonReentrant onlyRelayer {
// Call the claim interface of source chain message service
// And it will inner call the `claimMessageCallback` interface of source chain L1Gateway
// In the `claimMessageCallback` of L1Gateway, it will inner call `receiveMessage` of Arbitrator
// No use of return value
Address.functionCall(_sourceChainCanonicalMessageService, _sourceChainClaimCallData);

// Load the transient `finalizeMessageHash`
bytes32 _finalizeMessageHash;
assembly {
_finalizeMessageHash := tload(finalizeMessageHash.slot)
}
require(
_finalizeMessageHash == keccak256(abi.encode(_sourceChainL1Gateway, _receiveValue, _receiveCallData)),
"Incorrect finalize data"
);

// The msg value should be equal to the combined cost of all messages delivered from l1 to l2
// The excess fees will be refunded to the relayer by rollup canonical message service
if (_sourceChainL1Gateway == primaryChainGateway) {
// Unpack destination chain and final callData
bytes[] memory gatewayDataList = abi.decode(_receiveCallData, (bytes[]));
bytes[] memory gatewayForwardParamsList = abi.decode(_forwardParams, (bytes[]));
uint256 gatewayLength = gatewayDataList.length;
require(gatewayLength == gatewayForwardParamsList.length, "Invalid forward params length");
uint256 totalCallValue;
uint256 totalSendMsgFee;
unchecked {
for (uint256 i = 0; i < gatewayLength; ++i) {
bytes memory gatewayData = gatewayDataList[i];
bytes memory gatewayForwardParams = gatewayForwardParamsList[i];
(IL1Gateway targetGateway, uint256 targetCallValue, bytes memory targetCallData) = abi.decode(
gatewayData,
(IL1Gateway, uint256, bytes)
);
require(secondaryChainGateways[targetGateway], "Invalid secondary chain gateway");
totalCallValue += targetCallValue;
(uint256 sendMsgFee, bytes memory adapterParams) = abi.decode(
gatewayForwardParams,
(uint256, bytes)
);
totalSendMsgFee += sendMsgFee;
// Forward fee to send message
targetGateway.sendMessage{value: sendMsgFee + targetCallValue}(
targetCallValue,
targetCallData,
adapterParams
);
emit MessageForwarded(targetGateway, targetCallValue, targetCallData);
}
}
require(totalCallValue == _receiveValue, "Invalid call value");
require(totalSendMsgFee == msg.value, "Invalid send msg fee");
} else {
IL1Gateway targetGateway = primaryChainGateway;
// Forward fee to send message
targetGateway.sendMessage{value: msg.value + _receiveValue}(
_receiveValue,
_receiveCallData,
_forwardParams
);
emit MessageForwarded(targetGateway, _receiveValue, _receiveCallData);
}
}
}
59 changes: 58 additions & 1 deletion contracts/ZkLink.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,15 @@ contract ZkLink is
public isEthWithdrawalFinalized;
/// @dev The forward fee allocator
address public forwardFeeAllocator;
/// @dev The range batch root hash of [fromBatchNumber, toBatchNumber]
/// The key is keccak256(abi.encodePacked(fromBatchNumber, toBatchNumber))
mapping(bytes32 range => bytes32 rangeBatchRootHash) public rangBatchRootHashes;
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
uint256[49] private __gap;

/// @notice Gateway init
event InitGateway(IL2Gateway indexed gateway);
Expand All @@ -107,6 +110,15 @@ contract ZkLink is
event SyncL2Requests(uint256 totalSyncedPriorityTxs, bytes32 syncHash, uint256 forwardEthAmount);
/// @notice Emitted when receive batch root from primary chain.
event SyncBatchRoot(uint256 batchNumber, bytes32 l2LogsRootHash, uint256 forwardEthAmount);
/// @notice Emitted when receive range batch root hash from primary chain.
event SyncRangeBatchRoot(
uint256 fromBatchNumber,
uint256 toBatchNumber,
bytes32 rangeBatchRootHash,
uint256 forwardEthAmount
);
/// @notice Emitted when open range batch root hash.
event OpenRangeBatchRoot(uint256 fromBatchNumber, uint256 toBatchNumber);
/// @notice Emitted when receive l2 tx hash from primary chain.
event SyncL2TxHash(bytes32 l2TxHash, bytes32 primaryChainL2TxHash);
/// @notice Emitted when validator withdraw forward fee
Expand Down Expand Up @@ -458,6 +470,51 @@ contract ZkLink is
emit SyncBatchRoot(_batchNumber, _l2LogsRootHash, _forwardEthAmount);
}

function syncRangeBatchRoot(
uint256 _fromBatchNumber,
uint256 _toBatchNumber,
bytes32 _rangeBatchRootHash,
uint256 _forwardEthAmount
) external payable onlyGateway {
require(_toBatchNumber >= _fromBatchNumber, "Invalid range");
require(msg.value == _forwardEthAmount, "Invalid forward amount");
bytes32 range = keccak256(abi.encodePacked(_fromBatchNumber, _toBatchNumber));
rangBatchRootHashes[range] = _rangeBatchRootHash;
emit SyncRangeBatchRoot(_fromBatchNumber, _toBatchNumber, _rangeBatchRootHash, _forwardEthAmount);
}

/// @dev Unzip the root hashes in the range
/// @param _fromBatchNumber The batch number from
/// @param _toBatchNumber The batch number to
/// @param _l2LogsRootHashes The l2LogsRootHash list in the range [`_fromBatchNumber`, `_toBatchNumber`]
function openRangeBatchRootHash(
uint256 _fromBatchNumber,
uint256 _toBatchNumber,
bytes32[] memory _l2LogsRootHashes
) external onlyValidator {
require(_toBatchNumber >= _fromBatchNumber, "Invalid range");
bytes32 range = keccak256(abi.encodePacked(_fromBatchNumber, _toBatchNumber));
bytes32 rangeBatchRootHash = rangBatchRootHashes[range];
require(rangeBatchRootHash != bytes32(0), "Rang batch root hash not exist");
uint256 rootHashesLength = _l2LogsRootHashes.length;
require(rootHashesLength == _toBatchNumber - _fromBatchNumber + 1, "Invalid root hashes length");
bytes32 _rangeBatchRootHash = _l2LogsRootHashes[0];
l2LogsRootHashes[_fromBatchNumber] = _rangeBatchRootHash;
unchecked {
for (uint256 i = 1; i < rootHashesLength; ++i) {
bytes32 _l2LogsRootHash = _l2LogsRootHashes[i];
l2LogsRootHashes[_fromBatchNumber + i] = _l2LogsRootHash;
_rangeBatchRootHash = Merkle._efficientHash(_rangeBatchRootHash, _l2LogsRootHash);
}
}
require(_rangeBatchRootHash == rangeBatchRootHash, "Incorrect root hash");
delete rangBatchRootHashes[range];
if (_toBatchNumber > totalBatchesExecuted) {
totalBatchesExecuted = _toBatchNumber;
}
emit OpenRangeBatchRoot(_fromBatchNumber, _toBatchNumber);
}

function syncL2TxHash(bytes32 _l2TxHash, bytes32 _primaryChainL2TxHash) external onlyGateway {
l2TxHashMap[_l2TxHash] = _primaryChainL2TxHash;
emit SyncL2TxHash(_l2TxHash, _primaryChainL2TxHash);
Expand Down
16 changes: 16 additions & 0 deletions contracts/dev-contracts/DummyArbitrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ contract DummyArbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Re

function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}

function enqueueMessage(uint256 _value, bytes calldata _callData) external payable {
require(msg.value == _value, "Invalid msg value");
emit ReceiveMessage(_value, _callData);
}

function receiveMessage(uint256 _value, bytes calldata _callData) external payable {
require(msg.value == _value, "Invalid msg value");
emit ReceiveMessage(_value, _callData);
Expand All @@ -33,4 +38,15 @@ contract DummyArbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Re
// Forward fee to send message
_gateway.sendMessage{value: msg.value + _value}(_value, _callData, _adapterParams);
}

function claimMessage(
address,
bytes calldata,
IL1Gateway,
uint256,
bytes calldata,
bytes calldata
) external payable {
// do nothing
}
}
15 changes: 15 additions & 0 deletions contracts/dev-contracts/DummyZkLink.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ contract DummyZkLink is IZkLink, OwnableUpgradeable, UUPSUpgradeable, Reentrancy
IL2Gateway public gateway;

event ReceiveBatchRoot(uint256 batchNumber, bytes32 l2LogsRootHash, uint256 forwardEthAmount);
event ReceiveRangeBatchRoot(
uint256 fromBatchNumber,
uint256 toBatchNumber,
bytes32 rangeBatchRootHash,
uint256 forwardEthAmount
);
event ReceiveL2TxHash(bytes32 l2TxHash, bytes32 primaryChainL2TxHash);

modifier onlyGateway() {
Expand Down Expand Up @@ -55,6 +61,15 @@ contract DummyZkLink is IZkLink, OwnableUpgradeable, UUPSUpgradeable, Reentrancy
emit ReceiveBatchRoot(_batchNumber, _l2LogsRootHash, _forwardEthAmount);
}

function syncRangeBatchRoot(
uint256 _fromBatchNumber,
uint256 _toBatchNumber,
bytes32 _rangeBatchRootHash,
uint256 _forwardEthAmount
) external payable {
emit ReceiveRangeBatchRoot(_fromBatchNumber, _toBatchNumber, _rangeBatchRootHash, _forwardEthAmount);
}

function syncL2TxHash(bytes32 _l2TxHash, bytes32 _primaryChainL2TxHash) external onlyGateway {
emit ReceiveL2TxHash(_l2TxHash, _primaryChainL2TxHash);
}
Expand Down
2 changes: 1 addition & 1 deletion contracts/gateway/ethereum/EthereumGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ contract EthereumGateway is
function sendMessage(uint256 _value, bytes calldata _callData) external payable override onlyZkLink {
require(msg.value == _value, "Invalid value");
// Forward message to arbitrator
ARBITRATOR.receiveMessage{value: _value}(_value, _callData);
ARBITRATOR.enqueueMessage{value: _value}(_value, _callData);
emit L2GatewayMessageSent(_value, _callData);
}
}
3 changes: 2 additions & 1 deletion contracts/gateway/optimism/OptimismL1Gateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ contract OptimismL1Gateway is L1BaseGateway, OptimismGateway {
uint256 _value,
bytes calldata _callData
) external payable onlyMessageService onlyRemoteGateway {
require(msg.value == _value, "Invalid value");
// Blast will return more value(the stake profit) than burned on L2
require(msg.value >= _value, "Invalid value");
// Forward message to arbitrator
ARBITRATOR.receiveMessage{value: _value}(_value, _callData);
}
Expand Down
25 changes: 24 additions & 1 deletion contracts/interfaces/IArbitrator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ pragma solidity ^0.8.0;
import {IL1Gateway} from "./IL1Gateway.sol";

interface IArbitrator {
/// @notice Receive message from one L1 gateway to another L1 gateway
/// @notice Enqueue message from EthereumGateway
/// @dev Used by EthereumGateway to temporarily store message
/// @param _value The msg value
/// @param _callData The call data
function enqueueMessage(uint256 _value, bytes calldata _callData) external payable;

/// @notice Deliver message from one L1 gateway to another L1 gateway
/// @dev Used by L1Gateways to deliver message
/// @param _value The msg value
/// @param _callData The call data
function receiveMessage(uint256 _value, bytes calldata _callData) external payable;
Expand All @@ -20,4 +27,20 @@ interface IArbitrator {
bytes calldata _callData,
bytes calldata _adapterParams
) external payable;

/// @notice Claim a message of source chain and deliver it to the target chain
/// @param _sourceChainCanonicalMessageService The message service to claim message
/// @param _sourceChainClaimCallData The call data that need to claim message from source chain
/// @param _sourceChainL1Gateway The msg.sender passed in the `receiveMessage` interface
/// @param _receiveValue The value passed in the `receiveMessage` interface
/// @param _receiveCallData The call data passed in the `receiveMessage` interface
/// @param _forwardParams Some params need to call canonical message service of target chain
function claimMessage(
address _sourceChainCanonicalMessageService,
bytes calldata _sourceChainClaimCallData,
IL1Gateway _sourceChainL1Gateway,
uint256 _receiveValue,
bytes calldata _receiveCallData,
bytes calldata _forwardParams
) external payable;
}
12 changes: 12 additions & 0 deletions contracts/interfaces/IZkLink.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ interface IZkLink {
/// @param _forwardEthAmount The forward eth amount
function syncBatchRoot(uint256 _batchNumber, bytes32 _l2LogsRootHash, uint256 _forwardEthAmount) external payable;

/// @notice Receive range batch root hash from primary chain
/// @param _fromBatchNumber The batch number from
/// @param _toBatchNumber The batch number to
/// @param _rangeBatchRootHash The accumulation hash of l2LogsRootHash in the range [`_fromBatchNumber`, `_toBatchNumber`]
/// @param _forwardEthAmount The forward eth amount
function syncRangeBatchRoot(
uint256 _fromBatchNumber,
uint256 _toBatchNumber,
bytes32 _rangeBatchRootHash,
uint256 _forwardEthAmount
) external payable;

/// @notice Receive l2 tx hash from primary chain
/// @param _l2TxHash The l2 tx hash on local chain
/// @param _primaryChainL2TxHash The l2 tx hash on primary chain
Expand Down
2 changes: 1 addition & 1 deletion contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ library Merkle {
}

/// @dev Keccak hash of the concatenation of two 32-byte words
function _efficientHash(bytes32 _lhs, bytes32 _rhs) private pure returns (bytes32 result) {
function _efficientHash(bytes32 _lhs, bytes32 _rhs) internal pure returns (bytes32 result) {
assembly {
mstore(0x00, _lhs)
mstore(0x20, _rhs)
Expand Down
Loading

0 comments on commit 131834e

Please sign in to comment.