Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol): improve DelegateOwner to have an optional L2 admin #17445

Merged
merged 16 commits into from
Jun 18, 2024
Merged
Show file tree
Hide file tree
Changes from 10 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
5 changes: 3 additions & 2 deletions packages/protocol/contract_layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,9 @@
| lastUnpausedAt | uint64 | 201 | 2 | 8 | contracts/L2/DelegateOwner.sol:DelegateOwner |
| __gap | uint256[49] | 202 | 0 | 1568 | contracts/L2/DelegateOwner.sol:DelegateOwner |
| l1ChainId | uint64 | 251 | 0 | 8 | contracts/L2/DelegateOwner.sol:DelegateOwner |
| nextTxId | uint64 | 251 | 8 | 8 | contracts/L2/DelegateOwner.sol:DelegateOwner |
| realOwner | address | 252 | 0 | 20 | contracts/L2/DelegateOwner.sol:DelegateOwner |
| admin | address | 251 | 8 | 20 | contracts/L2/DelegateOwner.sol:DelegateOwner |
| nextTxId | uint64 | 252 | 0 | 8 | contracts/L2/DelegateOwner.sol:DelegateOwner |
| realOwner | address | 252 | 8 | 20 | contracts/L2/DelegateOwner.sol:DelegateOwner |
| __gap | uint256[48] | 253 | 0 | 1536 | contracts/L2/DelegateOwner.sol:DelegateOwner |

