Skip to content

Commit 131834e

Browse files
authored
Merge pull request #90 from zkLinkProtocol/arbitratorUpgrade
Arbitrator upgrade
2 parents a523286 + b8a850f commit 131834e

File tree

12 files changed

+251
-14
lines changed

12 files changed

+251
-14
lines changed

.solhint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"max-states-count": "off",
1010
"no-inline-assembly": "off",
1111
"not-rely-on-time": "off",
12-
"no-empty-blocks": "off"
12+
"no-empty-blocks": "off",
13+
"avoid-tx-origin": "off"
1314
}
1415
}

contracts/Arbitrator.sol

Lines changed: 95 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/Own
66
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
77
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
88
import {DoubleEndedQueueUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/structs/DoubleEndedQueueUpgradeable.sol";
9+
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
910
import {IArbitrator} from "./interfaces/IArbitrator.sol";
1011
import {IL1Gateway} from "./interfaces/IL1Gateway.sol";
1112
import {IAdmin} from "./zksync/l1-contracts/zksync/interfaces/IAdmin.sol";
@@ -27,12 +28,14 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra
2728
mapping(IL1Gateway => DoubleEndedQueueUpgradeable.Bytes32Deque) public secondaryChainMessageHashQueues;
2829
/// @notice List of permitted relayers
2930
mapping(address relayerAddress => bool isRelayer) public relayers;
31+
/// @dev A transient storage value for forwarding message from source chain to target chains
32+
bytes32 private finalizeMessageHash;
3033
/**
3134
* @dev This empty reserved space is put in place to allow future versions to add new
3235
* variables without shifting down storage in the inheritance chain.
3336
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
3437
*/
35-
uint256[50] private __gap;
38+
uint256[49] private __gap;
3639

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

140-
function receiveMessage(uint256 _value, bytes calldata _callData) external payable {
143+
function enqueueMessage(uint256 _value, bytes calldata _callData) external payable {
141144
require(msg.value == _value, "Invalid msg value");
142145
// store message hash for forwarding
143-
bytes32 finalizeMessageHash = keccak256(abi.encode(_value, _callData));
146+
bytes32 _finalizeMessageHash = keccak256(abi.encode(_value, _callData));
144147
IL1Gateway gateway = IL1Gateway(msg.sender);
145148
if (gateway == primaryChainGateway) {
146-
primaryChainMessageHashQueue.pushBack(finalizeMessageHash);
149+
primaryChainMessageHashQueue.pushBack(_finalizeMessageHash);
147150
} else {
148151
require(secondaryChainGateways[gateway], "Not secondary chain gateway");
149-
secondaryChainMessageHashQueues[gateway].pushBack(finalizeMessageHash);
152+
secondaryChainMessageHashQueues[gateway].pushBack(_finalizeMessageHash);
150153
}
151154
emit MessageReceived(_value, _callData);
152155
}
153156

157+
/// @dev This function is called within the `claimMessageCallback` of L1 gateway
158+
function receiveMessage(uint256 _value, bytes calldata _callData) external payable {
159+
require(msg.value == _value, "Invalid msg value");
160+
// temporary store message hash for forwarding
161+
IL1Gateway gateway = IL1Gateway(msg.sender);
162+
require(gateway == primaryChainGateway || secondaryChainGateways[gateway], "Invalid gateway");
163+
bytes32 _finalizeMessageHash = keccak256(abi.encode(msg.sender, _value, _callData));
164+
assembly {
165+
tstore(finalizeMessageHash.slot, _finalizeMessageHash)
166+
}
167+
}
168+
154169
function forwardMessage(
155170
IL1Gateway _gateway,
156171
uint256 _value,
157172
bytes calldata _callData,
158173
bytes calldata _adapterParams
159174
) external payable nonReentrant onlyRelayer {
160-
bytes32 finalizeMessageHash = keccak256(abi.encode(_value, _callData));
175+
bytes32 _finalizeMessageHash = keccak256(abi.encode(_value, _callData));
161176
if (_gateway == primaryChainGateway) {
162-
require(finalizeMessageHash == primaryChainMessageHashQueue.popFront(), "Invalid finalize message hash");
177+
require(_finalizeMessageHash == primaryChainMessageHashQueue.popFront(), "Invalid finalize message hash");
163178
// Unpack destination chain and final callData
164179
(IL1Gateway secondaryChainGateway, bytes memory finalCallData) = abi.decode(_callData, (IL1Gateway, bytes));
165180
require(secondaryChainGateways[secondaryChainGateway], "Invalid secondary chain gateway");
@@ -168,12 +183,84 @@ contract Arbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Reentra
168183
} else {
169184
require(secondaryChainGateways[_gateway], "Not secondary chain gateway");
170185
require(
171-
finalizeMessageHash == secondaryChainMessageHashQueues[_gateway].popFront(),
186+
_finalizeMessageHash == secondaryChainMessageHashQueues[_gateway].popFront(),
172187
"Invalid finalize message hash"
173188
);
174189
// Forward fee to send message
175190
primaryChainGateway.sendMessage{value: msg.value + _value}(_value, _callData, _adapterParams);
176191
}
177192
emit MessageForwarded(_gateway, _value, _callData);
178193
}
194+
195+
function claimMessage(
196+
address _sourceChainCanonicalMessageService,
197+
bytes calldata _sourceChainClaimCallData,
198+
IL1Gateway _sourceChainL1Gateway,
199+
uint256 _receiveValue,
200+
bytes calldata _receiveCallData,
201+
bytes calldata _forwardParams
202+
) external payable nonReentrant onlyRelayer {
203+
// Call the claim interface of source chain message service
204+
// And it will inner call the `claimMessageCallback` interface of source chain L1Gateway
205+
// In the `claimMessageCallback` of L1Gateway, it will inner call `receiveMessage` of Arbitrator
206+
// No use of return value
207+
Address.functionCall(_sourceChainCanonicalMessageService, _sourceChainClaimCallData);
208+
209+
// Load the transient `finalizeMessageHash`
210+
bytes32 _finalizeMessageHash;
211+
assembly {
212+
_finalizeMessageHash := tload(finalizeMessageHash.slot)
213+
}
214+
require(
215+
_finalizeMessageHash == keccak256(abi.encode(_sourceChainL1Gateway, _receiveValue, _receiveCallData)),
216+
"Incorrect finalize data"
217+
);
218+
219+
// The msg value should be equal to the combined cost of all messages delivered from l1 to l2
220+
// The excess fees will be refunded to the relayer by rollup canonical message service
221+
if (_sourceChainL1Gateway == primaryChainGateway) {
222+
// Unpack destination chain and final callData
223+
bytes[] memory gatewayDataList = abi.decode(_receiveCallData, (bytes[]));
224+
bytes[] memory gatewayForwardParamsList = abi.decode(_forwardParams, (bytes[]));
225+
uint256 gatewayLength = gatewayDataList.length;
226+
require(gatewayLength == gatewayForwardParamsList.length, "Invalid forward params length");
227+
uint256 totalCallValue;
228+
uint256 totalSendMsgFee;
229+
unchecked {
230+
for (uint256 i = 0; i < gatewayLength; ++i) {
231+
bytes memory gatewayData = gatewayDataList[i];
232+
bytes memory gatewayForwardParams = gatewayForwardParamsList[i];
233+
(IL1Gateway targetGateway, uint256 targetCallValue, bytes memory targetCallData) = abi.decode(
234+
gatewayData,
235+
(IL1Gateway, uint256, bytes)
236+
);
237+
require(secondaryChainGateways[targetGateway], "Invalid secondary chain gateway");
238+
totalCallValue += targetCallValue;
239+
(uint256 sendMsgFee, bytes memory adapterParams) = abi.decode(
240+
gatewayForwardParams,
241+
(uint256, bytes)
242+
);
243+
totalSendMsgFee += sendMsgFee;
244+
// Forward fee to send message
245+
targetGateway.sendMessage{value: sendMsgFee + targetCallValue}(
246+
targetCallValue,
247+
targetCallData,
248+
adapterParams
249+
);
250+
emit MessageForwarded(targetGateway, targetCallValue, targetCallData);
251+
}
252+
}
253+
require(totalCallValue == _receiveValue, "Invalid call value");
254+
require(totalSendMsgFee == msg.value, "Invalid send msg fee");
255+
} else {
256+
IL1Gateway targetGateway = primaryChainGateway;
257+
// Forward fee to send message
258+
targetGateway.sendMessage{value: msg.value + _receiveValue}(
259+
_receiveValue,
260+
_receiveCallData,
261+
_forwardParams
262+
);
263+
emit MessageForwarded(targetGateway, _receiveValue, _receiveCallData);
264+
}
265+
}
179266
}

contracts/ZkLink.sol

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,15 @@ contract ZkLink is
8484
public isEthWithdrawalFinalized;
8585
/// @dev The forward fee allocator
8686
address public forwardFeeAllocator;
87+
/// @dev The range batch root hash of [fromBatchNumber, toBatchNumber]
88+
/// The key is keccak256(abi.encodePacked(fromBatchNumber, toBatchNumber))
89+
mapping(bytes32 range => bytes32 rangeBatchRootHash) public rangBatchRootHashes;
8790
/**
8891
* @dev This empty reserved space is put in place to allow future versions to add new
8992
* variables without shifting down storage in the inheritance chain.
9093
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
9194
*/
92-
uint256[50] private __gap;
95+
uint256[49] private __gap;
9396

9497
/// @notice Gateway init
9598
event InitGateway(IL2Gateway indexed gateway);
@@ -107,6 +110,15 @@ contract ZkLink is
107110
event SyncL2Requests(uint256 totalSyncedPriorityTxs, bytes32 syncHash, uint256 forwardEthAmount);
108111
/// @notice Emitted when receive batch root from primary chain.
109112
event SyncBatchRoot(uint256 batchNumber, bytes32 l2LogsRootHash, uint256 forwardEthAmount);
113+
/// @notice Emitted when receive range batch root hash from primary chain.
114+
event SyncRangeBatchRoot(
115+
uint256 fromBatchNumber,
116+
uint256 toBatchNumber,
117+
bytes32 rangeBatchRootHash,
118+
uint256 forwardEthAmount
119+
);
120+
/// @notice Emitted when open range batch root hash.
121+
event OpenRangeBatchRoot(uint256 fromBatchNumber, uint256 toBatchNumber);
110122
/// @notice Emitted when receive l2 tx hash from primary chain.
111123
event SyncL2TxHash(bytes32 l2TxHash, bytes32 primaryChainL2TxHash);
112124
/// @notice Emitted when validator withdraw forward fee
@@ -458,6 +470,51 @@ contract ZkLink is
458470
emit SyncBatchRoot(_batchNumber, _l2LogsRootHash, _forwardEthAmount);
459471
}
460472

