Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions src/bridges/layerZero/IexecLayerZeroBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -189,20 +189,43 @@ contract IexecLayerZeroBridge is
// ============ ACCESS CONTROL OVERRIDES ============

/**
* @notice Returns the owner of the contract
* @return The address of the current owner
*
* @dev This override resolves the conflict between OwnableUpgradeable and
* AccessControlDefaultAdminRulesUpgradeable, both of which define owner().
* We use the OwnableUpgradeable version for consistency.
* @dev Overridden to prevent ownership renouncement.
* AccessControlDefaultAdminRulesUpgradeable is used to manage ownership.
*/
function renounceOwnership() public pure override {
revert OperationNotAllowed("Use AccessControlDefaultAdminRulesUpgradeable instead");
}

/**
* @dev Overridden to prevent ownership transfer.
* AccessControlDefaultAdminRulesUpgradeable is used to manage ownership.
*/
function transferOwnership(address) public pure override {
revert OperationNotAllowed("Use AccessControlDefaultAdminRulesUpgradeable instead");
}

/**
* Returns the owner of the contract which is also the default admin.
* @return The address of the current owner and default admin
*/
function owner()
public
view
override(OwnableUpgradeable, AccessControlDefaultAdminRulesUpgradeable)
returns (address)
{
return OwnableUpgradeable.owner();
return AccessControlDefaultAdminRulesUpgradeable.owner();
}

/**
* Accepts the default admin transfer and sets the owner to the new admin.
* @dev This ensures the state variable `OwnableUpgradeable._owner` is set correctly after the default
* admin transfer. Even though `OwnableUpgradeable._owner` is not used in `owner()` accessor, we chose
* to update it for consistency purposes.
*/
function _acceptDefaultAdminTransfer() internal override {
super._acceptDefaultAdminTransfer();
_transferOwnership(defaultAdmin());
}

// ============ CORE BRIDGE FUNCTIONS ============
Expand Down
2 changes: 2 additions & 0 deletions src/interfaces/IIexecLayerZeroBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
pragma solidity ^0.8.22;

interface IIexecLayerZeroBridge {
error OperationNotAllowed(string message);

/**
* @notice Pauses the contract, disabling `_credit` & `_debit` functions.
* @dev Should only be callable by authorized accounts: PAUSER_ROLE.
Expand Down
64 changes: 64 additions & 0 deletions test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import {MessagingFee, SendParam, IOFT} from "@layerzerolabs/oft-evm/contracts/in
import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import {IERC7802} from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IAccessControlDefaultAdminRules} from
"@openzeppelin/contracts/access/extensions/IAccessControlDefaultAdminRules.sol";
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {TestHelperOz5} from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol";
import {IexecLayerZeroBridgeHarness} from "../../mocks/IexecLayerZeroBridgeHarness.sol";
import {IIexecLayerZeroBridge} from "../../../../src/interfaces/IIexecLayerZeroBridge.sol";
import {DualPausableUpgradeable} from "../../../../src/bridges/utils/DualPausableUpgradeable.sol";
import {TestUtils} from "../../utils/TestUtils.sol";
import {RLCCrosschainToken} from "../../../../src/RLCCrosschainToken.sol";
Expand Down Expand Up @@ -264,6 +269,65 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 {
test_SendToken_WhenOperational_WithoutApproval();
}

// ============ renounceOwnership, transferOwnership, owner ============

function test_renounceOwnership_IsNotAllowed() public {
vm.expectRevert(
abi.encodeWithSelector(
IIexecLayerZeroBridge.OperationNotAllowed.selector,
"Use AccessControlDefaultAdminRulesUpgradeable instead"
)
);
iexecLayerZeroBridgeChainX.renounceOwnership();
}

function test_transferOwnership_IsNotAllowed() public {
vm.expectRevert(
abi.encodeWithSelector(
IIexecLayerZeroBridge.OperationNotAllowed.selector,
"Use AccessControlDefaultAdminRulesUpgradeable instead"
)
);
iexecLayerZeroBridgeChainX.transferOwnership(user1);
}

function test_owner_ReturnsDefaultAdmin() public view {
assertEq(iexecLayerZeroBridgeChainX.owner(), admin, "owner() should return the correct owner");
assertEq(
iexecLayerZeroBridgeChainX.owner(),
iexecLayerZeroBridgeChainX.defaultAdmin(),
"owner() should be equal to defaultAdmin()"
);
}

function test_acceptDefaultAdminTransfer_UpdatesOwnerInOwnable() public {
// Init admin transfer.
vm.expectEmit(true, true, true, true, address(iexecLayerZeroBridgeChainX));
emit IAccessControlDefaultAdminRules.DefaultAdminTransferScheduled(user1, 1); // block.timestamp == 1
vm.startPrank(admin);
iexecLayerZeroBridgeChainX.beginDefaultAdminTransfer(user1);
vm.stopPrank();
(, uint48 acceptSchedule) = iexecLayerZeroBridgeChainX.pendingDefaultAdmin();
// Finalize admin transfer.
vm.expectEmit(true, true, true, true, address(iexecLayerZeroBridgeChainX));
emit IAccessControl.RoleRevoked(iexecLayerZeroBridgeChainX.DEFAULT_ADMIN_ROLE(), admin, user1);
vm.expectEmit(true, true, true, true, address(iexecLayerZeroBridgeChainX));
emit IAccessControl.RoleGranted(iexecLayerZeroBridgeChainX.DEFAULT_ADMIN_ROLE(), user1, user1);
vm.expectEmit(true, true, true, true, address(iexecLayerZeroBridgeChainX));
emit OwnableUpgradeable.OwnershipTransferred(admin, user1);
vm.warp(acceptSchedule + 1); // Time travel to after the accept schedule.
vm.startPrank(user1);
iexecLayerZeroBridgeChainX.acceptDefaultAdminTransfer();
vm.stopPrank();
// Check the new owner.
assertEq(iexecLayerZeroBridgeChainX.owner(), user1, "owner() should return user1");
assertEq(
iexecLayerZeroBridgeChainX.owner(),
iexecLayerZeroBridgeChainX.defaultAdmin(),
"owner() should be equal to defaultAdmin()"
);
}

// ============ token and approvalRequired ============
function test_ReturnsApprovalRequired_WithApproval() public view {
assertEq(iexecLayerZeroBridgeEthereum.approvalRequired(), true, "approvalRequired() should return true");
Expand Down