## GuardianProver
Expand Down
40 changes: 34 additions & 6 deletions packages/protocol/contracts/L2/DelegateOwner.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ import "../bridge/IBridge.sol";
/// @custom:security-contact [email protected]
contract DelegateOwner is EssentialContract, IMessageInvocable {
/// @notice The owner chain ID.
uint64 public l1ChainId;
uint64 public l1ChainId; // slot 1

/// @notice The admin who can directly call `invokeCall`.
address public admin;

/// @notice The next transaction ID.
uint64 public nextTxId;
uint64 public nextTxId; // slot 2

/// @notice The real owner on L1, supposedly the DAO.
address public realOwner;
Expand All @@ -40,6 +43,11 @@ contract DelegateOwner is EssentialContract, IMessageInvocable {
uint64 indexed txId, address indexed target, bool isDelegateCall, bytes4 indexed selector
);

/// @notice Emitted when the admin has been changed.
/// @param oldAdmin The old admin address.
/// @param newAdmin The new admin address.
event AdminUpdated(address indexed oldAdmin, address indexed newAdmin);

error DO_DRYRUN_SUCCEEDED();
error DO_INVALID_PARAM();
error DO_INVALID_TARGET();
Expand All @@ -50,12 +58,14 @@ contract DelegateOwner is EssentialContract, IMessageInvocable {
/// @notice Initializes the contract.
/// @param _realOwner The real owner on L1 that can send a cross-chain message to invoke
/// `onMessageInvocation`.
/// @param _addressManager The address of the {AddressManager} contract.
/// @param _l1ChainId The L1 chain's ID.
/// @param _addressManager The address of the {AddressManager} contract.
/// @param _admin The admin address.
function init(
address _realOwner,
address _addressManager,
uint64 _l1ChainId
uint64 _l1ChainId,
address _admin
)
external
initializer
Expand All @@ -67,8 +77,9 @@ contract DelegateOwner is EssentialContract, IMessageInvocable {
revert DO_INVALID_PARAM();
}

realOwner = _realOwner;
l1ChainId = _l1ChainId;
realOwner = _realOwner;
admin = _admin;
}

/// @inheritdoc IMessageInvocable
Expand All @@ -86,11 +97,28 @@ contract DelegateOwner is EssentialContract, IMessageInvocable {
_invokeCall(_data, true);
}

/// @dev Invokes a call by the admin
/// @param _data The data for this contract to interpret.
function invokeCall(bytes calldata _data) external {
if (msg.sender != admin) revert DO_PERMISSION_DENIED();
_invokeCall(_data, true);
}

/// @dev Updates the admin address.
/// @param _admin The new admin address.
function setAdmin(address _admin) external {
if (msg.sender != owner() && msg.sender != admin) revert DO_PERMISSION_DENIED();
if (admin == _admin) revert DO_INVALID_PARAM();

emit AdminUpdated(admin, _admin);
admin = _admin;
}

/// @notice Dryruns a message invocation but always revert.
/// If this tx is reverted with DO_TRY_RUN_SUCCEEDED, the try run is successful.
/// Note that this function shall not be used in transaction and is designed for offchain
/// simulation only.
function dryrunMessageInvocation(bytes calldata _data) external payable {
function dryrunCall(bytes calldata _data) external payable {
_invokeCall(_data, false);
revert DO_DRYRUN_SUCCEEDED();
}
Expand Down
65 changes: 61 additions & 4 deletions packages/protocol/test/L2/DelegateOwner.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ contract TestDelegateOwner is TaikoTest {
name: "delegate_owner",
impl: address(new DelegateOwner()),
data: abi.encodeCall(
DelegateOwner.init, (remoteOwner, address(addressManager), remoteChainId)
DelegateOwner.init,
(remoteOwner, address(addressManager), remoteChainId, address(0))
),
registerTo: address(addressManager)
})
Expand Down Expand Up @@ -94,7 +95,7 @@ contract TestDelegateOwner is TaikoTest {
);

vm.expectRevert(DelegateOwner.DO_DRYRUN_SUCCEEDED.selector);
delegateOwner.dryrunMessageInvocation(data);
delegateOwner.dryrunCall(data);

IBridge.Message memory message;
message.from = remoteOwner;
Expand Down Expand Up @@ -127,7 +128,7 @@ contract TestDelegateOwner is TaikoTest {
);

vm.expectRevert(DelegateOwner.DO_DRYRUN_SUCCEEDED.selector);
delegateOwner.dryrunMessageInvocation(data);
delegateOwner.dryrunCall(data);

IBridge.Message memory message;
message.from = remoteOwner;
Expand Down Expand Up @@ -191,7 +192,7 @@ contract TestDelegateOwner is TaikoTest {
);

vm.expectRevert(DelegateOwner.DO_DRYRUN_SUCCEEDED.selector);
delegateOwner.dryrunMessageInvocation(data);
delegateOwner.dryrunCall(data);

IBridge.Message memory message;
message.from = remoteOwner;
Expand All @@ -212,4 +213,60 @@ contract TestDelegateOwner is TaikoTest {
assertEq(target2.impl(), impl2);
assertEq(delegateOwner.impl(), delegateOwnerImpl2);
}

function test_delegate_owner_update_admin() public {
assertEq(delegateOwner.admin(), address(0));
bytes memory data = abi.encode(
DelegateOwner.Call(
uint64(0),
address(delegateOwner),
false, // CALL
abi.encodeCall(DelegateOwner.setAdmin, (David))
)
);

vm.expectRevert(DelegateOwner.DO_DRYRUN_SUCCEEDED.selector);
delegateOwner.dryrunCall(data);

IBridge.Message memory message;
message.from = remoteOwner;
message.destChainId = uint64(block.chainid);
message.srcChainId = remoteChainId;
message.destOwner = Bob;
message.data = abi.encodeCall(DelegateOwner.onMessageInvocation, (data));
message.to = address(delegateOwner);

vm.prank(Bob);
bridge.processMessage(message, "");

bytes32 hash = bridge.hashMessage(message);
assertTrue(bridge.messageStatus(hash) == IBridge.Status.DONE);

assertEq(delegateOwner.nextTxId(), 1);
assertEq(delegateOwner.admin(), David);

// David is now the security council
data = abi.encode(
DelegateOwner.Call(
uint64(1),
address(delegateOwner),
false, // CALL
abi.encodeCall(DelegateOwner.setAdmin, (Emma))
)
);

vm.prank(Bob);
vm.expectRevert(DelegateOwner.DO_PERMISSION_DENIED.selector);
delegateOwner.invokeCall(data);

vm.prank(David);
delegateOwner.invokeCall(data);
assertEq(delegateOwner.nextTxId(), 2);
assertEq(delegateOwner.admin(), Emma);

vm.prank(Emma);
delegateOwner.setAdmin(Bob);
assertEq(delegateOwner.nextTxId(), 2);
assertEq(delegateOwner.admin(), Bob);
}
}
2 changes: 1 addition & 1 deletion packages/protocol/test/bridge/Bridge.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ contract BridgeTest is TaikoTest {
name: "delegate_owner",
impl: address(new DelegateOwner()),
data: abi.encodeCall(
DelegateOwner.init, (mockDAO, address(addressManager), l1ChainId)
DelegateOwner.init, (mockDAO, address(addressManager), l1ChainId, address(0))
)
})
)
Expand Down