473+
function syncRangeBatchRoot(
474+
uint256 _fromBatchNumber,
475+
uint256 _toBatchNumber,
476+
bytes32 _rangeBatchRootHash,
477+
uint256 _forwardEthAmount
478+
) external payable onlyGateway {
479+
require(_toBatchNumber >= _fromBatchNumber, "Invalid range");
480+
require(msg.value == _forwardEthAmount, "Invalid forward amount");
481+
bytes32 range = keccak256(abi.encodePacked(_fromBatchNumber, _toBatchNumber));
482+
rangBatchRootHashes[range] = _rangeBatchRootHash;
483+
emit SyncRangeBatchRoot(_fromBatchNumber, _toBatchNumber, _rangeBatchRootHash, _forwardEthAmount);
484+
}
485+
486+
/// @dev Unzip the root hashes in the range
487+
/// @param _fromBatchNumber The batch number from
488+
/// @param _toBatchNumber The batch number to
489+
/// @param _l2LogsRootHashes The l2LogsRootHash list in the range [`_fromBatchNumber`, `_toBatchNumber`]
490+
function openRangeBatchRootHash(
491+
uint256 _fromBatchNumber,
492+
uint256 _toBatchNumber,
493+
bytes32[] memory _l2LogsRootHashes
494+
) external onlyValidator {
495+
require(_toBatchNumber >= _fromBatchNumber, "Invalid range");
496+
bytes32 range = keccak256(abi.encodePacked(_fromBatchNumber, _toBatchNumber));
497+
bytes32 rangeBatchRootHash = rangBatchRootHashes[range];
498+
require(rangeBatchRootHash != bytes32(0), "Rang batch root hash not exist");
499+
uint256 rootHashesLength = _l2LogsRootHashes.length;
500+
require(rootHashesLength == _toBatchNumber - _fromBatchNumber + 1, "Invalid root hashes length");
501+
bytes32 _rangeBatchRootHash = _l2LogsRootHashes[0];
502+
l2LogsRootHashes[_fromBatchNumber] = _rangeBatchRootHash;
503+
unchecked {
504+
for (uint256 i = 1; i < rootHashesLength; ++i) {
505+
bytes32 _l2LogsRootHash = _l2LogsRootHashes[i];
506+
l2LogsRootHashes[_fromBatchNumber + i] = _l2LogsRootHash;
507+
_rangeBatchRootHash = Merkle._efficientHash(_rangeBatchRootHash, _l2LogsRootHash);
508+
}
509+
}
510+
require(_rangeBatchRootHash == rangeBatchRootHash, "Incorrect root hash");
511+
delete rangBatchRootHashes[range];
512+
if (_toBatchNumber > totalBatchesExecuted) {
513+
totalBatchesExecuted = _toBatchNumber;
514+
}
515+
emit OpenRangeBatchRoot(_fromBatchNumber, _toBatchNumber);
516+
}
517+
461518
function syncL2TxHash(bytes32 _l2TxHash, bytes32 _primaryChainL2TxHash) external onlyGateway {
462519
l2TxHashMap[_l2TxHash] = _primaryChainL2TxHash;
463520
emit SyncL2TxHash(_l2TxHash, _primaryChainL2TxHash);

contracts/dev-contracts/DummyArbitrator.sol

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ contract DummyArbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Re
1919

2020
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
2121

22+
function enqueueMessage(uint256 _value, bytes calldata _callData) external payable {
23+
require(msg.value == _value, "Invalid msg value");
24+
emit ReceiveMessage(_value, _callData);
25+
}
26+
2227
function receiveMessage(uint256 _value, bytes calldata _callData) external payable {
2328
require(msg.value == _value, "Invalid msg value");
2429
emit ReceiveMessage(_value, _callData);
@@ -33,4 +38,15 @@ contract DummyArbitrator is IArbitrator, OwnableUpgradeable, UUPSUpgradeable, Re
3338
// Forward fee to send message
3439
_gateway.sendMessage{value: msg.value + _value}(_value, _callData, _adapterParams);
3540
}
41+
42+
function claimMessage(
43+
address,
44+
bytes calldata,
45+
IL1Gateway,
46+
uint256,
47+
bytes calldata,
48+
bytes calldata
49+
) external payable {
50+
// do nothing
51+
}
3652
}

