mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 04:08:01 -05:00
feat: Add L2ETHBridge with the completeBridge function
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import { BaseScript } from "./Base.s.sol";
|
||||
import { BaseScript } from "../Base.s.sol";
|
||||
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
|
||||
import { DeploymentConfig } from "./DeploymentConfig.s.sol";
|
||||
import { L1ETHBridge } from "../../../src/yield/bridge/L1ETHBridge.sol";
|
||||
import { L1ETHBridge } from "../../../../src/yield/bridge/L1ETHBridge.sol";
|
||||
|
||||
contract DeployL1ETHBridge is BaseScript {
|
||||
function run() public returns (address, L1ETHBridge) {
|
||||
@@ -2,8 +2,8 @@
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
import { Script } from "forge-std/Script.sol";
|
||||
import { ETHYieldManagerMock } from "../../../test/foundry/yield/bridge/mocks/ETHYieldManagerMock.sol";
|
||||
import { RollupMock } from "../../../test/foundry/yield/bridge/mocks/RollupMock.sol";
|
||||
import { ETHYieldManagerMock } from "../../../../test/foundry/yield/bridge/mocks/ETHYieldManagerMock.sol";
|
||||
import { RollupMock } from "../../../../test/foundry/yield/bridge/mocks/RollupMock.sol";
|
||||
|
||||
contract DeploymentConfig is Script {
|
||||
error DeploymentConfig_InvalidDeployerAddress();
|
||||
28
contracts/scripts/yield/bridge/l2/DeployL2ETHBridge.s.sol
Normal file
28
contracts/scripts/yield/bridge/l2/DeployL2ETHBridge.s.sol
Normal file
@@ -0,0 +1,28 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import { BaseScript } from "../Base.s.sol";
|
||||
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
|
||||
import { DeploymentConfig } from "./DeploymentConfig.s.sol";
|
||||
import { L2ETHBridge } from "../../../../src/yield/bridge/L2ETHBridge.sol";
|
||||
|
||||
contract DeployL2ETHBridge is BaseScript {
|
||||
function run() public returns (address, L2ETHBridge) {
|
||||
DeploymentConfig deploymentConfig = new DeploymentConfig(broadcaster);
|
||||
(address deployer, address l2MessageService, address l1ETHBridge) = deploymentConfig
|
||||
.activeNetworkConfig();
|
||||
|
||||
vm.startBroadcast(deployer);
|
||||
|
||||
L2ETHBridge impl = new L2ETHBridge();
|
||||
bytes memory initializeData = abi.encodeCall(L2ETHBridge.initialize, (deployer, l2MessageService, l1ETHBridge));
|
||||
|
||||
address proxy = address(new ERC1967Proxy(address(impl), initializeData));
|
||||
|
||||
vm.stopBroadcast();
|
||||
|
||||
return (deployer, L2ETHBridge(proxy));
|
||||
}
|
||||
}
|
||||
|
||||
46
contracts/scripts/yield/bridge/l2/DeploymentConfig.s.sol
Normal file
46
contracts/scripts/yield/bridge/l2/DeploymentConfig.s.sol
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import { Script } from "forge-std/Script.sol";
|
||||
import { L2MessageServiceMock } from "../../../../test/foundry/yield/bridge/mocks/L2MessageServiceMock.sol";
|
||||
|
||||
contract DeploymentConfig is Script {
|
||||
error DeploymentConfig_InvalidDeployerAddress();
|
||||
error DeploymentConfig_NoConfigForChain(uint256);
|
||||
|
||||
struct NetworkConfig {
|
||||
address deployer;
|
||||
address l2MessageService;
|
||||
address l1ETHBridge;
|
||||
}
|
||||
|
||||
NetworkConfig public activeNetworkConfig;
|
||||
|
||||
address private deployer;
|
||||
|
||||
constructor(address _broadcaster) {
|
||||
if (_broadcaster == address(0)) revert DeploymentConfig_InvalidDeployerAddress();
|
||||
deployer = _broadcaster;
|
||||
if (block.chainid == 31_337) {
|
||||
activeNetworkConfig = getOrCreateAnvilEthConfig(deployer);
|
||||
} else {
|
||||
revert DeploymentConfig_NoConfigForChain(block.chainid);
|
||||
}
|
||||
}
|
||||
|
||||
function getOrCreateAnvilEthConfig(address _deployer) public returns (NetworkConfig memory) {
|
||||
return
|
||||
NetworkConfig({
|
||||
deployer: _deployer,
|
||||
l2MessageService: address(new L2MessageServiceMock()),
|
||||
l1ETHBridge: makeAddr("l1ETHBridge")
|
||||
});
|
||||
}
|
||||
|
||||
// This function is a hack to have it excluded by `forge coverage` until
|
||||
// https://github.com/foundry-rs/foundry/issues/2988 is fixed.
|
||||
// See: https://github.com/foundry-rs/foundry/issues/2988#issuecomment-1437784542
|
||||
// for more info.
|
||||
// solhint-disable-next-line
|
||||
function test() public {}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.19;
|
||||
pragma solidity 0.8.26;
|
||||
|
||||
import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
|
||||
@@ -41,8 +41,7 @@ contract L1ETHBridge is Initializable, UUPSUpgradeable, OwnableUpgradeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Initializes the contract.
|
||||
* @dev Disables initializers to prevent reinitialization.
|
||||
* @notice Disables initializers to prevent reinitialization.
|
||||
*/
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
|
||||
70
contracts/src/yield/bridge/L2ETHBridge.sol
Normal file
70
contracts/src/yield/bridge/L2ETHBridge.sol
Normal file
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
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 { MessageServiceBase } from "../../messaging/MessageServiceBase.sol";
|
||||
import { IMessageService } from "../../messaging/interfaces/IMessageService.sol";
|
||||
|
||||
contract L2ETHBridge is Initializable, UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable, MessageServiceBase {
|
||||
error L2ETHBridge__ETHTransferFailed();
|
||||
|
||||
/**
|
||||
* @notice Disables initializers to prevent reinitialization.
|
||||
*/
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Initializes the contract.
|
||||
* @param _initialOwner The initial owner of the contract.
|
||||
* @param _remoteSender The remote sender address.
|
||||
* @param _messageService The L2 MessageService address.
|
||||
*/
|
||||
function initialize(address _initialOwner, address _messageService, address _remoteSender) external initializer {
|
||||
__MessageServiceBase_init(_messageService);
|
||||
_setRemoteSender(_remoteSender);
|
||||
|
||||
_transferOwnership(_initialOwner);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the remote sender address.
|
||||
* @param _remoteSender The L1ETHBridge address.
|
||||
*/
|
||||
function setRemoteSender(address _remoteSender) internal onlyOwner {
|
||||
_setRemoteSender(_remoteSender);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the L2MessageService address.
|
||||
* @param _messageService The L2 MessageService address.
|
||||
*/
|
||||
function setMessageService(address _messageService) external onlyOwner {
|
||||
messageService = IMessageService(_messageService);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Completes the bridge. Callable only by the L2MessageService.
|
||||
* @param _to The recipient address.
|
||||
* @param _value The amount of ETH to transfer.
|
||||
* @param _calldata The calldata to pass to the recipient.
|
||||
*/
|
||||
function completeBridge(
|
||||
address _to,
|
||||
uint256 _value,
|
||||
bytes memory _calldata
|
||||
) external nonReentrant onlyMessagingService() onlyAuthorizedRemoteSender {
|
||||
(bool success, ) = _to.call{ value: _value }(_calldata);
|
||||
if (!success) {
|
||||
revert L2ETHBridge__ETHTransferFailed();
|
||||
}
|
||||
}
|
||||
|
||||
function _authorizeUpgrade(address) internal view override {
|
||||
_checkOwner();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.19;
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
interface IL2ETHBridge {
|
||||
function completeBridge(address _to, uint256 _value, bytes calldata _calldata) external;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity 0.8.19;
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
interface IRollup {
|
||||
function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable;
|
||||
|
||||
@@ -3,7 +3,7 @@ pragma solidity ^0.8.19;
|
||||
|
||||
import { Test } from "forge-std/Test.sol";
|
||||
import { L1ETHBridge } from "../../../../src/yield/bridge/L1ETHBridge.sol";
|
||||
import { DeployL1ETHBridge } from "../../../../scripts/yield/bridge/DeployL1ETHBridge.s.sol";
|
||||
import { DeployL1ETHBridge } from "../../../../scripts/yield/bridge/l1/DeployL1ETHBridge.s.sol";
|
||||
import { IL2ETHBridge } from "../../../../src/yield/bridge/interfaces/IL2ETHBridge.sol";
|
||||
|
||||
import { RollupMock } from "./mocks/RollupMock.sol";
|
||||
|
||||
60
contracts/test/foundry/yield/bridge/L2ETHBridge.t.sol
Normal file
60
contracts/test/foundry/yield/bridge/L2ETHBridge.t.sol
Normal file
@@ -0,0 +1,60 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
import { Test, console } from "forge-std/Test.sol";
|
||||
import { L2ETHBridge } from "../../../../src/yield/bridge/L2ETHBridge.sol";
|
||||
import { DeployL2ETHBridge } from "../../../../scripts/yield/bridge/l2/DeployL2ETHBridge.s.sol";
|
||||
import { RecipientMock } from "./mocks/RecipientMock.sol";
|
||||
import { L2MessageServiceMock } from "./mocks/L2MessageServiceMock.sol";
|
||||
|
||||
contract L2ETHBridgeTest is Test {
|
||||
L2ETHBridge bridge;
|
||||
|
||||
address deployer;
|
||||
address l1ETHBridge;
|
||||
address nonAuthorizedSender = makeAddr("nonAuthorizedSender");
|
||||
L2MessageServiceMock l2MessageService;
|
||||
|
||||
RecipientMock recipientMock;
|
||||
|
||||
function setUp() public {
|
||||
DeployL2ETHBridge script = new DeployL2ETHBridge();
|
||||
(deployer, bridge) = script.run();
|
||||
l1ETHBridge = bridge.remoteSender();
|
||||
l2MessageService = L2MessageServiceMock(address(bridge.messageService()));
|
||||
recipientMock = new RecipientMock();
|
||||
|
||||
vm.deal(address(bridge), 100e18);
|
||||
}
|
||||
|
||||
function test_RevertsIfMsgSenderIsNotL2MessageService() public {
|
||||
l2MessageService.setOriginalSender(l1ETHBridge);
|
||||
|
||||
vm.prank(nonAuthorizedSender);
|
||||
vm.expectRevert("CallerIsNotMessageService()");
|
||||
bridge.completeBridge(l1ETHBridge, 0, "");
|
||||
}
|
||||
|
||||
function test_RevertsIfRemoteSenderIsNotL1ETHBridge() public {
|
||||
l2MessageService.setOriginalSender(nonAuthorizedSender);
|
||||
|
||||
vm.prank(address(l2MessageService));
|
||||
vm.expectRevert("SenderNotAuthorized()");
|
||||
bridge.completeBridge(nonAuthorizedSender, 0, "");
|
||||
}
|
||||
|
||||
function test_CompleteBridge() public {
|
||||
l2MessageService.setOriginalSender(l1ETHBridge);
|
||||
|
||||
assertEq(recipientMock.lastCallParam(), 0);
|
||||
assertEq(address(recipientMock).balance, 0);
|
||||
|
||||
vm.prank(address(l2MessageService));
|
||||
bytes memory data = abi.encodeWithSelector(RecipientMock.foo.selector, 77);
|
||||
bridge.completeBridge(address(recipientMock), 100, data);
|
||||
|
||||
assertEq(recipientMock.lastCallParam(), 77);
|
||||
assertEq(address(recipientMock).balance, 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.19;
|
||||
|
||||
contract L2MessageServiceMock {
|
||||
address public originalSender;
|
||||
|
||||
function setOriginalSender(address _originalSender) external {
|
||||
originalSender = _originalSender;
|
||||
}
|
||||
|
||||
function sender() external view returns (address) {
|
||||
return originalSender;
|
||||
}
|
||||
}
|
||||
|
||||
10
contracts/test/foundry/yield/bridge/mocks/RecipientMock.sol
Normal file
10
contracts/test/foundry/yield/bridge/mocks/RecipientMock.sol
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.26;
|
||||
|
||||
contract RecipientMock {
|
||||
uint256 public lastCallParam;
|
||||
|
||||
function foo(uint256 _param) external payable {
|
||||
lastCallParam = _param;
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,3 @@ contract RollupMock is IRollup {
|
||||
return messages[messages.length - 1];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user