Skip to content

Commit

Permalink
Merge pull request #29 from init4tech/anna/immut-admin
Browse files Browse the repository at this point in the history
feat: immutable admin structure
  • Loading branch information
anna-carroll authored May 17, 2024
2 parents 9d6e73f + baf7fc2 commit 7e2f876
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 38 deletions.
13 changes: 6 additions & 7 deletions .gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
HelpersTest:test_signature() (gas: 6587)
ZenithTest:test_badSequence() (gas: 77167)
ZenithTest:test_badSignature() (gas: 66732)
ZenithTest:test_blockExpired() (gas: 55352)
ZenithTest:test_notSequencer() (gas: 58463)
ZenithTest:test_onePerBlock() (gas: 123114)
ZenithTest:test_submitBlock() (gas: 90231)
ZenithTest:test_badSequence() (gas: 77339)
ZenithTest:test_badSignature() (gas: 66856)
ZenithTest:test_blockExpired() (gas: 55471)
ZenithTest:test_notSequencer() (gas: 58576)
ZenithTest:test_onePerBlock() (gas: 123118)
ZenithTest:test_submitBlock() (gas: 90078)
33 changes: 28 additions & 5 deletions script/Zenith.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,34 @@ pragma solidity ^0.8.24;
import {Script} from "forge-std/Script.sol";
import {Zenith} from "../src/Zenith.sol";