contracts/dev-contracts/DummyZkLink.sol

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,12 @@ contract DummyZkLink is IZkLink, OwnableUpgradeable, UUPSUpgradeable, Reentrancy
1313
IL2Gateway public gateway;
1414

1515
event ReceiveBatchRoot(uint256 batchNumber, bytes32 l2LogsRootHash, uint256 forwardEthAmount);
16+
event ReceiveRangeBatchRoot(
17+
uint256 fromBatchNumber,
18+
uint256 toBatchNumber,
19+
bytes32 rangeBatchRootHash,
20+
uint256 forwardEthAmount
21+
);
1622
event ReceiveL2TxHash(bytes32 l2TxHash, bytes32 primaryChainL2TxHash);
1723

1824
modifier onlyGateway() {
@@ -55,6 +61,15 @@ contract DummyZkLink is IZkLink, OwnableUpgradeable, UUPSUpgradeable, Reentrancy
5561
emit ReceiveBatchRoot(_batchNumber, _l2LogsRootHash, _forwardEthAmount);
5662
}
5763

64+
function syncRangeBatchRoot(
65+
uint256 _fromBatchNumber,
66+
uint256 _toBatchNumber,
67+
bytes32 _rangeBatchRootHash,
68+
uint256 _forwardEthAmount
69+
) external payable {
70+
emit ReceiveRangeBatchRoot(_fromBatchNumber, _toBatchNumber, _rangeBatchRootHash, _forwardEthAmount);
71+
}
72+
5873
function syncL2TxHash(bytes32 _l2TxHash, bytes32 _primaryChainL2TxHash) external onlyGateway {
5974
emit ReceiveL2TxHash(_l2TxHash, _primaryChainL2TxHash);
6075
}

