Skip to content

Commit 7e2f876

Browse files
authored
Merge pull request #29 from init4tech/anna/immut-admin
feat: immutable admin structure
2 parents 9d6e73f + baf7fc2 commit 7e2f876

File tree

6 files changed

+92
-38
lines changed

6 files changed

+92
-38
lines changed

.gas-snapshot

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
HelpersTest:test_signature() (gas: 6587)
2-
ZenithTest:test_badSequence() (gas: 77167)
3-
ZenithTest:test_badSignature() (gas: 66732)
4-
ZenithTest:test_blockExpired() (gas: 55352)
5-
ZenithTest:test_notSequencer() (gas: 58463)
6-
ZenithTest:test_onePerBlock() (gas: 123114)
7-
ZenithTest:test_submitBlock() (gas: 90231)
1+
ZenithTest:test_badSequence() (gas: 77339)
2+
ZenithTest:test_badSignature() (gas: 66856)
3+
ZenithTest:test_blockExpired() (gas: 55471)
4+
ZenithTest:test_notSequencer() (gas: 58576)
5+
ZenithTest:test_onePerBlock() (gas: 123118)
6+
ZenithTest:test_submitBlock() (gas: 90078)

script/Zenith.s.sol

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,34 @@ pragma solidity ^0.8.24;
44
import {Script} from "forge-std/Script.sol";
55
import {Zenith} from "../src/Zenith.sol";
66

