Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"contracts/parent-chain/espresso-migration/EspressoSequencerInboxMigrationAction.sol": {
"content": "// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.16;
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "nitro-contracts/bridge/ISequencerInbox.sol";
import "nitro-contracts/bridge/SequencerInbox.sol";
import "nitro-contracts/bridge/IBridge.sol";
import "nitro-contracts/bridge/IInbox.sol";
import "nitro-contracts/bridge/IOutbox.sol";
import "nitro-contracts/rollup/IRollupAdmin.sol";
import "nitro-contracts/rollup/IRollupLogic.sol";
/// @notice Upgrades an Arbitrum orbit chain to use a SequencerInbox contract compatible with Espresso
/// @dev _newSequencerInboxImpl: This is the address of the SequencerInbox implementation to point rollup's upgradeable proxy to.
/// @dev _rollup: the address of the rollup to be migrated to the new SequencerInbox
/// @dev _proxyAdminAddr: the address of the proxyAdmin for the rollup being migrated to the new SequencerInbox
/// enable espresso confirmations at the end of the migration
/// @dev Modified from
/// https://github.com/ArbitrumFoundation/governance/blob/a5375eea133e1b88df2116ed510ab2e3c07293d3/src/gov-action-contracts/AIPs/ArbOS20/ArbOS20Action.sol
contract EspressoSequencerInboxMigrationAction {
address public immutable newSequencerInboxImpl;
address public immutable rollup;
address public immutable proxyAdminAddr;
address public immutable espressoTEEVerifier;
address public immutable oldBatchPosterAddr;
address public immutable newBatchPosterAddr;
address public immutable batchPosterManager;
bool public immutable isRevert;
error AddressIsNotContract(address incorrectAddr);
error OldBatchPosterMustNotBeZeroAddr();
error NewBatchPosterMustNotBeZeroAddr();
error MaxTimeVariationNotSet();
error SequencerInboxNotUpgraded(address oldSequencerInboxAddr);
error espressoTEEVerifierNotSet();
constructor(
address _newSequencerInboxImpl,
address _rollup,
address _proxyAdminAddr,
address _espressoTEEVerifier,
address _oldBatchPosterAddr,
address _newBatchPosterAddr,
address _batchPosterManager,
bool _isRevert
) {
// If the new impl addresses are contracts, we need to revert
if (!Address.isContract(_newSequencerInboxImpl)) {
revert AddressIsNotContract(_newSequencerInboxImpl);
}
if (!Address.isContract(_rollup)) {
revert AddressIsNotContract(_rollup);
}
if (!Address.isContract(_proxyAdminAddr)) {
revert AddressIsNotContract(_proxyAdminAddr);
}
if (!Address.isContract(_espressoTEEVerifier)) {
revert AddressIsNotContract(_espressoTEEVerifier);
}
if (_oldBatchPosterAddr == address(0x0)) {
revert OldBatchPosterMustNotBeZeroAddr();
}
if (_newBatchPosterAddr == address(0x0)) {
revert NewBatchPosterMustNotBeZeroAddr();
}
newSequencerInboxImpl = _newSequencerInboxImpl;
rollup = _rollup;
proxyAdminAddr = _proxyAdminAddr;
espressoTEEVerifier = _espressoTEEVerifier;
oldBatchPosterAddr = _oldBatchPosterAddr;
newBatchPosterAddr = _newBatchPosterAddr;
batchPosterManager = _batchPosterManager;
isRevert = _isRevert;
}
function perform() public {
// set up contracts we need to interact with.
IRollupCore rollupCore = IRollupCore(rollup);
ProxyAdmin proxyAdmin = ProxyAdmin(proxyAdminAddr);
TransparentUpgradeableProxy sequencerInbox =
TransparentUpgradeableProxy(payable(address(rollupCore.sequencerInbox())));
// migrate the rollup to the new sequencer inbox
proxyAdmin.upgrade(sequencerInbox, newSequencerInboxImpl);
address proxyImpl = proxyAdmin.getProxyImplementation(sequencerInbox);
// if the proxy implementation hasn't been updated, we need to revert.
if (proxyImpl != newSequencerInboxImpl) {
revert SequencerInboxNotUpgraded(proxyImpl);
}
SequencerInbox proxyInbox = SequencerInbox(address(rollupCore.sequencerInbox()));
// Set the TEE verifier address
if (!isRevert) {
proxyInbox.setEspressoTEEVerifier(espressoTEEVerifier);
}
// Remove the permissions for the old batch poster addresses
proxyInbox.setIsBatchPoster(oldBatchPosterAddr, false);
// Whitelist the new batch posters address to enable it to post batches
proxyInbox.setIsBatchPoster(newBatchPosterAddr, true);
// Set the batch poster manager.
if (batchPosterManager != address(0x0)) {
proxyInbox.setBatchPosterManager(batchPosterManager);
}
address proxyTEEVerifierAddr = address(proxyInbox.espressoTEEVerifier());
if (!isRevert && (proxyTEEVerifierAddr != espressoTEEVerifier)) {
revert espressoTEEVerifierNotSet();
}
}
}
"
},
"node_modules/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/transparent/ProxyAdmin.sol)
pragma solidity ^0.8.0;
import "./TransparentUpgradeableProxy.sol";
import "../../access/Ownable.sol";
/**
* @dev This is an auxiliary contract meant to be assigned as the admin of a {TransparentUpgradeableProxy}. For an
* explanation of why you would want to use this see the documentation for {TransparentUpgradeableProxy}.
*/
contract ProxyAdmin is Ownable {
/**
* @dev Returns the current implementation of `proxy`.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
*/
function getProxyImplementation(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
// We need to manually run the static call since the getter cannot be flagged as view
// bytes4(keccak256("implementation()")) == 0x5c60da1b
(bool success, bytes memory returndata) = address(proxy).staticcall(hex"5c60da1b");
require(success);
return abi.decode(returndata, (address));
}
/**
* @dev Returns the current admin of `proxy`.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
*/
function getProxyAdmin(TransparentUpgradeableProxy proxy) public view virtual returns (address) {
// We need to manually run the static call since the getter cannot be flagged as view
// bytes4(keccak256("admin()")) == 0xf851a440
(bool success, bytes memory returndata) = address(proxy).staticcall(hex"f851a440");
require(success);
return abi.decode(returndata, (address));
}
/**
* @dev Changes the admin of `proxy` to `newAdmin`.
*
* Requirements:
*
* - This contract must be the current admin of `proxy`.
*/
function changeProxyAdmin(TransparentUpgradeableProxy proxy, address newAdmin) public virtual onlyOwner {
proxy.changeAdmin(newAdmin);
}
/**
* @dev Upgrades `proxy` to `implementation`. See {TransparentUpgradeableProxy-upgradeTo}.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
*/
function upgrade(TransparentUpgradeableProxy proxy, address implementation) public virtual onlyOwner {
proxy.upgradeTo(implementation);
}
/**
* @dev Upgrades `proxy` to `implementation` and calls a function on the new implementation. See
* {TransparentUpgradeableProxy-upgradeToAndCall}.
*
* Requirements:
*
* - This contract must be the admin of `proxy`.
*/
function upgradeAndCall(
TransparentUpgradeableProxy proxy,
address implementation,
bytes memory data
) public payable virtual onlyOwner {
proxy.upgradeToAndCall{value: msg.value}(implementation, data);
}
}
"
},
"node_modules/@openzeppelin/contracts/utils/Address.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCall(target, data, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
require(isContract(target), "Address: call to non-contract");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
require(isContract(target), "Address: static call to non-contract");
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
require(isContract(target), "Address: delegate call to non-contract");
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResult(success, returndata, errorMessage);
}
/**
* @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
}
"
},
"lib/nitro-contracts/src/bridge/ISequencerInbox.sol": {
"content": "// 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
// solhint-disable-next-line compiler-version
pragma solidity >=0.6.9 <0.9.0;
pragma experimental ABIEncoderV2;
import "../libraries/IGasRefunder.sol";
import "./IDelayedMessageProvider.sol";
import "./IBridge.sol";
interface ISequencerInbox is IDelayedMessageProvider {
struct MaxTimeVariation {
uint256 delayBlocks;
uint256 futureBlocks;
uint256 delaySeconds;
uint256 futureSeconds;
}
event SequencerBatchDelivered(
uint256 indexed batchSequenceNumber,
bytes32 indexed beforeAcc,
bytes32 indexed afterAcc,
bytes32 delayedAcc,
uint256 afterDelayedMessagesRead,
IBridge.TimeBounds timeBounds,
IBridge.BatchDataLocation dataLocation
);
event OwnerFunctionCalled(uint256 indexed id);
/// @dev a separate event that emits batch data when this isn't easily accessible in the tx.input
event SequencerBatchData(uint256 indexed batchSequenceNumber, bytes data);
/// @dev a valid keyset was added
event SetValidKeyset(bytes32 indexed keysetHash, bytes keysetBytes);
/// @dev a keyset was invalidated
event InvalidateKeyset(bytes32 indexed keysetHash);
/// @dev Signature from a registered ephemeral key generated inside TEE was verified over the batch data hash
event TEESignatureVerified(uint256 indexed sequenceNumber, uint256 indexed hotshotHeight);
function totalDelayedMessagesRead() external view returns (uint256);
function bridge() external view returns (IBridge);
/// @dev The size of the batch header
// solhint-disable-next-line func-name-mixedcase
function HEADER_LENGTH() external view returns (uint256);
/// @dev If the first batch data byte after the header has this bit set,
/// the sequencer inbox has authenticated the data. Currently only used for 4844 blob support.
/// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go
// solhint-disable-next-line func-name-mixedcase
function DATA_AUTHENTICATED_FLAG() external view returns (bytes1);
/// @dev If the first data byte after the header has this bit set,
/// then the batch data is to be found in 4844 data blobs
/// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go
// solhint-disable-next-line func-name-mixedcase
function DATA_BLOB_HEADER_FLAG() external view returns (bytes1);
/// @dev If the first data byte after the header has this bit set,
/// then the batch data is a das message
/// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go
// solhint-disable-next-line func-name-mixedcase
function DAS_MESSAGE_HEADER_FLAG() external view returns (bytes1);
/// @dev If the first data byte after the header has this bit set,
/// then the batch data is a das message that employs a merklesization strategy
/// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go
// solhint-disable-next-line func-name-mixedcase
function TREE_DAS_MESSAGE_HEADER_FLAG() external view returns (bytes1);
/// @dev If the first data byte after the header has this bit set,
/// then the batch data has been brotli compressed
/// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go
// solhint-disable-next-line func-name-mixedcase
function BROTLI_MESSAGE_HEADER_FLAG() external view returns (bytes1);
/// @dev If the first data byte after the header has this bit set,
/// then the batch data uses a zero heavy encoding
/// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go
// solhint-disable-next-line func-name-mixedcase
function ZERO_HEAVY_MESSAGE_HEADER_FLAG() external view returns (bytes1);
function rollup() external view returns (IOwnable);
function isBatchPoster(address) external view returns (bool);
function isSequencer(address) external view returns (bool);
function maxDataSize() external view returns (uint256);
/// @notice The batch poster manager has the ability to change the batch poster addresses
/// This enables the batch poster to do key rotation
function batchPosterManager() external view returns (address);
struct DasKeySetInfo {
bool isValidKeyset;
uint64 creationBlock;
}
/// @dev returns 4 uint256 to be compatible with older version
function maxTimeVariation()
external
view
returns (
uint256 delayBlocks,
uint256 futureBlocks,
uint256 delaySeconds,
uint256 futureSeconds
);
function dasKeySetInfo(bytes32) external view returns (bool, uint64);
/// @notice Remove force inclusion delay after a L1 chainId fork
function removeDelayAfterFork() external;
/// @notice Force messages from the delayed inbox to be included in the chain
/// Callable by any address, but message can only be force-included after maxTimeVariation.delayBlocks and
/// maxTimeVariation.delaySeconds has elapsed. As part of normal behaviour the sequencer will include these
/// messages so it's only necessary to call this if the sequencer is down, or not including any delayed messages.
/// @param _totalDelayedMessagesRead The total number of messages to read up to
/// @param kind The kind of the last message to be included
/// @param l1BlockAndTime The l1 block and the l1 timestamp of the last message to be included
/// @param baseFeeL1 The l1 gas price of the last message to be included
/// @param sender The sender of the last message to be included
/// @param messageDataHash The messageDataHash of the last message to be included
function forceInclusion(
uint256 _totalDelayedMessagesRead,
uint8 kind,
uint64[2] calldata l1BlockAndTime,
uint256 baseFeeL1,
address sender,
bytes32 messageDataHash
) external;
function inboxAccs(uint256 index) external view returns (bytes32);
function batchCount() external view returns (uint256);
function isValidKeysetHash(bytes32 ksHash) external view returns (bool);
/// @notice the creation block is intended to still be available after a keyset is deleted
function getKeysetCreationBlock(bytes32 ksHash) external view returns (uint256);
// ---------- BatchPoster functions ----------
function addSequencerL2BatchFromOrigin(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder
) external;
function addSequencerL2BatchFromOrigin(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount
) external;
function addSequencerL2BatchFromOrigin(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount,
bytes memory espressoMetadata
) external;
function addSequencerL2Batch(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount
) external;
function addSequencerL2Batch(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount,
bytes memory espressoMetadata
) external;
function addSequencerL2BatchFromBlobs(
uint256 sequenceNumber,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount
) external;
function addSequencerL2BatchFromBlobs(
uint256 sequenceNumber,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount,
bytes memory espressoMetadata
) external;
// ---------- onlyRollupOrOwner functions ----------
/**
* @notice Set max delay for sequencer inbox
* @param maxTimeVariation_ the maximum time variation parameters
*/
function setMaxTimeVariation(MaxTimeVariation memory maxTimeVariation_) external;
/**
* @notice Updates whether an address is authorized to be a batch poster at the sequencer inbox
* @param addr the address
* @param isBatchPoster_ if the specified address should be authorized as a batch poster
*/
function setIsBatchPoster(address addr, bool isBatchPoster_) external;
/**
* @notice Makes Data Availability Service keyset valid
* @param keysetBytes bytes of the serialized keyset
*/
function setValidKeyset(bytes calldata keysetBytes) external;
/**
* @notice Invalidates a Data Availability Service keyset
* @param ksHash hash of the keyset
*/
function invalidateKeysetHash(bytes32 ksHash) external;
/**
* @notice Updates whether an address is authorized to be a sequencer.
* @dev The IsSequencer information is used only off-chain by the nitro node to validate sequencer feed signer.
* @param addr the address
* @param isSequencer_ if the specified address should be authorized as a sequencer
*/
function setIsSequencer(address addr, bool isSequencer_) external;
/**
* @notice Updates the batch poster manager, the address which has the ability to rotate batch poster keys
* @param newBatchPosterManager The new batch poster manager to be set
*/
function setBatchPosterManager(address newBatchPosterManager) external;
/// @notice Allows the rollup owner to sync the rollup address
function updateRollupAddress() external;
// ---------- initializer ----------
function initialize(IBridge bridge_, MaxTimeVariation calldata maxTimeVariation_) external;
function initialize(
IBridge bridge_,
MaxTimeVariation calldata maxTimeVariation_,
address _espressoTEEVerifier
) external;
}
"
},
"lib/nitro-contracts/src/bridge/SequencerInbox.sol": {
"content": "// 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 {
AlreadyInit,
HadZeroInit,
BadPostUpgradeInit,
NotOrigin,
DataTooLarge,
DelayedBackwards,
DelayedTooFar,
ForceIncludeBlockTooSoon,
ForceIncludeTimeTooSoon,
IncorrectMessagePreimage,
NotBatchPoster,
BadSequencerNumber,
AlreadyValidDASKeyset,
NoSuchKeyset,
NotForked,
NotBatchPosterManager,
NotCodelessOrigin,
RollupNotChanged,
DataBlobsNotSupported,
InitParamZero,
MissingDataHashes,
NotOwner,
InvalidHeaderFlag,
NativeTokenMismatch,
BadMaxTimeVariation,
Deprecated
} from "../libraries/Error.sol";
import "./IBridge.sol";
import "./IInboxBase.sol";
import "./ISequencerInbox.sol";
import "../rollup/IRollupLogic.sol";
import "./Messages.sol";
import "../precompiles/ArbGasInfo.sol";
import "../precompiles/ArbSys.sol";
import "../libraries/CallerChecker.sol";
import "../libraries/IReader4844.sol";
import {L1MessageType_batchPostingReport} from "../libraries/MessageTypes.sol";
import "../libraries/DelegateCallAware.sol";
import {IGasRefunder} from "../libraries/IGasRefunder.sol";
import {GasRefundEnabled} from "../libraries/GasRefundEnabled.sol";
import "../libraries/ArbitrumChecker.sol";
import {IERC20Bridge} from "./IERC20Bridge.sol";
import {IEspressoTEEVerifier} from "espresso-tee-contracts/interface/IEspressoTEEVerifier.sol";
/**
* @title Accepts batches from the sequencer and adds them to the rollup inbox.
* @notice Contains the inbox accumulator which is the ordering of all data and transactions to be processed by the rollup.
* As part of submitting a batch the sequencer is also expected to include items enqueued
* in the delayed inbox (Bridge.sol). If items in the delayed inbox are not included by a
* sequencer within a time limit they can be force included into the rollup inbox by anyone.
*/
contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox {
uint256 public totalDelayedMessagesRead;
IBridge public bridge;
/// @inheritdoc ISequencerInbox
uint256 public constant HEADER_LENGTH = 40;
/// @inheritdoc ISequencerInbox
bytes1 public constant DATA_AUTHENTICATED_FLAG = 0x40;
/// @inheritdoc ISequencerInbox
bytes1 public constant DATA_BLOB_HEADER_FLAG = DATA_AUTHENTICATED_FLAG | 0x10;
/// @inheritdoc ISequencerInbox
bytes1 public constant DAS_MESSAGE_HEADER_FLAG = 0x80;
/// @inheritdoc ISequencerInbox
bytes1 public constant TREE_DAS_MESSAGE_HEADER_FLAG = 0x08;
/// @inheritdoc ISequencerInbox
bytes1 public constant BROTLI_MESSAGE_HEADER_FLAG = 0x00;
/// @inheritdoc ISequencerInbox
bytes1 public constant ZERO_HEAVY_MESSAGE_HEADER_FLAG = 0x20;
// GAS_PER_BLOB from EIP-4844
uint256 internal constant GAS_PER_BLOB = 1 << 17;
IOwnable public rollup;
mapping(address => bool) public isBatchPoster;
// we previously stored the max time variation in a (uint,uint,uint,uint) struct here
// solhint-disable-next-line var-name-mixedcase
ISequencerInbox.MaxTimeVariation private __LEGACY_MAX_TIME_VARIATION;
mapping(bytes32 => DasKeySetInfo) public dasKeySetInfo;
modifier onlyRollupOwner() {
if (msg.sender != rollup.owner()) revert NotOwner(msg.sender, rollup.owner());
_;
}
modifier onlyRollupOwnerOrBatchPosterManager() {
if (msg.sender != rollup.owner() && msg.sender != batchPosterManager) {
revert NotBatchPosterManager(msg.sender);
}
_;
}
mapping(address => bool) public isSequencer;
IReader4844 public immutable reader4844;
// see ISequencerInbox.MaxTimeVariation
uint64 internal delayBlocks;
uint64 internal futureBlocks;
uint64 internal delaySeconds;
uint64 internal futureSeconds;
/// @inheritdoc ISequencerInbox
address public batchPosterManager;
// On L1 this should be set to 117964: 90% of Geth's 128KB tx size limit, leaving ~13KB for proving
uint256 public immutable maxDataSize;
uint256 internal immutable deployTimeChainId = block.chainid;
// If the chain this SequencerInbox is deployed on is an Arbitrum chain.
bool internal immutable hostChainIsArbitrum = ArbitrumChecker.runningOnArbitrum();
// True if the chain this SequencerInbox is deployed on uses custom fee token
bool public immutable isUsingFeeToken;
IEspressoTEEVerifier public espressoTEEVerifier;
constructor(uint256 _maxDataSize, IReader4844 reader4844_, bool _isUsingFeeToken) {
maxDataSize = _maxDataSize;
if (hostChainIsArbitrum) {
if (reader4844_ != IReader4844(address(0))) revert DataBlobsNotSupported();
} else {
if (reader4844_ == IReader4844(address(0))) revert InitParamZero("Reader4844");
}
reader4844 = reader4844_;
isUsingFeeToken = _isUsingFeeToken;
}
function _chainIdChanged() internal view returns (bool) {
return deployTimeChainId != block.chainid;
}
function postUpgradeInit() external onlyDelegated onlyProxyOwner {
// Assuming we would not upgrade from a version that have MaxTimeVariation all set to zero
// If that is the case, postUpgradeInit do not need to be called
if (
__LEGACY_MAX_TIME_VARIATION.delayBlocks == 0 &&
__LEGACY_MAX_TIME_VARIATION.futureBlocks == 0 &&
__LEGACY_MAX_TIME_VARIATION.delaySeconds == 0 &&
__LEGACY_MAX_TIME_VARIATION.futureSeconds == 0
) {
revert AlreadyInit();
}
if (
__LEGACY_MAX_TIME_VARIATION.delayBlocks > type(uint64).max ||
__LEGACY_MAX_TIME_VARIATION.futureBlocks > type(uint64).max ||
__LEGACY_MAX_TIME_VARIATION.delaySeconds > type(uint64).max ||
__LEGACY_MAX_TIME_VARIATION.futureSeconds > type(uint64).max
) {
revert BadPostUpgradeInit();
}
delayBlocks = uint64(__LEGACY_MAX_TIME_VARIATION.delayBlocks);
futureBlocks = uint64(__LEGACY_MAX_TIME_VARIATION.futureBlocks);
delaySeconds = uint64(__LEGACY_MAX_TIME_VARIATION.delaySeconds);
futureSeconds = uint64(__LEGACY_MAX_TIME_VARIATION.futureSeconds);
__LEGACY_MAX_TIME_VARIATION.delayBlocks = 0;
__LEGACY_MAX_TIME_VARIATION.futureBlocks = 0;
__LEGACY_MAX_TIME_VARIATION.delaySeconds = 0;
__LEGACY_MAX_TIME_VARIATION.futureSeconds = 0;
}
/**
Deprecated because we created another `initialize` function that accepts the `EspressoTEEVerifier` contract
address as a parameter which is used by the `SequencerInbox` contract to verify the TEE attestation quote.
*/
function initialize(
IBridge bridge_,
ISequencerInbox.MaxTimeVariation calldata maxTimeVariation_
) external onlyDelegated {
revert Deprecated();
}
function initialize(
IBridge bridge_,
ISequencerInbox.MaxTimeVariation calldata maxTimeVariation_,
address _espressoTEEVerifier
) external onlyDelegated {
if (bridge != IBridge(address(0))) revert AlreadyInit();
if (bridge_ == IBridge(address(0))) revert HadZeroInit();
// Make sure logic contract was created by proper value for 'isUsingFeeToken'.
// Bridge in ETH based chains doesn't implement nativeToken(). In future it might implement it and return address(0)
bool actualIsUsingFeeToken = false;
try IERC20Bridge(address(bridge_)).nativeToken() returns (address feeToken) {
if (feeToken != address(0)) {
actualIsUsingFeeToken = true;
}
} catch {}
if (isUsingFeeToken != actualIsUsingFeeToken) {
revert NativeTokenMismatch();
}
bridge = bridge_;
rollup = bridge_.rollup();
_setMaxTimeVariation(maxTimeVariation_);
espressoTEEVerifier = IEspressoTEEVerifier(_espressoTEEVerifier);
}
/// @notice Allows the rollup owner to sync the rollup address
function updateRollupAddress() external {
if (msg.sender != IOwnable(rollup).owner())
revert NotOwner(msg.sender, IOwnable(rollup).owner());
IOwnable newRollup = bridge.rollup();
if (rollup == newRollup) revert RollupNotChanged();
rollup = newRollup;
}
function getTimeBounds() internal view virtual returns (IBridge.TimeBounds memory) {
IBridge.TimeBounds memory bounds;
(
uint64 delayBlocks_,
uint64 futureBlocks_,
uint64 delaySeconds_,
uint64 futureSeconds_
) = maxTimeVariationInternal();
if (block.timestamp > delaySeconds_) {
bounds.minTimestamp = uint64(block.timestamp) - delaySeconds_;
}
bounds.maxTimestamp = uint64(block.timestamp) + futureSeconds_;
if (block.number > delayBlocks_) {
bounds.minBlockNumber = uint64(block.number) - delayBlocks_;
}
bounds.maxBlockNumber = uint64(block.number) + futureBlocks_;
return bounds;
}
/// @inheritdoc ISequencerInbox
function removeDelayAfterFork() external {
if (!_chainIdChanged()) revert NotForked();
delayBlocks = 1;
futureBlocks = 1;
delaySeconds = 1;
futureSeconds = 1;
}
function maxTimeVariation() external view returns (uint256, uint256, uint256, uint256) {
(
uint64 delayBlocks_,
uint64 futureBlocks_,
uint64 delaySeconds_,
uint64 futureSeconds_
) = maxTimeVariationInternal();
return (
uint256(delayBlocks_),
uint256(futureBlocks_),
uint256(delaySeconds_),
uint256(futureSeconds_)
);
}
function maxTimeVariationInternal() internal view returns (uint64, uint64, uint64, uint64) {
if (_chainIdChanged()) {
return (1, 1, 1, 1);
} else {
return (delayBlocks, futureBlocks, delaySeconds, futureSeconds);
}
}
/// @inheritdoc ISequencerInbox
function forceInclusion(
uint256 _totalDelayedMessagesRead,
uint8 kind,
uint64[2] calldata l1BlockAndTime,
uint256 baseFeeL1,
address sender,
bytes32 messageDataHash
) external {
if (_totalDelayedMessagesRead <= totalDelayedMessagesRead) revert DelayedBackwards();
bytes32 messageHash = Messages.messageHash(
kind,
sender,
l1BlockAndTime[0],
l1BlockAndTime[1],
_totalDelayedMessagesRead - 1,
baseFeeL1,
messageDataHash
);
// Can only force-include after the Sequencer-only window has expired.
if (l1BlockAndTime[0] + delayBlocks >= block.number) revert ForceIncludeBlockTooSoon();
if (l1BlockAndTime[1] + delaySeconds >= block.timestamp) revert ForceIncludeTimeTooSoon();
// Verify that message hash represents the last message sequence of delayed message to be included
bytes32 prevDelayedAcc = 0;
if (_totalDelayedMessagesRead > 1) {
prevDelayedAcc = bridge.delayedInboxAccs(_totalDelayedMessagesRead - 2);
}
if (
bridge.delayedInboxAccs(_totalDelayedMessagesRead - 1) !=
Messages.accumulateInboxMessage(prevDelayedAcc, messageHash)
) revert IncorrectMessagePreimage();
(bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formEmptyDataHash(
_totalDelayedMessagesRead
);
uint256 __totalDelayedMessagesRead = _totalDelayedMessagesRead;
uint256 prevSeqMsgCount = bridge.sequencerReportedSubMessageCount();
uint256 newSeqMsgCount = prevSeqMsgCount; // force inclusion should not modify sequencer message count
(
uint256 seqMessageIndex,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 afterAcc
) = addSequencerL2BatchImpl(
dataHash,
__totalDelayedMessagesRead,
0,
prevSeqMsgCount,
newSeqMsgCount
);
emit SequencerBatchDelivered(
seqMessageIndex,
beforeAcc,
afterAcc,
delayedAcc,
totalDelayedMessagesRead,
timeBounds,
IBridge.BatchDataLocation.NoData
);
}
/// @dev Deprecated, kept for abi generation and will be removed in the future
function addSequencerL2BatchFromOrigin(
uint256 sequencerNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder
) external pure {
revert Deprecated();
}
/**
Deprecated because we added a new method with TEE attestation quote
to verify that the batch is posted by the batch poster running in TEE.
*/
function addSequencerL2BatchFromOrigin(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount
) external refundsGas(gasRefunder, IReader4844(address(0))) {
revert Deprecated();
}
function addSequencerL2BatchFromOrigin(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount,
bytes memory espressoMetadata
) external refundsGas(gasRefunder, IReader4844(address(0))) {
if (!CallerChecker.isCallerCodelessOrigin()) revert NotCodelessOrigin();
if (!isBatchPoster[msg.sender]) revert NotBatchPoster();
// Verification
_verifyAttestation(
sequenceNumber,
data,
afterDelayedMessagesRead,
gasRefunder,
prevMessageCount,
newMessageCount,
espressoMetadata
);
(bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formCallDataHash(
data,
afterDelayedMessagesRead
);
// Reformat the stack to prevent "Stack too deep"
uint256 sequenceNumber_ = sequenceNumber;
IBridge.TimeBounds memory timeBounds_ = timeBounds;
bytes32 dataHash_ = dataHash;
uint256 dataLength = data.length;
uint256 afterDelayedMessagesRead_ = afterDelayedMessagesRead;
uint256 prevMessageCount_ = prevMessageCount;
uint256 newMessageCount_ = newMessageCount;
(
uint256 seqMessageIndex,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 afterAcc
) = addSequencerL2BatchImpl(
dataHash_,
afterDelayedMessagesRead_,
dataLength,
prevMessageCount_,
newMessageCount_
);
// ~uint256(0) is type(uint256).max, but ever so slightly cheaper
if (seqMessageIndex != sequenceNumber_ && sequenceNumber_ != ~uint256(0)) {
revert BadSequencerNumber(seqMessageIndex, sequenceNumber_);
}
emit SequencerBatchDelivered(
seqMessageIndex,
beforeAcc,
afterAcc,
delayedAcc,
totalDelayedMessagesRead,
timeBounds_,
IBridge.BatchDataLocation.TxInput
);
}
function _verifyAttestation(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount,
bytes memory espressoMetadata
) private {
(uint256 hotshotHeight, bytes memory signature, IEspressoTEEVerifier.TeeType teeType) = abi.decode(
espressoMetadata,
(uint256, bytes, IEspressoTEEVerifier.TeeType)
);
bytes32 reportDataHash = keccak256(
abi.encode(
sequenceNumber,
data,
afterDelayedMessagesRead,
address(gasRefunder),
prevMessageCount,
newMessageCount,
hotshotHeight
)
);
// verify the the reportDataHash was signed by the a registered ephemeral key
// generated inside a registered TEE
espressoTEEVerifier.verify(signature, reportDataHash, teeType);
// signature from a registered ephemeral key generated inside TEE
// was verified over the batch data hash
emit TEESignatureVerified(sequenceNumber, hotshotHeight);
}
function addSequencerL2BatchFromBlobs(
uint256 sequenceNumber,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount
) external refundsGas(gasRefunder, reader4844) {
revert Deprecated();
}
function addSequencerL2BatchFromBlobs(
uint256 sequenceNumber,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount,
bytes memory espressoMetadata
) external refundsGas(gasRefunder, reader4844) {
if (!isBatchPoster[msg.sender]) revert NotBatchPoster();
bytes32[] memory dataHashes = reader4844.getDataHashes();
// Verification logic extracted
_verifyBlobQuote(
sequenceNumber,
afterDelayedMessagesRead,
gasRefunder,
prevMessageCount,
newMessageCount,
espressoMetadata
);
(
bytes32 dataHash,
IBridge.TimeBounds memory timeBounds,
uint256 blobGas
) = formBlobDataHash(afterDelayedMessagesRead);
// Reformat the stack to prevent "Stack too deep"
uint256 sequenceNumber_ = sequenceNumber;
bytes32 dataHash_ = dataHash;
uint256 afterDelayedMessagesRead_ = afterDelayedMessagesRead;
uint256 prevMessageCount_ = prevMessageCount;
uint256 newMessageCount_ = newMessageCount;
IBridge.TimeBounds memory timeBounds_ = timeBounds;
// we use addSequencerL2BatchImpl for submitting the message
// normally this would also submit a batch spending report but that is skipped if we pass
// an empty call data size, then we submit a separate batch spending report later
(
uint256 seqMessageIndex,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 afterAcc
) = addSequencerL2BatchImpl(
dataHash_,
afterDelayedMessagesRead_,
0,
prevMessageCount_,
newMessageCount_
);
// ~uint256(0) is type(uint256).max, but ever so slightly cheaper
if (seqMessageIndex != sequenceNumber_ && sequenceNumber_ != ~uint256(0)) {
revert BadSequencerNumber(seqMessageIndex, sequenceNumber_);
}
emit SequencerBatchDelivered(
sequenceNumber_,
beforeAcc,
afterAcc,
delayedAcc,
totalDelayedMessagesRead,
timeBounds_,
IBridge.BatchDataLocation.Blob
);
// blobs are currently not supported on host arbitrum chains, when support is added it may
// consume gas in a different way to L1, so explicitly block host arb chains so that if support for blobs
// on arb is added it will need to explicitly turned on in the sequencer inbox
if (hostChainIsArbitrum) revert DataBlobsNotSupported();
// submit a batch spending report to refund the entity that produced the blob batch data
// same as using calldata, we only submit spending report if the caller is the origin and is codeless
// such that one cannot "double-claim" batch posting refund in the same tx
if (CallerChecker.isCallerCodelessOrigin() && !isUsingFeeToken) {
submitBatchSpendingReport(dataHash, seqMessageIndex, block.basefee, blobGas);
}
}
function _verifyBlobQuote(
uint256 sequenceNumber,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount,
bytes memory espressoMetadata
) private {
bytes32[] memory dataHashes = reader4844.getDataHashes();
if (dataHashes.length == 0) revert MissingDataHashes();
(uint256 hotshotHeight, bytes memory signature, IEspressoTEEVerifier.TeeType teeType) = abi.decode(
espressoMetadata,
(uint256, bytes, IEspressoTEEVerifier.TeeType)
);
// take keccak2256 hash of all the function arguments and encode packed blob hashes
// except the quote
bytes32 reportDataHash = keccak256(
abi.encode(
sequenceNumber,
afterDelayedMessagesRead,
address(gasRefunder),
prevMessageCount,
newMessageCount,
abi.encode(dataHashes),
hotshotHeight
)
);
// verify the signature over data hash for the batch poster running in the TEE
espressoTEEVerifier.verify(signature, reportDataHash, teeType);
emit TEESignatureVerified(sequenceNumber, hotshotHeight);
}
/**
Deprecated because we added a new method with TEE attestation quote
to verify that the batch is posted by the batch poster running in TEE.
*/
function addSequencerL2Batch(
uint256,
bytes calldata,
uint256,
IGasRefunder gasRefunder,
uint256,
uint256
) external override refundsGas(gasRefunder, IReader4844(address(0))) {
revert Deprecated();
}
/*
* addSequencerL2Batch is called by either the rollup admin or batch poster
* running in TEE to add a new batch
* @param sequenceNumber - the sequence number of the batch
* @param data - the data of the batch
* @param afterDelayedMessagesRead - the number of delayed messages read by the sequencer
* @param gasRefunder - the gas refunder contract
* @param prevMessageCount - the number of messages in the previous batch
* @param newMessageCount - the number of messages in the new batch
* @param espressoMetadata - the signature, the hotshot height, and TeeType
*/
function addSequencerL2Batch(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder,
uint256 prevMessageCount,
uint256 newMessageCount,
bytes memory espressoMetadata
) external override refundsGas(gasRefunder, IReader4844(address(0))) {
if (!isBatchPoster[msg.sender] && msg.sender != address(rollup)) revert NotBatchPoster();
// Only check the attestation quote if the batch has been posted by the
// batch poster
if (isBatchPoster[msg.sender]) {
(uint256 hotshotHeight, bytes memory signature, IEspressoTEEVerifier.TeeType teeType) = abi.decode(
espressoMetadata,
(uint256, bytes, IEspressoTEEVerifier.TeeType)
);
// take keccak2256 hash of all the function arguments
// along with the hotshot height
bytes32 reportDataHash = keccak256(
abi.encode(
sequenceNumber,
data,
afterDelayedMessagesRead,
address(gasRefunder),
prevMessageCount,
newMessageCount,
hotshotHeight
)
);
espressoTEEVerifier.verify(signature, reportDataHash, teeType);
// signature from a registered ephemeral key generated inside a registered TEE
// was verified over the batch data hash
emit TEESignatureVerified(sequenceNumber, hotshotHeight);
}
(bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formCallDataHash(
data,
afterDelayedMessagesRead
);
uint256 seqMessageIndex;
{
// Reformat the stack to prevent "Stack too deep"
uint256 sequenceNumber_ = sequenceNumber;
IBridge.TimeBounds memory timeBounds_ = timeBounds;
bytes32 dataHash_ = dataHash;
uint256 afterDelayedMessagesRead_ = afterDelayedMessagesRead;
uint256 prevMessageCount_ = prevMessageCount;
uint256 newMessageCount_ = newMessageCount;
// we set the calldata length posted to 0 here since the caller isn't the origin
// of the tx, so they might have not paid tx input cost for the calldata
bytes32 beforeAcc;
bytes32 delayedAcc;
bytes32 afterAcc;
(seqMessageIndex, beforeAcc, delayedAcc, afterAcc) = addSequencerL2BatchImpl(
dataHash_,
afterDelayedMessagesRead_,
0,
prevMessageCount_,
newMessageCount_
);
// ~uint256(0) is type(uint256).max, but ever so slightly cheaper
if (seqMessageIndex != sequenceNumber_ && sequenceNumber_ != ~uint256(0)) {
revert BadSequencerNumber(seqMessageIndex, sequenceNumber_);
}
emit SequencerBatchDelivered(
seqMessageIndex,
beforeAcc,
afterAcc,
delayedAcc,
totalDelayedMessagesRead,
timeBounds_,
IBridge.BatchDataLocation.SeparateBatchEvent
);
}
emit SequencerBatchData(seqMessageIndex, data);
}
function packHeader(
uint256 afterDelayedMessagesRead
) internal view returns (bytes memory, IBridge.TimeBounds memory) {
IBridge.TimeBounds memory timeBounds = getTimeBounds();
bytes memory header = abi.encodePacked(
timeBounds.minTimestamp,
timeBounds.maxTimestamp,
timeBounds.minBlockNumber,
timeBounds.maxBlockNumber,
uint64(afterDelayedMessagesRead)
);
// This must always be true from the packed encoding
assert(header.length == HEADER_LENGTH);
return (header, timeBounds);
}
/// @dev Form a hash for a sequencer message with no batch data
/// @param afterDelayedMessagesRead The delayed messages count read up to
/// @return The data hash
/// @return The timebounds within which the message should be processed
function formEmptyDataHash(
uint256 afterDelayedMessagesRead
) internal view returns (bytes32, IBridge.TimeBounds memory) {
(bytes memory header, IBridge.TimeBounds memory timeBounds) = packHeader(
afterDelayedMessagesRead
);
return (keccak256(header), timeBounds);
}
/// @dev Since the data is supplied from calldata, the batch poster can choose the data type
/// We need to ensure that this data cannot cause a collision with data supplied via another method (eg blobs)
/// therefore we restrict which flags can be provided as a header in this field
/// This also safe guards unused flags for future use, as we know they would have been disallowed up until this point
/// @param headerByte The first byte in the calldata
function isValidCallDataFlag(bytes1 headerByte) internal pure returns (bool) {
return
headerByte == BROTLI_MESSAGE_HEADER_FLAG ||
headerByte == DAS_MESSAGE_HEADER_FLAG ||
(headerByte == (DAS_MESSAGE_HEADER_FLAG | TREE_DAS_MESSAGE_HEADER_FLAG)) ||
headerByte == ZERO_HEAVY_MESSAGE_HEADER_FLAG;
}
/// @dev Form a hash of the data taken from the calldata
/// @param data The calldata to be hashed
/// @param afterDelayedMessagesRead The delayed messages count read up to
/// @return The data hash
/// @return The timebounds within which the message should be processed
function formCallDataHash(
bytes calldata data,
uint256 afterDelayedMessagesRead
) internal view returns (bytes32, IBridge.TimeBounds memory) {
uint256 fullDataLen = HEADER_LENGTH + data.length;
if (fullDataLen > maxDataSize) revert DataTooLarge(fullDataLen, maxDataSize);
(bytes memory header, IBridge.TimeBounds memory timeBounds) = packHeader(
afterDelayedMessagesRead
);
// the batch poster is allowed to submit an empty batch, they can use this to progress the
// delayed inbox without providing extra batch data
if (data.length > 0) {
// The first data byte cannot be the same as any that have been set via other methods (eg 4844 blob header) as this
// would allow the supplier of the data to spoof an incorrect 4844 data batch
if (!isValidCallDataFlag(data[0])) revert InvalidHeaderFlag(data[0]);
// the first byte is used to identify the type of batch data
// das batches expect to have the type byte set, followed by the keyset (so they should have at least 33 bytes)
// if invalid data is supplied here the state transition function will process it as an empty block
// however we can provide a nice additional check here for the batch poster
if (data[0] & DAS_MESSAGE_HEADER_FLAG != 0 && data.length >= 33) {
// we skip the first byte, then read the next 32 bytes for the keyset
bytes32 dasKeysetHash = bytes32(data[1:33]);
if (!dasKeySetInfo[dasKeysetHash].isValidKeyset) revert NoSuchKeyset(dasKeysetHash);
}
}
return (keccak256(bytes.concat(header, data)), timeBounds);
}
/// @dev Form a hash of the data being provided in 4844 data blobs
/// @param afterDelayedMessagesRead The delayed messages count read up to
/// @return The data hash
/// @return The timebounds within which the message should be processed
/// @return The normalized amount of gas used for blob posting
function formBlobDataHash(
uint256 afterDelayedMessagesRead
) internal view returns (bytes32, IBridge.TimeBounds memory, uint256) {
bytes32[] memory dataHashes = reader4844.getDataHashes();
if (dataHashes.length == 0) revert MissingDataHashes();
(bytes memory header, IBridge.TimeBounds memory timeBounds) = packHeader(
afterDelayedMessagesRead
);
uint256 blobCost = reader4844.getBlobBaseFee() * GAS_PER_BLOB * dataHashes.length;
return (
keccak256(bytes.concat(header, DATA_BLOB_HEADER_FLAG, abi.encodePacked(dataHashes))),
timeBounds,
block.basefee > 0 ? blobCost / block.basefee : 0
);
}
/// @dev Submit a batch spending report message so that the batch poster can be reimbursed on the rollup
/// This function expect msg.sender is tx.origin, and will always record tx.origin as the spender
/// @param dataHash The hash of the message the spending report is being submitted for
/// @param seqMessageIndex The index of the message to submit the spending report for
/// @param gasPrice The gas price that was paid for the data (standard gas or data gas)
function submitBatchSpendingReport(
bytes32 dataHash,
uint256 seqMessageIndex,
uint256 gasPrice,
uint256 extraGas
) internal {
// report the account who paid the gas (tx.origin) for the tx as batch poster
// if msg.sender is used and is a contract, it might not be able to spend the refund on l2
// solhint-disable-next-line avoid-tx-origin
address batchPoster = tx.origin;
// this msg isn't included in the current sequencer batch, but instead added to
// the delayed messages queue that is yet to be included
if (hostChainIsArbitrum) {
// Include extra gas for the host chain's L1 gas charging
uint256 l1Fees = ArbGasInfo(address(0x6c)).getCurrentTxL1GasFees();
extraGas += l1Fees / block.basefee;
}
require(extraGas <= type(uint64).max, "EXTRA_GAS_NOT_UINT64");
bytes memory spendingReportMsg = abi.encodePacked(
block.timestamp,
batchPoster,
dataHash,
seqMessageIndex,
gasPrice,
uint64(extraGas)
);
uint256 msgNum = bridge.submitBatchSpendingReport(
batchPoster,
keccak256(spendingReportMsg)
);
// this is the same event used by Inbox.sol after including a message to the delayed message accumulator
emit InboxMessageDelivered(msgNum, spendingReportMsg);
}
function addSequencerL2BatchImpl(
bytes32 dataHash,
uint256 afterDelayedMessagesRead,
uint256 calldataLengthPosted,
uint256 prevMessageCount,
uint256 newMessageCount
)
internal
returns (uint256 seqMessageIndex, bytes32 beforeAcc, bytes32 delayedAcc, bytes32 acc)
{
if (afterDelayedMessagesRead < totalDelayedMessagesRead) revert DelayedBackwards();
if (afterDelayedMessagesRead > bridge.delayedMessageCount()) revert DelayedTooFar();
(seqMessageIndex, beforeAcc, delayedAcc, acc) = bridge.enqueueSequencerMessage(
dataHash,
afterDelayedMessagesRead,
prevMessageCount,
newMessageCount
);
totalDelayedMessagesRead = afterDelayedMessagesRead;
if (calldataLengthPosted > 0 && !isUsingFeeToken) {
// only report batch poster spendings if chain is using ETH as native currency
submitBatchSpendingReport(dataHash, seqMessageIndex, block.basefee, 0);
}
}
function inboxAccs(uint256 index) external view returns (bytes32) {
return bridge.sequencerInboxAccs(index);
}
function batchCount() external view returns (uint256) {
return bridge.sequencerMessageCount();
}
function _setMaxTimeVariation(
ISequencerInbox.MaxTimeVariation memory maxTimeVariation_
) internal {
if (
maxTimeVariation_.delayBlocks > type(uint64).max ||
maxTimeVariation_.futureBlocks > type(uint64).max ||
maxTimeVariation_.delaySeconds > type(uint64).max ||
maxTimeVariation_.futureSeconds > type(uint64).max
) {
revert BadMaxTimeVariation();
}
delayBlocks = uint64(maxTimeVariation_.delayBlocks);
futureBlocks = uint64(maxTimeVariation_.futureBlocks);
delaySeconds = uint64(maxTimeVariation_.delaySeconds);
futureSeconds = uint64(maxTimeVariation_.futureSeconds);
}
/// @inheritdoc ISequencerInbox
function setMaxTimeVariation(
ISequencerInbox.MaxTimeVariation memory maxTimeVariation_
) external onlyRollupOwner {
_setMaxTimeVariation(maxTimeVariation_);
emit OwnerFunctionCalled(0);
}
/// @inheritdoc ISequencerInbox
function setIsBatchPoster(
address addr,
bool isBatchPoster_
) external onlyRollupOwnerOrBatchPosterManager {
isBatchPoster[addr] = isBatchPoster_;
emit OwnerFunctionCalled(1);
}
/// @inheritdoc ISequencerInbox
function setValidKeyset(bytes calldata keysetBytes) external onlyRollupOwner {
uint256 ksWord = uint256(keccak256(bytes.concat(hex"fe", keccak256(keysetBytes))));
bytes32 ksHash = bytes32(ksWord ^ (1 << 255));
require(keysetBytes.length < 64 * 1024, "keyset is too large");
if (dasKeySetInfo[ksHash].isValidKeyset) revert AlreadyValidDASKeyset(ksHash);
uint256 creationBlock = block.number;
if (hostChainIsArbitrum) {
creationBlock = ArbSys(address(100)).arbBlockNumber();
}
dasKeySetInf
Submitted on: 2025-09-30 17:16:27
Comments
Log in to comment.
No comments yet.