contracts/gateway/ethereum/EthereumGateway.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ contract EthereumGateway is
4242
function sendMessage(uint256 _value, bytes calldata _callData) external payable override onlyZkLink {
4343
require(msg.value == _value, "Invalid value");
4444
// Forward message to arbitrator
45-
ARBITRATOR.receiveMessage{value: _value}(_value, _callData);
45+
ARBITRATOR.enqueueMessage{value: _value}(_value, _callData);
4646
emit L2GatewayMessageSent(_value, _callData);
4747
}
4848
}

contracts/gateway/optimism/OptimismL1Gateway.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ contract OptimismL1Gateway is L1BaseGateway, OptimismGateway {
3434
uint256 _value,
3535
bytes calldata _callData
3636
) external payable onlyMessageService onlyRemoteGateway {
37-
require(msg.value == _value, "Invalid value");
37+
// Blast will return more value(the stake profit) than burned on L2
38+
require(msg.value >= _value, "Invalid value");
3839
// Forward message to arbitrator
3940
ARBITRATOR.receiveMessage{value: _value}(_value, _callData);
4041
}

contracts/interfaces/IArbitrator.sol

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,14 @@ pragma solidity ^0.8.0;
44
import {IL1Gateway} from "./IL1Gateway.sol";
55

66
interface IArbitrator {
7-
/// @notice Receive message from one L1 gateway to another L1 gateway
7+
/// @notice Enqueue message from EthereumGateway
8+
/// @dev Used by EthereumGateway to temporarily store message
9+
/// @param _value The msg value
10+
/// @param _callData The call data
11+
function enqueueMessage(uint256 _value, bytes calldata _callData) external payable;
12+
13+
/// @notice Deliver message from one L1 gateway to another L1 gateway
14+
/// @dev Used by L1Gateways to deliver message
815
/// @param _value The msg value
916
/// @param _callData The call data
1017
function receiveMessage(uint256 _value, bytes calldata _callData) external payable;
@@ -20,4 +27,20 @@ interface IArbitrator {
2027
bytes calldata _callData,
2128
bytes calldata _adapterParams
2229
) external payable;
30+
31+
/// @notice Claim a message of source chain and deliver it to the target chain
32+
/// @param _sourceChainCanonicalMessageService The message service to claim message
33+
/// @param _sourceChainClaimCallData The call data that need to claim message from source chain
34+
/// @param _sourceChainL1Gateway The msg.sender passed in the `receiveMessage` interface
35+
/// @param _receiveValue The value passed in the `receiveMessage` interface
36+
/// @param _receiveCallData The call data passed in the `receiveMessage` interface
37+
/// @param _forwardParams Some params need to call canonical message service of target chain
38+
function claimMessage(
39+
address _sourceChainCanonicalMessageService,
40+
bytes calldata _sourceChainClaimCallData,
41+
IL1Gateway _sourceChainL1Gateway,
42+
uint256 _receiveValue,
43+
bytes calldata _receiveCallData,
44+
bytes calldata _forwardParams
45+
) external payable;
2346
}

