Files
linea-monorepo/contracts/src/bridging/eth/L1ETHBridge.sol
2025-06-27 17:06:52 +02:00

190 lines
6.0 KiB
Solidity

// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.30;
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol";
import { IL1ETHBridge } from "./interfaces/IL1ETHBridge.sol";
import { IL2ETHBridge } from "./interfaces/IL2ETHBridge.sol";
import { MessageServiceBase } from "../../messaging/MessageServiceBase.sol";
import { IMessageService } from "../../messaging/interfaces/IMessageService.sol";
import { IETHYieldManager } from "./interfaces/IETHYieldManager.sol";
contract L1ETHBridge is IL1ETHBridge, Initializable, UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable, MessageServiceBase {
/**
* @notice The message status enum.
*/
enum MessageStatus {
Unknown,
Requested,
Finalized
}
/**
* @notice The completed message struct.
*/
struct Message {
uint256 withdrawalRequestId;
bytes32 messageHash;
MessageStatus status;
}
/**
* @notice The yield manager address.
*/
address public yieldManager;
/**
* @notice The next completed message id.
*/
uint256 public nextCompletedMessageId;
/**
* @notice The completed messages.
*/
mapping(uint256 => Message) public messages;
/**
* @dev Ensures the address is not address(0).
* @param _addr Address to check.
*/
modifier nonZeroAddress(address _addr) {
if (_addr == address(0)) revert L1ETHBridge__ZeroAddressNotAllowed();
_;
}
/**
* @dev Ensures the amount is not 0.
* @param _amount amount to check.
*/
modifier nonZeroAmount(uint256 _amount) {
if (_amount == 0) revert L1ETHBridge__ZeroValueNotAllowed();
_;
}
/**
* @notice Disables initializers to prevent reinitialization.
*/
constructor() {
_disableInitializers();
}
/**
* @notice Initializes the contract.
* @param _initialOwner The initial owner of the contract.
* @param _messageService The message service address (previously rollup).
* @param _yieldManager The yield manager address.
* @param _remoteSender The remote sender address (previously l2ETHBridge).
*/
function initialize(
address _initialOwner,
address _messageService,
address _remoteSender,
address _yieldManager
) external initializer nonZeroAddress(_yieldManager) {
_transferOwnership(_initialOwner);
__MessageServiceBase_init(_messageService);
_setRemoteSender(_remoteSender);
yieldManager = _yieldManager;
}
/**
* @notice Sets the message service address.
* @param _messageService The new message service address.
*/
function setMessageService(address _messageService) external onlyOwner nonZeroAddress(_messageService) {
emit MessageServiceUpdated(_messageService, address(messageService), msg.sender);
messageService = IMessageService(_messageService);
}
/**
* @notice Sets the remote sender address.
* @param _remoteSender The new remote sender address.
*/
function setRemoteSender(address _remoteSender) external onlyOwner {
_setRemoteSender(_remoteSender);
}
/**
* @notice Sets the yield manager address.
* @param _yieldManager The new yield manager address.
*/
function setYieldManager(address _yieldManager) external onlyOwner nonZeroAddress(_yieldManager) {
emit YieldManagerUpdated(_yieldManager, yieldManager, msg.sender);
yieldManager = _yieldManager;
}
/**
* @notice Completes the bridge. Callable only by the L1MessageService.
* @param _to The recipient address.
* @param _value The amount of ETH to transfer.
* @param _calldata The calldata to pass to the recipient.
*/
function completeBridging(
address _to,
uint256 _value,
bytes memory _calldata
) external nonReentrant onlyMessagingService onlyAuthorizedRemoteSender {
uint256 requestId = IETHYieldManager(yieldManager).requestWithdrawal(_value);
bytes32 hash = keccak256(abi.encode(_to, _value, _calldata));
messages[nextCompletedMessageId] = Message(requestId, hash, MessageStatus.Requested);
emit BridgingCompleted(nextCompletedMessageId);
nextCompletedMessageId++;
}
/**
* @notice Bridges ETH to the L2ETHBridge.
* @param _to The recipient address on the L2.
* @param _calldata The calldata to be sent to the L2ETHBridge.
*/
function bridgeETH(
address _to,
bytes memory _calldata
) external payable nonZeroAmount(msg.value) nonZeroAddress(_to) {
(bool success, ) = yieldManager.call{ value: msg.value }("");
if (!success) {
revert L1ETHBridge__YieldManagerDepositFailed();
}
bytes memory data = abi.encodeWithSelector(IL2ETHBridge.completeBridging.selector, _to, msg.value, _calldata);
messageService.sendMessage(remoteSender, 0, data);
}
function finalizeWithdrawal(uint256 _messageId, uint256 _hintId, address _to, uint256 _value, bytes memory _calldata) external {
Message memory message = messages[_messageId];
bytes32 hash = keccak256(abi.encode(_to, _value, _calldata));
if (message.status != MessageStatus.Requested) {
revert L1ETHBridge__MessageAlreadyFinalized();
}
if (message.messageHash != hash) {
revert L1ETHBridge__InvalidMessageParameters();
}
message.status = MessageStatus.Finalized;
uint256 balanceBefore = address(this).balance;
IETHYieldManager(yieldManager).claimWithdrawal(message.withdrawalRequestId, _hintId);
if (address(this).balance != balanceBefore + _value) {
revert L1ETHBridge__WithdrawalClaimFailed();
}
(bool success, ) = _to.call{ value: _value }(_calldata);
if (!success) {
revert L1ETHBridge__ETHTransferFailed();
}
emit MessageFinalized(_messageId);
}
function _authorizeUpgrade(address) internal view override {
_checkOwner();
}
}