mirror of
https://github.com/vacp2p/linea-monorepo.git
synced 2026-01-09 04:08:01 -05:00
feat: add YieldRollup
This commit is contained in:
71
contracts/src/yield/bridge/YieldRollup.sol
Normal file
71
contracts/src/yield/bridge/YieldRollup.sol
Normal file
@@ -0,0 +1,71 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import { IMessageService } from "../../messaging/interfaces/IMessageService.sol";
|
||||
import { L1MessageService } from "../../messaging/l1/L1MessageService.sol";
|
||||
import { LineaRollupBase } from "../../rollup/LineaRollupBase.sol";
|
||||
|
||||
contract YieldRollup is LineaRollupBase {
|
||||
error YieldRollup__L1ETHBridgeNotSet();
|
||||
error YieldRollup__L2ETHBridgeNotSet();
|
||||
error YieldRollup__InvalidValue();
|
||||
error YieldRollup__InvalidRecipient();
|
||||
|
||||
address public l1ETHBridge;
|
||||
address public l2ETHBridge;
|
||||
|
||||
/// @custom:oz-upgrades-unsafe-allow constructor
|
||||
constructor() {
|
||||
_disableInitializers();
|
||||
}
|
||||
|
||||
function initialize(InitializationData calldata _initializationData) external initializer {
|
||||
__LineaRollup_init(_initializationData);
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the L1ETHBridge address.
|
||||
* @param _l1ETHBridge The new L1ETHBridge address.
|
||||
*/
|
||||
function setL1ETHBridge(address _l1ETHBridge) public onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
l1ETHBridge = _l1ETHBridge;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sets the L2ETHBridge address.
|
||||
* @param _l2ETHBridge The new L2ETHBridge address.
|
||||
*/
|
||||
function setL2ETHBridge(address _l2ETHBridge) public onlyRole(DEFAULT_ADMIN_ROLE) {
|
||||
l2ETHBridge = _l2ETHBridge;
|
||||
}
|
||||
|
||||
/**
|
||||
* @notice Sends a message. It doesn't allow sending ETH. If the message is sent to the L2ETHBridge, it checks if the sender is the L1ETHBridge.
|
||||
* @param _to The recipient of the message.
|
||||
* @param _fee The fee for the message.
|
||||
* @param _calldata The calldata for the message.
|
||||
*/
|
||||
function sendMessage(
|
||||
address _to,
|
||||
uint256 _fee,
|
||||
bytes calldata _calldata
|
||||
) public payable override(IMessageService, L1MessageService) {
|
||||
if (l1ETHBridge == address(0)) {
|
||||
revert YieldRollup__L1ETHBridgeNotSet();
|
||||
}
|
||||
|
||||
if (l2ETHBridge == address(0)) {
|
||||
revert YieldRollup__L2ETHBridgeNotSet();
|
||||
}
|
||||
|
||||
if (msg.value > 0) {
|
||||
revert YieldRollup__InvalidValue();
|
||||
}
|
||||
|
||||
if (_to == l2ETHBridge && msg.sender != l1ETHBridge) {
|
||||
revert YieldRollup__InvalidRecipient();
|
||||
}
|
||||
|
||||
_sendMessage(_to, _fee, _calldata);
|
||||
}
|
||||
}
|
||||
46
contracts/test/foundry/yield/bridge/TestUtils.sol
Normal file
46
contracts/test/foundry/yield/bridge/TestUtils.sol
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.23;
|
||||
|
||||
library TestUtils {
|
||||
// Helper function to convert address to ascii string
|
||||
function _toAsciiString(address x) internal pure returns (string memory) {
|
||||
bytes memory s = new bytes(42);
|
||||
s[0] = "0";
|
||||
s[1] = "x";
|
||||
for (uint256 i = 0; i < 20; i++) {
|
||||
uint8 b = uint8(uint256(uint160(x)) / (2 ** (8 * (19 - i))));
|
||||
uint8 hi = b / 16;
|
||||
uint8 lo = b - 16 * hi;
|
||||
s[2 + 2 * i] = _char(hi);
|
||||
s[3 + 2 * i] = _char(lo);
|
||||
}
|
||||
return string(s);
|
||||
}
|
||||
|
||||
// Helper function to convert byte to char
|
||||
function _char(uint8 b) internal pure returns (bytes1 c) {
|
||||
if (b < 10) {
|
||||
return bytes1(b + 0x30);
|
||||
} else {
|
||||
return bytes1(b + 0x57);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to convert bytes32 to hex string
|
||||
function _toHexString(bytes32 data) internal pure returns (string memory) {
|
||||
return _toHexString(abi.encodePacked(data));
|
||||
}
|
||||
|
||||
// Helper function to convert bytes to hex string
|
||||
function _toHexString(bytes memory data) internal pure returns (string memory) {
|
||||
bytes memory hexString = new bytes(data.length * 2 + 2);
|
||||
hexString[0] = "0";
|
||||
hexString[1] = "x";
|
||||
bytes memory hexChars = "0123456789abcdef";
|
||||
for (uint256 i = 0; i < data.length; i++) {
|
||||
hexString[2 + i * 2] = hexChars[uint8(data[i] >> 4)];
|
||||
hexString[3 + i * 2] = hexChars[uint8(data[i] & 0x0f)];
|
||||
}
|
||||
return string(hexString);
|
||||
}
|
||||
}
|
||||
144
contracts/test/foundry/yield/bridge/YieldRollup.t.sol
Normal file
144
contracts/test/foundry/yield/bridge/YieldRollup.t.sol
Normal file
@@ -0,0 +1,144 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity ^0.8.23;
|
||||
|
||||
import { Test } from "forge-std/Test.sol";
|
||||
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
|
||||
import { ILineaRollup } from "../../../../src/rollup/interfaces/ILineaRollup.sol";
|
||||
import { IPermissionsManager } from "../../../../src/security/access/interfaces/IPermissionsManager.sol";
|
||||
import { IPauseManager } from "../../../../src/security/pausing/interfaces/IPauseManager.sol";
|
||||
import { IMessageService } from "../../../../src/messaging/interfaces/IMessageService.sol";
|
||||
import { LineaRollup } from "../../../../src/rollup/LineaRollup.sol";
|
||||
import { YieldRollup } from "../../../../src/yield/bridge/YieldRollup.sol";
|
||||
import { TestUtils } from "./TestUtils.sol";
|
||||
|
||||
contract YieldRollupTest is Test {
|
||||
YieldRollup yieldRollup;
|
||||
|
||||
address user1 = makeAddr("user1");
|
||||
address l1ETHBridge = makeAddr("l1ETHBridge");
|
||||
address l2ETHBridge = makeAddr("l2ETHBridge");
|
||||
|
||||
address operator = makeAddr("operator");
|
||||
address defaultAdmin = makeAddr("defaultAdmin");
|
||||
address verifier = makeAddr("verifier");
|
||||
address nonAuthorizedAccount = makeAddr("nonAuthorizedAccount");
|
||||
address securityCouncil = defaultAdmin;
|
||||
address fallbackOperator = makeAddr("fallbackOperator");
|
||||
|
||||
bytes32 VERIFIER_SETTER_ROLE;
|
||||
bytes32 VERIFIER_UNSETTER_ROLE;
|
||||
bytes32 OPERATOR_ROLE;
|
||||
bytes32 DEFAULT_ADMIN_ROLE;
|
||||
|
||||
function setUp() public {
|
||||
YieldRollup implementation = new YieldRollup();
|
||||
|
||||
ILineaRollup.InitializationData memory initData;
|
||||
initData.initialStateRootHash = bytes32(0x0);
|
||||
initData.initialL2BlockNumber = 0;
|
||||
initData.genesisTimestamp = block.timestamp;
|
||||
initData.defaultVerifier = verifier;
|
||||
initData.rateLimitPeriodInSeconds = 86400; // 1 day
|
||||
initData.rateLimitAmountInWei = 100 ether;
|
||||
|
||||
initData.roleAddresses = new IPermissionsManager.RoleAddress[](1);
|
||||
initData.roleAddresses[0] = IPermissionsManager.RoleAddress({
|
||||
addressWithRole: operator,
|
||||
role: implementation.OPERATOR_ROLE()
|
||||
});
|
||||
|
||||
initData.pauseTypeRoles = new IPauseManager.PauseTypeRole[](0);
|
||||
initData.unpauseTypeRoles = new IPauseManager.PauseTypeRole[](0);
|
||||
initData.fallbackOperator = fallbackOperator;
|
||||
initData.defaultAdmin = defaultAdmin;
|
||||
|
||||
bytes memory initializer = abi.encodeWithSelector(LineaRollup.initialize.selector, initData);
|
||||
|
||||
ERC1967Proxy proxy = new ERC1967Proxy(address(implementation), initializer);
|
||||
|
||||
yieldRollup = YieldRollup(address(proxy));
|
||||
|
||||
VERIFIER_SETTER_ROLE = yieldRollup.VERIFIER_SETTER_ROLE();
|
||||
VERIFIER_UNSETTER_ROLE = yieldRollup.VERIFIER_UNSETTER_ROLE();
|
||||
OPERATOR_ROLE = yieldRollup.OPERATOR_ROLE();
|
||||
DEFAULT_ADMIN_ROLE = yieldRollup.DEFAULT_ADMIN_ROLE();
|
||||
|
||||
assertEq(yieldRollup.hasRole(DEFAULT_ADMIN_ROLE, defaultAdmin), true, "Default admin not set");
|
||||
assertEq(yieldRollup.hasRole(OPERATOR_ROLE, operator), true, "Operator not set");
|
||||
|
||||
vm.startBroadcast(defaultAdmin);
|
||||
yieldRollup.setL1ETHBridge(l1ETHBridge);
|
||||
yieldRollup.setL2ETHBridge(l2ETHBridge);
|
||||
vm.stopBroadcast();
|
||||
}
|
||||
|
||||
function test_RevertsIfL1ETHBridgeIsNotSet() public {
|
||||
vm.prank(defaultAdmin);
|
||||
yieldRollup.setL1ETHBridge(address(0));
|
||||
|
||||
vm.expectRevert("YieldRollup__L1ETHBridgeNotSet()");
|
||||
yieldRollup.sendMessage(user1, 0, "");
|
||||
}
|
||||
|
||||
function test_RevertsIfL2ETHBridgeIsNotSet() public {
|
||||
vm.prank(defaultAdmin);
|
||||
yieldRollup.setL2ETHBridge(address(0));
|
||||
|
||||
vm.expectRevert("YieldRollup__L2ETHBridgeNotSet()");
|
||||
yieldRollup.sendMessage(user1, 0, "");
|
||||
}
|
||||
|
||||
function test_RevertsIfInvalidValue() public {
|
||||
vm.expectRevert("YieldRollup__InvalidValue()");
|
||||
yieldRollup.sendMessage{ value: 1 }(user1, 0, "");
|
||||
}
|
||||
|
||||
function test_RevertsIfInvalidRecipient() public {
|
||||
vm.expectRevert("YieldRollup__InvalidRecipient()");
|
||||
yieldRollup.sendMessage(l2ETHBridge, 0, "");
|
||||
}
|
||||
|
||||
function test_OnlyAdminCanSetL1ETHBridge() public {
|
||||
vm.prank(nonAuthorizedAccount);
|
||||
vm.expectRevert(
|
||||
abi.encodePacked(
|
||||
"AccessControl: account ",
|
||||
TestUtils._toAsciiString(nonAuthorizedAccount),
|
||||
" is missing role ",
|
||||
TestUtils._toHexString(DEFAULT_ADMIN_ROLE)
|
||||
)
|
||||
);
|
||||
yieldRollup.setL1ETHBridge(l1ETHBridge);
|
||||
}
|
||||
|
||||
function test_OnlyAdminCanSetL2ETHBridge() public {
|
||||
vm.prank(nonAuthorizedAccount);
|
||||
vm.expectRevert(
|
||||
abi.encodePacked(
|
||||
"AccessControl: account ",
|
||||
TestUtils._toAsciiString(nonAuthorizedAccount),
|
||||
" is missing role ",
|
||||
TestUtils._toHexString(DEFAULT_ADMIN_ROLE)
|
||||
)
|
||||
);
|
||||
yieldRollup.setL2ETHBridge(l2ETHBridge);
|
||||
}
|
||||
|
||||
function test_SendsMessage() public {
|
||||
vm.prank(l1ETHBridge);
|
||||
vm.expectEmit();
|
||||
emit IMessageService.MessageSent(
|
||||
l1ETHBridge,
|
||||
l2ETHBridge,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
"test-message",
|
||||
keccak256(abi.encode(l1ETHBridge, l2ETHBridge, 0, 0, 1, "test-message"))
|
||||
);
|
||||
yieldRollup.sendMessage(l2ETHBridge, 0, "test-message");
|
||||
|
||||
// Verify message was sent
|
||||
assertEq(yieldRollup.nextMessageNumber(), 2); // First message has nonce 1, so next should be 2
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user