contracts/interfaces/IZkLink.sol

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,18 @@ interface IZkLink {
1515
/// @param _forwardEthAmount The forward eth amount
1616
function syncBatchRoot(uint256 _batchNumber, bytes32 _l2LogsRootHash, uint256 _forwardEthAmount) external payable;
1717

18+
/// @notice Receive range batch root hash from primary chain
19+
/// @param _fromBatchNumber The batch number from
20+
/// @param _toBatchNumber The batch number to
21+
/// @param _rangeBatchRootHash The accumulation hash of l2LogsRootHash in the range [`_fromBatchNumber`, `_toBatchNumber`]
22+
/// @param _forwardEthAmount The forward eth amount
23+
function syncRangeBatchRoot(
24+
uint256 _fromBatchNumber,
25+
uint256 _toBatchNumber,
26+
bytes32 _rangeBatchRootHash,
27+
uint256 _forwardEthAmount
28+
) external payable;
29+
1830
/// @notice Receive l2 tx hash from primary chain
1931
/// @param _l2TxHash The l2 tx hash on local chain
2032
/// @param _primaryChainL2TxHash The l2 tx hash on primary chain

contracts/zksync/l1-contracts/zksync/libraries/Merkle.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ library Merkle {
3737
}
3838

3939
/// @dev Keccak hash of the concatenation of two 32-byte words
40-
function _efficientHash(bytes32 _lhs, bytes32 _rhs) private pure returns (bytes32 result) {
40+
function _efficientHash(bytes32 _lhs, bytes32 _rhs) internal pure returns (bytes32 result) {
4141
assembly {
4242
mstore(0x00, _lhs)
4343
mstore(0x20, _rhs)

0 commit comments

Comments
 (0)