feat: implement L1ETHBridge.finalizeWithdrawal

This commit is contained in:
Andrea Franz
2025-06-27 16:16:50 +02:00
parent 6200e94927
commit 962c731aa9
5 changed files with 67 additions and 8 deletions

View File

@@ -12,12 +12,22 @@ import { IMessageService } from "../../messaging/interfaces/IMessageService.sol"
import { IETHYieldManager } from "./interfaces/IETHYieldManager.sol"; import { IETHYieldManager } from "./interfaces/IETHYieldManager.sol";
contract L1ETHBridge is IL1ETHBridge, Initializable, UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable, MessageServiceBase { contract L1ETHBridge is IL1ETHBridge, Initializable, UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable, MessageServiceBase {
/**
* @notice The message status enum.
*/
enum MessageStatus {
Unknown,
Requested,
Finalized
}
/** /**
* @notice The completed message struct. * @notice The completed message struct.
*/ */
struct CompletedMessage { struct Message {
uint256 withdrawalRequestId; uint256 withdrawalRequestId;
bytes32 messageHash; bytes32 messageHash;
MessageStatus status;
} }
/** /**
@@ -33,7 +43,7 @@ contract L1ETHBridge is IL1ETHBridge, Initializable, UUPSUpgradeable, OwnableUpg
/** /**
* @notice The completed messages. * @notice The completed messages.
*/ */
mapping(uint256 => CompletedMessage) public completedMessages; mapping(uint256 => Message) public messages;
/** /**
* @dev Ensures the address is not address(0). * @dev Ensures the address is not address(0).
@@ -117,10 +127,12 @@ contract L1ETHBridge is IL1ETHBridge, Initializable, UUPSUpgradeable, OwnableUpg
uint256 _value, uint256 _value,
bytes memory _calldata bytes memory _calldata
) external nonReentrant onlyMessagingService onlyAuthorizedRemoteSender { ) external nonReentrant onlyMessagingService onlyAuthorizedRemoteSender {
emit MessageCompleted(nextCompletedMessageId);
bytes32 hash = keccak256(abi.encode(_to, _value, _calldata));
uint256 requestId = IETHYieldManager(yieldManager).requestWithdrawal(_value); uint256 requestId = IETHYieldManager(yieldManager).requestWithdrawal(_value);
completedMessages[nextCompletedMessageId] = CompletedMessage(requestId, hash); bytes32 hash = keccak256(abi.encode(_to, _value, _calldata));
messages[nextCompletedMessageId] = Message(requestId, hash, MessageStatus.Requested);
emit BridgingCompleted(nextCompletedMessageId);
nextCompletedMessageId++; nextCompletedMessageId++;
} }
@@ -142,6 +154,35 @@ contract L1ETHBridge is IL1ETHBridge, Initializable, UUPSUpgradeable, OwnableUpg
messageService.sendMessage(remoteSender, 0, data); 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 { function _authorizeUpgrade(address) internal view override {
_checkOwner(); _checkOwner();
} }

View File

@@ -3,4 +3,6 @@ pragma solidity ^0.8.26;
interface IETHYieldManager { interface IETHYieldManager {
function requestWithdrawal(uint256 _amount) external returns (uint256 requestId); function requestWithdrawal(uint256 _amount) external returns (uint256 requestId);
function claimWithdrawal(uint256 _requestId, uint256 _hintId) external returns (bool success);
} }

View File

@@ -14,7 +14,19 @@ interface IL1ETHBridge {
address indexed setBy address indexed setBy
); );
event MessageCompleted( /**
* @notice Emitted when a bridging is completed.
* @param messageId The indexed message id.
*/
event BridgingCompleted(
uint256 indexed messageId
);
/**
* @notice Emitted when a message is finalized.
* @param messageId The indexed message id.
*/
event MessageFinalized(
uint256 indexed messageId uint256 indexed messageId
); );
@@ -34,6 +46,9 @@ interface IL1ETHBridge {
error L1ETHBridge__ZeroAddressNotAllowed(); error L1ETHBridge__ZeroAddressNotAllowed();
error L1ETHBridge__ETHTransferFailed(); error L1ETHBridge__ETHTransferFailed();
error L1ETHBridge__YieldManagerDepositFailed(); error L1ETHBridge__YieldManagerDepositFailed();
error L1ETHBridge__MessageAlreadyFinalized();
error L1ETHBridge__InvalidMessageParameters();
error L1ETHBridge__WithdrawalClaimFailed();
function setRemoteSender(address _remoteSender) external; function setRemoteSender(address _remoteSender) external;

View File

@@ -149,8 +149,9 @@ contract L1ETHBridgeTest is Test {
bridge.completeBridging(user1, 100, "test-data"); bridge.completeBridging(user1, 100, "test-data");
assertEq(bridge.nextCompletedMessageId(), 1); assertEq(bridge.nextCompletedMessageId(), 1);
(uint256 withdrawalRequestId, bytes32 hash) = bridge.completedMessages(0); (uint256 withdrawalRequestId, bytes32 hash, L1ETHBridge.MessageStatus status) = bridge.messages(0);
assertEq(withdrawalRequestId, 0); assertEq(withdrawalRequestId, 0);
assertEq(hash, keccak256(abi.encode(user1, 100, "test-data"))); assertEq(hash, keccak256(abi.encode(user1, 100, "test-data")));
assertEq(uint256(status), uint256(L1ETHBridge.MessageStatus.Requested));
} }
} }

View File

@@ -24,7 +24,7 @@ contract ETHYieldManagerMock {
return deposits[deposits.length - 1]; return deposits[deposits.length - 1];
} }
function requestWithdrawal(uint256 _amount) external returns (uint256 requestId) { function requestWithdrawal(uint256) external returns (uint256 requestId) {
return nextRequestId++; return nextRequestId++;
} }
} }