mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 23:47:55 -05:00
Feat/1076 refactor and allow overriding (#1079)
* allow tokenbridge overrides * add L1MessageService overrides * refactor L2 MessageService * refactor L2 MessageService V1 * use correct modifier * refactor LineaRollup for overriding * allow other overrides * reinstate general in pause on tokenbridge * add missing NatSpec * sample overrides * add generic bridge and document placeholder * documentation and folder placement * documentation cleanup * use imported references * use variable pragma for inherited contracts * reset pragmas for some * use base abstract contracts with version overrides * use TokenBridgeBase as abstract * Update contracts/src/bridging/token/TokenBridgeBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/bridging/token/TokenBridgeBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/bridging/token/TokenBridgeBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/bridging/token/interfaces/ITokenBridge.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/messaging/l2/L2MessageServiceBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/messaging/l2/v1/interfaces/IL2MessageServiceV1.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/rollup/interfaces/ILineaRollup.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/rollup/LineaRollupBase.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/_testing/unit/bridging/InheritingTokenBridge.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierDev.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierForDataAggregation.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierForMultiTypeDataAggregation.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierMainnetFull.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * Update contracts/src/verifiers/PlonkVerifierSepoliaFull.sol Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com> Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> * linting * allow submitDataAsCalldata overriding * address missing test coverage * adjust gap name for storage clarity --------- Signed-off-by: The Dark Jester <thedarkjester@users.noreply.github.com> Co-authored-by: Victorien Gauch <85494462+VGau@users.noreply.github.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { IPlonkVerifier } from "../../verifiers/interfaces/IPlonkVerifier.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { LineaScenarioTesting } from "./LineaScenarioTesting.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/// @dev Test scenarios on Linea.
|
||||
contract LineaScenarioTesting {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { IPlonkVerifier } from "../../../verifiers/interfaces/IPlonkVerifier.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { IMessageService } from "../../../messaging/interfaces/IMessageService.sol";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
contract TestL1RevertContract {
|
||||
function errorWithMessage() external pure {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
contract TestReceivingContract {
|
||||
fallback() external payable {}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { IMessageService } from "../../../messaging/interfaces/IMessageService.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { IMessageService } from "../../../messaging/interfaces/IMessageService.sol";
|
||||
import { IGenericErrors } from "../../../interfaces/IGenericErrors.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { TokenBridge } from "../../../bridging/token/TokenBridge.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { TokenBridge } from "../../../bridging/token/TokenBridge.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { BridgedToken } from "../../../bridging/token/BridgedToken.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { ReentrancyContract } from "./ReentrancyContract.sol";
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
interface ITestExternalCalls {
|
||||
function revertWithError() external pure;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
|
||||
import { TokenBridgeBase } from "../../../bridging/token/TokenBridgeBase.sol";
|
||||
|
||||
contract InheritingL1TokenBridge is TokenBridgeBase {
|
||||
enum BridingStatus {
|
||||
REQUESTED,
|
||||
READY,
|
||||
COMPLETE
|
||||
}
|
||||
|
||||
mapping(address tokenAddress => address escrowAddress) escrowAddresses;
|
||||
mapping(bytes32 bridgingHash => BridingStatus status) public bridgingStatuses;
|
||||
|
||||
function initialize(
|
||||
InitializationData calldata _initializationData
|
||||
)
|
||||
external
|
||||
nonZeroAddress(_initializationData.messageService)
|
||||
nonZeroAddress(_initializationData.tokenBeacon)
|
||||
nonZeroChainId(_initializationData.sourceChainId)
|
||||
nonZeroChainId(_initializationData.targetChainId)
|
||||
initializer
|
||||
{
|
||||
// New custom initialization behavior here.
|
||||
__TokenBridge_init(_initializationData);
|
||||
}
|
||||
|
||||
function _getEscrowAddress(address _token) internal view virtual override returns (address escrowAddress) {
|
||||
// Overriden to allow the movement of specific assets to custom escrow services.
|
||||
|
||||
escrowAddress = escrowAddresses[_token];
|
||||
if (escrowAddress == address(0)) {
|
||||
escrowAddress = address(this);
|
||||
}
|
||||
}
|
||||
|
||||
// Implementor to add security.
|
||||
function setEscrowAddress(address _token, address _escrowAddress) external {
|
||||
// Validate fields
|
||||
// TBC check not already set?
|
||||
escrowAddresses[_token] = _escrowAddress;
|
||||
|
||||
// event emitted
|
||||
}
|
||||
|
||||
function finalizeBridging(
|
||||
address _nativeToken,
|
||||
uint256 _amount,
|
||||
address _recipient,
|
||||
uint256 _chainId,
|
||||
bytes calldata _tokenMetadata
|
||||
) external {
|
||||
// New custom function to finalize withdrawal if a multi-step approach is taken.
|
||||
}
|
||||
|
||||
function _completeBridging(
|
||||
address _nativeToken,
|
||||
uint256 _amount,
|
||||
address _recipient,
|
||||
uint256 _chainId,
|
||||
bytes calldata _tokenMetadata
|
||||
) internal virtual override {
|
||||
// Similar approach to the ETH bridge with hashing and withdrawal flow management.
|
||||
}
|
||||
}
|
||||
135
contracts/src/_testing/unit/bridging/l1/L1GenericBridge.sol
Normal file
135
contracts/src/_testing/unit/bridging/l1/L1GenericBridge.sol
Normal file
@@ -0,0 +1,135 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { MessageServiceBase } from "../../../../messaging/MessageServiceBase.sol";
|
||||
import { L2GenericBridge } from "../l2/L2GenericBridge.sol"; // Ideally a simple interface.
|
||||
|
||||
// Contract could be made upgradable with access control etc by implementors.
|
||||
contract L1GenericBridge is MessageServiceBase {
|
||||
uint256 public withdrawalDelay;
|
||||
address public escrowAddress;
|
||||
|
||||
enum BridingStatus {
|
||||
UNKNOWN,
|
||||
REQUESTED,
|
||||
COMPLETE
|
||||
}
|
||||
|
||||
enum WithdrawalOption {
|
||||
WAIT,
|
||||
IMMEDIATE
|
||||
}
|
||||
|
||||
mapping(bytes32 bridgingHash => BridingStatus status) public bridgingStatuses;
|
||||
|
||||
// Implementors would add access control.
|
||||
function setEscrowAddress(address _escrowAddress) external {
|
||||
if (escrowAddress == _escrowAddress) {
|
||||
revert("already set"); // error averts weird events for set address.
|
||||
}
|
||||
|
||||
escrowAddress = _escrowAddress;
|
||||
// emit events
|
||||
}
|
||||
|
||||
// Implementors would add access control.
|
||||
function setWithdrawalDelay(uint256 _withdrawalDelay) external {
|
||||
if (withdrawalDelay == _withdrawalDelay) {
|
||||
revert("already set"); // error averts weird events for set address.
|
||||
}
|
||||
|
||||
withdrawalDelay = _withdrawalDelay;
|
||||
// emit events
|
||||
}
|
||||
|
||||
// Implementors would add any additional modifiers ( pause etc ).
|
||||
function bridgeEth(address _to) external payable {
|
||||
// get next message number / nonce - this should come from the message service.
|
||||
uint256 nextMessageNumber;
|
||||
|
||||
messageService.sendMessage(
|
||||
remoteSender,
|
||||
0,
|
||||
abi.encodeCall(L2GenericBridge.receiveDepositedEth, (_to, msg.sender, msg.value, nextMessageNumber))
|
||||
);
|
||||
|
||||
// emit events etc
|
||||
}
|
||||
|
||||
// This would be called by the message service (onlyMessagingService)
|
||||
// with a check to make sure it came from the L1 Bridge (onlyAuthorizedRemoteSender)
|
||||
// The message service checks reentry already
|
||||
function receiveDepositedEth(
|
||||
address _to,
|
||||
address _from,
|
||||
uint256 _value,
|
||||
uint256 _nonce,
|
||||
WithdrawalOption _option
|
||||
) external onlyMessagingService onlyAuthorizedRemoteSender {
|
||||
uint256 minWithdrawTime = block.timestamp;
|
||||
|
||||
if (_option == WithdrawalOption.WAIT) {
|
||||
unchecked {
|
||||
minWithdrawTime += withdrawalDelay;
|
||||
}
|
||||
}
|
||||
|
||||
// hashing to be optimized
|
||||
bytes32 bridgingHash = keccak256(abi.encode(_to, _from, _value, _nonce, _option, minWithdrawTime));
|
||||
|
||||
if (bridgingStatuses[bridgingHash] != BridingStatus.UNKNOWN) {
|
||||
// reentry not allowed
|
||||
revert();
|
||||
}
|
||||
|
||||
if (_option == WithdrawalOption.WAIT) {
|
||||
bridgingStatuses[bridgingHash] = BridingStatus.REQUESTED;
|
||||
// emit requested
|
||||
} else {
|
||||
bridgingStatuses[bridgingHash] = BridingStatus.COMPLETE;
|
||||
// call escrowAddress for immediate transfer.
|
||||
}
|
||||
}
|
||||
|
||||
function completeWithdrawal(
|
||||
address _to,
|
||||
address _from,
|
||||
uint256 _value,
|
||||
uint256 _nonce,
|
||||
WithdrawalOption _option,
|
||||
uint256 _minWithdrawTime
|
||||
) external payable {
|
||||
if (block.timestamp < _minWithdrawTime) {
|
||||
revert("too early");
|
||||
}
|
||||
|
||||
if (_value != msg.value) {
|
||||
revert("value is wrong");
|
||||
}
|
||||
|
||||
// hashing to be optimized
|
||||
bytes32 bridgingHash = keccak256(abi.encode(_to, _from, _value, _nonce, _option, _minWithdrawTime));
|
||||
|
||||
if (bridgingStatuses[bridgingHash] != BridingStatus.REQUESTED) {
|
||||
// reentry not allowed
|
||||
revert();
|
||||
}
|
||||
|
||||
bridgingStatuses[bridgingHash] = BridingStatus.COMPLETE;
|
||||
|
||||
(bool callSuccess, bytes memory returnData) = _to.call{ value: _value }("");
|
||||
if (!callSuccess) {
|
||||
if (returnData.length > 0) {
|
||||
assembly {
|
||||
let data_size := mload(returnData)
|
||||
revert(add(32, returnData), data_size)
|
||||
}
|
||||
} else {
|
||||
//revert MessageSendingFailed(_to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// function: add ETH for user withdrawal (admin only)
|
||||
function addWithdrawalEth() external payable {}
|
||||
}
|
||||
33
contracts/src/_testing/unit/bridging/l2/L2GenericBridge.sol
Normal file
33
contracts/src/_testing/unit/bridging/l2/L2GenericBridge.sol
Normal file
@@ -0,0 +1,33 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { MessageServiceBase } from "../../../../messaging/MessageServiceBase.sol";
|
||||
import { L1GenericBridge } from "../l1/L1GenericBridge.sol"; // Ideally a simple interface.
|
||||
|
||||
contract L2GenericBridge is MessageServiceBase {
|
||||
// initialize etc
|
||||
|
||||
// add security
|
||||
function bridgeEth(address _to, L1GenericBridge.WithdrawalOption _option) external payable {
|
||||
// get next message number / nonce;
|
||||
uint256 nextMessageNumber;
|
||||
|
||||
messageService.sendMessage(
|
||||
remoteSender,
|
||||
0,
|
||||
abi.encodeCall(L1GenericBridge.receiveDepositedEth, (_to, msg.sender, msg.value, nextMessageNumber, _option))
|
||||
);
|
||||
}
|
||||
|
||||
// This would be called by the message service (onlyMessagingService)
|
||||
// with a check to make sure it came from the L1 Bridge (onlyAuthorizedRemoteSender)
|
||||
// The message service checks reentry already
|
||||
function receiveDepositedEth(
|
||||
address _to,
|
||||
address _from,
|
||||
uint256 _value,
|
||||
uint256 _nonce
|
||||
) external onlyMessagingService onlyAuthorizedRemoteSender {
|
||||
// Transfer to the Recipient or customize the process.
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { EfficientLeftRightKeccak } from "../../../libraries/EfficientLeftRightKeccak.sol";
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { L2MessageServiceBase } from "../../../messaging/l2/L2MessageServiceBase.sol";
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow missing-initializer
|
||||
contract InheritingL2MessageService is L2MessageServiceBase {
|
||||
error DirectETHSendingDisallowed();
|
||||
error FeeSendingDisallowed();
|
||||
error OnlyAllowedSendersToRemoteReceiver();
|
||||
error OnlyFromRemoteReceiverToAllowedSender();
|
||||
|
||||
error MessageSenderStateAlreadySet(address sender, bool state);
|
||||
error MessageReceiverAlreadySet(address receiver);
|
||||
|
||||
event AllowedMessageSenderStateSet(address sender, bool state);
|
||||
event RemoteReceiverSet(address receiver);
|
||||
|
||||
/// @notice The role required to set/remove allowed message senders.
|
||||
bytes32 public constant ALLOWED_MESSAGESENDER_SETTING_ROLE = keccak256("ALLOWED_MESSAGESENDER_SETTING_ROLE"); // should be security council restricted
|
||||
|
||||
/// @notice The role required to set the remote receiver.
|
||||
bytes32 public constant REMOTE_RECEIVER_SETTER_ROLE = keccak256("REMOTE_RECEIVER_SETTER_ROLE"); // should be security council restricted
|
||||
|
||||
mapping(address remoteReceiver => bool isRemoteReceiver) public remoteReceivers;
|
||||
mapping(address sender => bool isAllowedToSendToRemoteReceiver) public allowedMessageSenders;
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Initializes underlying message service dependencies.
|
||||
* @param _rateLimitPeriod The period to rate limit against.
|
||||
* @param _rateLimitAmount The limit allowed for withdrawing the period.
|
||||
* @param _defaultAdmin The account to be given DEFAULT_ADMIN_ROLE on initialization.
|
||||
* @param _roleAddresses The list of addresses to grant roles to.
|
||||
* @param _pauseTypeRoles The list of pause type roles.
|
||||
* @param _unpauseTypeRoles The list of unpause type roles.
|
||||
*/
|
||||
function initialize(
|
||||
uint256 _rateLimitPeriod,
|
||||
uint256 _rateLimitAmount,
|
||||
address _defaultAdmin,
|
||||
RoleAddress[] calldata _roleAddresses,
|
||||
PauseTypeRole[] calldata _pauseTypeRoles,
|
||||
PauseTypeRole[] calldata _unpauseTypeRoles
|
||||
) external initializer {
|
||||
__L2MessageService_init(
|
||||
_rateLimitPeriod,
|
||||
_rateLimitAmount,
|
||||
_defaultAdmin,
|
||||
_roleAddresses,
|
||||
_pauseTypeRoles,
|
||||
_unpauseTypeRoles
|
||||
);
|
||||
}
|
||||
|
||||
function setRemoteReceiver(address _receiver) external onlyRole(REMOTE_RECEIVER_SETTER_ROLE) {
|
||||
if (remoteReceivers[_receiver]) {
|
||||
revert MessageReceiverAlreadySet(_receiver);
|
||||
}
|
||||
|
||||
remoteReceivers[_receiver] = true;
|
||||
emit RemoteReceiverSet(_receiver);
|
||||
}
|
||||
|
||||
function setAllowedMessageSenderState(
|
||||
address _allowedSender,
|
||||
bool _isAllowedToSend
|
||||
) external onlyRole(ALLOWED_MESSAGESENDER_SETTING_ROLE) {
|
||||
if (allowedMessageSenders[_allowedSender] == _isAllowedToSend) {
|
||||
revert MessageSenderStateAlreadySet(_allowedSender, _isAllowedToSend); // this avoids confusing events
|
||||
}
|
||||
|
||||
emit AllowedMessageSenderStateSet(_allowedSender, _isAllowedToSend);
|
||||
}
|
||||
|
||||
function sendMessage(
|
||||
address _to,
|
||||
uint256 _fee,
|
||||
bytes calldata _calldata
|
||||
) external payable override whenTypeAndGeneralNotPaused(PauseType.L2_L1) {
|
||||
if (msg.value > 0) {
|
||||
revert DirectETHSendingDisallowed();
|
||||
}
|
||||
|
||||
if (_fee > 0) {
|
||||
revert FeeSendingDisallowed();
|
||||
}
|
||||
|
||||
if (remoteReceivers[_to] && !allowedMessageSenders[msg.sender]) {
|
||||
revert OnlyAllowedSendersToRemoteReceiver();
|
||||
}
|
||||
|
||||
_sendMessage(_to, _fee, _calldata);
|
||||
}
|
||||
|
||||
function _claimMessage(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _fee,
|
||||
uint256 _value,
|
||||
bytes calldata _calldata,
|
||||
uint256 _nonce
|
||||
) internal override {
|
||||
// This may be an issue with migration - TBD
|
||||
if (_value > 0) {
|
||||
revert DirectETHSendingDisallowed();
|
||||
}
|
||||
|
||||
// TBC: if running an L1 postman...
|
||||
// if (_params.fee > 0) {
|
||||
// revert DirectETHSendingDisallowed();
|
||||
// }
|
||||
|
||||
// is this from the remote bridge? and are they sending back to someone they are allowed to send back to?
|
||||
// NB: TBD - This might need to be done as pairs vs. open ended.
|
||||
if (remoteReceivers[_from] && !allowedMessageSenders[_to]) {
|
||||
revert OnlyFromRemoteReceiverToAllowedSender();
|
||||
}
|
||||
|
||||
super._claimMessage(_from, _to, _fee, _value, _calldata, _nonce);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { L1MessageManager } from "../../../messaging/l1/L1MessageManager.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { L1MessageService } from "../../../messaging/l1/L1MessageService.sol";
|
||||
import { TestSetPauseTypeRoles } from "../security/TestSetPauseTypeRoles.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { L1MessageService } from "../../../messaging/l1/L1MessageService.sol";
|
||||
import { IL1MessageService } from "../../../messaging/l1/interfaces/IL1MessageService.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
import { L2MessageManager } from "../../../messaging/l2/L2MessageManager.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { L2MessageService } from "../../../messaging/l2/L2MessageService.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { MessageServiceBase } from "../../../messaging/MessageServiceBase.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { SparseMerkleTreeVerifier } from "../../../messaging/libraries/SparseMerkleTreeVerifier.sol";
|
||||
import { EfficientLeftRightKeccak } from "../../../libraries/EfficientLeftRightKeccak.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
contract ErrorAndDestructionTesting {
|
||||
function externalRevert() external pure {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/// @dev Not intended for mainnet or testnet deployment, only for local testing
|
||||
contract OpcodeTestContract {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { ErrorAndDestructionTesting } from "./ErrorAndDestructionTesting.sol";
|
||||
|
||||
|
||||
104
contracts/src/_testing/unit/rollup/InheritingLineaRollup.sol
Normal file
104
contracts/src/_testing/unit/rollup/InheritingLineaRollup.sol
Normal file
@@ -0,0 +1,104 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { LineaRollupBase } from "../../../rollup/LineaRollupBase.sol";
|
||||
import { L1MessageService } from "../../../messaging/l1/L1MessageService.sol";
|
||||
import { IMessageService } from "../../../messaging/interfaces/IMessageService.sol";
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow missing-initializer
|
||||
contract InheritingRollup is LineaRollupBase {
|
||||
error DirectETHSendingDisallowed();
|
||||
error FeeSendingDisallowed();
|
||||
error OnlyAllowedSendersToRemoteReceiver();
|
||||
error OnlyFromRemoteReceiverToAllowedSender();
|
||||
|
||||
error MessageSenderStateAlreadySet(address sender, bool state);
|
||||
error MessageReceiverAlreadySet(address receiver);
|
||||
|
||||
event AllowedMessageSenderStateSet(address sender, bool state);
|
||||
event RemoteReceiverSet(address receiver);
|
||||
|
||||
/// @notice The role required to set/remove allowed message senders.
|
||||
bytes32 public constant ALLOWED_MESSAGESENDER_SETTING_ROLE = keccak256("ALLOWED_MESSAGESENDER_SETTING_ROLE"); // should be security council restricted
|
||||
|
||||
/// @notice The role required to set the remote receiver.
|
||||
bytes32 public constant REMOTE_RECEIVER_SETTER_ROLE = keccak256("REMOTE_RECEIVER_SETTER_ROLE"); // should be security council restricted
|
||||
|
||||
mapping(address remoteReceiver => bool isRemoteReceiver) public remoteReceivers;
|
||||
mapping(address sender => bool isAllowedToSendToRemoteReceiver) public allowedMessageSenders;
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
function initialize(InitializationData calldata _initializationData) external initializer {
|
||||
__LineaRollup_init(_initializationData);
|
||||
}
|
||||
|
||||
function setAllowedMessageSenderState(
|
||||
address _allowedSender,
|
||||
bool _isAllowedToSend
|
||||
) external onlyRole(ALLOWED_MESSAGESENDER_SETTING_ROLE) {
|
||||
if (allowedMessageSenders[_allowedSender] == _isAllowedToSend) {
|
||||
revert MessageSenderStateAlreadySet(_allowedSender, _isAllowedToSend); // this avoids confusing events
|
||||
}
|
||||
|
||||
emit AllowedMessageSenderStateSet(_allowedSender, _isAllowedToSend);
|
||||
}
|
||||
|
||||
function setRemoteReceiver(address _receiver) external onlyRole(REMOTE_RECEIVER_SETTER_ROLE) {
|
||||
if (remoteReceivers[_receiver]) {
|
||||
revert MessageReceiverAlreadySet(_receiver);
|
||||
}
|
||||
|
||||
remoteReceivers[_receiver] = true;
|
||||
emit RemoteReceiverSet(_receiver);
|
||||
}
|
||||
|
||||
function sendMessage(
|
||||
address _to,
|
||||
uint256 _fee,
|
||||
bytes calldata _calldata
|
||||
) external payable override(L1MessageService, IMessageService) whenTypeAndGeneralNotPaused(PauseType.L1_L2) {
|
||||
if (msg.value > 0) {
|
||||
revert DirectETHSendingDisallowed();
|
||||
}
|
||||
|
||||
if (_fee > 0) {
|
||||
revert FeeSendingDisallowed();
|
||||
}
|
||||
|
||||
if (remoteReceivers[_to] && !allowedMessageSenders[msg.sender]) {
|
||||
revert OnlyAllowedSendersToRemoteReceiver();
|
||||
}
|
||||
|
||||
_sendMessage(_to, _fee, _calldata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Claims and delivers a cross-chain message using a Merkle proof.
|
||||
* @dev if tree depth is empty, it will revert with L2MerkleRootDoesNotExist.
|
||||
* @dev if tree depth is different than proof size, it will revert with ProofLengthDifferentThanMerkleDepth.
|
||||
* @param _params Collection of claim data with proof and supporting data.
|
||||
*/
|
||||
function _claimMessageWithProof(ClaimMessageWithProofParams calldata _params) internal virtual override {
|
||||
// custom code here
|
||||
|
||||
if (_params.value > 0) {
|
||||
revert DirectETHSendingDisallowed();
|
||||
}
|
||||
|
||||
if (_params.fee > 0) {
|
||||
revert DirectETHSendingDisallowed();
|
||||
}
|
||||
|
||||
// is this from the remote bridge? and are they sending back to someone they are allowed to send back to?
|
||||
// NB: TBD - This could also be done as pairs vs. open ended.
|
||||
if (remoteReceivers[_params.from] && !allowedMessageSenders[_params.to]) {
|
||||
revert OnlyFromRemoteReceiverToAllowedSender();
|
||||
}
|
||||
|
||||
super._claimMessageWithProof(_params);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@ import { LineaRollup } from "../../../rollup/LineaRollup.sol";
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow missing-initializer
|
||||
contract TestLineaRollup is LineaRollup {
|
||||
function setFallbackOperatorAddress(address _fallbackOperator) external {
|
||||
fallbackOperator = _fallbackOperator;
|
||||
}
|
||||
|
||||
function addRollingHash(uint256 _messageNumber, bytes32 _messageHash) external {
|
||||
_addRollingHash(_messageNumber, _messageHash);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { PauseManager } from "../../../security/pausing/PauseManager.sol";
|
||||
import { TestSetPauseTypeRoles } from "./TestSetPauseTypeRoles.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
import { RateLimiter } from "../../../security/limiting/RateLimiter.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { PauseManager } from "../../../security/pausing/PauseManager.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/// @dev Test contract to test LXP-L minting
|
||||
interface ITransferSurgeXP {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
// Code generated by gnark DO NOT EDIT
|
||||
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
contract TestPlonkVerifierForDataAggregation {
|
||||
uint256 private constant R_MOD = 21888242871839275222246405745257275088548364400416034343698204186575808495617;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { ERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { BridgedToken } from "./BridgedToken.sol";
|
||||
|
||||
|
||||
@@ -1,134 +1,15 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
|
||||
import { ITokenBridge } from "./interfaces/ITokenBridge.sol";
|
||||
import { IMessageService } from "../../messaging/interfaces/IMessageService.sol";
|
||||
import { TokenBridgeBase } from "./TokenBridgeBase.sol";
|
||||
|
||||
import { IERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol";
|
||||
import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
|
||||
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
||||
import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
|
||||
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
|
||||
|
||||
import { BridgedToken } from "./BridgedToken.sol";
|
||||
import { MessageServiceBase } from "../../messaging/MessageServiceBase.sol";
|
||||
|
||||
import { TokenBridgePauseManager } from "../../security/pausing/TokenBridgePauseManager.sol";
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { StorageFiller39 } from "./utils/StorageFiller39.sol";
|
||||
import { PermissionsManager } from "../../security/access/PermissionsManager.sol";
|
||||
|
||||
import { EfficientLeftRightKeccak } from "../../libraries/EfficientLeftRightKeccak.sol";
|
||||
/**
|
||||
* @title Linea Canonical Token Bridge
|
||||
* @notice Contract to manage cross-chain ERC-20 bridging.
|
||||
* @author ConsenSys Software Inc.
|
||||
* @custom:security-contact security-report@linea.build
|
||||
*/
|
||||
contract TokenBridge is
|
||||
ITokenBridge,
|
||||
ReentrancyGuardUpgradeable,
|
||||
AccessControlUpgradeable,
|
||||
MessageServiceBase,
|
||||
TokenBridgePauseManager,
|
||||
PermissionsManager,
|
||||
StorageFiller39
|
||||
{
|
||||
using EfficientLeftRightKeccak for *;
|
||||
using SafeERC20Upgradeable for IERC20Upgradeable;
|
||||
|
||||
/// @dev This is the ABI version and not the reinitialize version.
|
||||
string public constant CONTRACT_VERSION = "1.1";
|
||||
|
||||
/// @notice Role used for setting the message service address.
|
||||
bytes32 public constant SET_MESSAGE_SERVICE_ROLE = keccak256("SET_MESSAGE_SERVICE_ROLE");
|
||||
|
||||
/// @notice Role used for setting a reserved token address.
|
||||
bytes32 public constant SET_RESERVED_TOKEN_ROLE = keccak256("SET_RESERVED_TOKEN_ROLE");
|
||||
|
||||
/// @notice Role used for removing a reserved token address.
|
||||
bytes32 public constant REMOVE_RESERVED_TOKEN_ROLE = keccak256("REMOVE_RESERVED_TOKEN_ROLE");
|
||||
|
||||
/// @notice Role used for setting a custom token contract address.
|
||||
bytes32 public constant SET_CUSTOM_CONTRACT_ROLE = keccak256("SET_CUSTOM_CONTRACT_ROLE");
|
||||
|
||||
// Special addresses used in the mappings to mark specific states for tokens.
|
||||
/// @notice EMPTY means a token is not present in the mapping.
|
||||
address internal constant EMPTY = address(0x0);
|
||||
/// @notice RESERVED means a token is reserved and cannot be bridged.
|
||||
address internal constant RESERVED_STATUS = address(0x111);
|
||||
/// @notice NATIVE means a token is native to the current local chain.
|
||||
address internal constant NATIVE_STATUS = address(0x222);
|
||||
/// @notice DEPLOYED means the bridged token contract has been deployed on the remote chain.
|
||||
address internal constant DEPLOYED_STATUS = address(0x333);
|
||||
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
/// @dev The permit selector to be used when decoding the permit.
|
||||
bytes4 internal constant _PERMIT_SELECTOR = IERC20PermitUpgradeable.permit.selector;
|
||||
|
||||
/// @notice These 3 variables are used for the token metadata.
|
||||
bytes private constant METADATA_NAME = abi.encodeCall(IERC20MetadataUpgradeable.name, ());
|
||||
bytes private constant METADATA_SYMBOL = abi.encodeCall(IERC20MetadataUpgradeable.symbol, ());
|
||||
bytes private constant METADATA_DECIMALS = abi.encodeCall(IERC20MetadataUpgradeable.decimals, ());
|
||||
|
||||
/// @dev These 3 values are used when checking for token decimals and string values.
|
||||
uint256 private constant VALID_DECIMALS_ENCODING_LENGTH = 32;
|
||||
uint256 private constant SHORT_STRING_ENCODING_LENGTH = 32;
|
||||
uint256 private constant MINIMUM_STRING_ABI_DECODE_LENGTH = 64;
|
||||
|
||||
/// @notice The token beacon for deployed tokens.
|
||||
address public tokenBeacon;
|
||||
|
||||
/// @notice The chainId mapped to a native token address which is then mapped to the bridged token address.
|
||||
mapping(uint256 chainId => mapping(address native => address bridged)) public nativeToBridgedToken;
|
||||
|
||||
/// @notice The bridged token address mapped to the native token address.
|
||||
mapping(address bridged => address native) public bridgedToNativeToken;
|
||||
|
||||
/// @notice The current layer's chainId from where the bridging is triggered.
|
||||
uint256 public sourceChainId;
|
||||
|
||||
/// @notice The targeted layer's chainId where the bridging is received.
|
||||
uint256 public targetChainId;
|
||||
|
||||
/// @dev Keep free storage slots for future implementation updates to avoid storage collision.
|
||||
uint256[50] private __gap;
|
||||
|
||||
/// @dev Ensures the token has not been bridged before.
|
||||
modifier isNewToken(address _token) {
|
||||
if (bridgedToNativeToken[_token] != EMPTY || nativeToBridgedToken[sourceChainId][_token] != EMPTY)
|
||||
revert AlreadyBridgedToken(_token);
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Ensures the address is not address(0).
|
||||
* @param _addr Address to check.
|
||||
*/
|
||||
modifier nonZeroAddress(address _addr) {
|
||||
if (_addr == EMPTY) revert ZeroAddressNotAllowed();
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Ensures the amount is not 0.
|
||||
* @param _amount amount to check.
|
||||
*/
|
||||
modifier nonZeroAmount(uint256 _amount) {
|
||||
if (_amount == 0) revert ZeroAmountNotAllowed(_amount);
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Ensures the chainId is not 0.
|
||||
* @param _chainId chainId to check.
|
||||
*/
|
||||
modifier nonZeroChainId(uint256 _chainId) {
|
||||
if (_chainId == 0) revert ZeroChainIdNotAllowed();
|
||||
_;
|
||||
}
|
||||
|
||||
contract TokenBridge is TokenBridgeBase {
|
||||
/// @dev Disable constructor for safety
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() {
|
||||
@@ -150,429 +31,6 @@ contract TokenBridge is
|
||||
nonZeroChainId(_initializationData.targetChainId)
|
||||
initializer
|
||||
{
|
||||
__ReentrancyGuard_init();
|
||||
__MessageServiceBase_init(_initializationData.messageService);
|
||||
__PauseManager_init(_initializationData.pauseTypeRoles, _initializationData.unpauseTypeRoles);
|
||||
|
||||
if (_initializationData.defaultAdmin == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev DEFAULT_ADMIN_ROLE is set for the security council explicitly,
|
||||
* as the permissions init purposefully does not allow DEFAULT_ADMIN_ROLE to be set.
|
||||
*/
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, _initializationData.defaultAdmin);
|
||||
|
||||
__Permissions_init(_initializationData.roleAddresses);
|
||||
|
||||
tokenBeacon = _initializationData.tokenBeacon;
|
||||
if (_initializationData.sourceChainId == _initializationData.targetChainId) revert SourceChainSameAsTargetChain();
|
||||
_setRemoteSender(_initializationData.remoteSender);
|
||||
sourceChainId = _initializationData.sourceChainId;
|
||||
targetChainId = _initializationData.targetChainId;
|
||||
|
||||
unchecked {
|
||||
for (uint256 i; i < _initializationData.reservedTokens.length; ) {
|
||||
if (_initializationData.reservedTokens[i] == EMPTY) revert ZeroAddressNotAllowed();
|
||||
nativeToBridgedToken[_initializationData.sourceChainId][
|
||||
_initializationData.reservedTokens[i]
|
||||
] = RESERVED_STATUS;
|
||||
emit TokenReserved(_initializationData.reservedTokens[i]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function is the single entry point to bridge tokens to the
|
||||
* other chain, both for native and already bridged tokens. You can use it
|
||||
* to bridge any ERC-20. If the token is bridged for the first time an ERC-20
|
||||
* (BridgedToken.sol) will be automatically deployed on the target chain.
|
||||
* @dev User should first allow the bridge to transfer tokens on his behalf.
|
||||
* Alternatively, you can use BridgeTokenWithPermit to do so in a single
|
||||
* transaction. If you want the transfer to be automatically executed on the
|
||||
* destination chain. You should send enough ETH to pay the postman fees.
|
||||
* Note that Linea can reserve some tokens (which use a dedicated bridge).
|
||||
* In this case, the token cannot be bridged. Linea can only reserve tokens
|
||||
* that have not been bridged yet.
|
||||
* Linea can pause the bridge for security reason. In this case new bridge
|
||||
* transaction would revert.
|
||||
* @dev Note: If, when bridging an unbridged token and decimals are unknown,
|
||||
* the call will revert to prevent mismatched decimals. Only those ERC-20s,
|
||||
* with a decimals function are supported.
|
||||
* @param _token The address of the token to be bridged.
|
||||
* @param _amount The amount of the token to be bridged.
|
||||
* @param _recipient The address that will receive the tokens on the other chain.
|
||||
*/
|
||||
function bridgeToken(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
address _recipient
|
||||
) public payable nonZeroAddress(_token) nonZeroAddress(_recipient) nonZeroAmount(_amount) nonReentrant {
|
||||
_requireTypeAndGeneralNotPaused(PauseType.INITIATE_TOKEN_BRIDGING);
|
||||
uint256 sourceChainIdCache = sourceChainId;
|
||||
address nativeMappingValue = nativeToBridgedToken[sourceChainIdCache][_token];
|
||||
if (nativeMappingValue == RESERVED_STATUS) {
|
||||
// Token is reserved
|
||||
revert ReservedToken(_token);
|
||||
}
|
||||
|
||||
address nativeToken = bridgedToNativeToken[_token];
|
||||
uint256 chainId;
|
||||
bytes memory tokenMetadata;
|
||||
|
||||
if (nativeToken != EMPTY) {
|
||||
BridgedToken(_token).burn(msg.sender, _amount);
|
||||
chainId = targetChainId;
|
||||
} else {
|
||||
// Token is native
|
||||
|
||||
// For tokens with special fee logic, ensure that only the amount received
|
||||
// by the bridge will be minted on the target chain.
|
||||
uint256 balanceBefore = IERC20Upgradeable(_token).balanceOf(address(this));
|
||||
IERC20Upgradeable(_token).safeTransferFrom(msg.sender, address(this), _amount);
|
||||
_amount = IERC20Upgradeable(_token).balanceOf(address(this)) - balanceBefore;
|
||||
nativeToken = _token;
|
||||
|
||||
if (nativeMappingValue == EMPTY) {
|
||||
// New token
|
||||
nativeToBridgedToken[sourceChainIdCache][_token] = NATIVE_STATUS;
|
||||
emit NewToken(_token);
|
||||
}
|
||||
|
||||
// Send Metadata only when the token has not been deployed on the other chain yet
|
||||
if (nativeMappingValue != DEPLOYED_STATUS) {
|
||||
tokenMetadata = abi.encode(_safeName(_token), _safeSymbol(_token), _safeDecimals(_token));
|
||||
}
|
||||
chainId = sourceChainIdCache;
|
||||
}
|
||||
messageService.sendMessage{ value: msg.value }(
|
||||
remoteSender,
|
||||
msg.value, // fees
|
||||
abi.encodeCall(ITokenBridge.completeBridging, (nativeToken, _amount, _recipient, chainId, tokenMetadata))
|
||||
);
|
||||
emit BridgingInitiatedV2(msg.sender, _recipient, _token, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Similar to `bridgeToken` function but allows to pass additional
|
||||
* permit data to do the ERC-20 approval in a single transaction.
|
||||
* @notice _permit can fail silently, don't rely on this function passing as a form
|
||||
* of authentication
|
||||
* @dev There is no need for validation at this level as the validation on pausing,
|
||||
* and empty values exists on the "bridgeToken" call.
|
||||
* @param _token The address of the token to be bridged.
|
||||
* @param _amount The amount of the token to be bridged.
|
||||
* @param _recipient The address that will receive the tokens on the other chain.
|
||||
* @param _permitData The permit data for the token, if applicable.
|
||||
*/
|
||||
function bridgeTokenWithPermit(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
address _recipient,
|
||||
bytes calldata _permitData
|
||||
) external payable {
|
||||
if (_permitData.length != 0) {
|
||||
_permit(_token, _permitData);
|
||||
}
|
||||
bridgeToken(_token, _amount, _recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev It can only be called from the Message Service. To finalize the bridging
|
||||
* process, a user or postman needs to use the `claimMessage` function of the
|
||||
* Message Service to trigger the transaction.
|
||||
* @param _nativeToken The address of the token on its native chain.
|
||||
* @param _amount The amount of the token to be received.
|
||||
* @param _recipient The address that will receive the tokens.
|
||||
* @param _chainId The token's origin layer chainId
|
||||
* @param _tokenMetadata Additional data used to deploy the bridged token if it
|
||||
* doesn't exist already.
|
||||
*/
|
||||
function completeBridging(
|
||||
address _nativeToken,
|
||||
uint256 _amount,
|
||||
address _recipient,
|
||||
uint256 _chainId,
|
||||
bytes calldata _tokenMetadata
|
||||
)
|
||||
external
|
||||
nonReentrant
|
||||
onlyMessagingService
|
||||
onlyAuthorizedRemoteSender
|
||||
whenTypeAndGeneralNotPaused(PauseType.COMPLETE_TOKEN_BRIDGING)
|
||||
{
|
||||
address nativeMappingValue = nativeToBridgedToken[_chainId][_nativeToken];
|
||||
address bridgedToken;
|
||||
|
||||
if (nativeMappingValue == NATIVE_STATUS || nativeMappingValue == DEPLOYED_STATUS) {
|
||||
// Token is native on the local chain
|
||||
IERC20Upgradeable(_nativeToken).safeTransfer(_recipient, _amount);
|
||||
} else {
|
||||
bridgedToken = nativeMappingValue;
|
||||
if (nativeMappingValue == EMPTY) {
|
||||
// New token
|
||||
bridgedToken = deployBridgedToken(_nativeToken, _tokenMetadata, sourceChainId);
|
||||
bridgedToNativeToken[bridgedToken] = _nativeToken;
|
||||
nativeToBridgedToken[targetChainId][_nativeToken] = bridgedToken;
|
||||
}
|
||||
BridgedToken(bridgedToken).mint(_recipient, _amount);
|
||||
}
|
||||
emit BridgingFinalizedV2(_nativeToken, bridgedToken, _amount, _recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Change the address of the Message Service.
|
||||
* @dev SET_MESSAGE_SERVICE_ROLE is required to execute.
|
||||
* @param _messageService The address of the new Message Service.
|
||||
*/
|
||||
function setMessageService(
|
||||
address _messageService
|
||||
) external nonZeroAddress(_messageService) onlyRole(SET_MESSAGE_SERVICE_ROLE) {
|
||||
address oldMessageService = address(messageService);
|
||||
messageService = IMessageService(_messageService);
|
||||
emit MessageServiceUpdated(_messageService, oldMessageService, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Change the status to DEPLOYED to the tokens passed in parameter
|
||||
* Will call the method setDeployed on the other chain using the message Service
|
||||
* @param _tokens Array of bridged tokens that have been deployed.
|
||||
*/
|
||||
function confirmDeployment(address[] memory _tokens) external payable {
|
||||
uint256 tokensLength = _tokens.length;
|
||||
if (tokensLength == 0) {
|
||||
revert TokenListEmpty();
|
||||
}
|
||||
|
||||
// Check that the tokens have actually been deployed
|
||||
for (uint256 i; i < tokensLength; i++) {
|
||||
address nativeToken = bridgedToNativeToken[_tokens[i]];
|
||||
if (nativeToken == EMPTY) {
|
||||
revert TokenNotDeployed(_tokens[i]);
|
||||
}
|
||||
_tokens[i] = nativeToken;
|
||||
}
|
||||
|
||||
messageService.sendMessage{ value: msg.value }(
|
||||
remoteSender,
|
||||
msg.value, // fees
|
||||
abi.encodeCall(ITokenBridge.setDeployed, (_tokens))
|
||||
);
|
||||
|
||||
emit DeploymentConfirmed(_tokens, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Change the status of tokens to DEPLOYED. New bridge transaction will not
|
||||
* contain token metadata, which save gas.
|
||||
* Can only be called from the Message Service. A user or postman needs to use
|
||||
* the `claimMessage` function of the Message Service to trigger the transaction.
|
||||
* @param _nativeTokens Array of native tokens for which the DEPLOYED status must be set.
|
||||
*/
|
||||
function setDeployed(address[] calldata _nativeTokens) external onlyMessagingService onlyAuthorizedRemoteSender {
|
||||
unchecked {
|
||||
uint256 cachedSourceChainId = sourceChainId;
|
||||
for (uint256 i; i < _nativeTokens.length; ) {
|
||||
nativeToBridgedToken[cachedSourceChainId][_nativeTokens[i]] = DEPLOYED_STATUS;
|
||||
emit TokenDeployed(_nativeTokens[i]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Deploy a new EC20 contract for bridged token using a beacon proxy pattern.
|
||||
* To adapt to future requirements, Linea can update the implementation of
|
||||
* all (existing and future) contracts by updating the beacon. This update is
|
||||
* subject to a delay by a time lock.
|
||||
* Contracts are deployed using CREATE2 so deployment address is deterministic.
|
||||
* @param _nativeToken The address of the native token on the source chain.
|
||||
* @param _tokenMetadata The encoded metadata for the token.
|
||||
* @param _chainId The chain id on which the token will be deployed, used to calculate the salt
|
||||
* @return bridgedTokenAddress The address of the newly deployed BridgedToken contract.
|
||||
*/
|
||||
function deployBridgedToken(
|
||||
address _nativeToken,
|
||||
bytes calldata _tokenMetadata,
|
||||
uint256 _chainId
|
||||
) internal returns (address bridgedTokenAddress) {
|
||||
bridgedTokenAddress = address(
|
||||
new BeaconProxy{ salt: EfficientLeftRightKeccak._efficientKeccak(_chainId, _nativeToken) }(tokenBeacon, "")
|
||||
);
|
||||
|
||||
(string memory name, string memory symbol, uint8 decimals) = abi.decode(_tokenMetadata, (string, string, uint8));
|
||||
BridgedToken(bridgedTokenAddress).initialize(name, symbol, decimals);
|
||||
emit NewTokenDeployed(bridgedTokenAddress, _nativeToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Linea can reserve tokens. In this case, the token cannot be bridged.
|
||||
* Linea can only reserve tokens that have not been bridged before.
|
||||
* @dev SET_RESERVED_TOKEN_ROLE is required to execute.
|
||||
* @notice Make sure that _token is native to the current chain
|
||||
* where you are calling this function from
|
||||
* @param _token The address of the token to be set as reserved.
|
||||
*/
|
||||
function setReserved(
|
||||
address _token
|
||||
) external nonZeroAddress(_token) isNewToken(_token) onlyRole(SET_RESERVED_TOKEN_ROLE) {
|
||||
nativeToBridgedToken[sourceChainId][_token] = RESERVED_STATUS;
|
||||
emit TokenReserved(_token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes a token from the reserved list.
|
||||
* @dev REMOVE_RESERVED_TOKEN_ROLE is required to execute.
|
||||
* @param _token The address of the token to be removed from the reserved list.
|
||||
*/
|
||||
function removeReserved(address _token) external nonZeroAddress(_token) onlyRole(REMOVE_RESERVED_TOKEN_ROLE) {
|
||||
uint256 cachedSourceChainId = sourceChainId;
|
||||
|
||||
if (nativeToBridgedToken[cachedSourceChainId][_token] != RESERVED_STATUS) revert NotReserved(_token);
|
||||
nativeToBridgedToken[cachedSourceChainId][_token] = EMPTY;
|
||||
|
||||
emit ReservationRemoved(_token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Linea can set a custom ERC-20 contract for specific ERC-20.
|
||||
* For security purposes, Linea can only call this function if the token has
|
||||
* not been bridged yet.
|
||||
* @dev SET_CUSTOM_CONTRACT_ROLE is required to execute.
|
||||
* @param _nativeToken The address of the token on the source chain.
|
||||
* @param _targetContract The address of the custom contract.
|
||||
*/
|
||||
function setCustomContract(
|
||||
address _nativeToken,
|
||||
address _targetContract
|
||||
)
|
||||
external
|
||||
nonZeroAddress(_nativeToken)
|
||||
nonZeroAddress(_targetContract)
|
||||
onlyRole(SET_CUSTOM_CONTRACT_ROLE)
|
||||
isNewToken(_nativeToken)
|
||||
{
|
||||
if (bridgedToNativeToken[_targetContract] != EMPTY) {
|
||||
revert AlreadyBrigedToNativeTokenSet(_targetContract);
|
||||
}
|
||||
if (_targetContract == NATIVE_STATUS || _targetContract == DEPLOYED_STATUS || _targetContract == RESERVED_STATUS) {
|
||||
revert StatusAddressNotAllowed(_targetContract);
|
||||
}
|
||||
|
||||
uint256 cachedTargetChainId = targetChainId;
|
||||
|
||||
if (nativeToBridgedToken[cachedTargetChainId][_nativeToken] != EMPTY) {
|
||||
revert NativeToBridgedTokenAlreadySet(_nativeToken);
|
||||
}
|
||||
|
||||
nativeToBridgedToken[cachedTargetChainId][_nativeToken] = _targetContract;
|
||||
bridgedToNativeToken[_targetContract] = _nativeToken;
|
||||
emit CustomContractSet(_nativeToken, _targetContract, msg.sender);
|
||||
}
|
||||
|
||||
// Helpers to safely get the metadata from a token, inspired by
|
||||
// https://github.com/traderjoe-xyz/joe-core/blob/main/contracts/MasterChefJoeV3.sol#L55-L95
|
||||
|
||||
/**
|
||||
* @dev Provides a safe ERC-20.name version which returns 'NO_NAME' as fallback string.
|
||||
* @param _token The address of the ERC-20 token contract
|
||||
* @return tokenName Returns the string of the token name.
|
||||
*/
|
||||
function _safeName(address _token) internal view returns (string memory tokenName) {
|
||||
(bool success, bytes memory data) = _token.staticcall(METADATA_NAME);
|
||||
tokenName = success ? _returnDataToString(data) : "NO_NAME";
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Provides a safe ERC-20.symbol version which returns 'NO_SYMBOL' as fallback string
|
||||
* @param _token The address of the ERC-20 token contract
|
||||
* @return symbol Returns the string of the symbol.
|
||||
*/
|
||||
function _safeSymbol(address _token) internal view returns (string memory symbol) {
|
||||
(bool success, bytes memory data) = _token.staticcall(METADATA_SYMBOL);
|
||||
symbol = success ? _returnDataToString(data) : "NO_SYMBOL";
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Provides a safe ERC-20.decimals version which reverts when decimals are unknown
|
||||
* Note Tokens with (decimals > 255) are not supported
|
||||
* @param _token The address of the ERC-20 token contract
|
||||
* @return Returns the token's decimals value.
|
||||
*/
|
||||
function _safeDecimals(address _token) internal view returns (uint8) {
|
||||
(bool success, bytes memory data) = _token.staticcall(METADATA_DECIMALS);
|
||||
|
||||
if (success && data.length == VALID_DECIMALS_ENCODING_LENGTH) {
|
||||
return abi.decode(data, (uint8));
|
||||
}
|
||||
|
||||
revert DecimalsAreUnknown(_token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Converts returned data to string. Returns 'NOT_VALID_ENCODING' as fallback value.
|
||||
* @param _data returned data.
|
||||
* @return decodedString The decoded string data.
|
||||
*/
|
||||
function _returnDataToString(bytes memory _data) internal pure returns (string memory decodedString) {
|
||||
if (_data.length >= MINIMUM_STRING_ABI_DECODE_LENGTH) {
|
||||
return abi.decode(_data, (string));
|
||||
} else if (_data.length != SHORT_STRING_ENCODING_LENGTH) {
|
||||
return "NOT_VALID_ENCODING";
|
||||
}
|
||||
|
||||
// Since the strings on bytes32 are encoded left-right, check the first zero in the data
|
||||
uint256 nonZeroBytes;
|
||||
unchecked {
|
||||
while (nonZeroBytes < SHORT_STRING_ENCODING_LENGTH && _data[nonZeroBytes] != 0) {
|
||||
nonZeroBytes++;
|
||||
}
|
||||
}
|
||||
|
||||
// If the first one is 0, we do not handle the encoding
|
||||
if (nonZeroBytes == 0) {
|
||||
return "NOT_VALID_ENCODING";
|
||||
}
|
||||
// Create a byte array with nonZeroBytes length
|
||||
bytes memory bytesArray = new bytes(nonZeroBytes);
|
||||
unchecked {
|
||||
for (uint256 i; i < nonZeroBytes; ) {
|
||||
bytesArray[i] = _data[i];
|
||||
++i;
|
||||
}
|
||||
}
|
||||
decodedString = string(bytesArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Call the token permit method of extended ERC-20
|
||||
* @notice Only support tokens implementing ERC-2612
|
||||
* @param _token ERC-20 token address
|
||||
* @param _permitData Raw data of the call `permit` of the token
|
||||
*/
|
||||
function _permit(address _token, bytes calldata _permitData) internal {
|
||||
if (bytes4(_permitData[:4]) != _PERMIT_SELECTOR)
|
||||
revert InvalidPermitData(bytes4(_permitData[:4]), _PERMIT_SELECTOR);
|
||||
// Decode the permit data
|
||||
// The parameters are:
|
||||
// 1. owner: The address of the wallet holding the tokens
|
||||
// 2. spender: The address of the entity permitted to spend the tokens
|
||||
// 3. value: The maximum amount of tokens the spender is allowed to spend
|
||||
// 4. deadline: The time until which the permit is valid
|
||||
// 5. v: Part of the signature (along with r and s), these three values form the signature of the permit
|
||||
// 6. r: Part of the signature
|
||||
// 7. s: Part of the signature
|
||||
(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) = abi.decode(
|
||||
_permitData[4:],
|
||||
(address, address, uint256, uint256, uint8, bytes32, bytes32)
|
||||
);
|
||||
if (owner != msg.sender) revert PermitNotFromSender(owner);
|
||||
if (spender != address(this)) revert PermitNotAllowingBridge(spender);
|
||||
|
||||
if (IERC20Upgradeable(_token).allowance(owner, spender) < amount) {
|
||||
IERC20PermitUpgradeable(_token).permit(msg.sender, address(this), amount, deadline, v, r, s);
|
||||
}
|
||||
__TokenBridge_init(_initializationData);
|
||||
}
|
||||
}
|
||||
|
||||
630
contracts/src/bridging/token/TokenBridgeBase.sol
Normal file
630
contracts/src/bridging/token/TokenBridgeBase.sol
Normal file
@@ -0,0 +1,630 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { ITokenBridge } from "./interfaces/ITokenBridge.sol";
|
||||
import { IMessageService } from "../../messaging/interfaces/IMessageService.sol";
|
||||
|
||||
import { IERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol";
|
||||
import { IERC20MetadataUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
|
||||
import { IERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
|
||||
import { SafeERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
|
||||
import { BeaconProxy } from "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
|
||||
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
|
||||
|
||||
import { BridgedToken } from "./BridgedToken.sol";
|
||||
import { MessageServiceBase } from "../../messaging/MessageServiceBase.sol";
|
||||
|
||||
import { TokenBridgePauseManager } from "../../security/pausing/TokenBridgePauseManager.sol";
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { StorageFiller39 } from "./utils/StorageFiller39.sol";
|
||||
import { PermissionsManager } from "../../security/access/PermissionsManager.sol";
|
||||
|
||||
import { EfficientLeftRightKeccak } from "../../libraries/EfficientLeftRightKeccak.sol";
|
||||
/**
|
||||
* @title Linea Canonical Token Bridge
|
||||
* @notice Contract to manage cross-chain ERC-20 bridging.
|
||||
* @author ConsenSys Software Inc.
|
||||
* @custom:security-contact security-report@linea.build
|
||||
*/
|
||||
abstract contract TokenBridgeBase is
|
||||
ITokenBridge,
|
||||
ReentrancyGuardUpgradeable,
|
||||
AccessControlUpgradeable,
|
||||
MessageServiceBase,
|
||||
TokenBridgePauseManager,
|
||||
PermissionsManager,
|
||||
StorageFiller39
|
||||
{
|
||||
using EfficientLeftRightKeccak for *;
|
||||
using SafeERC20Upgradeable for IERC20Upgradeable;
|
||||
|
||||
/// @notice Role used for setting the message service address.
|
||||
bytes32 public constant SET_MESSAGE_SERVICE_ROLE = keccak256("SET_MESSAGE_SERVICE_ROLE");
|
||||
|
||||
/// @notice Role used for setting a reserved token address.
|
||||
bytes32 public constant SET_RESERVED_TOKEN_ROLE = keccak256("SET_RESERVED_TOKEN_ROLE");
|
||||
|
||||
/// @notice Role used for removing a reserved token address.
|
||||
bytes32 public constant REMOVE_RESERVED_TOKEN_ROLE = keccak256("REMOVE_RESERVED_TOKEN_ROLE");
|
||||
|
||||
/// @notice Role used for setting a custom token contract address.
|
||||
bytes32 public constant SET_CUSTOM_CONTRACT_ROLE = keccak256("SET_CUSTOM_CONTRACT_ROLE");
|
||||
|
||||
// Special addresses used in the mappings to mark specific states for tokens.
|
||||
/// @notice EMPTY means a token is not present in the mapping.
|
||||
address internal constant EMPTY = address(0x0);
|
||||
/// @notice RESERVED means a token is reserved and cannot be bridged.
|
||||
address internal constant RESERVED_STATUS = address(0x111);
|
||||
/// @notice NATIVE means a token is native to the current local chain.
|
||||
address internal constant NATIVE_STATUS = address(0x222);
|
||||
/// @notice DEPLOYED means the bridged token contract has been deployed on the remote chain.
|
||||
address internal constant DEPLOYED_STATUS = address(0x333);
|
||||
|
||||
// solhint-disable-next-line var-name-mixedcase
|
||||
/// @dev The permit selector to be used when decoding the permit.
|
||||
bytes4 internal constant _PERMIT_SELECTOR = IERC20PermitUpgradeable.permit.selector;
|
||||
|
||||
/// @dev This is the ABI version and not the reinitialize version.
|
||||
string private constant _CONTRACT_VERSION = "1.1";
|
||||
|
||||
/// @notice These 3 variables are used for the token metadata.
|
||||
bytes private constant METADATA_NAME = abi.encodeCall(IERC20MetadataUpgradeable.name, ());
|
||||
bytes private constant METADATA_SYMBOL = abi.encodeCall(IERC20MetadataUpgradeable.symbol, ());
|
||||
bytes private constant METADATA_DECIMALS = abi.encodeCall(IERC20MetadataUpgradeable.decimals, ());
|
||||
|
||||
/// @dev These 3 values are used when checking for token decimals and string values.
|
||||
uint256 private constant VALID_DECIMALS_ENCODING_LENGTH = 32;
|
||||
uint256 private constant SHORT_STRING_ENCODING_LENGTH = 32;
|
||||
uint256 private constant MINIMUM_STRING_ABI_DECODE_LENGTH = 64;
|
||||
|
||||
/// @notice The token beacon for deployed tokens.
|
||||
address public tokenBeacon;
|
||||
|
||||
/// @notice The chainId mapped to a native token address which is then mapped to the bridged token address.
|
||||
mapping(uint256 chainId => mapping(address native => address bridged)) public nativeToBridgedToken;
|
||||
|
||||
/// @notice The bridged token address mapped to the native token address.
|
||||
mapping(address bridged => address native) public bridgedToNativeToken;
|
||||
|
||||
/// @notice The current layer's chainId from where the bridging is triggered.
|
||||
uint256 public sourceChainId;
|
||||
|
||||
/// @notice The targeted layer's chainId where the bridging is received.
|
||||
uint256 public targetChainId;
|
||||
|
||||
/// @dev Keep free storage slots for future implementation updates to avoid storage collision.
|
||||
uint256[50] private __gap;
|
||||
|
||||
/// @dev Ensures the token has not been bridged before.
|
||||
modifier isNewToken(address _token) {
|
||||
if (bridgedToNativeToken[_token] != EMPTY || nativeToBridgedToken[sourceChainId][_token] != EMPTY)
|
||||
revert AlreadyBridgedToken(_token);
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Ensures the address is not address(0).
|
||||
* @param _addr Address to check.
|
||||
*/
|
||||
modifier nonZeroAddress(address _addr) {
|
||||
if (_addr == EMPTY) revert ZeroAddressNotAllowed();
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Ensures the amount is not 0.
|
||||
* @param _amount amount to check.
|
||||
*/
|
||||
modifier nonZeroAmount(uint256 _amount) {
|
||||
if (_amount == 0) revert ZeroAmountNotAllowed(_amount);
|
||||
_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Ensures the chainId is not 0.
|
||||
* @param _chainId chainId to check.
|
||||
*/
|
||||
modifier nonZeroChainId(uint256 _chainId) {
|
||||
if (_chainId == 0) revert ZeroChainIdNotAllowed();
|
||||
_;
|
||||
}
|
||||
|
||||
/// @dev Disable constructor for safety
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Initializes TokenBridge and underlying service dependencies - used for new networks only.
|
||||
* @param _initializationData The initial data used for initializing the TokenBridge contract.
|
||||
*/
|
||||
function __TokenBridge_init(InitializationData calldata _initializationData) internal virtual {
|
||||
__ReentrancyGuard_init();
|
||||
__MessageServiceBase_init(_initializationData.messageService);
|
||||
__PauseManager_init(_initializationData.pauseTypeRoles, _initializationData.unpauseTypeRoles);
|
||||
|
||||
if (_initializationData.defaultAdmin == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev DEFAULT_ADMIN_ROLE is set for the security council explicitly,
|
||||
* as the permissions init purposefully does not allow DEFAULT_ADMIN_ROLE to be set.
|
||||
*/
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, _initializationData.defaultAdmin);
|
||||
|
||||
__Permissions_init(_initializationData.roleAddresses);
|
||||
|
||||
tokenBeacon = _initializationData.tokenBeacon;
|
||||
if (_initializationData.sourceChainId == _initializationData.targetChainId) revert SourceChainSameAsTargetChain();
|
||||
_setRemoteSender(_initializationData.remoteSender);
|
||||
sourceChainId = _initializationData.sourceChainId;
|
||||
targetChainId = _initializationData.targetChainId;
|
||||
|
||||
unchecked {
|
||||
for (uint256 i; i < _initializationData.reservedTokens.length; ) {
|
||||
if (_initializationData.reservedTokens[i] == EMPTY) revert ZeroAddressNotAllowed();
|
||||
nativeToBridgedToken[_initializationData.sourceChainId][
|
||||
_initializationData.reservedTokens[i]
|
||||
] = RESERVED_STATUS;
|
||||
emit TokenReserved(_initializationData.reservedTokens[i]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the ABI version and not the reinitialize version.
|
||||
* @return contractVersion The contract ABI version.
|
||||
*/
|
||||
function CONTRACT_VERSION() external view virtual returns (string memory contractVersion) {
|
||||
contractVersion = _CONTRACT_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function is the single entry point to bridge tokens to the
|
||||
* other chain, both for native and already bridged tokens. You can use it
|
||||
* to bridge any ERC-20. If the token is bridged for the first time an ERC-20
|
||||
* (BridgedToken.sol) will be automatically deployed on the target chain.
|
||||
* @dev User should first allow the bridge to transfer tokens on his behalf.
|
||||
* Alternatively, you can use BridgeTokenWithPermit to do so in a single
|
||||
* transaction. If you want the transfer to be automatically executed on the
|
||||
* destination chain. You should send enough ETH to pay the postman fees.
|
||||
* Note that Linea can reserve some tokens (which use a dedicated bridge).
|
||||
* In this case, the token cannot be bridged. Linea can only reserve tokens
|
||||
* that have not been bridged yet.
|
||||
* Linea can pause the bridge for security reason. In this case new bridge
|
||||
* transaction would revert.
|
||||
* @dev Note: If, when bridging an unbridged token and decimals are unknown,
|
||||
* the call will revert to prevent mismatched decimals. Only those ERC-20s,
|
||||
* with a decimals function are supported.
|
||||
* @param _token The address of the token to be bridged.
|
||||
* @param _amount The amount of the token to be bridged.
|
||||
* @param _recipient The address that will receive the tokens on the other chain.
|
||||
*/
|
||||
function bridgeToken(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
address _recipient
|
||||
) public payable virtual nonReentrant whenTypeAndGeneralNotPaused(PauseType.INITIATE_TOKEN_BRIDGING) {
|
||||
_bridgeToken(_token, _amount, _recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice This function is the internal implementation of bridgeToken.
|
||||
* @param _token The address of the token to be bridged.
|
||||
* @param _amount The amount of the token to be bridged.
|
||||
* @param _recipient The address that will receive the tokens on the other chain.
|
||||
*/
|
||||
function _bridgeToken(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
address _recipient
|
||||
) internal virtual nonZeroAddress(_token) nonZeroAddress(_recipient) nonZeroAmount(_amount) {
|
||||
uint256 sourceChainIdCache = sourceChainId;
|
||||
address nativeMappingValue = nativeToBridgedToken[sourceChainIdCache][_token];
|
||||
if (nativeMappingValue == RESERVED_STATUS) {
|
||||
revert ReservedToken(_token);
|
||||
}
|
||||
|
||||
address nativeToken = bridgedToNativeToken[_token];
|
||||
uint256 chainId;
|
||||
bytes memory tokenMetadata;
|
||||
|
||||
if (nativeToken != EMPTY) {
|
||||
BridgedToken(_token).burn(msg.sender, _amount);
|
||||
chainId = targetChainId;
|
||||
} else {
|
||||
// Token is native
|
||||
|
||||
_amount = _tranferTokensToEscrow(_token, _amount);
|
||||
|
||||
nativeToken = _token;
|
||||
|
||||
if (nativeMappingValue == EMPTY) {
|
||||
// New token
|
||||
nativeToBridgedToken[sourceChainIdCache][_token] = NATIVE_STATUS;
|
||||
emit NewToken(_token);
|
||||
}
|
||||
|
||||
// Send Metadata only when the token has not been deployed on the other chain yet
|
||||
if (nativeMappingValue != DEPLOYED_STATUS) {
|
||||
tokenMetadata = abi.encode(_safeName(_token), _safeSymbol(_token), _safeDecimals(_token));
|
||||
}
|
||||
chainId = sourceChainIdCache;
|
||||
}
|
||||
messageService.sendMessage{ value: msg.value }(
|
||||
remoteSender,
|
||||
msg.value, // fees
|
||||
abi.encodeCall(ITokenBridge.completeBridging, (nativeToken, _amount, _recipient, chainId, tokenMetadata))
|
||||
);
|
||||
emit BridgingInitiatedV2(msg.sender, _recipient, _token, _amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Transfers the token to the escrow address (default address(this)).
|
||||
* @param _token The address of the token to be bridged.
|
||||
* @param _amount The amount of the token to be bridged.
|
||||
* @return amount The real amount being transferred to the recipient.
|
||||
*/
|
||||
function _tranferTokensToEscrow(address _token, uint256 _amount) internal virtual returns (uint256 amount) {
|
||||
// For tokens with special fee logic, ensure that only the amount received by the bridge will be minted on the target chain.
|
||||
address escrowAddress = _getEscrowAddress(_token);
|
||||
uint256 balanceBefore = IERC20Upgradeable(_token).balanceOf(escrowAddress);
|
||||
IERC20Upgradeable(_token).safeTransferFrom(msg.sender, escrowAddress, _amount);
|
||||
amount = IERC20Upgradeable(_token).balanceOf(escrowAddress) - balanceBefore;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Retrieves the escrow address (default address(this)).
|
||||
* @dev _token is only used in overrides allowing different escrow per token.
|
||||
* @param _token The address of the token to be bridged.
|
||||
* @return escrowAddress The address of where the bridged local token will be kept.
|
||||
*/
|
||||
function _getEscrowAddress(address _token) internal view virtual returns (address escrowAddress) {
|
||||
escrowAddress = address(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Similar to `bridgeToken` function but allows to pass additional
|
||||
* permit data to do the ERC-20 approval in a single transaction.
|
||||
* @notice _permit can fail silently, don't rely on this function passing as a form
|
||||
* of authentication
|
||||
* @dev There is no need for validation at this level as the validation on pausing,
|
||||
* and empty values exists on the "bridgeToken" call.
|
||||
* @param _token The address of the token to be bridged.
|
||||
* @param _amount The amount of the token to be bridged.
|
||||
* @param _recipient The address that will receive the tokens on the other chain.
|
||||
* @param _permitData The permit data for the token, if applicable.
|
||||
*/
|
||||
function bridgeTokenWithPermit(
|
||||
address _token,
|
||||
uint256 _amount,
|
||||
address _recipient,
|
||||
bytes calldata _permitData
|
||||
) external payable virtual nonReentrant whenTypeAndGeneralNotPaused(PauseType.INITIATE_TOKEN_BRIDGING) {
|
||||
if (_permitData.length != 0) {
|
||||
_permit(_token, _permitData);
|
||||
}
|
||||
_bridgeToken(_token, _amount, _recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Completes bridging deploying new tokens if needed.
|
||||
* @dev It can only be called from the Message Service. To finalize the bridging
|
||||
* process, a user or postman needs to use the `claimMessage` function of the
|
||||
* Message Service to trigger the transaction.
|
||||
* @param _nativeToken The address of the token on its native chain.
|
||||
* @param _amount The amount of the token to be received.
|
||||
* @param _recipient The address that will receive the tokens.
|
||||
* @param _chainId The token's origin layer chainId
|
||||
* @param _tokenMetadata Additional data used to deploy the bridged token if it
|
||||
* doesn't exist already.
|
||||
*/
|
||||
function completeBridging(
|
||||
address _nativeToken,
|
||||
uint256 _amount,
|
||||
address _recipient,
|
||||
uint256 _chainId,
|
||||
bytes calldata _tokenMetadata
|
||||
)
|
||||
external
|
||||
virtual
|
||||
nonReentrant
|
||||
onlyMessagingService
|
||||
onlyAuthorizedRemoteSender
|
||||
whenTypeAndGeneralNotPaused(PauseType.COMPLETE_TOKEN_BRIDGING)
|
||||
{
|
||||
_completeBridging(_nativeToken, _amount, _recipient, _chainId, _tokenMetadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Completes bridging.
|
||||
* @param _nativeToken The address of the token on its native chain.
|
||||
* @param _amount The amount of the token to be received.
|
||||
* @param _recipient The address that will receive the tokens.
|
||||
* @param _chainId The token's origin layer chainId
|
||||
* @param _tokenMetadata Additional data used to deploy the bridged token if it
|
||||
* doesn't exist already.
|
||||
*/
|
||||
function _completeBridging(
|
||||
address _nativeToken,
|
||||
uint256 _amount,
|
||||
address _recipient,
|
||||
uint256 _chainId,
|
||||
bytes calldata _tokenMetadata
|
||||
) internal virtual {
|
||||
address nativeMappingValue = nativeToBridgedToken[_chainId][_nativeToken];
|
||||
address bridgedToken;
|
||||
|
||||
if (nativeMappingValue == NATIVE_STATUS || nativeMappingValue == DEPLOYED_STATUS) {
|
||||
// Token is native on the local chain
|
||||
IERC20Upgradeable(_nativeToken).safeTransfer(_recipient, _amount);
|
||||
} else {
|
||||
bridgedToken = nativeMappingValue;
|
||||
if (nativeMappingValue == EMPTY) {
|
||||
// New token
|
||||
bridgedToken = deployBridgedToken(_nativeToken, _tokenMetadata, sourceChainId);
|
||||
bridgedToNativeToken[bridgedToken] = _nativeToken;
|
||||
nativeToBridgedToken[targetChainId][_nativeToken] = bridgedToken;
|
||||
}
|
||||
BridgedToken(bridgedToken).mint(_recipient, _amount);
|
||||
}
|
||||
emit BridgingFinalizedV2(_nativeToken, bridgedToken, _amount, _recipient);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Change the address of the Message Service.
|
||||
* @dev SET_MESSAGE_SERVICE_ROLE is required to execute.
|
||||
* @param _messageService The address of the new Message Service.
|
||||
*/
|
||||
function setMessageService(
|
||||
address _messageService
|
||||
) external nonZeroAddress(_messageService) onlyRole(SET_MESSAGE_SERVICE_ROLE) {
|
||||
address oldMessageService = address(messageService);
|
||||
messageService = IMessageService(_messageService);
|
||||
emit MessageServiceUpdated(_messageService, oldMessageService, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Change the status to DEPLOYED to the tokens passed in parameter
|
||||
* Will call the method setDeployed on the other chain using the message Service
|
||||
* @param _tokens Array of bridged tokens that have been deployed.
|
||||
*/
|
||||
function confirmDeployment(address[] memory _tokens) external payable {
|
||||
uint256 tokensLength = _tokens.length;
|
||||
if (tokensLength == 0) {
|
||||
revert TokenListEmpty();
|
||||
}
|
||||
|
||||
// Check that the tokens have actually been deployed
|
||||
for (uint256 i; i < tokensLength; i++) {
|
||||
address nativeToken = bridgedToNativeToken[_tokens[i]];
|
||||
if (nativeToken == EMPTY) {
|
||||
revert TokenNotDeployed(_tokens[i]);
|
||||
}
|
||||
_tokens[i] = nativeToken;
|
||||
}
|
||||
|
||||
messageService.sendMessage{ value: msg.value }(
|
||||
remoteSender,
|
||||
msg.value, // fees
|
||||
abi.encodeCall(ITokenBridge.setDeployed, (_tokens))
|
||||
);
|
||||
|
||||
emit DeploymentConfirmed(_tokens, msg.sender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Change the status of tokens to DEPLOYED. New bridge transaction will not
|
||||
* contain token metadata, which save gas.
|
||||
* Can only be called from the Message Service. A user or postman needs to use
|
||||
* the `claimMessage` function of the Message Service to trigger the transaction.
|
||||
* @param _nativeTokens Array of native tokens for which the DEPLOYED status must be set.
|
||||
*/
|
||||
function setDeployed(address[] calldata _nativeTokens) external onlyMessagingService onlyAuthorizedRemoteSender {
|
||||
unchecked {
|
||||
uint256 cachedSourceChainId = sourceChainId;
|
||||
for (uint256 i; i < _nativeTokens.length; ) {
|
||||
nativeToBridgedToken[cachedSourceChainId][_nativeTokens[i]] = DEPLOYED_STATUS;
|
||||
emit TokenDeployed(_nativeTokens[i]);
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Deploy a new EC20 contract for bridged token using a beacon proxy pattern.
|
||||
* To adapt to future requirements, Linea can update the implementation of
|
||||
* all (existing and future) contracts by updating the beacon. This update is
|
||||
* subject to a delay by a time lock.
|
||||
* Contracts are deployed using CREATE2 so deployment address is deterministic.
|
||||
* @param _nativeToken The address of the native token on the source chain.
|
||||
* @param _tokenMetadata The encoded metadata for the token.
|
||||
* @param _chainId The chain id on which the token will be deployed, used to calculate the salt
|
||||
* @return bridgedTokenAddress The address of the newly deployed BridgedToken contract.
|
||||
*/
|
||||
function deployBridgedToken(
|
||||
address _nativeToken,
|
||||
bytes calldata _tokenMetadata,
|
||||
uint256 _chainId
|
||||
) internal returns (address bridgedTokenAddress) {
|
||||
bridgedTokenAddress = address(
|
||||
new BeaconProxy{ salt: EfficientLeftRightKeccak._efficientKeccak(_chainId, _nativeToken) }(tokenBeacon, "")
|
||||
);
|
||||
|
||||
(string memory name, string memory symbol, uint8 decimals) = abi.decode(_tokenMetadata, (string, string, uint8));
|
||||
BridgedToken(bridgedTokenAddress).initialize(name, symbol, decimals);
|
||||
emit NewTokenDeployed(bridgedTokenAddress, _nativeToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Linea can reserve tokens. In this case, the token cannot be bridged.
|
||||
* Linea can only reserve tokens that have not been bridged before.
|
||||
* @dev SET_RESERVED_TOKEN_ROLE is required to execute.
|
||||
* @notice Make sure that _token is native to the current chain
|
||||
* where you are calling this function from
|
||||
* @param _token The address of the token to be set as reserved.
|
||||
*/
|
||||
function setReserved(
|
||||
address _token
|
||||
) external nonZeroAddress(_token) isNewToken(_token) onlyRole(SET_RESERVED_TOKEN_ROLE) {
|
||||
nativeToBridgedToken[sourceChainId][_token] = RESERVED_STATUS;
|
||||
emit TokenReserved(_token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Removes a token from the reserved list.
|
||||
* @dev REMOVE_RESERVED_TOKEN_ROLE is required to execute.
|
||||
* @param _token The address of the token to be removed from the reserved list.
|
||||
*/
|
||||
function removeReserved(address _token) external nonZeroAddress(_token) onlyRole(REMOVE_RESERVED_TOKEN_ROLE) {
|
||||
uint256 cachedSourceChainId = sourceChainId;
|
||||
|
||||
if (nativeToBridgedToken[cachedSourceChainId][_token] != RESERVED_STATUS) revert NotReserved(_token);
|
||||
nativeToBridgedToken[cachedSourceChainId][_token] = EMPTY;
|
||||
|
||||
emit ReservationRemoved(_token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Linea can set a custom ERC-20 contract for specific ERC-20.
|
||||
* For security purposes, Linea can only call this function if the token has
|
||||
* not been bridged yet.
|
||||
* @dev SET_CUSTOM_CONTRACT_ROLE is required to execute.
|
||||
* @param _nativeToken The address of the token on the source chain.
|
||||
* @param _targetContract The address of the custom contract.
|
||||
*/
|
||||
function setCustomContract(
|
||||
address _nativeToken,
|
||||
address _targetContract
|
||||
)
|
||||
external
|
||||
nonZeroAddress(_nativeToken)
|
||||
nonZeroAddress(_targetContract)
|
||||
onlyRole(SET_CUSTOM_CONTRACT_ROLE)
|
||||
isNewToken(_nativeToken)
|
||||
{
|
||||
if (bridgedToNativeToken[_targetContract] != EMPTY) {
|
||||
revert AlreadyBrigedToNativeTokenSet(_targetContract);
|
||||
}
|
||||
if (_targetContract == NATIVE_STATUS || _targetContract == DEPLOYED_STATUS || _targetContract == RESERVED_STATUS) {
|
||||
revert StatusAddressNotAllowed(_targetContract);
|
||||
}
|
||||
|
||||
uint256 cachedTargetChainId = targetChainId;
|
||||
|
||||
if (nativeToBridgedToken[cachedTargetChainId][_nativeToken] != EMPTY) {
|
||||
revert NativeToBridgedTokenAlreadySet(_nativeToken);
|
||||
}
|
||||
|
||||
nativeToBridgedToken[cachedTargetChainId][_nativeToken] = _targetContract;
|
||||
bridgedToNativeToken[_targetContract] = _nativeToken;
|
||||
emit CustomContractSet(_nativeToken, _targetContract, msg.sender);
|
||||
}
|
||||
|
||||
// Helpers to safely get the metadata from a token, inspired by
|
||||
// https://github.com/traderjoe-xyz/joe-core/blob/main/contracts/MasterChefJoeV3.sol#L55-L95
|
||||
|
||||
/**
|
||||
* @dev Provides a safe ERC-20.name version which returns 'NO_NAME' as fallback string.
|
||||
* @param _token The address of the ERC-20 token contract
|
||||
* @return tokenName Returns the string of the token name.
|
||||
*/
|
||||
function _safeName(address _token) internal view returns (string memory tokenName) {
|
||||
(bool success, bytes memory data) = _token.staticcall(METADATA_NAME);
|
||||
tokenName = success ? _returnDataToString(data) : "NO_NAME";
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Provides a safe ERC-20.symbol version which returns 'NO_SYMBOL' as fallback string
|
||||
* @param _token The address of the ERC-20 token contract
|
||||
* @return symbol Returns the string of the symbol.
|
||||
*/
|
||||
function _safeSymbol(address _token) internal view returns (string memory symbol) {
|
||||
(bool success, bytes memory data) = _token.staticcall(METADATA_SYMBOL);
|
||||
symbol = success ? _returnDataToString(data) : "NO_SYMBOL";
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Provides a safe ERC-20.decimals version which reverts when decimals are unknown
|
||||
* Note Tokens with (decimals > 255) are not supported
|
||||
* @param _token The address of the ERC-20 token contract
|
||||
* @return Returns the token's decimals value.
|
||||
*/
|
||||
function _safeDecimals(address _token) internal view returns (uint8) {
|
||||
(bool success, bytes memory data) = _token.staticcall(METADATA_DECIMALS);
|
||||
|
||||
if (success && data.length == VALID_DECIMALS_ENCODING_LENGTH) {
|
||||
return abi.decode(data, (uint8));
|
||||
}
|
||||
|
||||
revert DecimalsAreUnknown(_token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Converts returned data to string. Returns 'NOT_VALID_ENCODING' as fallback value.
|
||||
* @param _data returned data.
|
||||
* @return decodedString The decoded string data.
|
||||
*/
|
||||
function _returnDataToString(bytes memory _data) internal pure returns (string memory decodedString) {
|
||||
if (_data.length >= MINIMUM_STRING_ABI_DECODE_LENGTH) {
|
||||
return abi.decode(_data, (string));
|
||||
} else if (_data.length != SHORT_STRING_ENCODING_LENGTH) {
|
||||
return "NOT_VALID_ENCODING";
|
||||
}
|
||||
|
||||
// Since the strings on bytes32 are encoded left-right, check the first zero in the data
|
||||
uint256 nonZeroBytes;
|
||||
unchecked {
|
||||
while (nonZeroBytes < SHORT_STRING_ENCODING_LENGTH && _data[nonZeroBytes] != 0) {
|
||||
nonZeroBytes++;
|
||||
}
|
||||
}
|
||||
|
||||
// If the first one is 0, we do not handle the encoding
|
||||
if (nonZeroBytes == 0) {
|
||||
return "NOT_VALID_ENCODING";
|
||||
}
|
||||
// Create a byte array with nonZeroBytes length
|
||||
bytes memory bytesArray = new bytes(nonZeroBytes);
|
||||
unchecked {
|
||||
for (uint256 i; i < nonZeroBytes; ) {
|
||||
bytesArray[i] = _data[i];
|
||||
++i;
|
||||
}
|
||||
}
|
||||
decodedString = string(bytesArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Call the token permit method of extended ERC-20
|
||||
* @notice Only support tokens implementing ERC-2612
|
||||
* @param _token ERC-20 token address
|
||||
* @param _permitData Raw data of the call `permit` of the token
|
||||
*/
|
||||
function _permit(address _token, bytes calldata _permitData) internal virtual {
|
||||
if (bytes4(_permitData[:4]) != _PERMIT_SELECTOR)
|
||||
revert InvalidPermitData(bytes4(_permitData[:4]), _PERMIT_SELECTOR);
|
||||
// Decode the permit data
|
||||
// The parameters are:
|
||||
// 1. owner: The address of the wallet holding the tokens
|
||||
// 2. spender: The address of the entity permitted to spend the tokens
|
||||
// 3. value: The maximum amount of tokens the spender is allowed to spend
|
||||
// 4. deadline: The time until which the permit is valid
|
||||
// 5. v: Part of the signature (along with r and s), these three values form the signature of the permit
|
||||
// 6. r: Part of the signature
|
||||
// 7. s: Part of the signature
|
||||
(address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s) = abi.decode(
|
||||
_permitData[4:],
|
||||
(address, address, uint256, uint256, uint8, bytes32, bytes32)
|
||||
);
|
||||
if (owner != msg.sender) revert PermitNotFromSender(owner);
|
||||
if (spender != address(this)) revert PermitNotAllowingBridge(spender);
|
||||
|
||||
if (IERC20Upgradeable(_token).allowance(owner, spender) < amount) {
|
||||
IERC20PermitUpgradeable(_token).permit(msg.sender, address(this), amount, deadline, v, r, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { IPauseManager } from "../../../security/pausing/interfaces/IPauseManager.sol";
|
||||
import { IPermissionsManager } from "../../../security/access/interfaces/IPermissionsManager.sol";
|
||||
@@ -226,6 +226,12 @@ interface ITokenBridge {
|
||||
*/
|
||||
error SourceChainSameAsTargetChain();
|
||||
|
||||
/**
|
||||
* @notice Returns the ABI version and not the reinitialize version.
|
||||
* @return contractVersion The contract ABI version.
|
||||
*/
|
||||
function CONTRACT_VERSION() external view returns (string memory contractVersion);
|
||||
|
||||
/**
|
||||
* @notice Similar to `bridgeToken` function but allows to pass additional
|
||||
* permit data to do the ERC-20 approval in a single transaction.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Contract to fill space in storage to maintain storage layout.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { TimelockController } from "@openzeppelin/contracts/governance/TimelockController.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Interface declaring generic errors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Contract to manage some efficient hashing functions.
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
// Code generated by gnark DO NOT EDIT
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Library to perform MiMC hashing
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { Mimc } from "./Mimc.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
import { IMessageService } from "./interfaces/IMessageService.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Interface declaring pre-existing cross-chain messaging functions, events and errors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { BitMaps } from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
|
||||
import { L1MessageManagerV1 } from "./v1/L1MessageManagerV1.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { L1MessageServiceV1 } from "./v1/L1MessageServiceV1.sol";
|
||||
@@ -57,7 +57,17 @@ abstract contract L1MessageService is
|
||||
address _to,
|
||||
uint256 _fee,
|
||||
bytes calldata _calldata
|
||||
) external payable whenTypeAndGeneralNotPaused(PauseType.L1_L2) {
|
||||
) external payable virtual whenTypeAndGeneralNotPaused(PauseType.L1_L2) {
|
||||
_sendMessage(_to, _fee, _calldata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds a message for sending cross-chain and emits MessageSent.
|
||||
* @param _to The address the message is intended for.
|
||||
* @param _fee The fee being paid for the message delivery.
|
||||
* @param _calldata The calldata to pass to the recipient.
|
||||
*/
|
||||
function _sendMessage(address _to, uint256 _fee, bytes calldata _calldata) internal virtual {
|
||||
if (_to == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
@@ -84,7 +94,15 @@ abstract contract L1MessageService is
|
||||
*/
|
||||
function claimMessageWithProof(
|
||||
ClaimMessageWithProofParams calldata _params
|
||||
) external nonReentrant distributeFees(_params.fee, _params.to, _params.data, _params.feeRecipient) {
|
||||
) external virtual nonReentrant distributeFees(_params.fee, _params.to, _params.data, _params.feeRecipient) {
|
||||
_claimMessageWithProof(_params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Claims and delivers a cross-chain message using a Merkle proof.
|
||||
* @param _params Collection of claim data with proof and supporting data.
|
||||
*/
|
||||
function _claimMessageWithProof(ClaimMessageWithProofParams calldata _params) internal virtual {
|
||||
_requireTypeAndGeneralNotPaused(PauseType.L2_L1);
|
||||
|
||||
uint256 merkleDepth = l2MerkleRootsDepths[_params.merkleRoot];
|
||||
@@ -144,7 +162,7 @@ abstract contract L1MessageService is
|
||||
* @dev The message sender address is set temporarily in the transient storage when claiming.
|
||||
* @return originalSender The message sender address that is stored temporarily in the transient storage when claiming.
|
||||
*/
|
||||
function sender() external view returns (address originalSender) {
|
||||
function sender() external view virtual returns (address originalSender) {
|
||||
originalSender = TRANSIENT_MESSAGE_SENDER;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title L1 Message manager interface for current functions, events and errors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title L1 Message Service interface for pre-existing functions, events, structs and errors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { IL1MessageManagerV1 } from "./interfaces/IL1MessageManagerV1.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { LineaRollupPauseManager } from "../../../security/pausing/LineaRollupPauseManager.sol";
|
||||
import { RateLimiter } from "../../../security/limiting/RateLimiter.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title L1 Message manager V1 interface for pre-existing errors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { L2MessageManagerV1 } from "./v1/L2MessageManagerV1.sol";
|
||||
|
||||
@@ -1,20 +1,12 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { L2MessageServiceV1 } from "./v1/L2MessageServiceV1.sol";
|
||||
import { L2MessageManager } from "./L2MessageManager.sol";
|
||||
import { PermissionsManager } from "../../security/access/PermissionsManager.sol";
|
||||
|
||||
import { L2MessageServiceBase } from "./L2MessageServiceBase.sol";
|
||||
/**
|
||||
* @title Contract to manage cross-chain messaging on L2.
|
||||
* @author ConsenSys Software Inc.
|
||||
* @custom:security-contact security-report@linea.build
|
||||
*/
|
||||
contract L2MessageService is AccessControlUpgradeable, L2MessageServiceV1, L2MessageManager, PermissionsManager {
|
||||
/// @dev This is the ABI version and not the reinitialize version.
|
||||
string public constant CONTRACT_VERSION = "1.0";
|
||||
|
||||
contract L2MessageService is L2MessageServiceBase {
|
||||
/// @dev Total contract storage is 50 slots with the gap below.
|
||||
/// @dev Keep 50 free storage slots for future implementation updates to avoid storage collision.
|
||||
uint256[50] private __gap_L2MessageService;
|
||||
@@ -40,46 +32,14 @@ contract L2MessageService is AccessControlUpgradeable, L2MessageServiceV1, L2Mes
|
||||
RoleAddress[] calldata _roleAddresses,
|
||||
PauseTypeRole[] calldata _pauseTypeRoleAssignments,
|
||||
PauseTypeRole[] calldata _unpauseTypeRoleAssignments
|
||||
) external initializer {
|
||||
__ERC165_init();
|
||||
__Context_init();
|
||||
__AccessControl_init();
|
||||
__RateLimiter_init(_rateLimitPeriod, _rateLimitAmount);
|
||||
|
||||
__PauseManager_init(_pauseTypeRoleAssignments, _unpauseTypeRoleAssignments);
|
||||
__ReentrancyGuard_init();
|
||||
|
||||
if (_defaultAdmin == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev DEFAULT_ADMIN_ROLE is set for the security council explicitly,
|
||||
* as the permissions init purposefully does not allow DEFAULT_ADMIN_ROLE to be set.
|
||||
*/
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
|
||||
|
||||
__Permissions_init(_roleAddresses);
|
||||
|
||||
nextMessageNumber = 1;
|
||||
|
||||
_messageSender = DEFAULT_SENDER_ADDRESS;
|
||||
minimumFeeInWei = 0.0001 ether;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets permissions for a list of addresses and their roles as well as initialises the PauseManager pauseType:role mappings.
|
||||
* @dev This function is a reinitializer and can only be called once per version. Should be called using an upgradeAndCall transaction to the ProxyAdmin.
|
||||
* @param _roleAddresses The list of addresses and roles to assign permissions to.
|
||||
* @param _pauseTypeRoleAssignments The list of pause types to associate with roles.
|
||||
* @param _unpauseTypeRoleAssignments The list of unpause types to associate with roles.
|
||||
*/
|
||||
function reinitializePauseTypesAndPermissions(
|
||||
RoleAddress[] calldata _roleAddresses,
|
||||
PauseTypeRole[] calldata _pauseTypeRoleAssignments,
|
||||
PauseTypeRole[] calldata _unpauseTypeRoleAssignments
|
||||
) external reinitializer(2) {
|
||||
__Permissions_init(_roleAddresses);
|
||||
__PauseManager_init(_pauseTypeRoleAssignments, _unpauseTypeRoleAssignments);
|
||||
) external virtual initializer {
|
||||
__L2MessageService_init(
|
||||
_rateLimitPeriod,
|
||||
_rateLimitAmount,
|
||||
_defaultAdmin,
|
||||
_roleAddresses,
|
||||
_pauseTypeRoleAssignments,
|
||||
_unpauseTypeRoleAssignments
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
82
contracts/src/messaging/l2/L2MessageServiceBase.sol
Normal file
82
contracts/src/messaging/l2/L2MessageServiceBase.sol
Normal file
@@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { L2MessageServiceV1 } from "./v1/L2MessageServiceV1.sol";
|
||||
import { L2MessageManager } from "./L2MessageManager.sol";
|
||||
import { PermissionsManager } from "../../security/access/PermissionsManager.sol";
|
||||
|
||||
/**
|
||||
* @title Contract to manage cross-chain messaging on L2.
|
||||
* @author ConsenSys Software Inc.
|
||||
* @custom:security-contact security-report@linea.build
|
||||
*/
|
||||
abstract contract L2MessageServiceBase is
|
||||
AccessControlUpgradeable,
|
||||
L2MessageServiceV1,
|
||||
L2MessageManager,
|
||||
PermissionsManager
|
||||
{
|
||||
/// @dev This is the ABI version and not the reinitialize version.
|
||||
string private constant _CONTRACT_VERSION = "1.0";
|
||||
|
||||
/// @dev Total contract storage is 50 slots with the gap below.
|
||||
/// @dev Keep 50 free storage slots for future implementation updates to avoid storage collision.
|
||||
uint256[50] private __gap_L2MessageService;
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Initializes underlying message service dependencies.
|
||||
* @param _rateLimitPeriod The period to rate limit against.
|
||||
* @param _rateLimitAmount The limit allowed for withdrawing the period.
|
||||
* @param _defaultAdmin The account to be given DEFAULT_ADMIN_ROLE on initialization.
|
||||
* @param _roleAddresses The list of addresses to grant roles to.
|
||||
* @param _pauseTypeRoleAssignments The list of pause type roles.
|
||||
* @param _unpauseTypeRoleAssignments The list of unpause type roles.
|
||||
*/
|
||||
function __L2MessageService_init(
|
||||
uint256 _rateLimitPeriod,
|
||||
uint256 _rateLimitAmount,
|
||||
address _defaultAdmin,
|
||||
RoleAddress[] calldata _roleAddresses,
|
||||
PauseTypeRole[] calldata _pauseTypeRoleAssignments,
|
||||
PauseTypeRole[] calldata _unpauseTypeRoleAssignments
|
||||
) internal virtual {
|
||||
__ERC165_init();
|
||||
__Context_init();
|
||||
__AccessControl_init();
|
||||
__RateLimiter_init(_rateLimitPeriod, _rateLimitAmount);
|
||||
|
||||
__PauseManager_init(_pauseTypeRoleAssignments, _unpauseTypeRoleAssignments);
|
||||
__ReentrancyGuard_init();
|
||||
|
||||
if (_defaultAdmin == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev DEFAULT_ADMIN_ROLE is set for the security council explicitly,
|
||||
* as the permissions init purposefully does not allow DEFAULT_ADMIN_ROLE to be set.
|
||||
*/
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, _defaultAdmin);
|
||||
|
||||
__Permissions_init(_roleAddresses);
|
||||
|
||||
nextMessageNumber = 1;
|
||||
|
||||
_messageSender = DEFAULT_SENDER_ADDRESS;
|
||||
minimumFeeInWei = 0.0001 ether;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the ABI version and not the reinitialize version.
|
||||
* @return contractVersion The contract ABI version.
|
||||
*/
|
||||
function CONTRACT_VERSION() external view virtual returns (string memory contractVersion) {
|
||||
contractVersion = _CONTRACT_VERSION;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Interface declaring cross-chain messaging on L2 functions, events and errors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
import { IL2MessageManagerV1 } from "./interfaces/IL2MessageManagerV1.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
|
||||
import { IMessageService } from "../../interfaces/IMessageService.sol";
|
||||
@@ -29,7 +29,7 @@ abstract contract L2MessageServiceV1 is
|
||||
* NB: Take note that this is at the beginning of the file where other storage gaps,
|
||||
* are at the end of files. Be careful with how storage is adjusted on upgrades.
|
||||
*/
|
||||
uint256[50] private __gap_L2MessageService;
|
||||
uint256[50] private __gap_L2MessageServiceV1;
|
||||
|
||||
/// @notice The role required to set the minimum DDOS fee.
|
||||
bytes32 public constant MINIMUM_FEE_SETTER_ROLE = keccak256("MINIMUM_FEE_SETTER_ROLE");
|
||||
@@ -63,9 +63,21 @@ abstract contract L2MessageServiceV1 is
|
||||
* @param _fee The fee being paid for the message delivery.
|
||||
* @param _calldata The calldata to pass to the recipient.
|
||||
*/
|
||||
function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable {
|
||||
_requireTypeAndGeneralNotPaused(PauseType.L2_L1);
|
||||
function sendMessage(
|
||||
address _to,
|
||||
uint256 _fee,
|
||||
bytes calldata _calldata
|
||||
) external payable virtual whenTypeAndGeneralNotPaused(PauseType.L2_L1) {
|
||||
_sendMessage(_to, _fee, _calldata);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds a message for sending cross-chain and emits a relevant event.
|
||||
* @param _to The address the message is intended for.
|
||||
* @param _fee The fee being paid for the message delivery.
|
||||
* @param _calldata The calldata to pass to the recipient.
|
||||
*/
|
||||
function _sendMessage(address _to, uint256 _fee, bytes calldata _calldata) internal virtual {
|
||||
if (_to == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
@@ -121,9 +133,33 @@ abstract contract L2MessageServiceV1 is
|
||||
address payable _feeRecipient,
|
||||
bytes calldata _calldata,
|
||||
uint256 _nonce
|
||||
) external nonReentrant distributeFees(_fee, _to, _calldata, _feeRecipient) {
|
||||
_requireTypeAndGeneralNotPaused(PauseType.L1_L2);
|
||||
)
|
||||
external
|
||||
virtual
|
||||
nonReentrant
|
||||
distributeFees(_fee, _to, _calldata, _feeRecipient)
|
||||
whenTypeAndGeneralNotPaused(PauseType.L1_L2)
|
||||
{
|
||||
_claimMessage(_from, _to, _fee, _value, _calldata, _nonce);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Claims and delivers a cross-chain message.
|
||||
* @param _from The address of the original sender.
|
||||
* @param _to The address the message is intended for.
|
||||
* @param _fee The fee being paid for the message delivery.
|
||||
* @param _value The value to be transferred to the destination address.
|
||||
* @param _calldata The calldata to pass to the recipient.
|
||||
* @param _nonce The unique auto generated message number used when sending the message.
|
||||
*/
|
||||
function _claimMessage(
|
||||
address _from,
|
||||
address _to,
|
||||
uint256 _fee,
|
||||
uint256 _value,
|
||||
bytes calldata _calldata,
|
||||
uint256 _nonce
|
||||
) internal virtual {
|
||||
bytes32 messageHash = MessageHashing._hashMessage(_from, _to, _fee, _value, _nonce, _calldata);
|
||||
|
||||
/// @dev Status check and revert is in the message manager.
|
||||
@@ -163,7 +199,7 @@ abstract contract L2MessageServiceV1 is
|
||||
* @dev The _messageSender address is set temporarily when claiming.
|
||||
* @return originalSender The original sender stored temporarily at the _messageSender address in storage.
|
||||
*/
|
||||
function sender() external view returns (address originalSender) {
|
||||
function sender() external view virtual returns (address originalSender) {
|
||||
originalSender = _messageSender;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Interface declaring pre-existing cross-chain messaging on L2 functions, events and errors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title L2 Message Service interface for pre-existing functions, events and errors.
|
||||
@@ -7,6 +7,12 @@ pragma solidity 0.8.30;
|
||||
* @custom:security-contact security-report@linea.build
|
||||
*/
|
||||
interface IL2MessageServiceV1 {
|
||||
/**
|
||||
* @notice Returns the ABI version and not the reinitialize version.
|
||||
* @return contractVersion The contract ABI version.
|
||||
*/
|
||||
function CONTRACT_VERSION() external view returns (string memory contractVersion);
|
||||
|
||||
/**
|
||||
* @notice The Fee Manager sets a minimum fee to address DOS protection.
|
||||
* @dev MINIMUM_FEE_SETTER_ROLE is required to set the minimum fee.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Library to hash cross-chain messages.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { EfficientLeftRightKeccak } from "../../libraries/EfficientLeftRightKeccak.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Contract to forward calls to an underlying contract.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { IGenericErrors } from "../interfaces/IGenericErrors.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Interface declaring IRecoverFunds errors and functions.
|
||||
|
||||
@@ -1,86 +1,13 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { L1MessageService } from "../messaging/l1/L1MessageService.sol";
|
||||
import { ZkEvmV2 } from "./ZkEvmV2.sol";
|
||||
import { ILineaRollup } from "./interfaces/ILineaRollup.sol";
|
||||
import { PermissionsManager } from "../security/access/PermissionsManager.sol";
|
||||
|
||||
import { EfficientLeftRightKeccak } from "../libraries/EfficientLeftRightKeccak.sol";
|
||||
import { LineaRollupBase } from "./LineaRollupBase.sol";
|
||||
/**
|
||||
* @title Contract to manage cross-chain messaging on L1, L2 data submission, and rollup proof verification.
|
||||
* @author ConsenSys Software Inc.
|
||||
* @custom:security-contact security-report@linea.build
|
||||
*/
|
||||
contract LineaRollup is AccessControlUpgradeable, ZkEvmV2, L1MessageService, PermissionsManager, ILineaRollup {
|
||||
using EfficientLeftRightKeccak for *;
|
||||
|
||||
/// @notice This is the ABI version and not the reinitialize version.
|
||||
string public constant CONTRACT_VERSION = "7.0";
|
||||
|
||||
/// @notice The role required to set/add proof verifiers by type.
|
||||
bytes32 public constant VERIFIER_SETTER_ROLE = keccak256("VERIFIER_SETTER_ROLE");
|
||||
|
||||
/// @notice The role required to set/remove proof verifiers by type.
|
||||
bytes32 public constant VERIFIER_UNSETTER_ROLE = keccak256("VERIFIER_UNSETTER_ROLE");
|
||||
|
||||
/// @dev Value indicating a shnarf exists.
|
||||
uint256 internal constant SHNARF_EXISTS_DEFAULT_VALUE = 1;
|
||||
|
||||
/// @dev The default hash value.
|
||||
bytes32 internal constant EMPTY_HASH = 0x0;
|
||||
|
||||
/// @dev The BLS Curve modulus value used.
|
||||
uint256 internal constant BLS_CURVE_MODULUS =
|
||||
52435875175126190479447740508185965837690552500527637822603658699938581184513;
|
||||
|
||||
/// @dev The well-known precompile address for point evaluation.
|
||||
address internal constant POINT_EVALUATION_PRECOMPILE_ADDRESS = address(0x0a);
|
||||
|
||||
/// @dev The expected point evaluation return data length.
|
||||
uint256 internal constant POINT_EVALUATION_RETURN_DATA_LENGTH = 64;
|
||||
|
||||
/// @dev The expected point evaluation field element length returned.
|
||||
uint256 internal constant POINT_EVALUATION_FIELD_ELEMENTS_LENGTH = 4096;
|
||||
|
||||
/// @dev In practice, when used, this is expected to be a close approximation to 6 months, and is intentional.
|
||||
uint256 internal constant SIX_MONTHS_IN_SECONDS = (365 / 2) * 24 * 60 * 60;
|
||||
|
||||
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
|
||||
mapping(bytes32 dataHash => bytes32 finalStateRootHash) private dataFinalStateRootHashes_DEPRECATED;
|
||||
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
|
||||
mapping(bytes32 dataHash => bytes32 parentHash) private dataParents_DEPRECATED;
|
||||
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
|
||||
mapping(bytes32 dataHash => bytes32 shnarfHash) private dataShnarfHashes_DEPRECATED;
|
||||
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
|
||||
mapping(bytes32 dataHash => uint256 startingBlock) private dataStartingBlock_DEPRECATED;
|
||||
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
|
||||
mapping(bytes32 dataHash => uint256 endingBlock) private dataEndingBlock_DEPRECATED;
|
||||
|
||||
/// @dev DEPRECATED in favor of currentFinalizedState hash.
|
||||
uint256 private currentL2StoredL1MessageNumber_DEPRECATED;
|
||||
/// @dev DEPRECATED in favor of currentFinalizedState hash.
|
||||
bytes32 private currentL2StoredL1RollingHash_DEPRECATED;
|
||||
|
||||
/// @notice Contains the most recent finalized shnarf.
|
||||
bytes32 public currentFinalizedShnarf;
|
||||
|
||||
/**
|
||||
* @dev NB: THIS IS THE ONLY MAPPING BEING USED FOR DATA SUBMISSION TRACKING.
|
||||
* @dev NB: This was shnarfFinalBlockNumbers and is replaced to indicate only that a shnarf exists with a value of 1.
|
||||
*/
|
||||
mapping(bytes32 shnarf => uint256 exists) public blobShnarfExists;
|
||||
|
||||
/// @notice Hash of the L2 computed L1 message number, rolling hash and finalized timestamp.
|
||||
bytes32 public currentFinalizedState;
|
||||
|
||||
/// @notice The address of the fallback operator.
|
||||
/// @dev This address is granted the OPERATOR_ROLE after six months of finalization inactivity by the current operators.
|
||||
address public fallbackOperator;
|
||||
|
||||
/// @dev Total contract storage is 11 slots.
|
||||
|
||||
contract LineaRollup is LineaRollupBase {
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
@@ -94,623 +21,6 @@ contract LineaRollup is AccessControlUpgradeable, ZkEvmV2, L1MessageService, Per
|
||||
* @param _initializationData The initial data used for proof verification.
|
||||
*/
|
||||
function initialize(InitializationData calldata _initializationData) external initializer {
|
||||
if (_initializationData.defaultVerifier == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
__PauseManager_init(_initializationData.pauseTypeRoles, _initializationData.unpauseTypeRoles);
|
||||
|
||||
__MessageService_init(_initializationData.rateLimitPeriodInSeconds, _initializationData.rateLimitAmountInWei);
|
||||
|
||||
if (_initializationData.defaultAdmin == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev DEFAULT_ADMIN_ROLE is set for the security council explicitly,
|
||||
* as the permissions init purposefully does not allow DEFAULT_ADMIN_ROLE to be set.
|
||||
*/
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, _initializationData.defaultAdmin);
|
||||
|
||||
__Permissions_init(_initializationData.roleAddresses);
|
||||
|
||||
verifiers[0] = _initializationData.defaultVerifier;
|
||||
|
||||
if (_initializationData.fallbackOperator == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
fallbackOperator = _initializationData.fallbackOperator;
|
||||
emit FallbackOperatorAddressSet(msg.sender, _initializationData.fallbackOperator);
|
||||
|
||||
currentL2BlockNumber = _initializationData.initialL2BlockNumber;
|
||||
stateRootHashes[_initializationData.initialL2BlockNumber] = _initializationData.initialStateRootHash;
|
||||
|
||||
bytes32 genesisShnarf = _computeShnarf(
|
||||
EMPTY_HASH,
|
||||
EMPTY_HASH,
|
||||
_initializationData.initialStateRootHash,
|
||||
EMPTY_HASH,
|
||||
EMPTY_HASH
|
||||
);
|
||||
|
||||
blobShnarfExists[genesisShnarf] = SHNARF_EXISTS_DEFAULT_VALUE;
|
||||
currentFinalizedShnarf = genesisShnarf;
|
||||
currentFinalizedState = _computeLastFinalizedState(0, EMPTY_HASH, _initializationData.genesisTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets permissions for a list of addresses and their roles as well as initialises the PauseManager pauseType:role mappings and fallback operator.
|
||||
* @dev This function is a reinitializer and can only be called once per version. Should be called using an upgradeAndCall transaction to the ProxyAdmin.
|
||||
* @param _roleAddresses The list of addresses and roles to assign permissions to.
|
||||
* @param _pauseTypeRoleAssignments The list of pause types to associate with roles.
|
||||
* @param _unpauseTypeRoleAssignments The list of unpause types to associate with roles.
|
||||
* @param _fallbackOperator The address of the fallback operator.
|
||||
*/
|
||||
function reinitializeLineaRollupV6(
|
||||
RoleAddress[] calldata _roleAddresses,
|
||||
PauseTypeRole[] calldata _pauseTypeRoleAssignments,
|
||||
PauseTypeRole[] calldata _unpauseTypeRoleAssignments,
|
||||
address _fallbackOperator
|
||||
) external reinitializer(6) {
|
||||
__Permissions_init(_roleAddresses);
|
||||
__PauseManager_init(_pauseTypeRoleAssignments, _unpauseTypeRoleAssignments);
|
||||
|
||||
if (_fallbackOperator == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
fallbackOperator = _fallbackOperator;
|
||||
emit FallbackOperatorAddressSet(msg.sender, _fallbackOperator);
|
||||
|
||||
/// @dev using the constants requires string memory and more complex code.
|
||||
emit LineaRollupVersionChanged(bytes8("5.0"), bytes8("6.0"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Revokes `role` from the calling account.
|
||||
* @dev Fallback operator cannot renounce role. Reverts with OnlyNonFallbackOperator.
|
||||
* @param _role The role to renounce.
|
||||
* @param _account The account to renounce - can only be the _msgSender().
|
||||
*/
|
||||
function renounceRole(bytes32 _role, address _account) public override {
|
||||
if (_account == fallbackOperator) {
|
||||
revert OnlyNonFallbackOperator();
|
||||
}
|
||||
|
||||
super.renounceRole(_role, _account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds or updates the verifier contract address for a proof type.
|
||||
* @dev VERIFIER_SETTER_ROLE is required to execute.
|
||||
* @param _newVerifierAddress The address for the verifier contract.
|
||||
* @param _proofType The proof type being set/updated.
|
||||
*/
|
||||
function setVerifierAddress(address _newVerifierAddress, uint256 _proofType) external onlyRole(VERIFIER_SETTER_ROLE) {
|
||||
if (_newVerifierAddress == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
emit VerifierAddressChanged(_newVerifierAddress, _proofType, msg.sender, verifiers[_proofType]);
|
||||
|
||||
verifiers[_proofType] = _newVerifierAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the fallback operator role to the specified address if six months have passed since the last finalization.
|
||||
* @dev Reverts if six months have not passed since the last finalization.
|
||||
* @param _messageNumber Last finalized L1 message number as part of the feedback loop.
|
||||
* @param _rollingHash Last finalized L1 rolling hash as part of the feedback loop.
|
||||
* @param _lastFinalizedTimestamp Last finalized L2 block timestamp.
|
||||
*/
|
||||
function setFallbackOperator(uint256 _messageNumber, bytes32 _rollingHash, uint256 _lastFinalizedTimestamp) external {
|
||||
if (block.timestamp < _lastFinalizedTimestamp + SIX_MONTHS_IN_SECONDS) {
|
||||
revert LastFinalizationTimeNotLapsed();
|
||||
}
|
||||
if (currentFinalizedState != _computeLastFinalizedState(_messageNumber, _rollingHash, _lastFinalizedTimestamp)) {
|
||||
revert FinalizationStateIncorrect(
|
||||
currentFinalizedState,
|
||||
_computeLastFinalizedState(_messageNumber, _rollingHash, _lastFinalizedTimestamp)
|
||||
);
|
||||
}
|
||||
|
||||
address fallbackOperatorAddress = fallbackOperator;
|
||||
|
||||
_grantRole(OPERATOR_ROLE, fallbackOperatorAddress);
|
||||
emit FallbackOperatorRoleGranted(msg.sender, fallbackOperatorAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Unset the verifier contract address for a proof type.
|
||||
* @dev VERIFIER_UNSETTER_ROLE is required to execute.
|
||||
* @param _proofType The proof type being set/updated.
|
||||
*/
|
||||
function unsetVerifierAddress(uint256 _proofType) external onlyRole(VERIFIER_UNSETTER_ROLE) {
|
||||
emit VerifierAddressChanged(address(0), _proofType, msg.sender, verifiers[_proofType]);
|
||||
|
||||
delete verifiers[_proofType];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Submit one or more EIP-4844 blobs.
|
||||
* @dev OPERATOR_ROLE is required to execute.
|
||||
* @dev This should be a blob carrying transaction.
|
||||
* @param _blobSubmissions The data for blob submission including proofs and required polynomials.
|
||||
* @param _parentShnarf The parent shnarf used in continuity checks as it includes the parentStateRootHash in its computation.
|
||||
* @param _finalBlobShnarf The expected final shnarf post computation of all the blob shnarfs.
|
||||
*/
|
||||
function submitBlobs(
|
||||
BlobSubmission[] calldata _blobSubmissions,
|
||||
bytes32 _parentShnarf,
|
||||
bytes32 _finalBlobShnarf
|
||||
) external whenTypeAndGeneralNotPaused(PauseType.BLOB_SUBMISSION) onlyRole(OPERATOR_ROLE) {
|
||||
if (_blobSubmissions.length == 0) {
|
||||
revert BlobSubmissionDataIsMissing();
|
||||
}
|
||||
|
||||
if (blobhash(_blobSubmissions.length) != EMPTY_HASH) {
|
||||
revert BlobSubmissionDataEmpty(_blobSubmissions.length);
|
||||
}
|
||||
|
||||
if (blobShnarfExists[_parentShnarf] == 0) {
|
||||
revert ParentBlobNotSubmitted(_parentShnarf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev validate we haven't submitted the last shnarf. There is a final check at the end of the function verifying,
|
||||
* that _finalBlobShnarf was computed correctly.
|
||||
* Note: As only the last shnarf is stored, we don't need to validate shnarfs,
|
||||
* computed for any previous blobs in the submission (if multiple are submitted).
|
||||
*/
|
||||
if (blobShnarfExists[_finalBlobShnarf] != 0) {
|
||||
revert DataAlreadySubmitted(_finalBlobShnarf);
|
||||
}
|
||||
|
||||
bytes32 currentDataEvaluationPoint;
|
||||
bytes32 currentDataHash;
|
||||
|
||||
/// @dev Assigning in memory saves a lot of gas vs. calldata reading.
|
||||
BlobSubmission memory blobSubmission;
|
||||
|
||||
bytes32 computedShnarf = _parentShnarf;
|
||||
|
||||
for (uint256 i; i < _blobSubmissions.length; i++) {
|
||||
blobSubmission = _blobSubmissions[i];
|
||||
|
||||
currentDataHash = blobhash(i);
|
||||
|
||||
if (currentDataHash == EMPTY_HASH) {
|
||||
revert EmptyBlobDataAtIndex(i);
|
||||
}
|
||||
|
||||
bytes32 snarkHash = blobSubmission.snarkHash;
|
||||
|
||||
currentDataEvaluationPoint = EfficientLeftRightKeccak._efficientKeccak(snarkHash, currentDataHash);
|
||||
|
||||
_verifyPointEvaluation(
|
||||
currentDataHash,
|
||||
uint256(currentDataEvaluationPoint),
|
||||
blobSubmission.dataEvaluationClaim,
|
||||
blobSubmission.kzgCommitment,
|
||||
blobSubmission.kzgProof
|
||||
);
|
||||
|
||||
computedShnarf = _computeShnarf(
|
||||
computedShnarf,
|
||||
snarkHash,
|
||||
blobSubmission.finalStateRootHash,
|
||||
currentDataEvaluationPoint,
|
||||
bytes32(blobSubmission.dataEvaluationClaim)
|
||||
);
|
||||
}
|
||||
|
||||
if (_finalBlobShnarf != computedShnarf) {
|
||||
revert FinalShnarfWrong(_finalBlobShnarf, computedShnarf);
|
||||
}
|
||||
|
||||
/// @dev use the last shnarf as the submission to store as technically it becomes the next parent shnarf.
|
||||
blobShnarfExists[computedShnarf] = SHNARF_EXISTS_DEFAULT_VALUE;
|
||||
|
||||
emit DataSubmittedV3(_parentShnarf, computedShnarf, blobSubmission.finalStateRootHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Submit blobs using compressed data via calldata.
|
||||
* @dev OPERATOR_ROLE is required to execute.
|
||||
* @param _submission The supporting data for compressed data submission including compressed data.
|
||||
* @param _parentShnarf The parent shnarf used in continuity checks as it includes the parentStateRootHash in its computation.
|
||||
* @param _expectedShnarf The expected shnarf post computation of all the submission.
|
||||
*/
|
||||
function submitDataAsCalldata(
|
||||
CompressedCalldataSubmission calldata _submission,
|
||||
bytes32 _parentShnarf,
|
||||
bytes32 _expectedShnarf
|
||||
) external whenTypeAndGeneralNotPaused(PauseType.CALLDATA_SUBMISSION) onlyRole(OPERATOR_ROLE) {
|
||||
if (_submission.compressedData.length == 0) {
|
||||
revert EmptySubmissionData();
|
||||
}
|
||||
|
||||
if (blobShnarfExists[_expectedShnarf] != 0) {
|
||||
revert DataAlreadySubmitted(_expectedShnarf);
|
||||
}
|
||||
|
||||
if (blobShnarfExists[_parentShnarf] == 0) {
|
||||
revert ParentBlobNotSubmitted(_parentShnarf);
|
||||
}
|
||||
|
||||
bytes32 currentDataHash = keccak256(_submission.compressedData);
|
||||
|
||||
bytes32 dataEvaluationPoint = EfficientLeftRightKeccak._efficientKeccak(_submission.snarkHash, currentDataHash);
|
||||
|
||||
bytes32 computedShnarf = _computeShnarf(
|
||||
_parentShnarf,
|
||||
_submission.snarkHash,
|
||||
_submission.finalStateRootHash,
|
||||
dataEvaluationPoint,
|
||||
_calculateY(_submission.compressedData, dataEvaluationPoint)
|
||||
);
|
||||
|
||||
if (_expectedShnarf != computedShnarf) {
|
||||
revert FinalShnarfWrong(_expectedShnarf, computedShnarf);
|
||||
}
|
||||
|
||||
blobShnarfExists[computedShnarf] = SHNARF_EXISTS_DEFAULT_VALUE;
|
||||
|
||||
emit DataSubmittedV3(_parentShnarf, computedShnarf, _submission.finalStateRootHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Internal function to compute and save the finalization state.
|
||||
* @dev Using assembly this way is cheaper gas wise.
|
||||
* @param _messageNumber Is the last L2 computed L1 message number in the finalization.
|
||||
* @param _rollingHash Is the last L2 computed L1 rolling hash in the finalization.
|
||||
* @param _timestamp The final timestamp in the finalization.
|
||||
*/
|
||||
function _computeLastFinalizedState(
|
||||
uint256 _messageNumber,
|
||||
bytes32 _rollingHash,
|
||||
uint256 _timestamp
|
||||
) internal pure returns (bytes32 hashedFinalizationState) {
|
||||
assembly {
|
||||
let mPtr := mload(0x40)
|
||||
mstore(mPtr, _messageNumber)
|
||||
mstore(add(mPtr, 0x20), _rollingHash)
|
||||
mstore(add(mPtr, 0x40), _timestamp)
|
||||
hashedFinalizationState := keccak256(mPtr, 0x60)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Internal function to compute the shnarf more efficiently.
|
||||
* @dev Using assembly this way is cheaper gas wise.
|
||||
* @param _parentShnarf The shnarf of the parent data item.
|
||||
* @param _snarkHash Is the computed hash for compressed data (using a SNARK-friendly hash function) that aggregates per data submission to be used in public input.
|
||||
* @param _finalStateRootHash The final state root hash of the data being submitted.
|
||||
* @param _dataEvaluationPoint The data evaluation point.
|
||||
* @param _dataEvaluationClaim The data evaluation claim.
|
||||
*/
|
||||
function _computeShnarf(
|
||||
bytes32 _parentShnarf,
|
||||
bytes32 _snarkHash,
|
||||
bytes32 _finalStateRootHash,
|
||||
bytes32 _dataEvaluationPoint,
|
||||
bytes32 _dataEvaluationClaim
|
||||
) internal pure returns (bytes32 shnarf) {
|
||||
assembly {
|
||||
let mPtr := mload(0x40)
|
||||
mstore(mPtr, _parentShnarf)
|
||||
mstore(add(mPtr, 0x20), _snarkHash)
|
||||
mstore(add(mPtr, 0x40), _finalStateRootHash)
|
||||
mstore(add(mPtr, 0x60), _dataEvaluationPoint)
|
||||
mstore(add(mPtr, 0x80), _dataEvaluationClaim)
|
||||
shnarf := keccak256(mPtr, 0xA0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Performs point evaluation for the compressed blob.
|
||||
* @dev _dataEvaluationPoint is modular reduced to be lower than the BLS_CURVE_MODULUS for precompile checks.
|
||||
* @param _currentDataHash The current blob versioned hash.
|
||||
* @param _dataEvaluationPoint The data evaluation point.
|
||||
* @param _dataEvaluationClaim The data evaluation claim.
|
||||
* @param _kzgCommitment The blob KZG commitment.
|
||||
* @param _kzgProof The blob KZG point proof.
|
||||
*/
|
||||
function _verifyPointEvaluation(
|
||||
bytes32 _currentDataHash,
|
||||
uint256 _dataEvaluationPoint,
|
||||
uint256 _dataEvaluationClaim,
|
||||
bytes memory _kzgCommitment,
|
||||
bytes memory _kzgProof
|
||||
) internal view {
|
||||
assembly {
|
||||
_dataEvaluationPoint := mod(_dataEvaluationPoint, BLS_CURVE_MODULUS)
|
||||
}
|
||||
|
||||
(bool success, bytes memory returnData) = POINT_EVALUATION_PRECOMPILE_ADDRESS.staticcall(
|
||||
abi.encodePacked(_currentDataHash, _dataEvaluationPoint, _dataEvaluationClaim, _kzgCommitment, _kzgProof)
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
revert PointEvaluationFailed();
|
||||
}
|
||||
|
||||
if (returnData.length != POINT_EVALUATION_RETURN_DATA_LENGTH) {
|
||||
revert PrecompileReturnDataLengthWrong(POINT_EVALUATION_RETURN_DATA_LENGTH, returnData.length);
|
||||
}
|
||||
|
||||
uint256 fieldElements;
|
||||
uint256 blsCurveModulus;
|
||||
assembly {
|
||||
fieldElements := mload(add(returnData, 0x20))
|
||||
blsCurveModulus := mload(add(returnData, POINT_EVALUATION_RETURN_DATA_LENGTH))
|
||||
}
|
||||
if (fieldElements != POINT_EVALUATION_FIELD_ELEMENTS_LENGTH || blsCurveModulus != BLS_CURVE_MODULUS) {
|
||||
revert PointEvaluationResponseInvalid(fieldElements, blsCurveModulus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Finalize compressed blocks with proof.
|
||||
* @dev OPERATOR_ROLE is required to execute.
|
||||
* @param _aggregatedProof The aggregated proof.
|
||||
* @param _proofType The proof type.
|
||||
* @param _finalizationData The full finalization data.
|
||||
*/
|
||||
function finalizeBlocks(
|
||||
bytes calldata _aggregatedProof,
|
||||
uint256 _proofType,
|
||||
FinalizationDataV3 calldata _finalizationData
|
||||
) external whenTypeAndGeneralNotPaused(PauseType.FINALIZATION) onlyRole(OPERATOR_ROLE) {
|
||||
if (_aggregatedProof.length == 0) {
|
||||
revert ProofIsEmpty();
|
||||
}
|
||||
|
||||
uint256 lastFinalizedBlockNumber = currentL2BlockNumber;
|
||||
|
||||
if (stateRootHashes[lastFinalizedBlockNumber] != _finalizationData.parentStateRootHash) {
|
||||
revert StartingRootHashDoesNotMatch();
|
||||
}
|
||||
|
||||
/// @dev currentFinalizedShnarf is updated in _finalizeBlocks and lastFinalizedShnarf MUST be set beforehand for the transition.
|
||||
bytes32 lastFinalizedShnarf = currentFinalizedShnarf;
|
||||
|
||||
bytes32 finalShnarf = _finalizeBlocks(_finalizationData, lastFinalizedBlockNumber);
|
||||
|
||||
uint256 publicInput = _computePublicInput(
|
||||
_finalizationData,
|
||||
lastFinalizedShnarf,
|
||||
finalShnarf,
|
||||
lastFinalizedBlockNumber
|
||||
);
|
||||
|
||||
_verifyProof(publicInput, _proofType, _aggregatedProof);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Internal function to finalize compressed blocks.
|
||||
* @param _finalizationData The full finalization data.
|
||||
* @param _lastFinalizedBlock The last finalized block.
|
||||
* @return finalShnarf The final computed shnarf in finalizing.
|
||||
*/
|
||||
function _finalizeBlocks(
|
||||
FinalizationDataV3 calldata _finalizationData,
|
||||
uint256 _lastFinalizedBlock
|
||||
) internal returns (bytes32 finalShnarf) {
|
||||
_validateL2ComputedRollingHash(_finalizationData.l1RollingHashMessageNumber, _finalizationData.l1RollingHash);
|
||||
|
||||
if (
|
||||
_computeLastFinalizedState(
|
||||
_finalizationData.lastFinalizedL1RollingHashMessageNumber,
|
||||
_finalizationData.lastFinalizedL1RollingHash,
|
||||
_finalizationData.lastFinalizedTimestamp
|
||||
) != currentFinalizedState
|
||||
) {
|
||||
revert FinalizationStateIncorrect(
|
||||
_computeLastFinalizedState(
|
||||
_finalizationData.lastFinalizedL1RollingHashMessageNumber,
|
||||
_finalizationData.lastFinalizedL1RollingHash,
|
||||
_finalizationData.lastFinalizedTimestamp
|
||||
),
|
||||
currentFinalizedState
|
||||
);
|
||||
}
|
||||
|
||||
if (_finalizationData.finalTimestamp >= block.timestamp) {
|
||||
revert FinalizationInTheFuture(_finalizationData.finalTimestamp, block.timestamp);
|
||||
}
|
||||
|
||||
if (_finalizationData.shnarfData.finalStateRootHash == EMPTY_HASH) {
|
||||
revert FinalBlockStateEqualsZeroHash();
|
||||
}
|
||||
|
||||
finalShnarf = _computeShnarf(
|
||||
_finalizationData.shnarfData.parentShnarf,
|
||||
_finalizationData.shnarfData.snarkHash,
|
||||
_finalizationData.shnarfData.finalStateRootHash,
|
||||
_finalizationData.shnarfData.dataEvaluationPoint,
|
||||
_finalizationData.shnarfData.dataEvaluationClaim
|
||||
);
|
||||
|
||||
if (blobShnarfExists[finalShnarf] == 0) {
|
||||
revert FinalBlobNotSubmitted(finalShnarf);
|
||||
}
|
||||
|
||||
_addL2MerkleRoots(_finalizationData.l2MerkleRoots, _finalizationData.l2MerkleTreesDepth);
|
||||
_anchorL2MessagingBlocks(_finalizationData.l2MessagingBlocksOffsets, _lastFinalizedBlock);
|
||||
|
||||
stateRootHashes[_finalizationData.endBlockNumber] = _finalizationData.shnarfData.finalStateRootHash;
|
||||
|
||||
currentL2BlockNumber = _finalizationData.endBlockNumber;
|
||||
|
||||
currentFinalizedShnarf = finalShnarf;
|
||||
|
||||
currentFinalizedState = _computeLastFinalizedState(
|
||||
_finalizationData.l1RollingHashMessageNumber,
|
||||
_finalizationData.l1RollingHash,
|
||||
_finalizationData.finalTimestamp
|
||||
);
|
||||
|
||||
emit DataFinalizedV3(
|
||||
++_lastFinalizedBlock,
|
||||
_finalizationData.endBlockNumber,
|
||||
finalShnarf,
|
||||
_finalizationData.parentStateRootHash,
|
||||
_finalizationData.shnarfData.finalStateRootHash
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Internal function to validate l1 rolling hash.
|
||||
* @param _rollingHashMessageNumber Message number associated with the rolling hash as computed on L2.
|
||||
* @param _rollingHash L1 rolling hash as computed on L2.
|
||||
*/
|
||||
function _validateL2ComputedRollingHash(uint256 _rollingHashMessageNumber, bytes32 _rollingHash) internal view {
|
||||
if (_rollingHashMessageNumber == 0) {
|
||||
if (_rollingHash != EMPTY_HASH) {
|
||||
revert MissingMessageNumberForRollingHash(_rollingHash);
|
||||
}
|
||||
} else {
|
||||
if (_rollingHash == EMPTY_HASH) {
|
||||
revert MissingRollingHashForMessageNumber(_rollingHashMessageNumber);
|
||||
}
|
||||
if (rollingHashes[_rollingHashMessageNumber] != _rollingHash) {
|
||||
revert L1RollingHashDoesNotExistOnL1(_rollingHashMessageNumber, _rollingHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Internal function to calculate Y for public input generation.
|
||||
* @param _data Compressed data from submission data.
|
||||
* @param _dataEvaluationPoint The data evaluation point.
|
||||
* @dev Each chunk of 32 bytes must start with a 0 byte.
|
||||
* @dev The dataEvaluationPoint value is modulo-ed down during the computation and scalar field checking is not needed.
|
||||
* @dev There is a hard constraint in the circuit to enforce the polynomial degree limit (4096), which will also be enforced with EIP-4844.
|
||||
* @return compressedDataComputedY The Y calculated value using the Horner method.
|
||||
*/
|
||||
function _calculateY(
|
||||
bytes calldata _data,
|
||||
bytes32 _dataEvaluationPoint
|
||||
) internal pure returns (bytes32 compressedDataComputedY) {
|
||||
if (_data.length % 0x20 != 0) {
|
||||
revert BytesLengthNotMultipleOf32();
|
||||
}
|
||||
|
||||
bytes4 errorSelector = ILineaRollup.FirstByteIsNotZero.selector;
|
||||
assembly {
|
||||
for {
|
||||
let i := _data.length
|
||||
} gt(i, 0) {} {
|
||||
i := sub(i, 0x20)
|
||||
let chunk := calldataload(add(_data.offset, i))
|
||||
if iszero(iszero(and(chunk, 0xFF00000000000000000000000000000000000000000000000000000000000000))) {
|
||||
let ptr := mload(0x40)
|
||||
mstore(ptr, errorSelector)
|
||||
revert(ptr, 0x4)
|
||||
}
|
||||
compressedDataComputedY := addmod(
|
||||
mulmod(compressedDataComputedY, _dataEvaluationPoint, BLS_CURVE_MODULUS),
|
||||
chunk,
|
||||
BLS_CURVE_MODULUS
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Compute the public input.
|
||||
* @dev Using assembly this way is cheaper gas wise.
|
||||
* @dev NB: the dynamic sized fields are placed last in _finalizationData on purpose to optimise hashing ranges.
|
||||
* @dev Computing the public input as the following:
|
||||
* keccak256(
|
||||
* abi.encode(
|
||||
* _lastFinalizedShnarf,
|
||||
* _finalShnarf,
|
||||
* _finalizationData.lastFinalizedTimestamp,
|
||||
* _finalizationData.finalTimestamp,
|
||||
* _lastFinalizedBlockNumber,
|
||||
* _finalizationData.endBlockNumber,
|
||||
* _finalizationData.lastFinalizedL1RollingHash,
|
||||
* _finalizationData.l1RollingHash,
|
||||
* _finalizationData.lastFinalizedL1RollingHashMessageNumber,
|
||||
* _finalizationData.l1RollingHashMessageNumber,
|
||||
* _finalizationData.l2MerkleTreesDepth,
|
||||
* keccak256(
|
||||
* abi.encodePacked(_finalizationData.l2MerkleRoots)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* Data is found at the following offsets:
|
||||
* 0x00 parentStateRootHash
|
||||
* 0x20 endBlockNumber
|
||||
* 0x40 shnarfData.parentShnarf
|
||||
* 0x60 shnarfData.snarkHash
|
||||
* 0x80 shnarfData.finalStateRootHash
|
||||
* 0xa0 shnarfData.dataEvaluationPoint
|
||||
* 0xc0 shnarfData.dataEvaluationClaim
|
||||
* 0xe0 lastFinalizedTimestamp
|
||||
* 0x100 finalTimestamp
|
||||
* 0x120 lastFinalizedL1RollingHash
|
||||
* 0x140 l1RollingHash
|
||||
* 0x160 lastFinalizedL1RollingHashMessageNumber
|
||||
* 0x180 l1RollingHashMessageNumber
|
||||
* 0x1a0 l2MerkleTreesDepth
|
||||
* 0x1c0 l2MerkleRootsLengthLocation
|
||||
* 0x1e0 l2MessagingBlocksOffsetsLengthLocation
|
||||
* Dynamic l2MerkleRootsLength
|
||||
* Dynamic l2MerkleRoots
|
||||
* Dynamic l2MessagingBlocksOffsetsLength (location depends on where l2MerkleRoots ends)
|
||||
* Dynamic l2MessagingBlocksOffsets (location depends on where l2MerkleRoots ends)
|
||||
* @param _finalizationData The full finalization data.
|
||||
* @param _finalShnarf The final shnarf in the finalization.
|
||||
* @param _lastFinalizedBlockNumber The last finalized block number.
|
||||
*/
|
||||
function _computePublicInput(
|
||||
FinalizationDataV3 calldata _finalizationData,
|
||||
bytes32 _lastFinalizedShnarf,
|
||||
bytes32 _finalShnarf,
|
||||
uint256 _lastFinalizedBlockNumber
|
||||
) private pure returns (uint256 publicInput) {
|
||||
assembly {
|
||||
let mPtr := mload(0x40)
|
||||
mstore(mPtr, _lastFinalizedShnarf)
|
||||
mstore(add(mPtr, 0x20), _finalShnarf)
|
||||
|
||||
/**
|
||||
* _finalizationData.lastFinalizedTimestamp
|
||||
* _finalizationData.finalTimestamp
|
||||
*/
|
||||
calldatacopy(add(mPtr, 0x40), add(_finalizationData, 0xe0), 0x40)
|
||||
|
||||
mstore(add(mPtr, 0x80), _lastFinalizedBlockNumber)
|
||||
|
||||
// _finalizationData.endBlockNumber
|
||||
calldatacopy(add(mPtr, 0xA0), add(_finalizationData, 0x20), 0x20)
|
||||
|
||||
/**
|
||||
* _finalizationData.lastFinalizedL1RollingHash
|
||||
* _finalizationData.l1RollingHash
|
||||
* _finalizationData.lastFinalizedL1RollingHashMessageNumber
|
||||
* _finalizationData.l1RollingHashMessageNumber
|
||||
* _finalizationData.l2MerkleTreesDepth
|
||||
*/
|
||||
calldatacopy(add(mPtr, 0xC0), add(_finalizationData, 0x120), 0xA0)
|
||||
|
||||
/**
|
||||
* @dev Note the following in hashing the _finalizationData.l2MerkleRoots array:
|
||||
* The second memory pointer and free pointer are offset by 0x20 to temporarily hash the array outside the scope of working memory,
|
||||
* as we need the space left for the array hash to be stored at 0x160.
|
||||
*/
|
||||
let mPtrMerkleRoot := add(mPtr, 0x180)
|
||||
let merkleRootsLengthLocation := add(_finalizationData, calldataload(add(_finalizationData, 0x1c0)))
|
||||
let merkleRootsLen := calldataload(merkleRootsLengthLocation)
|
||||
calldatacopy(mPtrMerkleRoot, add(merkleRootsLengthLocation, 0x20), mul(merkleRootsLen, 0x20))
|
||||
let l2MerkleRootsHash := keccak256(mPtrMerkleRoot, mul(merkleRootsLen, 0x20))
|
||||
mstore(add(mPtr, 0x160), l2MerkleRootsHash)
|
||||
|
||||
publicInput := mod(keccak256(mPtr, 0x180), MODULO_R)
|
||||
}
|
||||
__LineaRollup_init(_initializationData);
|
||||
}
|
||||
}
|
||||
|
||||
727
contracts/src/rollup/LineaRollupBase.sol
Normal file
727
contracts/src/rollup/LineaRollupBase.sol
Normal file
@@ -0,0 +1,727 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { L1MessageService } from "../messaging/l1/L1MessageService.sol";
|
||||
import { ZkEvmV2 } from "./ZkEvmV2.sol";
|
||||
import { ILineaRollup } from "./interfaces/ILineaRollup.sol";
|
||||
import { PermissionsManager } from "../security/access/PermissionsManager.sol";
|
||||
|
||||
import { EfficientLeftRightKeccak } from "../libraries/EfficientLeftRightKeccak.sol";
|
||||
/**
|
||||
* @title Contract to manage cross-chain messaging on L1, L2 data submission, and rollup proof verification.
|
||||
* @author ConsenSys Software Inc.
|
||||
* @custom:security-contact security-report@linea.build
|
||||
*/
|
||||
abstract contract LineaRollupBase is
|
||||
AccessControlUpgradeable,
|
||||
ZkEvmV2,
|
||||
L1MessageService,
|
||||
PermissionsManager,
|
||||
ILineaRollup
|
||||
{
|
||||
using EfficientLeftRightKeccak for *;
|
||||
|
||||
/// @notice The role required to set/add proof verifiers by type.
|
||||
bytes32 public constant VERIFIER_SETTER_ROLE = keccak256("VERIFIER_SETTER_ROLE");
|
||||
|
||||
/// @notice The role required to set/remove proof verifiers by type.
|
||||
bytes32 public constant VERIFIER_UNSETTER_ROLE = keccak256("VERIFIER_UNSETTER_ROLE");
|
||||
|
||||
/// @dev Value indicating a shnarf exists.
|
||||
uint256 internal constant SHNARF_EXISTS_DEFAULT_VALUE = 1;
|
||||
|
||||
/// @dev The default hash value.
|
||||
bytes32 internal constant EMPTY_HASH = 0x0;
|
||||
|
||||
/// @dev The BLS Curve modulus value used.
|
||||
uint256 internal constant BLS_CURVE_MODULUS =
|
||||
52435875175126190479447740508185965837690552500527637822603658699938581184513;
|
||||
|
||||
/// @dev The well-known precompile address for point evaluation.
|
||||
address internal constant POINT_EVALUATION_PRECOMPILE_ADDRESS = address(0x0a);
|
||||
|
||||
/// @dev The expected point evaluation return data length.
|
||||
uint256 internal constant POINT_EVALUATION_RETURN_DATA_LENGTH = 64;
|
||||
|
||||
/// @dev The expected point evaluation field element length returned.
|
||||
uint256 internal constant POINT_EVALUATION_FIELD_ELEMENTS_LENGTH = 4096;
|
||||
|
||||
/// @dev In practice, when used, this is expected to be a close approximation to 6 months, and is intentional.
|
||||
uint256 internal constant SIX_MONTHS_IN_SECONDS = (365 / 2) * 24 * 60 * 60;
|
||||
|
||||
/// @notice This is the ABI version and not the reinitialize version.
|
||||
string private constant _CONTRACT_VERSION = "7.0";
|
||||
|
||||
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
|
||||
mapping(bytes32 dataHash => bytes32 finalStateRootHash) private dataFinalStateRootHashes_DEPRECATED;
|
||||
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
|
||||
mapping(bytes32 dataHash => bytes32 parentHash) private dataParents_DEPRECATED;
|
||||
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
|
||||
mapping(bytes32 dataHash => bytes32 shnarfHash) private dataShnarfHashes_DEPRECATED;
|
||||
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
|
||||
mapping(bytes32 dataHash => uint256 startingBlock) private dataStartingBlock_DEPRECATED;
|
||||
/// @dev DEPRECATED in favor of the single blobShnarfExists mapping.
|
||||
mapping(bytes32 dataHash => uint256 endingBlock) private dataEndingBlock_DEPRECATED;
|
||||
|
||||
/// @dev DEPRECATED in favor of currentFinalizedState hash.
|
||||
uint256 private currentL2StoredL1MessageNumber_DEPRECATED;
|
||||
/// @dev DEPRECATED in favor of currentFinalizedState hash.
|
||||
bytes32 private currentL2StoredL1RollingHash_DEPRECATED;
|
||||
|
||||
/// @notice Contains the most recent finalized shnarf.
|
||||
bytes32 public currentFinalizedShnarf;
|
||||
|
||||
/**
|
||||
* @dev NB: THIS IS THE ONLY MAPPING BEING USED FOR DATA SUBMISSION TRACKING.
|
||||
* @dev NB: This was shnarfFinalBlockNumbers and is replaced to indicate only that a shnarf exists with a value of 1.
|
||||
*/
|
||||
mapping(bytes32 shnarf => uint256 exists) public blobShnarfExists;
|
||||
|
||||
/// @notice Hash of the L2 computed L1 message number, rolling hash and finalized timestamp.
|
||||
bytes32 public currentFinalizedState;
|
||||
|
||||
/// @notice The address of the fallback operator.
|
||||
/// @dev This address is granted the OPERATOR_ROLE after six months of finalization inactivity by the current operators.
|
||||
address public fallbackOperator;
|
||||
|
||||
/// @dev Keep 50 free storage slots for inheriting contracts.
|
||||
uint256[50] private __gap_LineaRollup;
|
||||
|
||||
/// @dev Total contract storage is 61 slots.
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
function __LineaRollup_init(InitializationData calldata _initializationData) internal virtual {
|
||||
if (_initializationData.defaultVerifier == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
__PauseManager_init(_initializationData.pauseTypeRoles, _initializationData.unpauseTypeRoles);
|
||||
|
||||
__MessageService_init(_initializationData.rateLimitPeriodInSeconds, _initializationData.rateLimitAmountInWei);
|
||||
|
||||
if (_initializationData.defaultAdmin == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev DEFAULT_ADMIN_ROLE is set for the security council explicitly,
|
||||
* as the permissions init purposefully does not allow DEFAULT_ADMIN_ROLE to be set.
|
||||
*/
|
||||
_grantRole(DEFAULT_ADMIN_ROLE, _initializationData.defaultAdmin);
|
||||
|
||||
__Permissions_init(_initializationData.roleAddresses);
|
||||
|
||||
verifiers[0] = _initializationData.defaultVerifier;
|
||||
|
||||
if (_initializationData.fallbackOperator == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
fallbackOperator = _initializationData.fallbackOperator;
|
||||
emit FallbackOperatorAddressSet(msg.sender, _initializationData.fallbackOperator);
|
||||
|
||||
currentL2BlockNumber = _initializationData.initialL2BlockNumber;
|
||||
stateRootHashes[_initializationData.initialL2BlockNumber] = _initializationData.initialStateRootHash;
|
||||
|
||||
bytes32 genesisShnarf = _computeShnarf(
|
||||
EMPTY_HASH,
|
||||
EMPTY_HASH,
|
||||
_initializationData.initialStateRootHash,
|
||||
EMPTY_HASH,
|
||||
EMPTY_HASH
|
||||
);
|
||||
|
||||
blobShnarfExists[genesisShnarf] = SHNARF_EXISTS_DEFAULT_VALUE;
|
||||
currentFinalizedShnarf = genesisShnarf;
|
||||
currentFinalizedState = _computeLastFinalizedState(0, EMPTY_HASH, _initializationData.genesisTimestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Revokes `role` from the calling account.
|
||||
* @dev Fallback operator cannot renounce role. Reverts with OnlyNonFallbackOperator.
|
||||
* @param _role The role to renounce.
|
||||
* @param _account The account to renounce - can only be the _msgSender().
|
||||
*/
|
||||
function renounceRole(bytes32 _role, address _account) public override {
|
||||
if (_account == fallbackOperator) {
|
||||
revert OnlyNonFallbackOperator();
|
||||
}
|
||||
|
||||
super.renounceRole(_role, _account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Returns the ABI version and not the reinitialize version.
|
||||
* @return contractVersion The contract ABI version.
|
||||
*/
|
||||
function CONTRACT_VERSION() external view virtual returns (string memory contractVersion) {
|
||||
contractVersion = _CONTRACT_VERSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Adds or updates the verifier contract address for a proof type.
|
||||
* @dev VERIFIER_SETTER_ROLE is required to execute.
|
||||
* @param _newVerifierAddress The address for the verifier contract.
|
||||
* @param _proofType The proof type being set/updated.
|
||||
*/
|
||||
function setVerifierAddress(address _newVerifierAddress, uint256 _proofType) external onlyRole(VERIFIER_SETTER_ROLE) {
|
||||
if (_newVerifierAddress == address(0)) {
|
||||
revert ZeroAddressNotAllowed();
|
||||
}
|
||||
|
||||
emit VerifierAddressChanged(_newVerifierAddress, _proofType, msg.sender, verifiers[_proofType]);
|
||||
|
||||
verifiers[_proofType] = _newVerifierAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the fallback operator role to the specified address if six months have passed since the last finalization.
|
||||
* @dev Reverts if six months have not passed since the last finalization.
|
||||
* @param _messageNumber Last finalized L1 message number as part of the feedback loop.
|
||||
* @param _rollingHash Last finalized L1 rolling hash as part of the feedback loop.
|
||||
* @param _lastFinalizedTimestamp Last finalized L2 block timestamp.
|
||||
*/
|
||||
function setFallbackOperator(uint256 _messageNumber, bytes32 _rollingHash, uint256 _lastFinalizedTimestamp) external {
|
||||
if (block.timestamp < _lastFinalizedTimestamp + SIX_MONTHS_IN_SECONDS) {
|
||||
revert LastFinalizationTimeNotLapsed();
|
||||
}
|
||||
if (currentFinalizedState != _computeLastFinalizedState(_messageNumber, _rollingHash, _lastFinalizedTimestamp)) {
|
||||
revert FinalizationStateIncorrect(
|
||||
currentFinalizedState,
|
||||
_computeLastFinalizedState(_messageNumber, _rollingHash, _lastFinalizedTimestamp)
|
||||
);
|
||||
}
|
||||
|
||||
address fallbackOperatorAddress = fallbackOperator;
|
||||
|
||||
_grantRole(OPERATOR_ROLE, fallbackOperatorAddress);
|
||||
emit FallbackOperatorRoleGranted(msg.sender, fallbackOperatorAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Unset the verifier contract address for a proof type.
|
||||
* @dev VERIFIER_UNSETTER_ROLE is required to execute.
|
||||
* @param _proofType The proof type being set/updated.
|
||||
*/
|
||||
function unsetVerifierAddress(uint256 _proofType) external onlyRole(VERIFIER_UNSETTER_ROLE) {
|
||||
emit VerifierAddressChanged(address(0), _proofType, msg.sender, verifiers[_proofType]);
|
||||
|
||||
delete verifiers[_proofType];
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Submit one or more EIP-4844 blobs.
|
||||
* @dev OPERATOR_ROLE is required to execute.
|
||||
* @dev This should be a blob carrying transaction.
|
||||
* @param _blobSubmissions The data for blob submission including proofs and required polynomials.
|
||||
* @param _parentShnarf The parent shnarf used in continuity checks as it includes the parentStateRootHash in its computation.
|
||||
* @param _finalBlobShnarf The expected final shnarf post computation of all the blob shnarfs.
|
||||
*/
|
||||
function submitBlobs(
|
||||
BlobSubmission[] calldata _blobSubmissions,
|
||||
bytes32 _parentShnarf,
|
||||
bytes32 _finalBlobShnarf
|
||||
) external virtual whenTypeAndGeneralNotPaused(PauseType.BLOB_SUBMISSION) onlyRole(OPERATOR_ROLE) {
|
||||
_submitBlobs(_blobSubmissions, _parentShnarf, _finalBlobShnarf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Submit one or more EIP-4844 blobs.
|
||||
* @param _blobSubmissions The data for blob submission including proofs and required polynomials.
|
||||
* @param _parentShnarf The parent shnarf used in continuity checks as it includes the parentStateRootHash in its computation.
|
||||
* @param _finalBlobShnarf The expected final shnarf post computation of all the blob shnarfs.
|
||||
*/
|
||||
function _submitBlobs(
|
||||
BlobSubmission[] calldata _blobSubmissions,
|
||||
bytes32 _parentShnarf,
|
||||
bytes32 _finalBlobShnarf
|
||||
) internal virtual {
|
||||
if (_blobSubmissions.length == 0) {
|
||||
revert BlobSubmissionDataIsMissing();
|
||||
}
|
||||
|
||||
if (blobhash(_blobSubmissions.length) != EMPTY_HASH) {
|
||||
revert BlobSubmissionDataEmpty(_blobSubmissions.length);
|
||||
}
|
||||
|
||||
if (blobShnarfExists[_parentShnarf] == 0) {
|
||||
revert ParentBlobNotSubmitted(_parentShnarf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev validate we haven't submitted the last shnarf. There is a final check at the end of the function verifying,
|
||||
* that _finalBlobShnarf was computed correctly.
|
||||
* Note: As only the last shnarf is stored, we don't need to validate shnarfs,
|
||||
* computed for any previous blobs in the submission (if multiple are submitted).
|
||||
*/
|
||||
if (blobShnarfExists[_finalBlobShnarf] != 0) {
|
||||
revert DataAlreadySubmitted(_finalBlobShnarf);
|
||||
}
|
||||
|
||||
bytes32 currentDataEvaluationPoint;
|
||||
bytes32 currentDataHash;
|
||||
|
||||
/// @dev Assigning in memory saves a lot of gas vs. calldata reading.
|
||||
BlobSubmission memory blobSubmission;
|
||||
|
||||
bytes32 computedShnarf = _parentShnarf;
|
||||
|
||||
for (uint256 i; i < _blobSubmissions.length; i++) {
|
||||
blobSubmission = _blobSubmissions[i];
|
||||
|
||||
currentDataHash = blobhash(i);
|
||||
|
||||
if (currentDataHash == EMPTY_HASH) {
|
||||
revert EmptyBlobDataAtIndex(i);
|
||||
}
|
||||
|
||||
bytes32 snarkHash = blobSubmission.snarkHash;
|
||||
|
||||
currentDataEvaluationPoint = EfficientLeftRightKeccak._efficientKeccak(snarkHash, currentDataHash);
|
||||
|
||||
_verifyPointEvaluation(
|
||||
currentDataHash,
|
||||
uint256(currentDataEvaluationPoint),
|
||||
blobSubmission.dataEvaluationClaim,
|
||||
blobSubmission.kzgCommitment,
|
||||
blobSubmission.kzgProof
|
||||
);
|
||||
|
||||
computedShnarf = _computeShnarf(
|
||||
computedShnarf,
|
||||
snarkHash,
|
||||
blobSubmission.finalStateRootHash,
|
||||
currentDataEvaluationPoint,
|
||||
bytes32(blobSubmission.dataEvaluationClaim)
|
||||
);
|
||||
}
|
||||
|
||||
if (_finalBlobShnarf != computedShnarf) {
|
||||
revert FinalShnarfWrong(_finalBlobShnarf, computedShnarf);
|
||||
}
|
||||
|
||||
/// @dev use the last shnarf as the submission to store as technically it becomes the next parent shnarf.
|
||||
blobShnarfExists[computedShnarf] = SHNARF_EXISTS_DEFAULT_VALUE;
|
||||
|
||||
emit DataSubmittedV3(_parentShnarf, computedShnarf, blobSubmission.finalStateRootHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Submit blobs using compressed data via calldata.
|
||||
* @dev OPERATOR_ROLE is required to execute.
|
||||
* @param _submission The supporting data for compressed data submission including compressed data.
|
||||
* @param _parentShnarf The parent shnarf used in continuity checks as it includes the parentStateRootHash in its computation.
|
||||
* @param _expectedShnarf The expected shnarf post computation of all the submission.
|
||||
*/
|
||||
function submitDataAsCalldata(
|
||||
CompressedCalldataSubmission calldata _submission,
|
||||
bytes32 _parentShnarf,
|
||||
bytes32 _expectedShnarf
|
||||
) external virtual whenTypeAndGeneralNotPaused(PauseType.CALLDATA_SUBMISSION) onlyRole(OPERATOR_ROLE) {
|
||||
_submitDataAsCalldata(_submission, _parentShnarf, _expectedShnarf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Submit blobs using compressed data via calldata.
|
||||
* @dev OPERATOR_ROLE is required to execute.
|
||||
* @param _submission The supporting data for compressed data submission including compressed data.
|
||||
* @param _parentShnarf The parent shnarf used in continuity checks as it includes the parentStateRootHash in its computation.
|
||||
* @param _expectedShnarf The expected shnarf post computation of all the submission.
|
||||
*/
|
||||
function _submitDataAsCalldata(
|
||||
CompressedCalldataSubmission calldata _submission,
|
||||
bytes32 _parentShnarf,
|
||||
bytes32 _expectedShnarf
|
||||
) internal virtual {
|
||||
if (_submission.compressedData.length == 0) {
|
||||
revert EmptySubmissionData();
|
||||
}
|
||||
|
||||
if (blobShnarfExists[_expectedShnarf] != 0) {
|
||||
revert DataAlreadySubmitted(_expectedShnarf);
|
||||
}
|
||||
|
||||
if (blobShnarfExists[_parentShnarf] == 0) {
|
||||
revert ParentBlobNotSubmitted(_parentShnarf);
|
||||
}
|
||||
|
||||
bytes32 currentDataHash = keccak256(_submission.compressedData);
|
||||
|
||||
bytes32 dataEvaluationPoint = EfficientLeftRightKeccak._efficientKeccak(_submission.snarkHash, currentDataHash);
|
||||
|
||||
bytes32 computedShnarf = _computeShnarf(
|
||||
_parentShnarf,
|
||||
_submission.snarkHash,
|
||||
_submission.finalStateRootHash,
|
||||
dataEvaluationPoint,
|
||||
_calculateY(_submission.compressedData, dataEvaluationPoint)
|
||||
);
|
||||
|
||||
if (_expectedShnarf != computedShnarf) {
|
||||
revert FinalShnarfWrong(_expectedShnarf, computedShnarf);
|
||||
}
|
||||
|
||||
blobShnarfExists[computedShnarf] = SHNARF_EXISTS_DEFAULT_VALUE;
|
||||
|
||||
emit DataSubmittedV3(_parentShnarf, computedShnarf, _submission.finalStateRootHash);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Internal function to compute and save the finalization state.
|
||||
* @dev Using assembly this way is cheaper gas wise.
|
||||
* @param _messageNumber Is the last L2 computed L1 message number in the finalization.
|
||||
* @param _rollingHash Is the last L2 computed L1 rolling hash in the finalization.
|
||||
* @param _timestamp The final timestamp in the finalization.
|
||||
*/
|
||||
function _computeLastFinalizedState(
|
||||
uint256 _messageNumber,
|
||||
bytes32 _rollingHash,
|
||||
uint256 _timestamp
|
||||
) internal pure returns (bytes32 hashedFinalizationState) {
|
||||
assembly {
|
||||
let mPtr := mload(0x40)
|
||||
mstore(mPtr, _messageNumber)
|
||||
mstore(add(mPtr, 0x20), _rollingHash)
|
||||
mstore(add(mPtr, 0x40), _timestamp)
|
||||
hashedFinalizationState := keccak256(mPtr, 0x60)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Internal function to compute the shnarf more efficiently.
|
||||
* @dev Using assembly this way is cheaper gas wise.
|
||||
* @param _parentShnarf The shnarf of the parent data item.
|
||||
* @param _snarkHash Is the computed hash for compressed data (using a SNARK-friendly hash function) that aggregates per data submission to be used in public input.
|
||||
* @param _finalStateRootHash The final state root hash of the data being submitted.
|
||||
* @param _dataEvaluationPoint The data evaluation point.
|
||||
* @param _dataEvaluationClaim The data evaluation claim.
|
||||
*/
|
||||
function _computeShnarf(
|
||||
bytes32 _parentShnarf,
|
||||
bytes32 _snarkHash,
|
||||
bytes32 _finalStateRootHash,
|
||||
bytes32 _dataEvaluationPoint,
|
||||
bytes32 _dataEvaluationClaim
|
||||
) internal pure returns (bytes32 shnarf) {
|
||||
assembly {
|
||||
let mPtr := mload(0x40)
|
||||
mstore(mPtr, _parentShnarf)
|
||||
mstore(add(mPtr, 0x20), _snarkHash)
|
||||
mstore(add(mPtr, 0x40), _finalStateRootHash)
|
||||
mstore(add(mPtr, 0x60), _dataEvaluationPoint)
|
||||
mstore(add(mPtr, 0x80), _dataEvaluationClaim)
|
||||
shnarf := keccak256(mPtr, 0xA0)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Performs point evaluation for the compressed blob.
|
||||
* @dev _dataEvaluationPoint is modular reduced to be lower than the BLS_CURVE_MODULUS for precompile checks.
|
||||
* @param _currentDataHash The current blob versioned hash.
|
||||
* @param _dataEvaluationPoint The data evaluation point.
|
||||
* @param _dataEvaluationClaim The data evaluation claim.
|
||||
* @param _kzgCommitment The blob KZG commitment.
|
||||
* @param _kzgProof The blob KZG point proof.
|
||||
*/
|
||||
function _verifyPointEvaluation(
|
||||
bytes32 _currentDataHash,
|
||||
uint256 _dataEvaluationPoint,
|
||||
uint256 _dataEvaluationClaim,
|
||||
bytes memory _kzgCommitment,
|
||||
bytes memory _kzgProof
|
||||
) internal view {
|
||||
assembly {
|
||||
_dataEvaluationPoint := mod(_dataEvaluationPoint, BLS_CURVE_MODULUS)
|
||||
}
|
||||
|
||||
(bool success, bytes memory returnData) = POINT_EVALUATION_PRECOMPILE_ADDRESS.staticcall(
|
||||
abi.encodePacked(_currentDataHash, _dataEvaluationPoint, _dataEvaluationClaim, _kzgCommitment, _kzgProof)
|
||||
);
|
||||
|
||||
if (!success) {
|
||||
revert PointEvaluationFailed();
|
||||
}
|
||||
|
||||
if (returnData.length != POINT_EVALUATION_RETURN_DATA_LENGTH) {
|
||||
revert PrecompileReturnDataLengthWrong(POINT_EVALUATION_RETURN_DATA_LENGTH, returnData.length);
|
||||
}
|
||||
|
||||
uint256 fieldElements;
|
||||
uint256 blsCurveModulus;
|
||||
assembly {
|
||||
fieldElements := mload(add(returnData, 0x20))
|
||||
blsCurveModulus := mload(add(returnData, POINT_EVALUATION_RETURN_DATA_LENGTH))
|
||||
}
|
||||
if (fieldElements != POINT_EVALUATION_FIELD_ELEMENTS_LENGTH || blsCurveModulus != BLS_CURVE_MODULUS) {
|
||||
revert PointEvaluationResponseInvalid(fieldElements, blsCurveModulus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Finalize compressed blocks with proof.
|
||||
* @dev OPERATOR_ROLE is required to execute.
|
||||
* @param _aggregatedProof The aggregated proof.
|
||||
* @param _proofType The proof type.
|
||||
* @param _finalizationData The full finalization data.
|
||||
*/
|
||||
function finalizeBlocks(
|
||||
bytes calldata _aggregatedProof,
|
||||
uint256 _proofType,
|
||||
FinalizationDataV3 calldata _finalizationData
|
||||
) external virtual whenTypeAndGeneralNotPaused(PauseType.FINALIZATION) onlyRole(OPERATOR_ROLE) {
|
||||
if (_aggregatedProof.length == 0) {
|
||||
revert ProofIsEmpty();
|
||||
}
|
||||
|
||||
uint256 lastFinalizedBlockNumber = currentL2BlockNumber;
|
||||
|
||||
if (stateRootHashes[lastFinalizedBlockNumber] != _finalizationData.parentStateRootHash) {
|
||||
revert StartingRootHashDoesNotMatch();
|
||||
}
|
||||
|
||||
/// @dev currentFinalizedShnarf is updated in _finalizeBlocks and lastFinalizedShnarf MUST be set beforehand for the transition.
|
||||
bytes32 lastFinalizedShnarf = currentFinalizedShnarf;
|
||||
|
||||
bytes32 finalShnarf = _finalizeBlocks(_finalizationData, lastFinalizedBlockNumber);
|
||||
|
||||
uint256 publicInput = _computePublicInput(
|
||||
_finalizationData,
|
||||
lastFinalizedShnarf,
|
||||
finalShnarf,
|
||||
lastFinalizedBlockNumber
|
||||
);
|
||||
|
||||
_verifyProof(publicInput, _proofType, _aggregatedProof);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Internal function to finalize compressed blocks.
|
||||
* @param _finalizationData The full finalization data.
|
||||
* @param _lastFinalizedBlock The last finalized block.
|
||||
* @return finalShnarf The final computed shnarf in finalizing.
|
||||
*/
|
||||
function _finalizeBlocks(
|
||||
FinalizationDataV3 calldata _finalizationData,
|
||||
uint256 _lastFinalizedBlock
|
||||
) internal virtual returns (bytes32 finalShnarf) {
|
||||
_validateL2ComputedRollingHash(_finalizationData.l1RollingHashMessageNumber, _finalizationData.l1RollingHash);
|
||||
|
||||
if (
|
||||
_computeLastFinalizedState(
|
||||
_finalizationData.lastFinalizedL1RollingHashMessageNumber,
|
||||
_finalizationData.lastFinalizedL1RollingHash,
|
||||
_finalizationData.lastFinalizedTimestamp
|
||||
) != currentFinalizedState
|
||||
) {
|
||||
revert FinalizationStateIncorrect(
|
||||
_computeLastFinalizedState(
|
||||
_finalizationData.lastFinalizedL1RollingHashMessageNumber,
|
||||
_finalizationData.lastFinalizedL1RollingHash,
|
||||
_finalizationData.lastFinalizedTimestamp
|
||||
),
|
||||
currentFinalizedState
|
||||
);
|
||||
}
|
||||
|
||||
if (_finalizationData.finalTimestamp >= block.timestamp) {
|
||||
revert FinalizationInTheFuture(_finalizationData.finalTimestamp, block.timestamp);
|
||||
}
|
||||
|
||||
if (_finalizationData.shnarfData.finalStateRootHash == EMPTY_HASH) {
|
||||
revert FinalBlockStateEqualsZeroHash();
|
||||
}
|
||||
|
||||
finalShnarf = _computeShnarf(
|
||||
_finalizationData.shnarfData.parentShnarf,
|
||||
_finalizationData.shnarfData.snarkHash,
|
||||
_finalizationData.shnarfData.finalStateRootHash,
|
||||
_finalizationData.shnarfData.dataEvaluationPoint,
|
||||
_finalizationData.shnarfData.dataEvaluationClaim
|
||||
);
|
||||
|
||||
if (blobShnarfExists[finalShnarf] == 0) {
|
||||
revert FinalBlobNotSubmitted(finalShnarf);
|
||||
}
|
||||
|
||||
_addL2MerkleRoots(_finalizationData.l2MerkleRoots, _finalizationData.l2MerkleTreesDepth);
|
||||
_anchorL2MessagingBlocks(_finalizationData.l2MessagingBlocksOffsets, _lastFinalizedBlock);
|
||||
|
||||
stateRootHashes[_finalizationData.endBlockNumber] = _finalizationData.shnarfData.finalStateRootHash;
|
||||
|
||||
currentL2BlockNumber = _finalizationData.endBlockNumber;
|
||||
|
||||
currentFinalizedShnarf = finalShnarf;
|
||||
|
||||
currentFinalizedState = _computeLastFinalizedState(
|
||||
_finalizationData.l1RollingHashMessageNumber,
|
||||
_finalizationData.l1RollingHash,
|
||||
_finalizationData.finalTimestamp
|
||||
);
|
||||
|
||||
emit DataFinalizedV3(
|
||||
++_lastFinalizedBlock,
|
||||
_finalizationData.endBlockNumber,
|
||||
finalShnarf,
|
||||
_finalizationData.parentStateRootHash,
|
||||
_finalizationData.shnarfData.finalStateRootHash
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Internal function to validate l1 rolling hash.
|
||||
* @param _rollingHashMessageNumber Message number associated with the rolling hash as computed on L2.
|
||||
* @param _rollingHash L1 rolling hash as computed on L2.
|
||||
*/
|
||||
function _validateL2ComputedRollingHash(uint256 _rollingHashMessageNumber, bytes32 _rollingHash) internal view {
|
||||
if (_rollingHashMessageNumber == 0) {
|
||||
if (_rollingHash != EMPTY_HASH) {
|
||||
revert MissingMessageNumberForRollingHash(_rollingHash);
|
||||
}
|
||||
} else {
|
||||
if (_rollingHash == EMPTY_HASH) {
|
||||
revert MissingRollingHashForMessageNumber(_rollingHashMessageNumber);
|
||||
}
|
||||
if (rollingHashes[_rollingHashMessageNumber] != _rollingHash) {
|
||||
revert L1RollingHashDoesNotExistOnL1(_rollingHashMessageNumber, _rollingHash);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Internal function to calculate Y for public input generation.
|
||||
* @param _data Compressed data from submission data.
|
||||
* @param _dataEvaluationPoint The data evaluation point.
|
||||
* @dev Each chunk of 32 bytes must start with a 0 byte.
|
||||
* @dev The dataEvaluationPoint value is modulo-ed down during the computation and scalar field checking is not needed.
|
||||
* @dev There is a hard constraint in the circuit to enforce the polynomial degree limit (4096), which will also be enforced with EIP-4844.
|
||||
* @return compressedDataComputedY The Y calculated value using the Horner method.
|
||||
*/
|
||||
function _calculateY(
|
||||
bytes calldata _data,
|
||||
bytes32 _dataEvaluationPoint
|
||||
) internal pure returns (bytes32 compressedDataComputedY) {
|
||||
if (_data.length % 0x20 != 0) {
|
||||
revert BytesLengthNotMultipleOf32();
|
||||
}
|
||||
|
||||
bytes4 errorSelector = ILineaRollup.FirstByteIsNotZero.selector;
|
||||
assembly {
|
||||
for {
|
||||
let i := _data.length
|
||||
} gt(i, 0) {} {
|
||||
i := sub(i, 0x20)
|
||||
let chunk := calldataload(add(_data.offset, i))
|
||||
if iszero(iszero(and(chunk, 0xFF00000000000000000000000000000000000000000000000000000000000000))) {
|
||||
let ptr := mload(0x40)
|
||||
mstore(ptr, errorSelector)
|
||||
revert(ptr, 0x4)
|
||||
}
|
||||
compressedDataComputedY := addmod(
|
||||
mulmod(compressedDataComputedY, _dataEvaluationPoint, BLS_CURVE_MODULUS),
|
||||
chunk,
|
||||
BLS_CURVE_MODULUS
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Compute the public input.
|
||||
* @dev Using assembly this way is cheaper gas wise.
|
||||
* @dev NB: the dynamic sized fields are placed last in _finalizationData on purpose to optimise hashing ranges.
|
||||
* @dev Computing the public input as the following:
|
||||
* keccak256(
|
||||
* abi.encode(
|
||||
* _lastFinalizedShnarf,
|
||||
* _finalShnarf,
|
||||
* _finalizationData.lastFinalizedTimestamp,
|
||||
* _finalizationData.finalTimestamp,
|
||||
* _lastFinalizedBlockNumber,
|
||||
* _finalizationData.endBlockNumber,
|
||||
* _finalizationData.lastFinalizedL1RollingHash,
|
||||
* _finalizationData.l1RollingHash,
|
||||
* _finalizationData.lastFinalizedL1RollingHashMessageNumber,
|
||||
* _finalizationData.l1RollingHashMessageNumber,
|
||||
* _finalizationData.l2MerkleTreesDepth,
|
||||
* keccak256(
|
||||
* abi.encodePacked(_finalizationData.l2MerkleRoots)
|
||||
* )
|
||||
* )
|
||||
* )
|
||||
* Data is found at the following offsets:
|
||||
* 0x00 parentStateRootHash
|
||||
* 0x20 endBlockNumber
|
||||
* 0x40 shnarfData.parentShnarf
|
||||
* 0x60 shnarfData.snarkHash
|
||||
* 0x80 shnarfData.finalStateRootHash
|
||||
* 0xa0 shnarfData.dataEvaluationPoint
|
||||
* 0xc0 shnarfData.dataEvaluationClaim
|
||||
* 0xe0 lastFinalizedTimestamp
|
||||
* 0x100 finalTimestamp
|
||||
* 0x120 lastFinalizedL1RollingHash
|
||||
* 0x140 l1RollingHash
|
||||
* 0x160 lastFinalizedL1RollingHashMessageNumber
|
||||
* 0x180 l1RollingHashMessageNumber
|
||||
* 0x1a0 l2MerkleTreesDepth
|
||||
* 0x1c0 l2MerkleRootsLengthLocation
|
||||
* 0x1e0 l2MessagingBlocksOffsetsLengthLocation
|
||||
* Dynamic l2MerkleRootsLength
|
||||
* Dynamic l2MerkleRoots
|
||||
* Dynamic l2MessagingBlocksOffsetsLength (location depends on where l2MerkleRoots ends)
|
||||
* Dynamic l2MessagingBlocksOffsets (location depends on where l2MerkleRoots ends)
|
||||
* @param _finalizationData The full finalization data.
|
||||
* @param _finalShnarf The final shnarf in the finalization.
|
||||
* @param _lastFinalizedBlockNumber The last finalized block number.
|
||||
*/
|
||||
function _computePublicInput(
|
||||
FinalizationDataV3 calldata _finalizationData,
|
||||
bytes32 _lastFinalizedShnarf,
|
||||
bytes32 _finalShnarf,
|
||||
uint256 _lastFinalizedBlockNumber
|
||||
) private pure returns (uint256 publicInput) {
|
||||
assembly {
|
||||
let mPtr := mload(0x40)
|
||||
mstore(mPtr, _lastFinalizedShnarf)
|
||||
mstore(add(mPtr, 0x20), _finalShnarf)
|
||||
|
||||
/**
|
||||
* _finalizationData.lastFinalizedTimestamp
|
||||
* _finalizationData.finalTimestamp
|
||||
*/
|
||||
calldatacopy(add(mPtr, 0x40), add(_finalizationData, 0xe0), 0x40)
|
||||
|
||||
mstore(add(mPtr, 0x80), _lastFinalizedBlockNumber)
|
||||
|
||||
// _finalizationData.endBlockNumber
|
||||
calldatacopy(add(mPtr, 0xA0), add(_finalizationData, 0x20), 0x20)
|
||||
|
||||
/**
|
||||
* _finalizationData.lastFinalizedL1RollingHash
|
||||
* _finalizationData.l1RollingHash
|
||||
* _finalizationData.lastFinalizedL1RollingHashMessageNumber
|
||||
* _finalizationData.l1RollingHashMessageNumber
|
||||
* _finalizationData.l2MerkleTreesDepth
|
||||
*/
|
||||
calldatacopy(add(mPtr, 0xC0), add(_finalizationData, 0x120), 0xA0)
|
||||
|
||||
/**
|
||||
* @dev Note the following in hashing the _finalizationData.l2MerkleRoots array:
|
||||
* The second memory pointer and free pointer are offset by 0x20 to temporarily hash the array outside the scope of working memory,
|
||||
* as we need the space left for the array hash to be stored at 0x160.
|
||||
*/
|
||||
let mPtrMerkleRoot := add(mPtr, 0x180)
|
||||
let merkleRootsLengthLocation := add(_finalizationData, calldataload(add(_finalizationData, 0x1c0)))
|
||||
let merkleRootsLen := calldataload(merkleRootsLengthLocation)
|
||||
calldatacopy(mPtrMerkleRoot, add(merkleRootsLengthLocation, 0x20), mul(merkleRootsLen, 0x20))
|
||||
let l2MerkleRootsHash := keccak256(mPtrMerkleRoot, mul(merkleRootsLen, 0x20))
|
||||
mstore(add(mPtr, 0x160), l2MerkleRootsHash)
|
||||
|
||||
publicInput := mod(keccak256(mPtr, 0x180), MODULO_R)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { L1MessageServiceV1 } from "../messaging/l1/v1/L1MessageServiceV1.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { IPauseManager } from "../../security/pausing/interfaces/IPauseManager.sol";
|
||||
import { IPermissionsManager } from "../../security/access/interfaces/IPermissionsManager.sol";
|
||||
@@ -283,6 +283,12 @@ interface ILineaRollup {
|
||||
*/
|
||||
error OnlyNonFallbackOperator();
|
||||
|
||||
/**
|
||||
* @notice Returns the ABI version and not the reinitialize version.
|
||||
* @return contractVersion The contract ABI version.
|
||||
*/
|
||||
function CONTRACT_VERSION() external view returns (string memory contractVersion);
|
||||
|
||||
/**
|
||||
* @notice Adds or updates the verifier contract address for a proof type.
|
||||
* @dev VERIFIER_SETTER_ROLE is required to execute.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title ZkEvm rollup interface for pre-existing functions, events and errors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { IGenericErrors } from "../../interfaces/IGenericErrors.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Interface declaring permissions manager related data types.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { IRateLimiter } from "./interfaces/IRateLimiter.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Interface declaring rate limiting messaging functions, events and errors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { PauseManager } from "./PauseManager.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { PauseManager } from "./PauseManager.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
|
||||
import { IPauseManager } from "./interfaces/IPauseManager.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { PauseManager } from "./PauseManager.sol";
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Interface declaring pre-existing pausing functions, events and errors.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Contract that helps prevent reentrant calls.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
||||
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
|
||||
import { ITokenMinter } from "./interfaces/ITokenMinter.sol";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Token Minter Interface.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Token Minting Rate Limiter Interface.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
pragma solidity 0.8.30;
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
/**
|
||||
* @title Interface declaring verifier functions.
|
||||
|
||||
Reference in New Issue
Block a user