contract DeployZenith is Script {
contract ZenithScript is Script {
// deploy:
// forge script DeployZenith --sig "run()" --rpc-url $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY --private-key $PRIVATE_KEY --broadcast --verify
function run() public {
vm.broadcast();
new Zenith(block.chainid + 1, msg.sender);
// 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
function deploy(uint256 defaultRollupChainId, address withdrawalAdmin, address sequencerAdmin)
public
returns (Zenith z)
{
vm.startBroadcast();
z = new Zenith(defaultRollupChainId, withdrawalAdmin, sequencerAdmin);
// send some ETH to newly deployed Zenith to populate some rollup state
payable(address(z)).transfer(0.00123 ether);
}

// NOTE: script must be run using SequencerAdmin key
// set sequencer:
// forge script ZenithScript --sig "setSequencerRole(address,address)" --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast $ZENITH_ADDRESS $SEQUENCER_ADDRESS
function setSequencerRole(address payable z, address sequencer) public {
vm.startBroadcast();
Zenith zenith = Zenith(z);
zenith.addSequencer(sequencer);
}

// NOTE: script must be run using SequencerAdmin key
// revoke sequencer:
// forge script ZenithScript --sig "revokeSequencerRole(address,address)" --rpc-url $RPC_URL --private-key $PRIVATE_KEY --broadcast $ZENITH_ADDRESS $SEQUENCER_ADDRESS
function revokeSequencerRole(address payable z, address sequencer) public {
vm.startBroadcast();
Zenith zenith = Zenith(z);
zenith.removeSequencer(sequencer);
}
}
22 changes: 11 additions & 11 deletions src/Passage.sol
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

// import IERC20 from OpenZeppelin
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {AccessControlDefaultAdminRules} from
"openzeppelin-contracts/contracts/access/extensions/AccessControlDefaultAdminRules.sol";

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

/// @notice The address that is allowed to withdraw funds from the contract.
address public immutable withdrawalAdmin;

/// @notice Thrown when attempting to fulfill an exit order with a deadline that has passed.
error OrderExpired();

/// @notice Thrown when attempting to withdraw funds if not withdrawal admin.
error OnlyWithdrawalAdmin();

/// @notice Emitted when tokens enter the rollup.
/// @param token - The address of the token entering the rollup.
/// @param rollupRecipient - The recipient of the token on the rollup.
Expand Down Expand Up @@ -57,15 +60,11 @@ contract Passage is AccessControlDefaultAdminRules {
uint256 amount;
}

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

/// @notice Allows native Ether to enter the rollup by being sent directly to the contract.
Expand Down Expand Up @@ -139,7 +138,8 @@ contract Passage is AccessControlDefaultAdminRules {
/// @notice Allows the admin to withdraw tokens from the contract.
/// @dev Only the admin can call this function.
/// @param withdrawals - The withdrawals to process. See Withdrawal struct docs for details.
function withdraw(Withdrawal[] calldata withdrawals) external onlyRole(DEFAULT_ADMIN_ROLE) {
function withdraw(Withdrawal[] calldata withdrawals) external {
if (msg.sender != withdrawalAdmin) revert OnlyWithdrawalAdmin();
for (uint256 i = 0; i < withdrawals.length; i++) {
// transfer ether
if (withdrawals[i].ethAmount > 0) {
Expand Down
52 changes: 41 additions & 11 deletions src/Zenith.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.24;

// import openzeppelin Role contracts
import {Passage} from "./Passage.sol";

contract Zenith is Passage {
/// @notice The address that is allowed to set/remove sequencers.
address public immutable sequencerAdmin;

/// @notice Block header information for the rollup block, signed by the sequencer.
/// @param rollupChainId - the chainId of the rollup chain. Any chainId is accepted by the contract.
/// @param sequence - the sequence number of the rollup block. Must be monotonically increasing. Enforced by the contract.
Expand All @@ -19,9 +21,6 @@ contract Zenith is Passage {
address rewardAddress;
}

/// @notice Role that allows a key to sign commitments to rollup blocks.
bytes32 public constant SEQUENCER_ROLE = bytes32("SEQUENCER_ROLE");

/// @notice The sequence number of the next block that can be submitted for a given rollup chainId.
/// rollupChainId => nextSequence number
mapping(uint256 => uint256) public nextSequence;
Expand All @@ -30,6 +29,10 @@ contract Zenith is Passage {
/// rollupChainId => host blockNumber that block was last submitted at
mapping(uint256 => uint256) public lastSubmittedAtBlock;

/// @notice Registry of permissioned sequencers.
/// address => TRUE if it's a permissioned sequencer
mapping(address => bool) public isSequencer;

/// @notice Thrown when a block submission is attempted with a sequence number that is not the next block for the rollup chainId.
/// @dev Blocks must be submitted in strict monotonic increasing order.
/// @param expected - the correct next sequence number for the given rollup chainId.
Expand All @@ -46,6 +49,9 @@ contract Zenith is Passage {
/// @notice Thrown when attempting to submit more than one rollup block per host block
error OneRollupBlockPerHostBlock();

/// @notice Thrown when attempting to modify sequencer roles if not sequencerAdmin.
error OnlySequencerAdmin();

/// @notice Emitted when a new rollup block is successfully submitted.
/// @param sequencer - the address of the sequencer that signed the block.
/// @param rollupChainId - the chainId of the rollup chain.
Expand All @@ -67,12 +73,36 @@ contract Zenith is Passage {
/// @notice Emit the entire block data for easy visibility
event BlockData(bytes blockData);

/// @notice Initializes the Admin role.
/// @dev See `AccessControlDefaultAdminRules` for information on contract administration.
/// - Admin role can grant and revoke Sequencer roles.
/// - Admin role can be transferred via two-step process with a 1 day timelock.
/// @param admin - the address that will be the initial admin.
constructor(uint256 defaultRollupChainId, address admin) Passage(defaultRollupChainId, admin) {}
/// @notice Emitted when a sequencer is added or removed.
event SequencerSet(address indexed sequencer, bool indexed permissioned);

constructor(uint256 _defaultRollupChainId, address _withdrawalAdmin, address _sequencerAdmin)
Passage(_defaultRollupChainId, _withdrawalAdmin)
{
sequencerAdmin = _sequencerAdmin;
}

/// @notice Add a sequencer to the permissioned sequencer list.
/// @param sequencer - the address of the sequencer to add.
/// @custom:emits SequencerSet if the sequencer is added.
/// @custom:reverts OnlySequencerAdmin if the caller is not the sequencerAdmin.
function addSequencer(address sequencer) external {
if (msg.sender != sequencerAdmin) revert OnlySequencerAdmin();
if (isSequencer[sequencer]) return;
isSequencer[sequencer] = true;
emit SequencerSet(sequencer, true);
}

/// @notice Remove a sequencer from the permissioned sequencer list.
/// @param sequencer - the address of the sequencer to remove.
/// @custom:emits SequencerSet if the sequencer is removed.
/// @custom:reverts OnlySequencerAdmin if the caller is not the sequencerAdmin.
function removeSequencer(address sequencer) external {
if (msg.sender != sequencerAdmin) revert OnlySequencerAdmin();
if (!isSequencer[sequencer]) return;
delete isSequencer[sequencer];
emit SequencerSet(sequencer, false);
}

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

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

// assert this is the first rollup block submitted for this host block
if (lastSubmittedAtBlock[header.rollupChainId] == block.number) revert OneRollupBlockPerHostBlock();
Expand Down
6 changes: 4 additions & 2 deletions test/Helpers.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ contract HelpersTest is Test {

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

function test_signature() public {
function check_signature() public {
bytes32 hash = 0xdcd0af9a45fa82dcdd1e4f9ef703d8cd459b6950c0638154c67117e86facf9c1;
uint8 v = 28;
bytes32 r = 0xb89764d107f812dbbebb925711b320d336ff8d03f08570f051123df86334f3f5;
Expand Down
4 changes: 2 additions & 2 deletions test/Zenith.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ contract ZenithTest is Test {
);

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

// set default block values
header.rollupChainId = block.chainid + 1;
Expand Down

0 comments on commit 7e2f876

Please sign in to comment.