Commit 3d233485 authored by dcbuilder.eth's avatar dcbuilder.eth Committed by Mark Tyneway

contracts-bedrock: CrossDomainOwnable3

Adds a `CrossDomainOwnable3` contract that allows the
owner to be toggled between an L1 account and an L2 account.
In the L1 version, it is expected that the account goes
through the cross domain messenger.
Co-authored-by: default avatardcbuilder.eth <dcbuilder@protonmail.com>
Co-authored-by: default avatarMark Tyneway <mark.tyneway@gmail.com>
parent ed4d716a
......@@ -24,6 +24,15 @@ CrossDomainOwnable2_Test:test_onlyOwner_notMessenger_reverts() (gas: 8416)
CrossDomainOwnable2_Test:test_onlyOwner_notOwner2_reverts() (gas: 61738)
CrossDomainOwnable2_Test:test_onlyOwner_notOwner_reverts() (gas: 16588)
CrossDomainOwnable2_Test:test_onlyOwner_succeeds() (gas: 77766)
CrossDomainOwnable3_Test:test_crossDomainOnlyOwner_notMessenger_reverts() (gas: 28227)
CrossDomainOwnable3_Test:test_crossDomainOnlyOwner_notOwner2_reverts() (gas: 77262)
CrossDomainOwnable3_Test:test_crossDomainOnlyOwner_notOwner_reverts() (gas: 31938)
CrossDomainOwnable3_Test:test_crossDomainTransferOwnership_succeeds() (gas: 94644)
CrossDomainOwnable3_Test:test_localOnlyOwner_notOwner_reverts() (gas: 13150)
CrossDomainOwnable3_Test:test_localOnlyOwner_succeeds() (gas: 34232)
CrossDomainOwnable3_Test:test_localTransferOwnership_succeeds() (gas: 51003)
CrossDomainOwnable3_Test:test_transferOwnership_notOwner_reverts() (gas: 13521)
CrossDomainOwnable3_Test:test_transferOwnership_zeroAddress_reverts() (gas: 11683)
DeployerWhitelist_Test:test_owner_succeeds() (gas: 7538)
DeployerWhitelist_Test:test_storageSlots_succeeds() (gas: 33395)
FeeVault_Test:test_constructor_succeeds() (gas: 10647)
......
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { Predeploys } from "../libraries/Predeploys.sol";
import { L2CrossDomainMessenger } from "./L2CrossDomainMessenger.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title CrossDomainOwnable3
* @notice This contract extends the OpenZeppelin `Ownable` contract for L2 contracts to be owned
* by contracts on either L1 or L2. Note that this contract is meant to be used with systems that use
* the CrossDomainMessenger system. It will not work if the OptimismPortal is used directly.
*/
abstract contract CrossDomainOwnable3 is Ownable {
/**
* @notice If true, the contract uses the cross domain _checkOwner function override. If false
* it uses the standard Ownable _checkOwner function.
*/
bool public isLocal = true;
/**
* @notice Emits when ownership of the contract is transferred. Includes the
* isLocal field in addition to the standard `Ownable` OwnershipTransferred event.
*/
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner,
bool isLocal
);
/**
* @notice Overrides the implementation of the `onlyOwner` modifier to check that the unaliased
* `xDomainMessageSender` is the owner of the contract. This value is set to the caller
* of the L1CrossDomainMessenger.
*/
function _checkOwner() internal view override {
if (isLocal) {
require(owner() == _msgSender(), "CrossDomainOwnable3: caller is not the owner");
} else {
L2CrossDomainMessenger messenger = L2CrossDomainMessenger(
Predeploys.L2_CROSS_DOMAIN_MESSENGER
);
require(
msg.sender == address(messenger),
"CrossDomainOwnable3: caller is not the messenger"
);
require(
owner() == messenger.xDomainMessageSender(),
"CrossDomainOwnable3: caller is not the owner"
);
}
}
/**
* @notice Allows for ownership to be transferred with specifying the locality.
* @param _owner The new owner of the contract.
* @param _isLocal Configures the locality of the ownership.
*/
function transferOwnership(address _owner, bool _isLocal) external onlyOwner {
require(_owner != address(0), "CrossDomainOwnable3: new owner is the zero address");
address oldOwner = owner();
_transferOwnership(_owner);
isLocal = _isLocal;
emit OwnershipTransferred(oldOwner, _owner, _isLocal);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Bytes32AddressLib } from "@rari-capital/solmate/src/utils/Bytes32AddressLib.sol";
import { CommonTest, Messenger_Initializer } from "./CommonTest.t.sol";
import { CrossDomainOwnable3 } from "../L2/CrossDomainOwnable3.sol";
import { AddressAliasHelper } from "../vendor/AddressAliasHelper.sol";
import { Hashing } from "../libraries/Hashing.sol";
import { Encoding } from "../libraries/Encoding.sol";
contract XDomainSetter3 is CrossDomainOwnable3 {
uint256 public value;
function set(uint256 _value) external onlyOwner {
value = _value;
}
}
contract CrossDomainOwnable3_Test is Messenger_Initializer {
XDomainSetter3 setter;
/**
* @notice OpenZeppelin Ownable.sol transferOwnership event
*/
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @notice CrossDomainOwnable3.sol transferOwnership event
*/
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner,
bool isLocal
);
function setUp() public override {
super.setUp();
vm.prank(alice);
setter = new XDomainSetter3();
}
function test_constructor_succeeds() public {
assertEq(setter.owner(), alice);
assertEq(setter.isLocal(), true);
}
function test_localOnlyOwner_notOwner_reverts() public {
vm.prank(bob);
vm.expectRevert("CrossDomainOwnable3: caller is not the owner");
setter.set(1);
}
function test_transferOwnership_notOwner_reverts() public {
vm.prank(bob);
vm.expectRevert("CrossDomainOwnable3: caller is not the owner");
setter.transferOwnership({ _owner: bob, _isLocal: true });
}
function test_crossDomainOnlyOwner_notOwner_reverts() public {
vm.expectEmit(true, true, true, true);
// OpenZeppelin Ownable.sol transferOwnership event
emit OwnershipTransferred(alice, alice);
// CrossDomainOwnable3.sol transferOwnership event
emit OwnershipTransferred(alice, alice, false);
vm.prank(setter.owner());
setter.transferOwnership({ _owner: alice, _isLocal: false });
// set the xDomainMsgSender storage slot
bytes32 key = bytes32(uint256(204));
bytes32 value = Bytes32AddressLib.fillLast12Bytes(bob);
vm.store(address(L2Messenger), key, value);
vm.prank(address(L2Messenger));
vm.expectRevert("CrossDomainOwnable3: caller is not the owner");
setter.set(1);
}
function test_crossDomainOnlyOwner_notOwner2_reverts() public {
vm.expectEmit(true, true, true, true);
// OpenZeppelin Ownable.sol transferOwnership event
emit OwnershipTransferred(alice, alice);
// CrossDomainOwnable3.sol transferOwnership event
emit OwnershipTransferred(alice, alice, false);
vm.prank(setter.owner());
setter.transferOwnership({ _owner: alice, _isLocal: false });
assertEq(setter.isLocal(), false);
uint240 nonce = 0;
address sender = bob;
address target = address(setter);
uint256 value = 0;
uint256 minGasLimit = 0;
bytes memory message = abi.encodeWithSelector(XDomainSetter3.set.selector, 1);
bytes32 hash = Hashing.hashCrossDomainMessage(
Encoding.encodeVersionedNonce(nonce, 1),
sender,
target,
value,
minGasLimit,
message
);
// It should be a failed message. The revert is caught,
// so we cannot expectRevert here.
vm.expectEmit(true, true, true, true, address(L2Messenger));
emit FailedRelayedMessage(hash);
vm.prank(AddressAliasHelper.applyL1ToL2Alias(address(L1Messenger)));
L2Messenger.relayMessage(
Encoding.encodeVersionedNonce(nonce, 1),
sender,
target,
value,
minGasLimit,
message
);
assertEq(setter.value(), 0);
}
function test_crossDomainOnlyOwner_notMessenger_reverts() public {
vm.expectEmit(true, true, true, true);
// OpenZeppelin Ownable.sol transferOwnership event
emit OwnershipTransferred(alice, alice);
// CrossDomainOwnable3.sol transferOwnership event
emit OwnershipTransferred(alice, alice, false);
vm.prank(setter.owner());
setter.transferOwnership({ _owner: alice, _isLocal: false });
vm.prank(bob);
vm.expectRevert("CrossDomainOwnable3: caller is not the messenger");
setter.set(1);
}
function test_transferOwnership_zeroAddress_reverts() public {
vm.prank(setter.owner());
vm.expectRevert("CrossDomainOwnable3: new owner is the zero address");
setter.transferOwnership({ _owner: address(0), _isLocal: true });
}
function test_transferOwnership_noLocal_zeroAddress_reverts() public {
vm.prank(setter.owner());
vm.expectRevert("Ownable: new owner is the zero address");
setter.transferOwnership(address(0));
}
function test_localOnlyOwner_succeeds() public {
assertEq(setter.isLocal(), true);
vm.prank(setter.owner());
setter.set(1);
assertEq(setter.value(), 1);
}
function test_localTransferOwnership_succeeds() public {
vm.expectEmit(true, true, true, true, address(setter));
emit OwnershipTransferred(alice, bob);
emit OwnershipTransferred(alice, bob, true);
vm.prank(setter.owner());
setter.transferOwnership({ _owner: bob, _isLocal: true });
assertEq(setter.isLocal(), true);
vm.prank(bob);
setter.set(2);
assertEq(setter.value(), 2);
}
/**
* @notice The existing transferOwnership(address) method
* still exists on the contract
*/
function test_transferOwnership_noLocal_succeeds() public {
bool isLocal = setter.isLocal();
vm.expectEmit(true, true, true, true, address(setter));
emit OwnershipTransferred(alice, bob);
vm.prank(setter.owner());
setter.transferOwnership(bob);
// isLocal has not changed
assertEq(setter.isLocal(), isLocal);
vm.prank(bob);
setter.set(2);
assertEq(setter.value(), 2);
}
function test_crossDomainTransferOwnership_succeeds() public {
vm.expectEmit(true, true, true, true, address(setter));
emit OwnershipTransferred(alice, bob);
emit OwnershipTransferred(alice, bob, false);
vm.prank(setter.owner());
setter.transferOwnership({ _owner: bob, _isLocal: false });
assertEq(setter.isLocal(), false);
// Simulate the L2 execution where the call is coming from
// the L1CrossDomainMessenger
vm.prank(AddressAliasHelper.applyL1ToL2Alias(address(L1Messenger)));
L2Messenger.relayMessage(
Encoding.encodeVersionedNonce(1, 1),
bob,
address(setter),
0,
0,
abi.encodeWithSelector(XDomainSetter3.set.selector, 2)
);
assertEq(setter.value(), 2);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment