// Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.0; import "./IRollupAdmin.sol"; import "./IRollupLogic.sol"; import "./RollupCore.sol"; import "../bridge/IOutbox.sol"; import "../bridge/ISequencerInbox.sol"; import "../challenge/IChallengeManager.sol"; import "../libraries/DoubleLogicUUPSUpgradeable.sol"; import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol"; import {NO_CHAL_INDEX} from "../libraries/Constants.sol"; contract RollupAdminLogic is RollupCore, IRollupAdmin, DoubleLogicUUPSUpgradeable { function initialize(Config calldata config, ContractDependencies calldata connectedContracts) external override onlyProxy initializer { rollupDeploymentBlock = block.number; bridge = connectedContracts.bridge; sequencerInbox = connectedContracts.sequencerInbox; connectedContracts.bridge.setDelayedInbox(address(connectedContracts.inbox), true); connectedContracts.bridge.setSequencerInbox(address(connectedContracts.sequencerInbox)); inbox = connectedContracts.inbox; outbox = connectedContracts.outbox; connectedContracts.bridge.setOutbox(address(connectedContracts.outbox), true); rollupEventInbox = connectedContracts.rollupEventInbox; connectedContracts.bridge.setDelayedInbox( address(connectedContracts.rollupEventInbox), true ); connectedContracts.rollupEventInbox.rollupInitialized(config.chainId, config.chainConfig); connectedContracts.sequencerInbox.addSequencerL2Batch( 0, "", 1, IGasRefunder(address(0)), 0, 1 ); validatorUtils = connectedContracts.validatorUtils; validatorWalletCreator = connectedContracts.validatorWalletCreator; challengeManager = connectedContracts.challengeManager; Node memory node = createInitialNode(); initializeCore(node); confirmPeriodBlocks = config.confirmPeriodBlocks; extraChallengeTimeBlocks = config.extraChallengeTimeBlocks; chainId = config.chainId; baseStake = config.baseStake; wasmModuleRoot = config.wasmModuleRoot; // A little over 15 minutes minimumAssertionPeriod = 75; // the owner can't access the rollup user facet where escrow is redeemable require(config.loserStakeEscrow != _getAdmin(), "INVALID_ESCROW_ADMIN"); // this next check shouldn't be an issue if the owner controls an AdminProxy // that accesses the admin facet, but still seems like a good extra precaution require(config.loserStakeEscrow != config.owner, "INVALID_ESCROW_OWNER"); loserStakeEscrow = config.loserStakeEscrow; stakeToken = config.stakeToken; emit RollupInitialized(config.wasmModuleRoot, config.chainId); } function createInitialNode() private view returns (Node memory) { GlobalState memory emptyGlobalState; bytes32 state = RollupLib.stateHashMem( ExecutionState(emptyGlobalState, MachineStatus.FINISHED), 1 // inboxMaxCount - force the first assertion to read a message ); return NodeLib.createNode( state, 0, // challenge hash (not challengeable) 0, // confirm data 0, // prev node uint64(block.number), // deadline block (not challengeable) 0 // initial node has a node hash of 0 ); } /** * Functions are only to reach this logic contract if the caller is the owner * so there is no need for a redundant onlyOwner check */ /** * @notice Add a contract authorized to put messages into this rollup's inbox * @param _outbox Outbox contract to add */ function setOutbox(IOutbox _outbox) external override { outbox = _outbox; bridge.setOutbox(address(_outbox), true); emit OwnerFunctionCalled(0); } /** * @notice Disable an old outbox from interacting with the bridge * @param _outbox Outbox contract to remove */ function removeOldOutbox(address _outbox) external override { require(_outbox != address(outbox), "CUR_OUTBOX"); bridge.setOutbox(_outbox, false); emit OwnerFunctionCalled(1); } /** * @notice Enable or disable an inbox contract * @param _inbox Inbox contract to add or remove * @param _enabled New status of inbox */ function setDelayedInbox(address _inbox, bool _enabled) external override { bridge.setDelayedInbox(address(_inbox), _enabled); emit OwnerFunctionCalled(2); } /** * @notice Pause interaction with the rollup contract. * The time spent paused is not incremented in the rollup's timing for node validation. * @dev this function may be frontrun by a validator (ie to create a node before the system is paused). * The pause should be called atomically with required checks to be sure the system is paused in a consistent state. * The RollupAdmin may execute a check against the Rollup's latest node num or the ChallengeManager, then execute this function atomically with it. */ function pause() external override { _pause(); emit OwnerFunctionCalled(3); } /** * @notice Resume interaction with the rollup contract */ function resume() external override { _unpause(); emit OwnerFunctionCalled(4); } /// @notice allows the admin to upgrade the primary logic contract (ie rollup admin logic, aka this) /// @dev this function doesn't revert as this primary logic contract is only /// reachable by the proxy's admin function _authorizeUpgrade(address newImplementation) internal override {} /// @notice allows the admin to upgrade the secondary logic contract (ie rollup user logic) /// @dev this function doesn't revert as this primary logic contract is only /// reachable by the proxy's admin function _authorizeSecondaryUpgrade(address newImplementation) internal override {} /** * @notice Set the addresses of the validator whitelist * @dev It is expected that both arrays are same length, and validator at * position i corresponds to the value at position i * @param _validator addresses to set in the whitelist * @param _val value to set in the whitelist for corresponding address */ function setValidator(address[] calldata _validator, bool[] calldata _val) external override { require(_validator.length > 0, "EMPTY_ARRAY"); require(_validator.length == _val.length, "WRONG_LENGTH"); for (uint256 i = 0; i < _validator.length; i++) { isValidator[_validator[i]] = _val[i]; } emit OwnerFunctionCalled(6); } /** * @notice Set a new owner address for the rollup * @dev it is expected that only the rollup admin can use this facet to set a new owner * @param newOwner address of new rollup owner */ function setOwner(address newOwner) external override { _changeAdmin(newOwner); emit OwnerFunctionCalled(7); } /** * @notice Set minimum assertion period for the rollup * @param newPeriod new minimum period for assertions */ function setMinimumAssertionPeriod(uint256 newPeriod) external override { minimumAssertionPeriod = newPeriod; emit OwnerFunctionCalled(8); } /** * @notice Set number of blocks until a node is considered confirmed * @param newConfirmPeriod new number of blocks */ function setConfirmPeriodBlocks(uint64 newConfirmPeriod) external override { require(newConfirmPeriod > 0, "INVALID_CONFIRM_PERIOD"); confirmPeriodBlocks = newConfirmPeriod; emit OwnerFunctionCalled(9); } /** * @notice Set number of extra blocks after a challenge * @param newExtraTimeBlocks new number of blocks */ function setExtraChallengeTimeBlocks(uint64 newExtraTimeBlocks) external override { extraChallengeTimeBlocks = newExtraTimeBlocks; emit OwnerFunctionCalled(10); } /** * @notice Set base stake required for an assertion * @param newBaseStake minimum amount of stake required */ function setBaseStake(uint256 newBaseStake) external override { baseStake = newBaseStake; emit OwnerFunctionCalled(12); } /** * @notice Set the token used for stake, where address(0) == eth * @dev Before changing the base stake token, you might need to change the * implementation of the Rollup User facet! * @param newStakeToken address of token used for staking */ function setStakeToken(address newStakeToken) external override whenPaused { /* * To change the stake token without breaking consistency one would need to: * Pause the system, have all stakers remove their funds, * update the user logic to handle ERC20s, change the stake token, then resume. * * Note: To avoid loss of funds stakers must remove their funds and claim all the * available withdrawable funds before the system is paused. */ bool expectERC20Support = newStakeToken != address(0); // this assumes the rollup isn't its own admin. if needed, instead use a ProxyAdmin by OZ! bool actualERC20Support = IRollupUser(address(this)).isERC20Enabled(); require(actualERC20Support == expectERC20Support, "NO_USER_LOGIC_SUPPORT"); require(stakerCount() == 0, "NO_ACTIVE_STAKERS"); require(totalWithdrawableFunds == 0, "NO_PENDING_WITHDRAW"); stakeToken = newStakeToken; emit OwnerFunctionCalled(13); } /** * @notice Upgrades the implementation of a beacon controlled by the rollup * @param beacon address of beacon to be upgraded * @param newImplementation new address of implementation */ function upgradeBeacon(address beacon, address newImplementation) external override { UpgradeableBeacon(beacon).upgradeTo(newImplementation); emit OwnerFunctionCalled(20); } function forceResolveChallenge(address[] calldata stakerA, address[] calldata stakerB) external override whenPaused { require(stakerA.length > 0, "EMPTY_ARRAY"); require(stakerA.length == stakerB.length, "WRONG_LENGTH"); for (uint256 i = 0; i < stakerA.length; i++) { uint64 chall = inChallenge(stakerA[i], stakerB[i]); require(chall != NO_CHAL_INDEX, "NOT_IN_CHALL"); clearChallenge(stakerA[i]); clearChallenge(stakerB[i]); challengeManager.clearChallenge(chall); } emit OwnerFunctionCalled(21); } function forceRefundStaker(address[] calldata staker) external override whenPaused { require(staker.length > 0, "EMPTY_ARRAY"); for (uint256 i = 0; i < staker.length; i++) { require(_stakerMap[staker[i]].currentChallenge == NO_CHAL_INDEX, "STAKER_IN_CHALL"); reduceStakeTo(staker[i], 0); turnIntoZombie(staker[i]); } emit OwnerFunctionCalled(22); } function forceCreateNode( uint64 prevNode, uint256 prevNodeInboxMaxCount, Assertion calldata assertion, bytes32 expectedNodeHash ) external override whenPaused { require(prevNode == latestConfirmed(), "ONLY_LATEST_CONFIRMED"); createNewNode(assertion, prevNode, prevNodeInboxMaxCount, expectedNodeHash); emit OwnerFunctionCalled(23); } function forceConfirmNode( uint64 nodeNum, bytes32 blockHash, bytes32 sendRoot ) external override whenPaused { // this skips deadline, staker and zombie validation confirmNode(nodeNum, blockHash, sendRoot); emit OwnerFunctionCalled(24); } function setLoserStakeEscrow(address newLoserStakerEscrow) external override { // escrow holder can't be proxy admin, since escrow is only redeemable through // the primary user logic contract require(newLoserStakerEscrow != _getAdmin(), "INVALID_ESCROW"); loserStakeEscrow = newLoserStakerEscrow; emit OwnerFunctionCalled(25); } /** * @notice Set the proving WASM module root * @param newWasmModuleRoot new module root */ function setWasmModuleRoot(bytes32 newWasmModuleRoot) external override { wasmModuleRoot = newWasmModuleRoot; emit OwnerFunctionCalled(26); } /** * @notice set a new sequencer inbox contract * @param _sequencerInbox new address of sequencer inbox */ function setSequencerInbox(address _sequencerInbox) external override { bridge.setSequencerInbox(_sequencerInbox); emit OwnerFunctionCalled(27); } /** * @notice sets the rollup's inbox reference. Does not update the bridge's view. * @param newInbox new address of inbox */ function setInbox(IInboxBase newInbox) external { inbox = newInbox; emit OwnerFunctionCalled(28); } function createNitroMigrationGenesis(Assertion calldata assertion) external whenPaused { bytes32 expectedSendRoot = bytes32(0); uint64 expectedInboxCount = 1; require(latestNodeCreated() == 0, "NON_GENESIS_NODES_EXIST"); require(GlobalStateLib.isEmpty(assertion.beforeState.globalState), "NOT_EMPTY_BEFORE"); require( assertion.beforeState.machineStatus == MachineStatus.FINISHED, "BEFORE_MACHINE_NOT_FINISHED" ); // accessors such as state.getSendRoot not available for calldata structs, only memory require( assertion.afterState.globalState.bytes32Vals[1] == expectedSendRoot, "NOT_ZERO_SENDROOT" ); require( assertion.afterState.globalState.u64Vals[0] == expectedInboxCount, "INBOX_NOT_AT_ONE" ); require(assertion.afterState.globalState.u64Vals[1] == 0, "POSITION_IN_MESSAGE_NOT_ZERO"); require( assertion.afterState.machineStatus == MachineStatus.FINISHED, "AFTER_MACHINE_NOT_FINISHED" ); bytes32 genesisBlockHash = assertion.afterState.globalState.bytes32Vals[0]; createNewNode(assertion, 0, expectedInboxCount, bytes32(0)); confirmNode(1, genesisBlockHash, expectedSendRoot); emit OwnerFunctionCalled(29); } /** * @notice set the validatorWhitelistDisabled flag * @param _validatorWhitelistDisabled new value of validatorWhitelistDisabled, i.e. true = disabled */ function setValidatorWhitelistDisabled(bool _validatorWhitelistDisabled) external { validatorWhitelistDisabled = _validatorWhitelistDisabled; emit OwnerFunctionCalled(30); } }