From ad86941f8fd38f48dbe0507080ab04c70ae4fa00 Mon Sep 17 00:00:00 2001 From: zkbenny Date: Wed, 27 Mar 2024 17:02:11 +0800 Subject: [PATCH 01/12] optimize claim message --- contracts/Arbitrator.sol | 48 +++++++++++++++++---- contracts/dev-contracts/DummyArbitrator.sol | 8 ++++ contracts/interfaces/IArbitrator.sol | 10 +++++ hardhat.base.config.js | 13 ++++++ 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/contracts/Arbitrator.sol b/contracts/Arbitrator.sol index 862e898..297d244 100644 --- a/contracts/Arbitrator.sol +++ b/contracts/Arbitrator.sol @@ -27,12 +27,16 @@ 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 The msg value and adapter params are used to forward a l2 message from source chain to target chain + bool private claiming; + uint256 private claimMsgValue; + bytes private claimAdapterParams; /** * @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[47] private __gap; /// @notice Primary chain gateway init event InitPrimaryChain(IL1Gateway indexed gateway); @@ -44,8 +48,6 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra event ValidatorStatusUpdate(IL1Gateway indexed gateway, address validatorAddress, bool isActive); /// @notice Fee params for L1->L2 transactions changed event NewFeeParams(IL1Gateway indexed gateway, FeeParams newFeeParams); - /// @notice Emit when receive message from l1 gateway - event MessageReceived(uint256 value, bytes callData); /// @notice Emit when forward message to l1 gateway event MessageForwarded(IL1Gateway indexed gateway, uint256 value, bytes callData); @@ -137,18 +139,26 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra emit NewFeeParams(_gateway, _newFeeParams); } + /// @dev This function is called within the `claimMessageCallback` of L1 gateway function receiveMessage(uint256 _value, bytes calldata _callData) external payable { + // `claiming`, `claimMsgValue` and `claimAdapterParams` are transient values and set in `claimMessage` + // Ensure claim start from call `claimMessage` + require(claiming, "Invalid claim"); require(msg.value == _value, "Invalid msg value"); - // store message hash for forwarding - bytes32 finalizeMessageHash = keccak256(abi.encode(_value, _callData)); IL1Gateway gateway = IL1Gateway(msg.sender); + // Ensure the caller is L1 gateway if (gateway == primaryChainGateway) { - primaryChainMessageHashQueue.pushBack(finalizeMessageHash); + // Unpack destination chain and final callData + (IL1Gateway secondaryChainGateway, bytes memory finalCallData) = abi.decode(_callData, (IL1Gateway, bytes)); + require(secondaryChainGateways[secondaryChainGateway], "Invalid secondary chain gateway"); + // Forward fee to send message + secondaryChainGateway.sendMessage{value: claimMsgValue + _value}(_value, finalCallData, claimAdapterParams); } else { require(secondaryChainGateways[gateway], "Not secondary chain gateway"); - secondaryChainMessageHashQueues[gateway].pushBack(finalizeMessageHash); + // Forward fee to send message + primaryChainGateway.sendMessage{value: claimMsgValue + _value}(_value, _callData, claimAdapterParams); } - emit MessageReceived(_value, _callData); + emit MessageForwarded(gateway, _value, _callData); } function forwardMessage( @@ -176,4 +186,26 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra } emit MessageForwarded(_gateway, _value, _callData); } + + function claimMessage( + address _sourceChainCanonicalMessageService, + bytes calldata _sourceChainClaimCallData, + bytes memory _targetChainAdapterParams + ) external payable nonReentrant onlyRelayer { + // The `claiming`, `claimMsgValue` and `claimAdapterParams` will be cleared after tx executed + assembly { + tstore(claiming.slot, true) + tstore(claimMsgValue.slot, callvalue()) + tstore(claimAdapterParams.slot, _targetChainAdapterParams) + } + // Call the claim interface of source chain message service + // And it will inner call the `claimCallback` interface of source chain L1Gateway + (bool success, bytes memory returnData) = _sourceChainCanonicalMessageService.call(_sourceChainClaimCallData); + if (!success) { + // Propagate an error if the call fails. + assembly { + revert(add(returnData, 0x20), mload(returnData)) + } + } + } } diff --git a/contracts/dev-contracts/DummyArbitrator.sol b/contracts/dev-contracts/DummyArbitrator.sol index 7c84a6f..0d88537 100644 --- a/contracts/dev-contracts/DummyArbitrator.sol +++ b/contracts/dev-contracts/DummyArbitrator.sol @@ -33,4 +33,12 @@ 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, + bytes memory + ) external payable { + // do nothing + } } diff --git a/contracts/interfaces/IArbitrator.sol b/contracts/interfaces/IArbitrator.sol index 83a1149..3d29084 100644 --- a/contracts/interfaces/IArbitrator.sol +++ b/contracts/interfaces/IArbitrator.sol @@ -20,4 +20,14 @@ 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 _targetChainAdapterParams Some params need to call canonical message service of target chain + function claimMessage( + address _sourceChainCanonicalMessageService, + bytes calldata _sourceChainClaimCallData, + bytes memory _targetChainAdapterParams + ) external payable; } diff --git a/hardhat.base.config.js b/hardhat.base.config.js index b69da83..5c08e70 100644 --- a/hardhat.base.config.js +++ b/hardhat.base.config.js @@ -15,6 +15,19 @@ const hardhatUserConfig = { }, }, ], + overrides: { + "contracts/Arbitrator.sol": { + version: "0.8.25", + settings: { + viaIR: true, + optimizer: { + enabled: true, + runs: 200, + }, + evmVersion: "cancun", + }, + } + } }, networks: { hardhat: { From 4b7fc2d4858142eb4f4b1fc114590f7e7d6ed410 Mon Sep 17 00:00:00 2001 From: zkbenny Date: Wed, 27 Mar 2024 19:01:06 +0800 Subject: [PATCH 02/12] use Address lib --- contracts/Arbitrator.sol | 10 +++------- contracts/dev-contracts/DummyArbitrator.sol | 6 +----- hardhat.base.config.js | 10 +++++----- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/contracts/Arbitrator.sol b/contracts/Arbitrator.sol index 297d244..b63e1ce 100644 --- a/contracts/Arbitrator.sol +++ b/contracts/Arbitrator.sol @@ -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"; @@ -200,12 +201,7 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra } // Call the claim interface of source chain message service // And it will inner call the `claimCallback` interface of source chain L1Gateway - (bool success, bytes memory returnData) = _sourceChainCanonicalMessageService.call(_sourceChainClaimCallData); - if (!success) { - // Propagate an error if the call fails. - assembly { - revert(add(returnData, 0x20), mload(returnData)) - } - } + // No use of return value + Address.functionCall(_sourceChainCanonicalMessageService, _sourceChainClaimCallData); } } diff --git a/contracts/dev-contracts/DummyArbitrator.sol b/contracts/dev-contracts/DummyArbitrator.sol index 0d88537..6bd8a3f 100644 --- a/contracts/dev-contracts/DummyArbitrator.sol +++ b/contracts/dev-contracts/DummyArbitrator.sol @@ -34,11 +34,7 @@ contract DummyArbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Re _gateway.sendMessage{value: msg.value + _value}(_value, _callData, _adapterParams); } - function claimMessage( - address, - bytes calldata, - bytes memory - ) external payable { + function claimMessage(address, bytes calldata, bytes memory) external payable { // do nothing } } diff --git a/hardhat.base.config.js b/hardhat.base.config.js index 5c08e70..aded78c 100644 --- a/hardhat.base.config.js +++ b/hardhat.base.config.js @@ -16,18 +16,18 @@ const hardhatUserConfig = { }, ], overrides: { - "contracts/Arbitrator.sol": { - version: "0.8.25", + 'contracts/Arbitrator.sol': { + version: '0.8.25', settings: { viaIR: true, optimizer: { enabled: true, runs: 200, }, - evmVersion: "cancun", + evmVersion: 'cancun', }, - } - } + }, + }, }, networks: { hardhat: { From 4785d6abc00cf3401785d6c5ddfbeab64e62aa6e Mon Sep 17 00:00:00 2001 From: zkbenny Date: Sat, 30 Mar 2024 13:54:36 +0800 Subject: [PATCH 03/12] add syncRangeBatchRoot --- contracts/Arbitrator.sol | 49 ++++++++++++++++++++------------ contracts/ZkLink.sol | 39 ++++++++++++++++++++++++- contracts/interfaces/IZkLink.sol | 6 ++++ 3 files changed, 75 insertions(+), 19 deletions(-) diff --git a/contracts/Arbitrator.sol b/contracts/Arbitrator.sol index b63e1ce..304f643 100644 --- a/contracts/Arbitrator.sol +++ b/contracts/Arbitrator.sol @@ -9,6 +9,7 @@ import {DoubleEndedQueueUpgradeable} from "@openzeppelin/contracts-upgradeable/u import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {IArbitrator} from "./interfaces/IArbitrator.sol"; import {IL1Gateway} from "./interfaces/IL1Gateway.sol"; +import {IZkLink} from "./interfaces/IZkLink.sol"; import {IAdmin} from "./zksync/l1-contracts/zksync/interfaces/IAdmin.sol"; import {IZkSync} from "./zksync/l1-contracts/zksync/interfaces/IZkSync.sol"; import {FeeParams} from "./zksync/l1-contracts/zksync/Storage.sol"; @@ -18,6 +19,11 @@ import {FeeParams} from "./zksync/l1-contracts/zksync/Storage.sol"; contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable { using DoubleEndedQueueUpgradeable for DoubleEndedQueueUpgradeable.Bytes32Deque; + struct GatewayAdapterParams { + IL1Gateway gateway; + bytes adapterParams; + } + /// @dev The gateway for sending message from ethereum to primary chain IL1Gateway public primaryChainGateway; /// @dev The gateway for sending message from ethereum to secondary chain @@ -28,16 +34,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 The msg value and adapter params are used to forward a l2 message from source chain to target chain - bool private claiming; - uint256 private claimMsgValue; - bytes private claimAdapterParams; + /// @dev The forward params are used to forward a l2 message from source chain to target chains + bytes private forwardParams; /** * @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[47] private __gap; + uint256[49] private __gap; /// @notice Primary chain gateway init event InitPrimaryChain(IL1Gateway indexed gateway); @@ -142,22 +146,33 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra /// @dev This function is called within the `claimMessageCallback` of L1 gateway function receiveMessage(uint256 _value, bytes calldata _callData) external payable { - // `claiming`, `claimMsgValue` and `claimAdapterParams` are transient values and set in `claimMessage` - // Ensure claim start from call `claimMessage` - require(claiming, "Invalid claim"); require(msg.value == _value, "Invalid msg value"); IL1Gateway gateway = IL1Gateway(msg.sender); // Ensure the caller is L1 gateway if (gateway == primaryChainGateway) { // Unpack destination chain and final callData - (IL1Gateway secondaryChainGateway, bytes memory finalCallData) = abi.decode(_callData, (IL1Gateway, bytes)); - require(secondaryChainGateways[secondaryChainGateway], "Invalid secondary chain gateway"); - // Forward fee to send message - secondaryChainGateway.sendMessage{value: claimMsgValue + _value}(_value, finalCallData, claimAdapterParams); + bytes[] memory gatewayCallDataLists = abi.decode(_callData, (bytes[])); + // `forwardParams` is set in `claimMessage` + bytes[] memory gatewayForwardParams = abi.decode(forwardParams, (bytes[])); + uint256 gatewayLength = gatewayCallDataLists.length; + require(gatewayLength == gatewayForwardParams.length, "Invalid forward params length"); + unchecked { + for (uint256 i = 0; i < gatewayLength; ++i) { + bytes memory gatewayCallData = gatewayCallDataLists[i]; + bytes memory gatewayForwardParam = gatewayForwardParams[i]; + (IL1Gateway secondaryChainGateway, uint256 callValue, bytes memory callData) = abi.decode(gatewayCallData, (IL1Gateway, uint256, bytes)); + require(secondaryChainGateways[secondaryChainGateway], "Invalid secondary chain gateway"); + (uint256 sendMsgFee, bytes memory adapterParams) = abi.decode(gatewayForwardParam, (uint256, bytes)); + // Forward fee to send message + secondaryChainGateway.sendMessage{value: sendMsgFee + callValue}(callValue, callData, adapterParams); + } + } } else { require(secondaryChainGateways[gateway], "Not secondary chain gateway"); + // `forwardParams` is set in `claimMessage` + (uint256 sendMsgFee, bytes memory adapterParams) = abi.decode(forwardParams, (uint256, bytes)); // Forward fee to send message - primaryChainGateway.sendMessage{value: claimMsgValue + _value}(_value, _callData, claimAdapterParams); + primaryChainGateway.sendMessage{value: sendMsgFee + _value}(_value, _callData, adapterParams); } emit MessageForwarded(gateway, _value, _callData); } @@ -191,13 +206,11 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra function claimMessage( address _sourceChainCanonicalMessageService, bytes calldata _sourceChainClaimCallData, - bytes memory _targetChainAdapterParams + bytes memory _forwardParams ) external payable nonReentrant onlyRelayer { - // The `claiming`, `claimMsgValue` and `claimAdapterParams` will be cleared after tx executed + // The `forwardParams` will be cleared after tx executed assembly { - tstore(claiming.slot, true) - tstore(claimMsgValue.slot, callvalue()) - tstore(claimAdapterParams.slot, _targetChainAdapterParams) + tstore(forwardParams.slot, _forwardParams) } // Call the claim interface of source chain message service // And it will inner call the `claimCallback` interface of source chain L1Gateway diff --git a/contracts/ZkLink.sol b/contracts/ZkLink.sol index 7ecbe73..56118b9 100644 --- a/contracts/ZkLink.sol +++ b/contracts/ZkLink.sol @@ -84,12 +84,15 @@ contract ZkLink is public isEthWithdrawalFinalized; /// @dev The forward fee allocator address public forwardFeeAllocator; + /// @dev The range root hash of [fromBatchNumber, toBatchNumber] + /// The range = keccak256(abi.encodePacked(fromBatchNumber, toBatchNumber)) + mapping(bytes32 range => bytes32 rangeRootHash) public rangRootHashMap; /** * @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); @@ -107,6 +110,8 @@ 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 from primary chain. + event SyncRangeBatchRoot(uint256 fromBatchNumber, uint256 toBatchNumber, bytes32 rangeRootHash, uint256 forwardEthAmount); /// @notice Emitted when receive l2 tx hash from primary chain. event SyncL2TxHash(bytes32 l2TxHash, bytes32 primaryChainL2TxHash); /// @notice Emitted when validator withdraw forward fee @@ -458,6 +463,38 @@ contract ZkLink is emit SyncBatchRoot(_batchNumber, _l2LogsRootHash, _forwardEthAmount); } + function syncRangeBatchRoot( + uint256 _fromBatchNumber, uint256 _toBatchNumber, bytes32 _rangeRootHash, uint256 _forwardEthAmount + ) external payable onlyGateway { + require(_toBatchNumber >= _fromBatchNumber, "Invalid range"); + require(msg.value == _forwardEthAmount, "Invalid forward amount"); + bytes32 range = keccak256(abi.encodePacked(_fromBatchNumber, _toBatchNumber)); + rangRootHashMap[range] = _rangeRootHash; + emit SyncRangeBatchRoot(_fromBatchNumber, _toBatchNumber, _rangeRootHash, _forwardEthAmount); + } + + function openRangeBatchRoot(uint256 _fromBatchNumber, uint256 _toBatchNumber, bytes32[] memory _l2LogsRootHashes) external onlyValidator { + require(_toBatchNumber >= _fromBatchNumber, "Invalid range"); + bytes32 range = keccak256(abi.encodePacked(_fromBatchNumber, _toBatchNumber)); + bytes32 rangeRootHash = rangRootHashMap[range]; + require(rangeRootHash != bytes32(0), "Rang root hash not exist"); + uint256 rootHashLength = _l2LogsRootHashes.length; + require(rootHashLength == _toBatchNumber - _fromBatchNumber + 1, "Invalid root hashes length"); + bytes32 openRangeRootHash = _l2LogsRootHashes[0]; + l2LogsRootHashes[_fromBatchNumber] = openRangeRootHash; + unchecked { + for (uint256 i = 1; i < rootHashLength; ++i) { + bytes32 l2LogsRootHash = _l2LogsRootHashes[i]; + l2LogsRootHashes[_fromBatchNumber + i] = l2LogsRootHash; + openRangeRootHash = Merkle._efficientHash(openRangeRootHash, l2LogsRootHash); + } + } + require(openRangeRootHash == rangeRootHash, "Incorrect range root hash"); + if (_toBatchNumber > totalBatchesExecuted) { + totalBatchesExecuted = _toBatchNumber; + } + } + function syncL2TxHash(bytes32 _l2TxHash, bytes32 _primaryChainL2TxHash) external onlyGateway { l2TxHashMap[_l2TxHash] = _primaryChainL2TxHash; emit SyncL2TxHash(_l2TxHash, _primaryChainL2TxHash); diff --git a/contracts/interfaces/IZkLink.sol b/contracts/interfaces/IZkLink.sol index 6f3db86..3b4c0a1 100644 --- a/contracts/interfaces/IZkLink.sol +++ b/contracts/interfaces/IZkLink.sol @@ -15,6 +15,12 @@ interface IZkLink { /// @param _forwardEthAmount The forward eth amount function syncBatchRoot(uint256 _batchNumber, bytes32 _l2LogsRootHash, uint256 _forwardEthAmount) external payable; + /// @notice Receive range batch root from primary chain + /// @param _fromBatchNumber The batch number from + /// @param _toBatchNumber The batch number to + /// @param _rangeRootHash The range root hash + function syncRangeBatchRoot(uint256 _fromBatchNumber, uint256 _toBatchNumber, bytes32 _rangeRootHash) external; + /// @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 From 6936ed11e0cbb64063ed99280d6b82b97a320114 Mon Sep 17 00:00:00 2001 From: zkbenny Date: Mon, 1 Apr 2024 14:55:38 +0800 Subject: [PATCH 04/12] add sync range batch root --- contracts/Arbitrator.sol | 55 +++++++++++------ contracts/ZkLink.sol | 60 ++++++++++++------- contracts/dev-contracts/DummyZkLink.sol | 15 +++++ contracts/interfaces/IArbitrator.sol | 4 +- contracts/interfaces/IZkLink.sol | 12 +++- .../l1-contracts/zksync/libraries/Merkle.sol | 2 +- 6 files changed, 101 insertions(+), 47 deletions(-) diff --git a/contracts/Arbitrator.sol b/contracts/Arbitrator.sol index 304f643..413d631 100644 --- a/contracts/Arbitrator.sol +++ b/contracts/Arbitrator.sol @@ -147,36 +147,52 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra /// @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"); - IL1Gateway gateway = IL1Gateway(msg.sender); + IL1Gateway sourceGateway = IL1Gateway(msg.sender); + // `forwardParams` is set in `claimMessage` + bytes memory _forwardParams; + assembly { + _forwardParams := tload(forwardParams.slot) + } // Ensure the caller is L1 gateway - if (gateway == primaryChainGateway) { + if (sourceGateway == primaryChainGateway) { // Unpack destination chain and final callData - bytes[] memory gatewayCallDataLists = abi.decode(_callData, (bytes[])); - // `forwardParams` is set in `claimMessage` - bytes[] memory gatewayForwardParams = abi.decode(forwardParams, (bytes[])); - uint256 gatewayLength = gatewayCallDataLists.length; - require(gatewayLength == gatewayForwardParams.length, "Invalid forward params length"); + bytes[] memory gatewayDataList = abi.decode(_callData, (bytes[])); + bytes[] memory gatewayForwardParamsList = abi.decode(_forwardParams, (bytes[])); + uint256 gatewayLength = gatewayDataList.length; + require(gatewayLength == gatewayForwardParamsList.length, "Invalid forward params length"); unchecked { for (uint256 i = 0; i < gatewayLength; ++i) { - bytes memory gatewayCallData = gatewayCallDataLists[i]; - bytes memory gatewayForwardParam = gatewayForwardParams[i]; - (IL1Gateway secondaryChainGateway, uint256 callValue, bytes memory callData) = abi.decode(gatewayCallData, (IL1Gateway, uint256, bytes)); - require(secondaryChainGateways[secondaryChainGateway], "Invalid secondary chain gateway"); - (uint256 sendMsgFee, bytes memory adapterParams) = abi.decode(gatewayForwardParam, (uint256, bytes)); + 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"); + (uint256 sendMsgFee, bytes memory adapterParams) = abi.decode( + gatewayForwardParams, + (uint256, bytes) + ); // Forward fee to send message - secondaryChainGateway.sendMessage{value: sendMsgFee + callValue}(callValue, callData, adapterParams); + targetGateway.sendMessage{value: sendMsgFee + targetCallValue}( + targetCallValue, + targetCallData, + adapterParams + ); + emit MessageForwarded(targetGateway, targetCallValue, targetCallData); } } } else { - require(secondaryChainGateways[gateway], "Not secondary chain gateway"); - // `forwardParams` is set in `claimMessage` - (uint256 sendMsgFee, bytes memory adapterParams) = abi.decode(forwardParams, (uint256, bytes)); + require(secondaryChainGateways[sourceGateway], "Not secondary chain gateway"); + (uint256 sendMsgFee, bytes memory adapterParams) = abi.decode(_forwardParams, (uint256, bytes)); // Forward fee to send message - primaryChainGateway.sendMessage{value: sendMsgFee + _value}(_value, _callData, adapterParams); + IL1Gateway targetGateway = primaryChainGateway; + targetGateway.sendMessage{value: sendMsgFee + _value}(_value, _callData, adapterParams); + emit MessageForwarded(targetGateway, _value, _callData); } - emit MessageForwarded(gateway, _value, _callData); } + /// @dev Deprecated after dencun upgrade function forwardMessage( IL1Gateway _gateway, uint256 _value, @@ -213,7 +229,8 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra tstore(forwardParams.slot, _forwardParams) } // Call the claim interface of source chain message service - // And it will inner call the `claimCallback` interface of source chain L1Gateway + // 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); } diff --git a/contracts/ZkLink.sol b/contracts/ZkLink.sol index 56118b9..d8362a9 100644 --- a/contracts/ZkLink.sol +++ b/contracts/ZkLink.sol @@ -84,9 +84,9 @@ contract ZkLink is public isEthWithdrawalFinalized; /// @dev The forward fee allocator address public forwardFeeAllocator; - /// @dev The range root hash of [fromBatchNumber, toBatchNumber] - /// The range = keccak256(abi.encodePacked(fromBatchNumber, toBatchNumber)) - mapping(bytes32 range => bytes32 rangeRootHash) public rangRootHashMap; + /// @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. @@ -110,8 +110,13 @@ 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 from primary chain. - event SyncRangeBatchRoot(uint256 fromBatchNumber, uint256 toBatchNumber, bytes32 rangeRootHash, 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 receive l2 tx hash from primary chain. event SyncL2TxHash(bytes32 l2TxHash, bytes32 primaryChainL2TxHash); /// @notice Emitted when validator withdraw forward fee @@ -464,32 +469,43 @@ contract ZkLink is } function syncRangeBatchRoot( - uint256 _fromBatchNumber, uint256 _toBatchNumber, bytes32 _rangeRootHash, uint256 _forwardEthAmount + 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)); - rangRootHashMap[range] = _rangeRootHash; - emit SyncRangeBatchRoot(_fromBatchNumber, _toBatchNumber, _rangeRootHash, _forwardEthAmount); - } - - function openRangeBatchRoot(uint256 _fromBatchNumber, uint256 _toBatchNumber, bytes32[] memory _l2LogsRootHashes) external onlyValidator { + 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 rangeRootHash = rangRootHashMap[range]; - require(rangeRootHash != bytes32(0), "Rang root hash not exist"); - uint256 rootHashLength = _l2LogsRootHashes.length; - require(rootHashLength == _toBatchNumber - _fromBatchNumber + 1, "Invalid root hashes length"); - bytes32 openRangeRootHash = _l2LogsRootHashes[0]; - l2LogsRootHashes[_fromBatchNumber] = openRangeRootHash; + 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 < rootHashLength; ++i) { - bytes32 l2LogsRootHash = _l2LogsRootHashes[i]; - l2LogsRootHashes[_fromBatchNumber + i] = l2LogsRootHash; - openRangeRootHash = Merkle._efficientHash(openRangeRootHash, l2LogsRootHash); + for (uint256 i = 1; i < rootHashesLength; ++i) { + bytes32 _l2LogsRootHash = _l2LogsRootHashes[i]; + l2LogsRootHashes[_fromBatchNumber + i] = _l2LogsRootHash; + _rangeBatchRootHash = Merkle._efficientHash(_rangeBatchRootHash, _l2LogsRootHash); } } - require(openRangeRootHash == rangeRootHash, "Incorrect range root hash"); + require(_rangeBatchRootHash == rangeBatchRootHash, "Incorrect root hash"); if (_toBatchNumber > totalBatchesExecuted) { totalBatchesExecuted = _toBatchNumber; } diff --git a/contracts/dev-contracts/DummyZkLink.sol b/contracts/dev-contracts/DummyZkLink.sol index 8d0be1b..4888416 100644 --- a/contracts/dev-contracts/DummyZkLink.sol +++ b/contracts/dev-contracts/DummyZkLink.sol @@ -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() { @@ -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); } diff --git a/contracts/interfaces/IArbitrator.sol b/contracts/interfaces/IArbitrator.sol index 3d29084..960ed5b 100644 --- a/contracts/interfaces/IArbitrator.sol +++ b/contracts/interfaces/IArbitrator.sol @@ -24,10 +24,10 @@ interface IArbitrator { /// @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 _targetChainAdapterParams Some params need to call canonical message service of target chain + /// @param _forwardParams Some params need to call canonical message service of target chain function claimMessage( address _sourceChainCanonicalMessageService, bytes calldata _sourceChainClaimCallData, - bytes memory _targetChainAdapterParams + bytes memory _forwardParams ) external payable; } diff --git a/contracts/interfaces/IZkLink.sol b/contracts/interfaces/IZkLink.sol index 3b4c0a1..26108ad 100644 --- a/contracts/interfaces/IZkLink.sol +++ b/contracts/interfaces/IZkLink.sol @@ -15,11 +15,17 @@ interface IZkLink { /// @param _forwardEthAmount The forward eth amount function syncBatchRoot(uint256 _batchNumber, bytes32 _l2LogsRootHash, uint256 _forwardEthAmount) external payable; - /// @notice Receive range batch root from primary chain + /// @notice Receive range batch root hash from primary chain /// @param _fromBatchNumber The batch number from /// @param _toBatchNumber The batch number to - /// @param _rangeRootHash The range root hash - function syncRangeBatchRoot(uint256 _fromBatchNumber, uint256 _toBatchNumber, bytes32 _rangeRootHash) external; + /// @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 diff --git a/contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol b/contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol index 77fc6a7..192c94c 100644 --- a/contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol +++ b/contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol @@ -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) public pure returns (bytes32 result) { assembly { mstore(0x00, _lhs) mstore(0x20, _rhs) From 03226653904c6261fa33ec85ed5950438173f50f Mon Sep 17 00:00:00 2001 From: zkbenny Date: Tue, 2 Apr 2024 10:53:19 +0800 Subject: [PATCH 05/12] update solhint --- .solhint.json | 3 ++- contracts/Arbitrator.sol | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.solhint.json b/.solhint.json index 48a3ace..72b2955 100644 --- a/.solhint.json +++ b/.solhint.json @@ -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" } } diff --git a/contracts/Arbitrator.sol b/contracts/Arbitrator.sol index 413d631..a7c0ef8 100644 --- a/contracts/Arbitrator.sol +++ b/contracts/Arbitrator.sol @@ -9,7 +9,6 @@ import {DoubleEndedQueueUpgradeable} from "@openzeppelin/contracts-upgradeable/u import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {IArbitrator} from "./interfaces/IArbitrator.sol"; import {IL1Gateway} from "./interfaces/IL1Gateway.sol"; -import {IZkLink} from "./interfaces/IZkLink.sol"; import {IAdmin} from "./zksync/l1-contracts/zksync/interfaces/IAdmin.sol"; import {IZkSync} from "./zksync/l1-contracts/zksync/interfaces/IZkSync.sol"; import {FeeParams} from "./zksync/l1-contracts/zksync/Storage.sol"; From 6c5cb384dafdf22259757d6332393473147488f2 Mon Sep 17 00:00:00 2001 From: zkbenny Date: Tue, 2 Apr 2024 11:27:19 +0800 Subject: [PATCH 06/12] resolve EthereumGateway send message --- contracts/Arbitrator.sol | 17 ++++++++++++++++- contracts/dev-contracts/DummyArbitrator.sol | 5 +++++ contracts/gateway/ethereum/EthereumGateway.sol | 2 +- contracts/interfaces/IArbitrator.sol | 9 ++++++++- 4 files changed, 30 insertions(+), 3 deletions(-) diff --git a/contracts/Arbitrator.sol b/contracts/Arbitrator.sol index a7c0ef8..62414a8 100644 --- a/contracts/Arbitrator.sol +++ b/contracts/Arbitrator.sol @@ -52,6 +52,8 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra event ValidatorStatusUpdate(IL1Gateway indexed gateway, address validatorAddress, bool isActive); /// @notice Fee params for L1->L2 transactions changed event NewFeeParams(IL1Gateway indexed gateway, FeeParams newFeeParams); + /// @notice Emit when receive message from l1 gateway + event MessageReceived(uint256 value, bytes callData); /// @notice Emit when forward message to l1 gateway event MessageForwarded(IL1Gateway indexed gateway, uint256 value, bytes callData); @@ -143,6 +145,20 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra emit NewFeeParams(_gateway, _newFeeParams); } + 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)); + IL1Gateway gateway = IL1Gateway(msg.sender); + if (gateway == primaryChainGateway) { + primaryChainMessageHashQueue.pushBack(finalizeMessageHash); + } else { + require(secondaryChainGateways[gateway], "Not secondary chain gateway"); + 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"); @@ -191,7 +207,6 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra } } - /// @dev Deprecated after dencun upgrade function forwardMessage( IL1Gateway _gateway, uint256 _value, diff --git a/contracts/dev-contracts/DummyArbitrator.sol b/contracts/dev-contracts/DummyArbitrator.sol index 6bd8a3f..cf8cba3 100644 --- a/contracts/dev-contracts/DummyArbitrator.sol +++ b/contracts/dev-contracts/DummyArbitrator.sol @@ -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); diff --git a/contracts/gateway/ethereum/EthereumGateway.sol b/contracts/gateway/ethereum/EthereumGateway.sol index 8f4dd82..603f4d9 100644 --- a/contracts/gateway/ethereum/EthereumGateway.sol +++ b/contracts/gateway/ethereum/EthereumGateway.sol @@ -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); } } diff --git a/contracts/interfaces/IArbitrator.sol b/contracts/interfaces/IArbitrator.sol index 960ed5b..a113283 100644 --- a/contracts/interfaces/IArbitrator.sol +++ b/contracts/interfaces/IArbitrator.sol @@ -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; From 98e229a711077c35b2b2f8ca8dcfcb2adbe74865 Mon Sep 17 00:00:00 2001 From: zkbenny Date: Tue, 2 Apr 2024 11:29:04 +0800 Subject: [PATCH 07/12] remove unused struct --- contracts/Arbitrator.sol | 5 ----- 1 file changed, 5 deletions(-) diff --git a/contracts/Arbitrator.sol b/contracts/Arbitrator.sol index 62414a8..410866e 100644 --- a/contracts/Arbitrator.sol +++ b/contracts/Arbitrator.sol @@ -18,11 +18,6 @@ import {FeeParams} from "./zksync/l1-contracts/zksync/Storage.sol"; contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, ReentrancyGuardUpgradeable { using DoubleEndedQueueUpgradeable for DoubleEndedQueueUpgradeable.Bytes32Deque; - struct GatewayAdapterParams { - IL1Gateway gateway; - bytes adapterParams; - } - /// @dev The gateway for sending message from ethereum to primary chain IL1Gateway public primaryChainGateway; /// @dev The gateway for sending message from ethereum to secondary chain From 5f8e98dacf8a7a4b5642e99df89c829c7c33503b Mon Sep 17 00:00:00 2001 From: zkbenny Date: Tue, 2 Apr 2024 18:29:26 +0800 Subject: [PATCH 08/12] revert merkle change --- contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol b/contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol index 192c94c..25984e2 100644 --- a/contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol +++ b/contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol @@ -37,7 +37,7 @@ library Merkle { } /// @dev Keccak hash of the concatenation of two 32-byte words - function _efficientHash(bytes32 _lhs, bytes32 _rhs) public pure returns (bytes32 result) { + function _efficientHash(bytes32 _lhs, bytes32 _rhs) internal pure returns (bytes32 result) { assembly { mstore(0x00, _lhs) mstore(0x20, _rhs) From 9bb3212ab06316575b9acd1708286e6a8abe2289 Mon Sep 17 00:00:00 2001 From: zkbenny Date: Wed, 3 Apr 2024 20:59:59 +0800 Subject: [PATCH 09/12] ignore compile of Arbitrator in zksync --- zksync/hardhat.config.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/zksync/hardhat.config.js b/zksync/hardhat.config.js index 63df337..bac9542 100644 --- a/zksync/hardhat.config.js +++ b/zksync/hardhat.config.js @@ -3,6 +3,8 @@ require('@matterlabs/hardhat-zksync-deploy'); require('@matterlabs/hardhat-zksync-solc'); require('@matterlabs/hardhat-zksync-verify'); require('@matterlabs/hardhat-zksync-upgradable'); +const { TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS } = require('hardhat/builtin-tasks/task-names'); +const path = require('path'); const fs = require('fs'); @@ -23,6 +25,16 @@ require('./script/deploy_l2_gateway'); require('./script/deploy_erc20_bridge'); const BaseConfig = require('../hardhat.base.config'); +const { subtask } = require('hardhat/config'); +subtask(TASK_COMPILE_SOLIDITY_GET_SOURCE_PATHS, async (_, { config }, runSuper) => { + const paths = await runSuper(); + + return paths.filter(solidityFilePath => { + const relativePath = path.relative(config.paths.sources, solidityFilePath); + + return relativePath !== 'Arbitrator.sol'; + }); +}); module.exports = Object.assign({}, BaseConfig, { zksolc: { From 12254109086906a43896153803de91efbaa6bbbe Mon Sep 17 00:00:00 2001 From: zkbenny Date: Sun, 7 Apr 2024 11:07:03 +0800 Subject: [PATCH 10/12] update transient storage --- contracts/Arbitrator.sol | 128 +++++++++++--------- contracts/dev-contracts/DummyArbitrator.sol | 9 +- contracts/interfaces/IArbitrator.sol | 8 +- 3 files changed, 89 insertions(+), 56 deletions(-) diff --git a/contracts/Arbitrator.sol b/contracts/Arbitrator.sol index 410866e..f955daa 100644 --- a/contracts/Arbitrator.sol +++ b/contracts/Arbitrator.sol @@ -28,8 +28,8 @@ 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 The forward params are used to forward a l2 message from source chain to target chains - bytes private forwardParams; + /// @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. @@ -143,13 +143,13 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra 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); } @@ -157,48 +157,12 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra /// @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"); - IL1Gateway sourceGateway = IL1Gateway(msg.sender); - // `forwardParams` is set in `claimMessage` - bytes memory _forwardParams; + // 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 { - _forwardParams := tload(forwardParams.slot) - } - // Ensure the caller is L1 gateway - if (sourceGateway == primaryChainGateway) { - // Unpack destination chain and final callData - bytes[] memory gatewayDataList = abi.decode(_callData, (bytes[])); - bytes[] memory gatewayForwardParamsList = abi.decode(_forwardParams, (bytes[])); - uint256 gatewayLength = gatewayDataList.length; - require(gatewayLength == gatewayForwardParamsList.length, "Invalid forward params length"); - 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"); - (uint256 sendMsgFee, bytes memory adapterParams) = abi.decode( - gatewayForwardParams, - (uint256, bytes) - ); - // Forward fee to send message - targetGateway.sendMessage{value: sendMsgFee + targetCallValue}( - targetCallValue, - targetCallData, - adapterParams - ); - emit MessageForwarded(targetGateway, targetCallValue, targetCallData); - } - } - } else { - require(secondaryChainGateways[sourceGateway], "Not secondary chain gateway"); - (uint256 sendMsgFee, bytes memory adapterParams) = abi.decode(_forwardParams, (uint256, bytes)); - // Forward fee to send message - IL1Gateway targetGateway = primaryChainGateway; - targetGateway.sendMessage{value: sendMsgFee + _value}(_value, _callData, adapterParams); - emit MessageForwarded(targetGateway, _value, _callData); + tstore(finalizeMessageHash.slot, _finalizeMessageHash) } } @@ -208,9 +172,9 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra 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"); @@ -219,7 +183,7 @@ 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 @@ -231,16 +195,72 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra function claimMessage( address _sourceChainCanonicalMessageService, bytes calldata _sourceChainClaimCallData, - bytes memory _forwardParams + IL1Gateway _sourceChainL1Gateway, + uint256 _receiveValue, + bytes calldata _receiveCallData, + bytes calldata _forwardParams ) external payable nonReentrant onlyRelayer { - // The `forwardParams` will be cleared after tx executed - assembly { - tstore(forwardParams.slot, _forwardParams) - } // 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); + } } } diff --git a/contracts/dev-contracts/DummyArbitrator.sol b/contracts/dev-contracts/DummyArbitrator.sol index cf8cba3..1d5dad8 100644 --- a/contracts/dev-contracts/DummyArbitrator.sol +++ b/contracts/dev-contracts/DummyArbitrator.sol @@ -39,7 +39,14 @@ contract DummyArbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Re _gateway.sendMessage{value: msg.value + _value}(_value, _callData, _adapterParams); } - function claimMessage(address, bytes calldata, bytes memory) external payable { + function claimMessage( + address, + bytes calldata, + IL1Gateway, + uint256, + bytes calldata, + bytes calldata + ) external payable { // do nothing } } diff --git a/contracts/interfaces/IArbitrator.sol b/contracts/interfaces/IArbitrator.sol index a113283..5d13d7c 100644 --- a/contracts/interfaces/IArbitrator.sol +++ b/contracts/interfaces/IArbitrator.sol @@ -31,10 +31,16 @@ interface IArbitrator { /// @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, - bytes memory _forwardParams + IL1Gateway _sourceChainL1Gateway, + uint256 _receiveValue, + bytes calldata _receiveCallData, + bytes calldata _forwardParams ) external payable; } From 23a36a3b9cd2f63ba7e0658ac6c554b84291bb74 Mon Sep 17 00:00:00 2001 From: zkbenny Date: Mon, 8 Apr 2024 10:10:28 +0800 Subject: [PATCH 11/12] emit event when open range batch root --- contracts/ZkLink.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/ZkLink.sol b/contracts/ZkLink.sol index d8362a9..443be1e 100644 --- a/contracts/ZkLink.sol +++ b/contracts/ZkLink.sol @@ -117,6 +117,8 @@ contract ZkLink is 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 @@ -506,9 +508,11 @@ contract ZkLink is } } 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 { From b8a850fe60efe58923cddc56afdddb43c1e687d7 Mon Sep 17 00:00:00 2001 From: zkbenny Date: Thu, 11 Apr 2024 11:21:21 +0800 Subject: [PATCH 12/12] allow to receive more value on op stack --- contracts/gateway/optimism/OptimismL1Gateway.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/gateway/optimism/OptimismL1Gateway.sol b/contracts/gateway/optimism/OptimismL1Gateway.sol index aefbf6a..a56b2f7 100644 --- a/contracts/gateway/optimism/OptimismL1Gateway.sol +++ b/contracts/gateway/optimism/OptimismL1Gateway.sol @@ -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); }