7-
contract DeployZenith is Script {
7+
contract ZenithScript is Script {
88
// deploy:
9-
// forge script DeployZenith --sig "run()" --rpc-url $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY --private-key $PRIVATE_KEY --broadcast --verify
10-
function run() public {
11-
vm.broadcast();
12-
new Zenith(block.chainid + 1, msg.sender);
9+
// forge script ZenithScript --sig "deploy(uint256,address,address)" --rpc-url $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY --private-key $PRIVATE_KEY --broadcast --verify $ROLLUP_CHAIN_ID $WITHDRAWAL_ADMIN_ADDRESS $SEQUENCER_ADMIN_ADDRESS
10+
function deploy(uint256 defaultRollupChainId, address withdrawalAdmin, address sequencerAdmin)
11+
public
12+
returns (Zenith z)
13+
{
14+
vm.startBroadcast();
15+
z = new Zenith(defaultRollupChainId, withdrawalAdmin, sequencerAdmin);
16+
// send some ETH to newly deployed Zenith to populate some rollup state
17+
payable(address(z)).transfer(0.00123 ether);
18+
}
19+
20+
// NOTE: script must be run using SequencerAdmin key
21+
// set sequencer:
22+
// forge script ZenithScript --sig "setSequencerRole(address,address)" --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast $ZENITH_ADDRESS $SEQUENCER_ADDRESS
23+
function setSequencerRole(address payable z, address sequencer) public {
24+
vm.startBroadcast();
25+
Zenith zenith = Zenith(z);
26+
zenith.addSequencer(sequencer);
27+
}
28+
29+
// NOTE: script must be run using SequencerAdmin key
30+
// revoke sequencer:
31+
// forge script ZenithScript --sig "revokeSequencerRole(address,address)" --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast $ZENITH_ADDRESS $SEQUENCER_ADDRESS
32+
function revokeSequencerRole(address payable z, address sequencer) public {
33+
vm.startBroadcast();
34+
Zenith zenith = Zenith(z);
35+
zenith.removeSequencer(sequencer);
1336
}
1437
}

src/Passage.sol

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
// SPDX-License-Identifier: UNLICENSED
22
pragma solidity ^0.8.24;
33

4-
// import IERC20 from OpenZeppelin
54
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
6-
import {AccessControlDefaultAdminRules} from
7-
"openzeppelin-contracts/contracts/access/extensions/AccessControlDefaultAdminRules.sol";
85

96
/// @notice A contract deployed to Host chain that allows tokens to enter the rollup,
107
/// and enables Builders to fulfill requests to exchange tokens on the Rollup for tokens on the Host.
11-
contract Passage is AccessControlDefaultAdminRules {
8+
contract Passage {
129
/// @notice The chainId of rollup that Ether will be sent to by default when entering the rollup via fallback() or receive().
1310
uint256 immutable defaultRollupChainId;
1411

12+
/// @notice The address that is allowed to withdraw funds from the contract.
13+
address public immutable withdrawalAdmin;
14+
1515
/// @notice Thrown when attempting to fulfill an exit order with a deadline that has passed.
1616
error OrderExpired();
1717

18+
/// @notice Thrown when attempting to withdraw funds if not withdrawal admin.
19+
error OnlyWithdrawalAdmin();
20+
1821
/// @notice Emitted when tokens enter the rollup.
1922
/// @param token - The address of the token entering the rollup.
2023
/// @param rollupRecipient - The recipient of the token on the rollup.
@@ -57,15 +60,11 @@ contract Passage is AccessControlDefaultAdminRules {
5760
uint256 amount;
5861
}
5962

60-
/// @notice Initializes the Admin role.
61-
/// @dev See `AccessControlDefaultAdminRules` for information on contract administration.
62-
/// - Admin role can grant and revoke Sequencer roles.
63-
/// - Admin role can be transferred via two-step process with a 1 day timelock.
6463
/// @param _defaultRollupChainId - the chainId of the rollup that Ether will be sent to by default
6564
/// when entering the rollup via fallback() or receive() fns.
66-
/// @param admin - the address that will be the initial admin.
67-
constructor(uint256 _defaultRollupChainId, address admin) AccessControlDefaultAdminRules(1 days, admin) {
65+
constructor(uint256 _defaultRollupChainId, address _withdrawalAdmin) {
6866
defaultRollupChainId = _defaultRollupChainId;
67+
withdrawalAdmin = _withdrawalAdmin;
6968
}
7069

7170
/// @notice Allows native Ether to enter the rollup by being sent directly to the contract.
@@ -139,7 +138,8 @@ contract Passage is AccessControlDefaultAdminRules {
139138
/// @notice Allows the admin to withdraw tokens from the contract.
140139
/// @dev Only the admin can call this function.
141140
/// @param withdrawals - The withdrawals to process. See Withdrawal struct docs for details.
142-
function withdraw(Withdrawal[] calldata withdrawals) external onlyRole(DEFAULT_ADMIN_ROLE) {
141+
function withdraw(Withdrawal[] calldata withdrawals) external {
142+
if (msg.sender != withdrawalAdmin) revert OnlyWithdrawalAdmin();
143143
for (uint256 i = 0; i < withdrawals.length; i++) {
144144
// transfer ether
145145
if (withdrawals[i].ethAmount > 0) {

src/Zenith.sol

Lines changed: 41 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
// SPDX-License-Identifier: UNLICENSED
22
pragma solidity ^0.8.24;
33

4-
// import openzeppelin Role contracts
54
import {Passage} from "./Passage.sol";
65

76
contract Zenith is Passage {
7+
/// @notice The address that is allowed to set/remove sequencers.
8+
address public immutable sequencerAdmin;
9+
810
/// @notice Block header information for the rollup block, signed by the sequencer.
911
/// @param rollupChainId - the chainId of the rollup chain. Any chainId is accepted by the contract.
1012
/// @param sequence - the sequence number of the rollup block. Must be monotonically increasing. Enforced by the contract.
@@ -19,9 +21,6 @@ contract Zenith is Passage {
1921
address rewardAddress;
2022
}
2123

22-
/// @notice Role that allows a key to sign commitments to rollup blocks.
23-
bytes32 public constant SEQUENCER_ROLE = bytes32("SEQUENCER_ROLE");
24-
2524
/// @notice The sequence number of the next block that can be submitted for a given rollup chainId.
2625
/// rollupChainId => nextSequence number
2726
mapping(uint256 => uint256) public nextSequence;
@@ -30,6 +29,10 @@ contract Zenith is Passage {
3029
/// rollupChainId => host blockNumber that block was last submitted at
3130
mapping(uint256 => uint256) public lastSubmittedAtBlock;
3231

32+
/// @notice Registry of permissioned sequencers.
33+
/// address => TRUE if it's a permissioned sequencer
34+
mapping(address => bool) public isSequencer;
35+
3336
/// @notice Thrown when a block submission is attempted with a sequence number that is not the next block for the rollup chainId.
3437
/// @dev Blocks must be submitted in strict monotonic increasing order.
3538
/// @param expected - the correct next sequence number for the given rollup chainId.
@@ -46,6 +49,9 @@ contract Zenith is Passage {
4649
/// @notice Thrown when attempting to submit more than one rollup block per host block
4750
error OneRollupBlockPerHostBlock();
4851

52+
/// @notice Thrown when attempting to modify sequencer roles if not sequencerAdmin.
53+
error OnlySequencerAdmin();
54+
4955
/// @notice Emitted when a new rollup block is successfully submitted.
5056
/// @param sequencer - the address of the sequencer that signed the block.
5157
/// @param rollupChainId - the chainId of the rollup chain.
@@ -67,12 +73,36 @@ contract Zenith is Passage {
6773
/// @notice Emit the entire block data for easy visibility
6874
event BlockData(bytes blockData);
6975

70-
/// @notice Initializes the Admin role.
71-
/// @dev See `AccessControlDefaultAdminRules` for information on contract administration.
72-
/// - Admin role can grant and revoke Sequencer roles.
73-
/// - Admin role can be transferred via two-step process with a 1 day timelock.
74-
/// @param admin - the address that will be the initial admin.
75-
constructor(uint256 defaultRollupChainId, address admin) Passage(defaultRollupChainId, admin) {}
76+
/// @notice Emitted when a sequencer is added or removed.
77+
event SequencerSet(address indexed sequencer, bool indexed permissioned);
78+
79+
constructor(uint256 _defaultRollupChainId, address _withdrawalAdmin, address _sequencerAdmin)
80+
Passage(_defaultRollupChainId, _withdrawalAdmin)
81+
{
82+
sequencerAdmin = _sequencerAdmin;
83+
}
84+
85+
/// @notice Add a sequencer to the permissioned sequencer list.
86+
/// @param sequencer - the address of the sequencer to add.
87+
/// @custom:emits SequencerSet if the sequencer is added.
88+
/// @custom:reverts OnlySequencerAdmin if the caller is not the sequencerAdmin.
89+
function addSequencer(address sequencer) external {
90+
if (msg.sender != sequencerAdmin) revert OnlySequencerAdmin();
91+
if (isSequencer[sequencer]) return;
92+
isSequencer[sequencer] = true;
93+
emit SequencerSet(sequencer, true);
94+
}
95+
96+
/// @notice Remove a sequencer from the permissioned sequencer list.
97+
/// @param sequencer - the address of the sequencer to remove.
98+
/// @custom:emits SequencerSet if the sequencer is removed.
99+
/// @custom:reverts OnlySequencerAdmin if the caller is not the sequencerAdmin.
100+
function removeSequencer(address sequencer) external {
101+
if (msg.sender != sequencerAdmin) revert OnlySequencerAdmin();
102+
if (!isSequencer[sequencer]) return;
103+
delete isSequencer[sequencer];
104+
emit SequencerSet(sequencer, false);
105+
}
76106

77107
/// @notice Submit a rollup block with block data submitted via calldata.
78108
/// @dev Blocks are submitted by Builders, with an attestation to the block data signed by a Sequencer.
@@ -115,7 +145,7 @@ contract Zenith is Passage {
115145
address sequencer = ecrecover(blockCommit, v, r, s);
116146

117147
// assert that signature is valid && sequencer is permissioned
118-
if (sequencer == address(0) || !hasRole(SEQUENCER_ROLE, sequencer)) revert BadSignature(sequencer);
148+
if (sequencer == address(0) || !isSequencer[sequencer]) revert BadSignature(sequencer);
119149

120150
// assert this is the first rollup block submitted for this host block
121151
if (lastSubmittedAtBlock[header.rollupChainId] == block.number) revert OneRollupBlockPerHostBlock();

test/Helpers.t.sol

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ contract HelpersTest is Test {
99

1010
function setUp() public {
1111
vm.createSelectFork("https://rpc.holesky.ethpandaops.io");
12-
target = new Zenith(block.chainid + 1, 0x0a53e650c6f015eF70a15Da7B18fa95F051465aB);
12+
target = new Zenith(
13+
block.chainid + 1, 0x11Aa4EBFbf7a481617c719a2Df028c9DA1a219aa, 0x29403F107781ea45Bf93710abf8df13F67f2008f
14+
);
1315
}
1416

15-
function test_signature() public {
17+
function check_signature() public {
1618
bytes32 hash = 0xdcd0af9a45fa82dcdd1e4f9ef703d8cd459b6950c0638154c67117e86facf9c1;
1719
uint8 v = 28;
1820
bytes32 r = 0xb89764d107f812dbbebb925711b320d336ff8d03f08570f051123df86334f3f5;

test/Zenith.t.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ contract ZenithTest is Test {
2727
);
2828

2929
function setUp() public {
30-
target = new Zenith(block.chainid + 1, address(this));
31-
target.grantRole(target.SEQUENCER_ROLE(), vm.addr(sequencerKey));
30+
target = new Zenith(block.chainid + 1, address(this), address(this));
31+
target.addSequencer(vm.addr(sequencerKey));
3232

3333
// set default block values
3434
header.rollupChainId = block.chainid + 1;

0 commit comments

Comments
 (0)