Loading...
Loading
Cross-Chain Transactions
Loading...
Loading
Contract Name:
RollupProxy
Compiler Version
v0.8.9+commit.e5eed63a
Optimization Enabled:
Yes with 100 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../libraries/AdminFallbackProxy.sol";
import "./IRollupLogic.sol";
contract RollupProxy is AdminFallbackProxy {
constructor(Config memory config, ContractDependencies memory connectedContracts)
AdminFallbackProxy(
address(connectedContracts.rollupAdminLogic),
abi.encodeWithSelector(IRollupAdmin.initialize.selector, config, connectedContracts),
address(connectedContracts.rollupUserLogic),
abi.encodeWithSelector(IRollupUserAbs.initialize.selector, config.stakeToken),
config.owner
)
{}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import {
NotContract,
NotRollupOrOwner,
NotDelayedInbox,
NotSequencerInbox,
NotOutbox,
InvalidOutboxSet
} from "../libraries/Error.sol";
import "./IBridge.sol";
import "./Messages.sol";
import "../libraries/DelegateCallAware.sol";
import {L1MessageType_batchPostingReport} from "../libraries/MessageTypes.sol";
/**
* @title Staging ground for incoming and outgoing messages
* @notice Holds the inbox accumulator for sequenced and delayed messages.
* It is also the ETH escrow for value sent with these messages.
* Since the escrow is held here, this contract also contains a list of allowed
* outboxes that can make calls from here and withdraw this escrow.
*/
contract Bridge is Initializable, DelegateCallAware, IBridge {
using AddressUpgradeable for address;
struct InOutInfo {
uint256 index;
bool allowed;
}
mapping(address => InOutInfo) private allowedDelayedInboxesMap;
mapping(address => InOutInfo) private allowedOutboxesMap;
address[] public allowedDelayedInboxList;
address[] public allowedOutboxList;
address private _activeOutbox;
/// @inheritdoc IBridge
bytes32[] public delayedInboxAccs;
/// @inheritdoc IBridge
bytes32[] public sequencerInboxAccs;
IOwnable public rollup;
address public sequencerInbox;
address private constant EMPTY_ACTIVEOUTBOX = address(type(uint160).max);
function initialize(IOwnable rollup_) external initializer onlyDelegated {
_activeOutbox = EMPTY_ACTIVEOUTBOX;
rollup = rollup_;
}
modifier onlyRollupOrOwner() {
if (msg.sender != address(rollup)) {
address rollupOwner = rollup.owner();
if (msg.sender != rollupOwner) {
revert NotRollupOrOwner(msg.sender, address(rollup), rollupOwner);
}
}
_;
}
/// @dev returns the address of current active Outbox, or zero if no outbox is active
function activeOutbox() public view returns (address) {
address outbox = _activeOutbox;
// address zero is returned if no outbox is set, but the value used in storage
// is non-zero to save users some gas (as storage refunds are usually maxed out)
// EIP-1153 would help here.
// we don't return `EMPTY_ACTIVEOUTBOX` to avoid a breaking change on the current api
if (outbox == EMPTY_ACTIVEOUTBOX) return address(0);
return outbox;
}
function allowedDelayedInboxes(address inbox) external view returns (bool) {
return allowedDelayedInboxesMap[inbox].allowed;
}
function allowedOutboxes(address outbox) external view returns (bool) {
return allowedOutboxesMap[outbox].allowed;
}
modifier onlySequencerInbox() {
if (msg.sender != sequencerInbox) revert NotSequencerInbox(msg.sender);
_;
}
function enqueueSequencerMessage(bytes32 dataHash, uint256 afterDelayedMessagesRead)
external
onlySequencerInbox
returns (
uint256 seqMessageIndex,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 acc
)
{
seqMessageIndex = sequencerInboxAccs.length;
if (sequencerInboxAccs.length > 0) {
beforeAcc = sequencerInboxAccs[sequencerInboxAccs.length - 1];
}
if (afterDelayedMessagesRead > 0) {
delayedAcc = delayedInboxAccs[afterDelayedMessagesRead - 1];
}
acc = keccak256(abi.encodePacked(beforeAcc, dataHash, delayedAcc));
sequencerInboxAccs.push(acc);
}
/// @inheritdoc IBridge
function submitBatchSpendingReport(address sender, bytes32 messageDataHash)
external
onlySequencerInbox
returns (uint256)
{
return
addMessageToDelayedAccumulator(
L1MessageType_batchPostingReport,
sender,
uint64(block.number),
uint64(block.timestamp), // solhint-disable-line not-rely-on-time,
block.basefee,
messageDataHash
);
}
/// @inheritdoc IBridge
function enqueueDelayedMessage(
uint8 kind,
address sender,
bytes32 messageDataHash
) external payable returns (uint256) {
if (!allowedDelayedInboxesMap[msg.sender].allowed) revert NotDelayedInbox(msg.sender);
return
addMessageToDelayedAccumulator(
kind,
sender,
uint64(block.number),
uint64(block.timestamp), // solhint-disable-line not-rely-on-time
block.basefee,
messageDataHash
);
}
function addMessageToDelayedAccumulator(
uint8 kind,
address sender,
uint64 blockNumber,
uint64 blockTimestamp,
uint256 baseFeeL1,
bytes32 messageDataHash
) internal returns (uint256) {
uint256 count = delayedInboxAccs.length;
bytes32 messageHash = Messages.messageHash(
kind,
sender,
blockNumber,
blockTimestamp,
count,
baseFeeL1,
messageDataHash
);
bytes32 prevAcc = 0;
if (count > 0) {
prevAcc = delayedInboxAccs[count - 1];
}
delayedInboxAccs.push(Messages.accumulateInboxMessage(prevAcc, messageHash));
emit MessageDelivered(
count,
prevAcc,
msg.sender,
kind,
sender,
messageDataHash,
baseFeeL1,
blockTimestamp
);
return count;
}
function executeCall(
address to,
uint256 value,
bytes calldata data
) external returns (bool success, bytes memory returnData) {
if (!allowedOutboxesMap[msg.sender].allowed) revert NotOutbox(msg.sender);
if (data.length > 0 && !to.isContract()) revert NotContract(to);
address prevOutbox = _activeOutbox;
_activeOutbox = msg.sender;
// We set and reset active outbox around external call so activeOutbox remains valid during call
// We use a low level call here since we want to bubble up whether it succeeded or failed to the caller
// rather than reverting on failure as well as allow contract and non-contract calls
// solhint-disable-next-line avoid-low-level-calls
(success, returnData) = to.call{value: value}(data);
_activeOutbox = prevOutbox;
emit BridgeCallTriggered(msg.sender, to, value, data);
}
function setSequencerInbox(address _sequencerInbox) external onlyRollupOrOwner {
sequencerInbox = _sequencerInbox;
emit SequencerInboxUpdated(_sequencerInbox);
}
function setDelayedInbox(address inbox, bool enabled) external onlyRollupOrOwner {
InOutInfo storage info = allowedDelayedInboxesMap[inbox];
bool alreadyEnabled = info.allowed;
emit InboxToggle(inbox, enabled);
if ((alreadyEnabled && enabled) || (!alreadyEnabled && !enabled)) {
return;
}
if (enabled) {
allowedDelayedInboxesMap[inbox] = InOutInfo(allowedDelayedInboxList.length, true);
allowedDelayedInboxList.push(inbox);
} else {
allowedDelayedInboxList[info.index] = allowedDelayedInboxList[
allowedDelayedInboxList.length - 1
];
allowedDelayedInboxesMap[allowedDelayedInboxList[info.index]].index = info.index;
allowedDelayedInboxList.pop();
delete allowedDelayedInboxesMap[inbox];
}
}
function setOutbox(address outbox, bool enabled) external onlyRollupOrOwner {
if (outbox == EMPTY_ACTIVEOUTBOX) revert InvalidOutboxSet(outbox);
InOutInfo storage info = allowedOutboxesMap[outbox];
bool alreadyEnabled = info.allowed;
emit OutboxToggle(outbox, enabled);
if ((alreadyEnabled && enabled) || (!alreadyEnabled && !enabled)) {
return;
}
if (enabled) {
allowedOutboxesMap[outbox] = InOutInfo(allowedOutboxList.length, true);
allowedOutboxList.push(outbox);
} else {
allowedOutboxList[info.index] = allowedOutboxList[allowedOutboxList.length - 1];
allowedOutboxesMap[allowedOutboxList[info.index]].index = info.index;
allowedOutboxList.pop();
delete allowedOutboxesMap[outbox];
}
}
function delayedMessageCount() external view returns (uint256) {
return delayedInboxAccs.length;
}
function sequencerMessageCount() external view returns (uint256) {
return sequencerInboxAccs.length;
}
/// @dev For the classic -> nitro migration. TODO: remove post-migration.
function acceptFundsFromOldBridge() external payable {}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.0;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
* initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() initializer {}
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
*/
bool private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Modifier to protect an initializer function from being invoked twice.
*/
modifier initializer() {
// If the contract is initializing we ignore whether _initialized is set in order to support multiple
// inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
// contract may have been reentered.
require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");
bool isTopLevelCall = !_initializing;
if (isTopLevelCall) {
_initializing = true;
_initialized = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
}
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} modifier, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
function _isConstructor() private view returns (bool) {
return !AddressUpgradeable.isContract(address(this));
}
}// 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 AddressUpgradeable {
/**
* @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 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);
}
}
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;
/// @dev Init was already called
error AlreadyInit();
/// Init was called with param set to zero that must be nonzero
error HadZeroInit();
/// @dev Thrown when non owner tries to access an only-owner function
/// @param sender The msg.sender who is not the owner
/// @param owner The owner address
error NotOwner(address sender, address owner);
/// @dev Thrown when an address that is not the rollup tries to call an only-rollup function
/// @param sender The sender who is not the rollup
/// @param rollup The rollup address authorized to call this function
error NotRollup(address sender, address rollup);
/// @dev Thrown when the contract was not called directly from the origin ie msg.sender != tx.origin
error NotOrigin();
/// @dev Provided data was too large
/// @param dataLength The length of the data that is too large
/// @param maxDataLength The max length the data can be
error DataTooLarge(uint256 dataLength, uint256 maxDataLength);
/// @dev The provided is not a contract and was expected to be
/// @param addr The adddress in question
error NotContract(address addr);
/// @dev The merkle proof provided was too long
/// @param actualLength The length of the merkle proof provided
/// @param maxProofLength The max length a merkle proof can have
error MerkleProofTooLong(uint256 actualLength, uint256 maxProofLength);
/// @dev Thrown when an un-authorized address tries to access an admin function
/// @param sender The un-authorized sender
/// @param rollup The rollup, which would be authorized
/// @param owner The rollup's owner, which would be authorized
error NotRollupOrOwner(address sender, address rollup, address owner);
// Bridge Errors
/// @dev Thrown when an un-authorized address tries to access an only-inbox function
/// @param sender The un-authorized sender
error NotDelayedInbox(address sender);
/// @dev Thrown when an un-authorized address tries to access an only-sequencer-inbox function
/// @param sender The un-authorized sender
error NotSequencerInbox(address sender);
/// @dev Thrown when an un-authorized address tries to access an only-outbox function
/// @param sender The un-authorized sender
error NotOutbox(address sender);
/// @dev the provided outbox address isn't valid
/// @param outbox address of outbox being set
error InvalidOutboxSet(address outbox);
// Inbox Errors
/// @dev The contract is paused, so cannot be paused
error AlreadyPaused();
/// @dev The contract is unpaused, so cannot be unpaused
error AlreadyUnpaused();
/// @dev The contract is paused
error Paused();
/// @dev msg.value sent to the inbox isn't high enough
error InsufficientValue(uint256 expected, uint256 actual);
/// @dev submission cost provided isn't enough to create retryable ticket
error InsufficientSubmissionCost(uint256 expected, uint256 actual);
/// @dev address not allowed to interact with the given contract
error NotAllowedOrigin(address origin);
/// @dev used to convey retryable tx data in eth calls without requiring a tx trace
/// this follows a pattern similar to EIP-3668 where reverts surface call information
error RetryableData(
address from,
address to,
uint256 l2CallValue,
uint256 deposit,
uint256 maxSubmissionCost,
address excessFeeRefundAddress,
address callValueRefundAddress,
uint256 gasLimit,
uint256 maxFeePerGas,
bytes data
);
// Outbox Errors
/// @dev The provided proof was too long
/// @param proofLength The length of the too-long proof
error ProofTooLong(uint256 proofLength);
/// @dev The output index was greater than the maximum
/// @param index The output index
/// @param maxIndex The max the index could be
error PathNotMinimal(uint256 index, uint256 maxIndex);
/// @dev The calculated root does not exist
/// @param root The calculated root
error UnknownRoot(bytes32 root);
/// @dev The record has already been spent
/// @param index The index of the spent record
error AlreadySpent(uint256 index);
/// @dev A call to the bridge failed with no return data
error BridgeCallFailed();
// Sequencer Inbox Errors
/// @dev Thrown when someone attempts to read fewer messages than have already been read
error DelayedBackwards();
/// @dev Thrown when someone attempts to read more messages than exist
error DelayedTooFar();
/// @dev Force include can only read messages more blocks old than the delay period
error ForceIncludeBlockTooSoon();
/// @dev Force include can only read messages more seconds old than the delay period
error ForceIncludeTimeTooSoon();
/// @dev The message provided did not match the hash in the delayed inbox
error IncorrectMessagePreimage();
/// @dev This can only be called by the batch poster
error NotBatchPoster();
/// @dev The sequence number provided to this message was inconsistent with the number of batches already included
error BadSequencerNumber(uint256 stored, uint256 received);
/// @dev The batch data has the inbox authenticated bit set, but the batch data was not authenticated by the inbox
error DataNotAuthenticated();
/// @dev Tried to create an already valid Data Availability Service keyset
error AlreadyValidDASKeyset(bytes32);
/// @dev Tried to use or invalidate an already invalid Data Availability Service keyset
error NoSuchKeyset(bytes32);// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
// solhint-disable-next-line compiler-version
pragma solidity >=0.6.9 <0.9.0;
import "./IOwnable.sol";
interface IBridge {
event MessageDelivered(
uint256 indexed messageIndex,
bytes32 indexed beforeInboxAcc,
address inbox,
uint8 kind,
address sender,
bytes32 messageDataHash,
uint256 baseFeeL1,
uint64 timestamp
);
event BridgeCallTriggered(
address indexed outbox,
address indexed to,
uint256 value,
bytes data
);
event InboxToggle(address indexed inbox, bool enabled);
event OutboxToggle(address indexed outbox, bool enabled);
event SequencerInboxUpdated(address newSequencerInbox);
function allowedDelayedInboxList(uint256) external returns (address);
function allowedOutboxList(uint256) external returns (address);
/// @dev Accumulator for delayed inbox messages; tail represents hash of the current state; each element represents the inclusion of a new message.
function delayedInboxAccs(uint256) external view returns (bytes32);
/// @dev Accumulator for sequencer inbox messages; tail represents hash of the current state; each element represents the inclusion of a new message.
function sequencerInboxAccs(uint256) external view returns (bytes32);
function rollup() external view returns (IOwnable);
function sequencerInbox() external view returns (address);
function activeOutbox() external view returns (address);
function allowedDelayedInboxes(address inbox) external view returns (bool);
function allowedOutboxes(address outbox) external view returns (bool);
/**
* @dev Enqueue a message in the delayed inbox accumulator.
* These messages are later sequenced in the SequencerInbox, either
* by the sequencer as part of a normal batch, or by force inclusion.
*/
function enqueueDelayedMessage(
uint8 kind,
address sender,
bytes32 messageDataHash
) external payable returns (uint256);
function executeCall(
address to,
uint256 value,
bytes calldata data
) external returns (bool success, bytes memory returnData);
function delayedMessageCount() external view returns (uint256);
function sequencerMessageCount() external view returns (uint256);
// ---------- onlySequencerInbox functions ----------
function enqueueSequencerMessage(bytes32 dataHash, uint256 afterDelayedMessagesRead)
external
returns (
uint256 seqMessageIndex,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 acc
);
/**
* @dev Allows the sequencer inbox to submit a delayed message of the batchPostingReport type
* This is done through a separate function entrypoint instead of allowing the sequencer inbox
* to call `enqueueDelayedMessage` to avoid the gas overhead of an extra SLOAD in either
* every delayed inbox or every sequencer inbox call.
*/
function submitBatchSpendingReport(address batchPoster, bytes32 dataHash)
external
returns (uint256 msgNum);
// ---------- onlyRollupOrOwner functions ----------
function setSequencerInbox(address _sequencerInbox) external;
function setDelayedInbox(address inbox, bool enabled) external;
function setOutbox(address inbox, bool enabled) external;
// ---------- initializer ----------
function initialize(IOwnable rollup_) external;
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
library Messages {
function messageHash(
uint8 kind,
address sender,
uint64 blockNumber,
uint64 timestamp,
uint256 inboxSeqNum,
uint256 baseFeeL1,
bytes32 messageDataHash
) internal pure returns (bytes32) {
return
keccak256(
abi.encodePacked(
kind,
sender,
blockNumber,
timestamp,
inboxSeqNum,
baseFeeL1,
messageDataHash
)
);
}
function accumulateInboxMessage(bytes32 prevAcc, bytes32 message)
internal
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(prevAcc, message));
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import {NotOwner} from "./Error.sol";
/// @dev A stateless contract that allows you to infer if the current call has been delegated or not
/// Pattern used here is from UUPS implementation by the OpenZeppelin team
abstract contract DelegateCallAware {
address private immutable __self = address(this);
/**
* @dev Check that the execution is being performed through a delegate call. This allows a function to be
* callable on the proxy contract but not on the logic contract.
*/
modifier onlyDelegated() {
require(address(this) != __self, "Function must be called through delegatecall");
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
require(address(this) == __self, "Function must not be called through delegatecall");
_;
}
/// @dev Check that msg.sender is the current EIP 1967 proxy admin
modifier onlyProxyOwner() {
// Storage slot with the admin of the proxy contract
// This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1
bytes32 slot = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
address admin;
assembly {
admin := sload(slot)
}
if (msg.sender != admin) revert NotOwner(msg.sender, admin);
_;
}
}// Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.4; uint8 constant L2_MSG = 3; uint8 constant L1MessageType_L2FundedByL1 = 7; uint8 constant L1MessageType_submitRetryableTx = 9; uint8 constant L1MessageType_ethDeposit = 12; uint8 constant L1MessageType_batchPostingReport = 13; uint8 constant L2MessageType_unsignedEOATx = 0; uint8 constant L2MessageType_unsignedContractTx = 1; uint8 constant ROLLUP_PROTOCOL_EVENT_TYPE = 8; uint8 constant INITIALIZATION_MSG_TYPE = 11;
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
// solhint-disable-next-line compiler-version
pragma solidity >=0.4.21 <0.9.0;
interface IOwnable {
function owner() external view returns (address);
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../bridge/Bridge.sol";
import "../bridge/SequencerInbox.sol";
import "../bridge/ISequencerInbox.sol";
import "../bridge/Inbox.sol";
import "../bridge/Outbox.sol";
import "./RollupEventInbox.sol";
import "../bridge/IBridge.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
contract BridgeCreator is Ownable {
Bridge public bridgeTemplate;
SequencerInbox public sequencerInboxTemplate;
Inbox public inboxTemplate;
RollupEventInbox public rollupEventInboxTemplate;
Outbox public outboxTemplate;
event TemplatesUpdated();
constructor() Ownable() {
bridgeTemplate = new Bridge();
sequencerInboxTemplate = new SequencerInbox();
inboxTemplate = new Inbox();
rollupEventInboxTemplate = new RollupEventInbox();
outboxTemplate = new Outbox();
}
function updateTemplates(
address _bridgeTemplate,
address _sequencerInboxTemplate,
address _inboxTemplate,
address _rollupEventInboxTemplate,
address _outboxTemplate
) external onlyOwner {
bridgeTemplate = Bridge(_bridgeTemplate);
sequencerInboxTemplate = SequencerInbox(_sequencerInboxTemplate);
inboxTemplate = Inbox(_inboxTemplate);
rollupEventInboxTemplate = RollupEventInbox(_rollupEventInboxTemplate);
outboxTemplate = Outbox(_outboxTemplate);
emit TemplatesUpdated();
}
struct CreateBridgeFrame {
ProxyAdmin admin;
Bridge bridge;
SequencerInbox sequencerInbox;
Inbox inbox;
RollupEventInbox rollupEventInbox;
Outbox outbox;
}
function createBridge(
address adminProxy,
address rollup,
ISequencerInbox.MaxTimeVariation memory maxTimeVariation
)
external
returns (
Bridge,
SequencerInbox,
Inbox,
RollupEventInbox,
Outbox
)
{
CreateBridgeFrame memory frame;
{
frame.bridge = Bridge(
address(new TransparentUpgradeableProxy(address(bridgeTemplate), adminProxy, ""))
);
frame.sequencerInbox = SequencerInbox(
address(
new TransparentUpgradeableProxy(address(sequencerInboxTemplate), adminProxy, "")
)
);
frame.inbox = Inbox(
address(new TransparentUpgradeableProxy(address(inboxTemplate), adminProxy, ""))
);
frame.rollupEventInbox = RollupEventInbox(
address(
new TransparentUpgradeableProxy(
address(rollupEventInboxTemplate),
adminProxy,
""
)
)
);
frame.outbox = Outbox(
address(new TransparentUpgradeableProxy(address(outboxTemplate), adminProxy, ""))
);
}
frame.bridge.initialize(IOwnable(rollup));
frame.sequencerInbox.initialize(IBridge(frame.bridge), maxTimeVariation);
frame.inbox.initialize(IBridge(frame.bridge), ISequencerInbox(frame.sequencerInbox));
frame.rollupEventInbox.initialize(IBridge(frame.bridge));
frame.outbox.initialize(IBridge(frame.bridge));
return (
frame.bridge,
frame.sequencerInbox,
frame.inbox,
frame.rollupEventInbox,
frame.outbox
);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import {
AlreadyInit,
HadZeroInit,
NotOrigin,
DataTooLarge,
NotRollup,
DelayedBackwards,
DelayedTooFar,
ForceIncludeBlockTooSoon,
ForceIncludeTimeTooSoon,
IncorrectMessagePreimage,
NotBatchPoster,
BadSequencerNumber,
DataNotAuthenticated,
AlreadyValidDASKeyset,
NoSuchKeyset
} from "../libraries/Error.sol";
import "./IBridge.sol";
import "./IInbox.sol";
import "./ISequencerInbox.sol";
import "../rollup/IRollupLogic.sol";
import "./Messages.sol";
import {L1MessageType_batchPostingReport} from "../libraries/MessageTypes.sol";
import {GasRefundEnabled, IGasRefunder} from "../libraries/IGasRefunder.sol";
import "../libraries/DelegateCallAware.sol";
import {MAX_DATA_SIZE} from "../libraries/Constants.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;
IOwnable public rollup;
mapping(address => bool) public isBatchPoster;
ISequencerInbox.MaxTimeVariation public maxTimeVariation;
mapping(bytes32 => DasKeySetInfo) public dasKeySetInfo;
modifier onlyRollupOwner() {
if (msg.sender != rollup.owner()) revert NotOwner(msg.sender, address(rollup));
_;
}
function initialize(
IBridge bridge_,
ISequencerInbox.MaxTimeVariation calldata maxTimeVariation_
) external onlyDelegated {
if (bridge != IBridge(address(0))) revert AlreadyInit();
if (bridge_ == IBridge(address(0))) revert HadZeroInit();
bridge = bridge_;
rollup = bridge_.rollup();
maxTimeVariation = maxTimeVariation_;
}
function getTimeBounds() internal view virtual returns (TimeBounds memory) {
TimeBounds memory bounds;
if (block.timestamp > maxTimeVariation.delaySeconds) {
bounds.minTimestamp = uint64(block.timestamp - maxTimeVariation.delaySeconds);
}
bounds.maxTimestamp = uint64(block.timestamp + maxTimeVariation.futureSeconds);
if (block.number > maxTimeVariation.delayBlocks) {
bounds.minBlockNumber = uint64(block.number - maxTimeVariation.delayBlocks);
}
bounds.maxBlockNumber = uint64(block.number + maxTimeVariation.futureBlocks);
return bounds;
}
/// @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] + maxTimeVariation.delayBlocks >= block.number)
revert ForceIncludeBlockTooSoon();
if (l1BlockAndTime[1] + maxTimeVariation.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, TimeBounds memory timeBounds) = formEmptyDataHash(
_totalDelayedMessagesRead
);
(
uint256 seqMessageIndex,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 afterAcc
) = addSequencerL2BatchImpl(dataHash, _totalDelayedMessagesRead, 0);
emit SequencerBatchDelivered(
seqMessageIndex,
beforeAcc,
afterAcc,
delayedAcc,
totalDelayedMessagesRead,
timeBounds,
BatchDataLocation.NoData
);
}
function addSequencerL2BatchFromOrigin(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder
) external refundsGas(gasRefunder) {
// solhint-disable-next-line avoid-tx-origin
if (msg.sender != tx.origin) revert NotOrigin();
if (!isBatchPoster[msg.sender]) revert NotBatchPoster();
(bytes32 dataHash, TimeBounds memory timeBounds) = formDataHash(
data,
afterDelayedMessagesRead
);
(
uint256 seqMessageIndex,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 afterAcc
) = addSequencerL2BatchImpl(dataHash, afterDelayedMessagesRead, data.length);
if (seqMessageIndex != sequenceNumber)
revert BadSequencerNumber(seqMessageIndex, sequenceNumber);
emit SequencerBatchDelivered(
sequenceNumber,
beforeAcc,
afterAcc,
delayedAcc,
totalDelayedMessagesRead,
timeBounds,
BatchDataLocation.TxInput
);
}
function addSequencerL2Batch(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder
) external refundsGas(gasRefunder) {
if (!isBatchPoster[msg.sender] && msg.sender != address(rollup)) revert NotBatchPoster();
(bytes32 dataHash, TimeBounds memory timeBounds) = formDataHash(
data,
afterDelayedMessagesRead
);
// 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
(
uint256 seqMessageIndex,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 afterAcc
) = addSequencerL2BatchImpl(dataHash, afterDelayedMessagesRead, 0);
if (seqMessageIndex != sequenceNumber)
revert BadSequencerNumber(seqMessageIndex, sequenceNumber);
emit SequencerBatchDelivered(
sequenceNumber,
beforeAcc,
afterAcc,
delayedAcc,
afterDelayedMessagesRead,
timeBounds,
BatchDataLocation.SeparateBatchEvent
);
emit SequencerBatchData(sequenceNumber, data);
}
modifier validateBatchData(bytes calldata data) {
uint256 fullDataLen = HEADER_LENGTH + data.length;
if (fullDataLen > MAX_DATA_SIZE) revert DataTooLarge(fullDataLen, MAX_DATA_SIZE);
if (data.length > 0 && (data[0] & DATA_AUTHENTICATED_FLAG) == DATA_AUTHENTICATED_FLAG) {
revert DataNotAuthenticated();
}
// 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 (data.length >= 33 && data[0] & 0x80 != 0) {
// 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);
}
_;
}
function packHeader(uint256 afterDelayedMessagesRead)
internal
view
returns (bytes memory, TimeBounds memory)
{
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);
}
function formDataHash(bytes calldata data, uint256 afterDelayedMessagesRead)
internal
view
validateBatchData(data)
returns (bytes32, TimeBounds memory)
{
(bytes memory header, TimeBounds memory timeBounds) = packHeader(afterDelayedMessagesRead);
bytes32 dataHash = keccak256(bytes.concat(header, data));
return (dataHash, timeBounds);
}
function formEmptyDataHash(uint256 afterDelayedMessagesRead)
internal
view
returns (bytes32, TimeBounds memory)
{
(bytes memory header, TimeBounds memory timeBounds) = packHeader(afterDelayedMessagesRead);
return (keccak256(header), timeBounds);
}
function addSequencerL2BatchImpl(
bytes32 dataHash,
uint256 afterDelayedMessagesRead,
uint256 calldataLengthPosted
)
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
);
totalDelayedMessagesRead = afterDelayedMessagesRead;
if (calldataLengthPosted > 0) {
// this msg isn't included in the current sequencer batch, but instead added to
// the delayed messages queue that is yet to be included
address batchPoster = msg.sender;
bytes memory spendingReportMsg = abi.encodePacked(
block.timestamp,
batchPoster,
dataHash,
seqMessageIndex,
block.basefee
);
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 inboxAccs(uint256 index) external view returns (bytes32) {
return bridge.sequencerInboxAccs(index);
}
function batchCount() external view returns (uint256) {
return bridge.sequencerMessageCount();
}
/// @inheritdoc ISequencerInbox
function setMaxTimeVariation(ISequencerInbox.MaxTimeVariation memory maxTimeVariation_)
external
onlyRollupOwner
{
maxTimeVariation = maxTimeVariation_;
emit OwnerFunctionCalled(0);
}
/// @inheritdoc ISequencerInbox
function setIsBatchPoster(address addr, bool isBatchPoster_) external onlyRollupOwner {
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);
dasKeySetInfo[ksHash] = DasKeySetInfo({
isValidKeyset: true,
creationBlock: uint64(block.number)
});
emit SetValidKeyset(ksHash, keysetBytes);
emit OwnerFunctionCalled(2);
}
/// @inheritdoc ISequencerInbox
function invalidateKeysetHash(bytes32 ksHash) external onlyRollupOwner {
if (!dasKeySetInfo[ksHash].isValidKeyset) revert NoSuchKeyset(ksHash);
// we don't delete the block creation value since its used to fetch the SetValidKeyset
// event efficiently. The event provides the hash preimage of the key.
// this is still needed when syncing the chain after a keyset is invalidated.
dasKeySetInfo[ksHash].isValidKeyset = false;
emit InvalidateKeyset(ksHash);
emit OwnerFunctionCalled(3);
}
function isValidKeysetHash(bytes32 ksHash) external view returns (bool) {
return dasKeySetInfo[ksHash].isValidKeyset;
}
/// @inheritdoc ISequencerInbox
function getKeysetCreationBlock(bytes32 ksHash) external view returns (uint256) {
DasKeySetInfo memory ksInfo = dasKeySetInfo[ksHash];
if (ksInfo.creationBlock == 0) revert NoSuchKeyset(ksHash);
return uint256(ksInfo.creationBlock);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/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;
}
struct TimeBounds {
uint64 minTimestamp;
uint64 maxTimestamp;
uint64 minBlockNumber;
uint64 maxBlockNumber;
}
enum BatchDataLocation {
TxInput,
SeparateBatchEvent,
NoData
}
event SequencerBatchDelivered(
uint256 indexed batchSequenceNumber,
bytes32 indexed beforeAcc,
bytes32 indexed afterAcc,
bytes32 delayedAcc,
uint256 afterDelayedMessagesRead,
TimeBounds timeBounds,
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);
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 not used.
// solhint-disable-next-line func-name-mixedcase
function DATA_AUTHENTICATED_FLAG() external view returns (bytes1);
function rollup() external view returns (IOwnable);
function isBatchPoster(address) external view returns (bool);
struct DasKeySetInfo {
bool isValidKeyset;
uint64 creationBlock;
}
// https://github.com/ethereum/solidity/issues/11826
// function maxTimeVariation() external view returns (MaxTimeVariation calldata);
// function dasKeySetInfo(bytes32) external view returns (DasKeySetInfo calldata);
/// @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 addSequencerL2Batch(
uint256 sequenceNumber,
bytes calldata data,
uint256 afterDelayedMessagesRead,
IGasRefunder gasRefunder
) 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;
// ---------- initializer ----------
function initialize(IBridge bridge_, MaxTimeVariation calldata maxTimeVariation_) external;
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;
import {
AlreadyInit,
NotOrigin,
DataTooLarge,
AlreadyPaused,
AlreadyUnpaused,
Paused,
InsufficientValue,
InsufficientSubmissionCost,
NotAllowedOrigin,
RetryableData,
NotRollupOrOwner
} from "../libraries/Error.sol";
import "./IInbox.sol";
import "./ISequencerInbox.sol";
import "./IBridge.sol";
import "./Messages.sol";
import "../libraries/AddressAliasHelper.sol";
import "../libraries/DelegateCallAware.sol";
import {
L2_MSG,
L1MessageType_L2FundedByL1,
L1MessageType_submitRetryableTx,
L1MessageType_ethDeposit,
L2MessageType_unsignedEOATx,
L2MessageType_unsignedContractTx
} from "../libraries/MessageTypes.sol";
import {MAX_DATA_SIZE} from "../libraries/Constants.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
/**
* @title Inbox for user and contract originated messages
* @notice Messages created via this inbox are enqueued in the delayed accumulator
* to await inclusion in the SequencerInbox
*/
contract Inbox is DelegateCallAware, PausableUpgradeable, IInbox {
IBridge public bridge;
ISequencerInbox public sequencerInbox;
/// ------------------------------------ allow list start ------------------------------------ ///
bool public allowListEnabled;
mapping(address => bool) public isAllowed;
event AllowListAddressSet(address indexed user, bool val);
event AllowListEnabledUpdated(bool isEnabled);
function setAllowList(address[] memory user, bool[] memory val) external onlyRollupOrOwner {
require(user.length == val.length, "INVALID_INPUT");
for (uint256 i = 0; i < user.length; i++) {
isAllowed[user[i]] = val[i];
emit AllowListAddressSet(user[i], val[i]);
}
}
function setAllowListEnabled(bool _allowListEnabled) external onlyRollupOrOwner {
require(_allowListEnabled != allowListEnabled, "ALREADY_SET");
allowListEnabled = _allowListEnabled;
emit AllowListEnabledUpdated(_allowListEnabled);
}
/// @dev this modifier checks the tx.origin instead of msg.sender for convenience (ie it allows
/// allowed users to interact with the token bridge without needing the token bridge to be allowList aware).
/// this modifier is not intended to use to be used for security (since this opens the allowList to
/// a smart contract phishing risk).
modifier onlyAllowed() {
// solhint-disable-next-line avoid-tx-origin
if (allowListEnabled && !isAllowed[tx.origin]) revert NotAllowedOrigin(tx.origin);
_;
}
/// ------------------------------------ allow list end ------------------------------------ ///
modifier onlyRollupOrOwner() {
IOwnable rollup = bridge.rollup();
if (msg.sender != address(rollup)) {
address rollupOwner = rollup.owner();
if (msg.sender != rollupOwner) {
revert NotRollupOrOwner(msg.sender, address(rollup), rollupOwner);
}
}
_;
}
/// @inheritdoc IInbox
function pause() external onlyRollupOrOwner {
_pause();
}
/// @inheritdoc IInbox
function unpause() external onlyRollupOrOwner {
_unpause();
}
function initialize(IBridge _bridge, ISequencerInbox _sequencerInbox)
external
initializer
onlyDelegated
{
bridge = _bridge;
sequencerInbox = _sequencerInbox;
allowListEnabled = false;
__Pausable_init();
}
/// @inheritdoc IInbox
function postUpgradeInit(IBridge _bridge) external onlyDelegated onlyProxyOwner {
uint8 slotsToWipe = 3;
for (uint8 i = 0; i < slotsToWipe; i++) {
assembly {
sstore(i, 0)
}
}
allowListEnabled = false;
bridge = _bridge;
}
/// @inheritdoc IInbox
function sendL2MessageFromOrigin(bytes calldata messageData)
external
whenNotPaused
onlyAllowed
returns (uint256)
{
// solhint-disable-next-line avoid-tx-origin
if (msg.sender != tx.origin) revert NotOrigin();
if (messageData.length > MAX_DATA_SIZE)
revert DataTooLarge(messageData.length, MAX_DATA_SIZE);
uint256 msgNum = deliverToBridge(L2_MSG, msg.sender, keccak256(messageData));
emit InboxMessageDeliveredFromOrigin(msgNum);
return msgNum;
}
/// @inheritdoc IInbox
function sendL2Message(bytes calldata messageData)
external
whenNotPaused
onlyAllowed
returns (uint256)
{
return _deliverMessage(L2_MSG, msg.sender, messageData);
}
function sendL1FundedUnsignedTransaction(
uint256 gasLimit,
uint256 maxFeePerGas,
uint256 nonce,
address to,
bytes calldata data
) external payable whenNotPaused onlyAllowed returns (uint256) {
return
_deliverMessage(
L1MessageType_L2FundedByL1,
msg.sender,
abi.encodePacked(
L2MessageType_unsignedEOATx,
gasLimit,
maxFeePerGas,
nonce,
uint256(uint160(to)),
msg.value,
data
)
);
}
function sendL1FundedContractTransaction(
uint256 gasLimit,
uint256 maxFeePerGas,
address to,
bytes calldata data
) external payable whenNotPaused onlyAllowed returns (uint256) {
return
_deliverMessage(
L1MessageType_L2FundedByL1,
msg.sender,
abi.encodePacked(
L2MessageType_unsignedContractTx,
gasLimit,
maxFeePerGas,
uint256(uint160(to)),
msg.value,
data
)
);
}
function sendUnsignedTransaction(
uint256 gasLimit,
uint256 maxFeePerGas,
uint256 nonce,
address to,
uint256 value,
bytes calldata data
) external whenNotPaused onlyAllowed returns (uint256) {
return
_deliverMessage(
L2_MSG,
msg.sender,
abi.encodePacked(
L2MessageType_unsignedEOATx,
gasLimit,
maxFeePerGas,
nonce,
uint256(uint160(to)),
value,
data
)
);
}
function sendContractTransaction(
uint256 gasLimit,
uint256 maxFeePerGas,
address to,
uint256 value,
bytes calldata data
) external whenNotPaused onlyAllowed returns (uint256) {
return
_deliverMessage(
L2_MSG,
msg.sender,
abi.encodePacked(
L2MessageType_unsignedContractTx,
gasLimit,
maxFeePerGas,
uint256(uint160(to)),
value,
data
)
);
}
/// @inheritdoc IInbox
function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee)
public
view
returns (uint256)
{
// Use current block basefee if baseFee parameter is 0
return (1400 + 6 * dataLength) * (baseFee == 0 ? block.basefee : baseFee);
}
/// @inheritdoc IInbox
function depositEth() public payable whenNotPaused onlyAllowed returns (uint256) {
address dest = msg.sender;
// solhint-disable-next-line avoid-tx-origin
if (AddressUpgradeable.isContract(msg.sender) || tx.origin != msg.sender) {
// isContract check fails if this function is called during a contract's constructor.
dest = AddressAliasHelper.applyL1ToL2Alias(msg.sender);
}
return
_deliverMessage(
L1MessageType_ethDeposit,
msg.sender,
abi.encodePacked(dest, msg.value)
);
}
/// @notice deprecated in favour of depositEth with no parameters
function depositEth(uint256) external payable whenNotPaused onlyAllowed returns (uint256) {
return depositEth();
}
/**
* @notice deprecated in favour of unsafeCreateRetryableTicket
* @dev deprecated in favour of unsafeCreateRetryableTicket
* @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error
* @param to destination L2 contract address
* @param l2CallValue call value for retryable L2 message
* @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
* @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance
* @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled
* @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
* @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
* @param data ABI encoded data of L2 message
* @return unique message number of the retryable transaction
*/
function createRetryableTicketNoRefundAliasRewrite(
address to,
uint256 l2CallValue,
uint256 maxSubmissionCost,
address excessFeeRefundAddress,
address callValueRefundAddress,
uint256 gasLimit,
uint256 maxFeePerGas,
bytes calldata data
) external payable whenNotPaused onlyAllowed returns (uint256) {
return
unsafeCreateRetryableTicket(
to,
l2CallValue,
maxSubmissionCost,
excessFeeRefundAddress,
callValueRefundAddress,
gasLimit,
maxFeePerGas,
data
);
}
/// @inheritdoc IInbox
function createRetryableTicket(
address to,
uint256 l2CallValue,
uint256 maxSubmissionCost,
address excessFeeRefundAddress,
address callValueRefundAddress,
uint256 gasLimit,
uint256 maxFeePerGas,
bytes calldata data
) external payable whenNotPaused onlyAllowed returns (uint256) {
// ensure the user's deposit alone will make submission succeed
if (msg.value < (maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas)) {
revert InsufficientValue(
maxSubmissionCost + l2CallValue + gasLimit * maxFeePerGas,
msg.value
);
}
// if a refund address is a contract, we apply the alias to it
// so that it can access its funds on the L2
// since the beneficiary and other refund addresses don't get rewritten by arb-os
if (AddressUpgradeable.isContract(excessFeeRefundAddress)) {
excessFeeRefundAddress = AddressAliasHelper.applyL1ToL2Alias(excessFeeRefundAddress);
}
if (AddressUpgradeable.isContract(callValueRefundAddress)) {
// this is the beneficiary. be careful since this is the address that can cancel the retryable in the L2
callValueRefundAddress = AddressAliasHelper.applyL1ToL2Alias(callValueRefundAddress);
}
return
unsafeCreateRetryableTicket(
to,
l2CallValue,
maxSubmissionCost,
excessFeeRefundAddress,
callValueRefundAddress,
gasLimit,
maxFeePerGas,
data
);
}
/// @inheritdoc IInbox
function unsafeCreateRetryableTicket(
address to,
uint256 l2CallValue,
uint256 maxSubmissionCost,
address excessFeeRefundAddress,
address callValueRefundAddress,
uint256 gasLimit,
uint256 maxFeePerGas,
bytes calldata data
) public payable whenNotPaused onlyAllowed returns (uint256) {
// gas price and limit of 1 should never be a valid input, so instead they are used as
// magic values to trigger a revert in eth calls that surface data without requiring a tx trace
if (gasLimit == 1 || maxFeePerGas == 1)
revert RetryableData(
msg.sender,
to,
l2CallValue,
msg.value,
maxSubmissionCost,
excessFeeRefundAddress,
callValueRefundAddress,
gasLimit,
maxFeePerGas,
data
);
uint256 submissionFee = calculateRetryableSubmissionFee(data.length, block.basefee);
if (maxSubmissionCost < submissionFee)
revert InsufficientSubmissionCost(submissionFee, maxSubmissionCost);
return
_deliverMessage(
L1MessageType_submitRetryableTx,
msg.sender,
abi.encodePacked(
uint256(uint160(to)),
l2CallValue,
msg.value,
maxSubmissionCost,
uint256(uint160(excessFeeRefundAddress)),
uint256(uint160(callValueRefundAddress)),
gasLimit,
maxFeePerGas,
data.length,
data
)
);
}
function _deliverMessage(
uint8 _kind,
address _sender,
bytes memory _messageData
) internal returns (uint256) {
if (_messageData.length > MAX_DATA_SIZE)
revert DataTooLarge(_messageData.length, MAX_DATA_SIZE);
uint256 msgNum = deliverToBridge(_kind, _sender, keccak256(_messageData));
emit InboxMessageDelivered(msgNum, _messageData);
return msgNum;
}
function deliverToBridge(
uint8 kind,
address sender,
bytes32 messageDataHash
) internal returns (uint256) {
return
bridge.enqueueDelayedMessage{value: msg.value}(
kind,
AddressAliasHelper.applyL1ToL2Alias(sender),
messageDataHash
);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;
import {
AlreadyInit,
NotRollup,
ProofTooLong,
PathNotMinimal,
UnknownRoot,
AlreadySpent,
BridgeCallFailed,
HadZeroInit
} from "../libraries/Error.sol";
import "./IBridge.sol";
import "./IOutbox.sol";
import "../libraries/MerkleLib.sol";
import "../libraries/DelegateCallAware.sol";
/// @dev this error is thrown since certain functions are only expected to be used in simulations, not in actual txs
error SimulationOnlyEntrypoint();
contract Outbox is DelegateCallAware, IOutbox {
address public rollup; // the rollup contract
IBridge public bridge; // the bridge contract
mapping(uint256 => bytes32) public spent; // packed spent bitmap
mapping(bytes32 => bytes32) public roots; // maps root hashes => L2 block hash
struct L2ToL1Context {
uint128 l2Block;
uint128 l1Block;
uint128 timestamp;
bytes32 outputId;
address sender;
}
// Note, these variables are set and then wiped during a single transaction.
// Therefore their values don't need to be maintained, and their slots will
// be empty outside of transactions
L2ToL1Context internal context;
// default context values to be used in storage instead of zero, to save on storage refunds
// it is assumed that arb-os never assigns these values to a valid leaf to be redeemed
uint128 private constant L2BLOCK_DEFAULT_CONTEXT = type(uint128).max;
uint128 private constant L1BLOCK_DEFAULT_CONTEXT = type(uint128).max;
uint128 private constant TIMESTAMP_DEFAULT_CONTEXT = type(uint128).max;
bytes32 private constant OUTPUTID_DEFAULT_CONTEXT = bytes32(type(uint256).max);
address private constant SENDER_DEFAULT_CONTEXT = address(type(uint160).max);
uint128 public constant OUTBOX_VERSION = 2;
function initialize(IBridge _bridge) external onlyDelegated {
if (address(_bridge) == address(0)) revert HadZeroInit();
if (address(bridge) != address(0)) revert AlreadyInit();
// address zero is returned if no context is set, but the values used in storage
// are non-zero to save users some gas (as storage refunds are usually maxed out)
// EIP-1153 would help here
context = L2ToL1Context({
l2Block: L2BLOCK_DEFAULT_CONTEXT,
l1Block: L1BLOCK_DEFAULT_CONTEXT,
timestamp: TIMESTAMP_DEFAULT_CONTEXT,
outputId: OUTPUTID_DEFAULT_CONTEXT,
sender: SENDER_DEFAULT_CONTEXT
});
bridge = _bridge;
rollup = address(_bridge.rollup());
}
function updateSendRoot(bytes32 root, bytes32 l2BlockHash) external {
if (msg.sender != rollup) revert NotRollup(msg.sender, rollup);
roots[root] = l2BlockHash;
emit SendRootUpdated(root, l2BlockHash);
}
/// @inheritdoc IOutbox
function l2ToL1Sender() external view returns (address) {
address sender = context.sender;
// we don't return the default context value to avoid a breaking change in the API
if (sender == SENDER_DEFAULT_CONTEXT) return address(0);
return sender;
}
/// @inheritdoc IOutbox
function l2ToL1Block() external view returns (uint256) {
uint128 l2Block = context.l2Block;
// we don't return the default context value to avoid a breaking change in the API
if (l2Block == L1BLOCK_DEFAULT_CONTEXT) return uint256(0);
return uint256(l2Block);
}
/// @inheritdoc IOutbox
function l2ToL1EthBlock() external view returns (uint256) {
uint128 l1Block = context.l1Block;
// we don't return the default context value to avoid a breaking change in the API
if (l1Block == L1BLOCK_DEFAULT_CONTEXT) return uint256(0);
return uint256(l1Block);
}
/// @inheritdoc IOutbox
function l2ToL1Timestamp() external view returns (uint256) {
uint128 timestamp = context.timestamp;
// we don't return the default context value to avoid a breaking change in the API
if (timestamp == TIMESTAMP_DEFAULT_CONTEXT) return uint256(0);
return uint256(timestamp);
}
/// @notice batch number is deprecated and now always returns 0
function l2ToL1BatchNum() external pure returns (uint256) {
return 0;
}
/// @inheritdoc IOutbox
function l2ToL1OutputId() external view returns (bytes32) {
bytes32 outputId = context.outputId;
// we don't return the default context value to avoid a breaking change in the API
if (outputId == OUTPUTID_DEFAULT_CONTEXT) return bytes32(0);
return outputId;
}
/// @inheritdoc IOutbox
function executeTransaction(
bytes32[] calldata proof,
uint256 index,
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
uint256 value,
bytes calldata data
) external {
bytes32 userTx = calculateItemHash(
l2Sender,
to,
l2Block,
l1Block,
l2Timestamp,
value,
data
);
recordOutputAsSpent(proof, index, userTx);
executeTransactionImpl(index, l2Sender, to, l2Block, l1Block, l2Timestamp, value, data);
}
/// @inheritdoc IOutbox
function executeTransactionSimulation(
uint256 index,
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
uint256 value,
bytes calldata data
) external {
if (msg.sender != address(0)) revert SimulationOnlyEntrypoint();
executeTransactionImpl(index, l2Sender, to, l2Block, l1Block, l2Timestamp, value, data);
}
function executeTransactionImpl(
uint256 outputId,
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
uint256 value,
bytes calldata data
) internal {
emit OutBoxTransactionExecuted(to, l2Sender, 0, outputId);
// we temporarily store the previous values so the outbox can naturally
// unwind itself when there are nested calls to `executeTransaction`
L2ToL1Context memory prevContext = context;
context = L2ToL1Context({
sender: l2Sender,
l2Block: uint128(l2Block),
l1Block: uint128(l1Block),
timestamp: uint128(l2Timestamp),
outputId: bytes32(outputId)
});
// set and reset vars around execution so they remain valid during call
executeBridgeCall(to, value, data);
context = prevContext;
}
function _calcSpentIndexOffset(uint256 index)
internal
view
returns (
uint256,
uint256,
bytes32
)
{
uint256 spentIndex = index / 255; // Note: Reserves the MSB.
uint256 bitOffset = index % 255;
bytes32 replay = spent[spentIndex];
return (spentIndex, bitOffset, replay);
}
function _isSpent(uint256 bitOffset, bytes32 replay) internal pure returns (bool) {
return ((replay >> bitOffset) & bytes32(uint256(1))) != bytes32(0);
}
/// @inheritdoc IOutbox
function isSpent(uint256 index) external view returns (bool) {
(, uint256 bitOffset, bytes32 replay) = _calcSpentIndexOffset(index);
return _isSpent(bitOffset, replay);
}
function recordOutputAsSpent(
bytes32[] memory proof,
uint256 index,
bytes32 item
) internal {
if (proof.length >= 256) revert ProofTooLong(proof.length);
if (index >= 2**proof.length) revert PathNotMinimal(index, 2**proof.length);
// Hash the leaf an extra time to prove it's a leaf
bytes32 calcRoot = calculateMerkleRoot(proof, index, item);
if (roots[calcRoot] == bytes32(0)) revert UnknownRoot(calcRoot);
(uint256 spentIndex, uint256 bitOffset, bytes32 replay) = _calcSpentIndexOffset(index);
if (_isSpent(bitOffset, replay)) revert AlreadySpent(index);
spent[spentIndex] = (replay | bytes32(1 << bitOffset));
}
function executeBridgeCall(
address to,
uint256 value,
bytes memory data
) internal {
(bool success, bytes memory returndata) = bridge.executeCall(to, value, data);
if (!success) {
if (returndata.length > 0) {
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert BridgeCallFailed();
}
}
}
function calculateItemHash(
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
uint256 value,
bytes calldata data
) public pure returns (bytes32) {
return
keccak256(abi.encodePacked(l2Sender, to, l2Block, l1Block, l2Timestamp, value, data));
}
function calculateMerkleRoot(
bytes32[] memory proof,
uint256 path,
bytes32 item
) public pure returns (bytes32) {
return MerkleLib.calculateRoot(proof, path, keccak256(abi.encodePacked(item)));
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./IRollupEventInbox.sol";
import "../bridge/IBridge.sol";
import "../bridge/IDelayedMessageProvider.sol";
import "../libraries/DelegateCallAware.sol";
import {INITIALIZATION_MSG_TYPE} from "../libraries/MessageTypes.sol";
import {AlreadyInit, HadZeroInit} from "../libraries/Error.sol";
/**
* @title The inbox for rollup protocol events
*/
contract RollupEventInbox is IRollupEventInbox, IDelayedMessageProvider, DelegateCallAware {
IBridge public override bridge;
address public override rollup;
modifier onlyRollup() {
require(msg.sender == rollup, "ONLY_ROLLUP");
_;
}
function initialize(IBridge _bridge) external override onlyDelegated {
if (address(bridge) != address(0)) revert AlreadyInit();
if (address(_bridge) == address(0)) revert HadZeroInit();
bridge = _bridge;
rollup = address(_bridge.rollup());
}
function rollupInitialized(uint256 chainId) external override onlyRollup {
bytes memory initMsg = abi.encodePacked(chainId);
uint256 num = bridge.enqueueDelayedMessage(
INITIALIZATION_MSG_TYPE,
address(0),
keccak256(initMsg)
);
emit InboxMessageDelivered(num, initMsg);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}// 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);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
// solhint-disable-next-line compiler-version
pragma solidity >=0.6.9 <0.9.0;
import "./IBridge.sol";
import "./IDelayedMessageProvider.sol";
import "./ISequencerInbox.sol";
interface IInbox is IDelayedMessageProvider {
function bridge() external view returns (IBridge);
function sequencerInbox() external view returns (ISequencerInbox);
/**
* @notice Send a generic L2 message to the chain
* @dev This method is an optimization to avoid having to emit the entirety of the messageData in a log. Instead validators are expected to be able to parse the data from the transaction's input
* @param messageData Data of the message being sent
*/
function sendL2MessageFromOrigin(bytes calldata messageData) external returns (uint256);
/**
* @notice Send a generic L2 message to the chain
* @dev This method can be used to send any type of message that doesn't require L1 validation
* @param messageData Data of the message being sent
*/
function sendL2Message(bytes calldata messageData) external returns (uint256);
function sendL1FundedUnsignedTransaction(
uint256 gasLimit,
uint256 maxFeePerGas,
uint256 nonce,
address to,
bytes calldata data
) external payable returns (uint256);
function sendL1FundedContractTransaction(
uint256 gasLimit,
uint256 maxFeePerGas,
address to,
bytes calldata data
) external payable returns (uint256);
function sendUnsignedTransaction(
uint256 gasLimit,
uint256 maxFeePerGas,
uint256 nonce,
address to,
uint256 value,
bytes calldata data
) external returns (uint256);
function sendContractTransaction(
uint256 gasLimit,
uint256 maxFeePerGas,
address to,
uint256 value,
bytes calldata data
) external returns (uint256);
/**
* @notice Get the L1 fee for submitting a retryable
* @dev This fee can be paid by funds already in the L2 aliased address or by the current message value
* @dev This formula may change in the future, to future proof your code query this method instead of inlining!!
* @param dataLength The length of the retryable's calldata, in bytes
* @param baseFee The block basefee when the retryable is included in the chain, if 0 current block.basefee will be used
*/
function calculateRetryableSubmissionFee(uint256 dataLength, uint256 baseFee)
external
view
returns (uint256);
/**
* @notice Deposit eth from L1 to L2 to address of the sender if sender is an EOA, and to its aliased address if the sender is a contract
* @dev This does not trigger the fallback function when receiving in the L2 side.
* Look into retryable tickets if you are interested in this functionality.
* @dev This function should not be called inside contract constructors
*/
function depositEth() external payable returns (uint256);
/**
* @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts
* @dev all msg.value will deposited to callValueRefundAddress on L2
* @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error
* @param to destination L2 contract address
* @param l2CallValue call value for retryable L2 message
* @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
* @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance
* @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled
* @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
* @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
* @param data ABI encoded data of L2 message
* @return unique message number of the retryable transaction
*/
function createRetryableTicket(
address to,
uint256 l2CallValue,
uint256 maxSubmissionCost,
address excessFeeRefundAddress,
address callValueRefundAddress,
uint256 gasLimit,
uint256 maxFeePerGas,
bytes calldata data
) external payable returns (uint256);
/**
* @notice Put a message in the L2 inbox that can be reexecuted for some fixed amount of time if it reverts
* @dev Same as createRetryableTicket, but does not guarantee that submission will succeed by requiring the needed funds
* come from the deposit alone, rather than falling back on the user's L2 balance
* @dev Advanced usage only (does not rewrite aliases for excessFeeRefundAddress and callValueRefundAddress).
* createRetryableTicket method is the recommended standard.
* @dev Gas limit and maxFeePerGas should not be set to 1 as that is used to trigger the RetryableData error
* @param to destination L2 contract address
* @param l2CallValue call value for retryable L2 message
* @param maxSubmissionCost Max gas deducted from user's L2 balance to cover base submission fee
* @param excessFeeRefundAddress gasLimit x maxFeePerGas - execution cost gets credited here on L2 balance
* @param callValueRefundAddress l2Callvalue gets credited here on L2 if retryable txn times out or gets cancelled
* @param gasLimit Max gas deducted from user's L2 balance to cover L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
* @param maxFeePerGas price bid for L2 execution. Should not be set to 1 (magic value used to trigger the RetryableData error)
* @param data ABI encoded data of L2 message
* @return unique message number of the retryable transaction
*/
function unsafeCreateRetryableTicket(
address to,
uint256 l2CallValue,
uint256 maxSubmissionCost,
address excessFeeRefundAddress,
address callValueRefundAddress,
uint256 gasLimit,
uint256 maxFeePerGas,
bytes calldata data
) external payable returns (uint256);
// ---------- onlyRollupOrOwner functions ----------
/// @notice pauses all inbox functionality
function pause() external;
/// @notice unpauses all inbox functionality
function unpause() external;
// ---------- initializer ----------
/**
* @dev function to be called one time during the inbox upgrade process
* this is used to fix the storage slots
*/
function postUpgradeInit(IBridge _bridge) external;
function initialize(IBridge _bridge, ISequencerInbox _sequencerInbox) external;
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./RollupLib.sol";
import "./IRollupCore.sol";
import "../bridge/ISequencerInbox.sol";
import "../bridge/IOutbox.sol";
import "../bridge/IOwnable.sol";
interface IRollupUserAbs is IRollupCore, IOwnable {
/// @dev the user logic just validated configuration and shouldn't write to state during init
/// this allows the admin logic to ensure consistency on parameters.
function initialize(address stakeToken) external view;
function isERC20Enabled() external view returns (bool);
function rejectNextNode(address stakerAddress) external;
function confirmNextNode(bytes32 blockHash, bytes32 sendRoot) external;
function stakeOnExistingNode(uint64 nodeNum, bytes32 nodeHash) external;
function stakeOnNewNode(
RollupLib.Assertion memory assertion,
bytes32 expectedNodeHash,
uint256 prevNodeInboxMaxCount
) external;
function returnOldDeposit(address stakerAddress) external;
function reduceDeposit(uint256 target) external;
function removeZombie(uint256 zombieNum, uint256 maxNodes) external;
function removeOldZombies(uint256 startIndex) external;
function requiredStake(
uint256 blockNumber,
uint64 firstUnresolvedNodeNum,
uint64 latestCreatedNode
) external view returns (uint256);
function currentRequiredStake() external view returns (uint256);
function countStakedZombies(uint64 nodeNum) external view returns (uint256);
function countZombiesStakedOnChildren(uint64 nodeNum) external view returns (uint256);
function requireUnresolvedExists() external view;
function requireUnresolved(uint256 nodeNum) external view;
function withdrawStakerFunds() external returns (uint256);
function createChallenge(
address[2] calldata stakers,
uint64[2] calldata nodeNums,
MachineStatus[2] calldata machineStatuses,
GlobalState[2] calldata globalStates,
uint64 numBlocks,
bytes32 secondExecutionHash,
uint256[2] calldata proposedTimes,
bytes32[2] calldata wasmModuleRoots
) external;
}
interface IRollupUser is IRollupUserAbs {
function newStakeOnExistingNode(uint64 nodeNum, bytes32 nodeHash) external payable;
function newStakeOnNewNode(
RollupLib.Assertion calldata assertion,
bytes32 expectedNodeHash,
uint256 prevNodeInboxMaxCount
) external payable;
function addToDeposit(address stakerAddress) external payable;
}
interface IRollupUserERC20 is IRollupUserAbs {
function newStakeOnExistingNode(
uint256 tokenAmount,
uint64 nodeNum,
bytes32 nodeHash
) external;
function newStakeOnNewNode(
uint256 tokenAmount,
RollupLib.Assertion calldata assertion,
bytes32 expectedNodeHash,
uint256 prevNodeInboxMaxCount
) external;
function addToDeposit(address stakerAddress, uint256 tokenAmount) external;
}
interface IRollupAdmin {
event OwnerFunctionCalled(uint256 indexed id);
function initialize(Config calldata config, ContractDependencies calldata connectedContracts)
external;
/**
* @notice Add a contract authorized to put messages into this rollup's inbox
* @param _outbox Outbox contract to add
*/
function setOutbox(IOutbox _outbox) external;
/**
* @notice Disable an old outbox from interacting with the bridge
* @param _outbox Outbox contract to remove
*/
function removeOldOutbox(address _outbox) external;
/**
* @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;
/**
* @notice Pause interaction with the rollup contract
*/
function pause() external;
/**
* @notice Resume interaction with the rollup contract
*/
function resume() external;
/**
* @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[] memory _validator, bool[] memory _val) external;
/**
* @notice Set a new owner address for the rollup proxy
* @param newOwner address of new rollup owner
*/
function setOwner(address newOwner) external;
/**
* @notice Set minimum assertion period for the rollup
* @param newPeriod new minimum period for assertions
*/
function setMinimumAssertionPeriod(uint256 newPeriod) external;
/**
* @notice Set number of blocks until a node is considered confirmed
* @param newConfirmPeriod new number of blocks until a node is confirmed
*/
function setConfirmPeriodBlocks(uint64 newConfirmPeriod) external;
/**
* @notice Set number of extra blocks after a challenge
* @param newExtraTimeBlocks new number of blocks
*/
function setExtraChallengeTimeBlocks(uint64 newExtraTimeBlocks) external;
/**
* @notice Set base stake required for an assertion
* @param newBaseStake maximum avmgas to be used per block
*/
function setBaseStake(uint256 newBaseStake) external;
/**
* @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 logic!
* @param newStakeToken address of token used for staking
*/
function setStakeToken(address newStakeToken) external;
/**
* @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;
function forceResolveChallenge(address[] memory stackerA, address[] memory stackerB) external;
function forceRefundStaker(address[] memory stacker) external;
function forceCreateNode(
uint64 prevNode,
uint256 prevNodeInboxMaxCount,
RollupLib.Assertion memory assertion,
bytes32 expectedNodeHash
) external;
function forceConfirmNode(
uint64 nodeNum,
bytes32 blockHash,
bytes32 sendRoot
) external;
function setLoserStakeEscrow(address newLoserStakerEscrow) external;
/**
* @notice Set the proving WASM module root
* @param newWasmModuleRoot new module root
*/
function setWasmModuleRoot(bytes32 newWasmModuleRoot) external;
/**
* @notice set a new sequencer inbox contract
* @param _sequencerInbox new address of sequencer inbox
*/
function setSequencerInbox(address _sequencerInbox) external;
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
// solhint-disable-next-line compiler-version
pragma solidity >=0.6.9 <0.9.0;
interface IGasRefunder {
function onGasSpent(
address payable spender,
uint256 gasUsed,
uint256 calldataSize
) external returns (bool success);
}
abstract contract GasRefundEnabled {
/// @dev this refunds the sender for execution costs of the tx
/// calldata costs are only refunded if `msg.sender == tx.origin` to guarantee the value refunded relates to charging
/// for the `tx.input`. this avoids a possible attack where you generate large calldata from a contract and get over-refunded
modifier refundsGas(IGasRefunder gasRefunder) {
uint256 startGasLeft = gasleft();
_;
if (address(gasRefunder) != address(0)) {
uint256 calldataSize = 0;
// if triggered in a contract call, the spender may be overrefunded by appending dummy data to the call
// so we check if it is a top level call, which would mean the sender paid calldata as part of tx.input
// solhint-disable-next-line avoid-tx-origin
if (msg.sender == tx.origin) {
assembly {
calldataSize := calldatasize()
}
}
gasRefunder.onGasSpent(payable(msg.sender), startGasLeft - gasleft(), calldataSize);
}
}
}// Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.4; // 90% of Geth's 128KB tx size limit, leaving ~13KB for proving uint256 constant MAX_DATA_SIZE = 117964; uint64 constant NO_CHAL_INDEX = 0;
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
// solhint-disable-next-line compiler-version
pragma solidity >=0.6.9 <0.9.0;
interface IDelayedMessageProvider {
/// @dev event emitted when a inbox message is added to the Bridge's delayed accumulator
event InboxMessageDelivered(uint256 indexed messageNum, bytes data);
/// @dev event emitted when a inbox message is added to the Bridge's delayed accumulator
/// same as InboxMessageDelivered but the batch data is available in tx.input
event InboxMessageDeliveredFromOrigin(uint256 indexed messageNum);
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../challenge/IChallengeManager.sol";
import "../challenge/ChallengeLib.sol";
import "../state/GlobalState.sol";
import "../bridge/ISequencerInbox.sol";
import "../bridge/IBridge.sol";
import "../bridge/IOutbox.sol";
import "../bridge/IInbox.sol";
import "./IRollupEventInbox.sol";
import "./IRollupLogic.sol";
struct Config {
uint64 confirmPeriodBlocks;
uint64 extraChallengeTimeBlocks;
address stakeToken;
uint256 baseStake;
bytes32 wasmModuleRoot;
address owner;
address loserStakeEscrow;
uint256 chainId;
uint64 genesisBlockNum;
ISequencerInbox.MaxTimeVariation sequencerInboxMaxTimeVariation;
}
struct ContractDependencies {
IBridge bridge;
ISequencerInbox sequencerInbox;
IInbox inbox;
IOutbox outbox;
IRollupEventInbox rollupEventInbox;
IChallengeManager challengeManager;
IRollupAdmin rollupAdminLogic;
IRollupUser rollupUserLogic;
// misc contracts that are useful when interacting with the rollup
address validatorUtils;
address validatorWalletCreator;
}
library RollupLib {
using GlobalStateLib for GlobalState;
struct ExecutionState {
GlobalState globalState;
MachineStatus machineStatus;
}
function stateHash(ExecutionState calldata execState, uint256 inboxMaxCount)
internal
pure
returns (bytes32)
{
return
keccak256(
abi.encodePacked(
execState.globalState.hash(),
inboxMaxCount,
execState.machineStatus
)
);
}
/// @dev same as stateHash but expects execState in memory instead of calldata
function stateHashMem(ExecutionState memory execState, uint256 inboxMaxCount)
internal
pure
returns (bytes32)
{
return
keccak256(
abi.encodePacked(
execState.globalState.hash(),
inboxMaxCount,
execState.machineStatus
)
);
}
struct Assertion {
ExecutionState beforeState;
ExecutionState afterState;
uint64 numBlocks;
}
function executionHash(Assertion memory assertion) internal pure returns (bytes32) {
MachineStatus[2] memory statuses;
statuses[0] = assertion.beforeState.machineStatus;
statuses[1] = assertion.afterState.machineStatus;
GlobalState[2] memory globalStates;
globalStates[0] = assertion.beforeState.globalState;
globalStates[1] = assertion.afterState.globalState;
// TODO: benchmark how much this abstraction adds of gas overhead
return executionHash(statuses, globalStates, assertion.numBlocks);
}
function executionHash(
MachineStatus[2] memory statuses,
GlobalState[2] memory globalStates,
uint64 numBlocks
) internal pure returns (bytes32) {
bytes32[] memory segments = new bytes32[](2);
segments[0] = ChallengeLib.blockStateHash(statuses[0], globalStates[0].hash());
segments[1] = ChallengeLib.blockStateHash(statuses[1], globalStates[1].hash());
return ChallengeLib.hashChallengeState(0, numBlocks, segments);
}
function challengeRootHash(
bytes32 execution,
uint256 proposedTime,
bytes32 wasmModuleRoot
) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(execution, proposedTime, wasmModuleRoot));
}
function confirmHash(Assertion memory assertion) internal pure returns (bytes32) {
return
confirmHash(
assertion.afterState.globalState.getBlockHash(),
assertion.afterState.globalState.getSendRoot()
);
}
function confirmHash(bytes32 blockHash, bytes32 sendRoot) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(blockHash, sendRoot));
}
function nodeHash(
bool hasSibling,
bytes32 lastHash,
bytes32 assertionExecHash,
bytes32 inboxAcc,
bytes32 wasmModuleRoot
) internal pure returns (bytes32) {
uint8 hasSiblingInt = hasSibling ? 1 : 0;
return
keccak256(
abi.encodePacked(
hasSiblingInt,
lastHash,
assertionExecHash,
inboxAcc,
wasmModuleRoot
)
);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./Node.sol";
import "./RollupLib.sol";
interface IRollupCore {
struct Staker {
uint256 amountStaked;
uint64 index;
uint64 latestStakedNode;
// currentChallenge is 0 if staker is not in a challenge
uint64 currentChallenge;
bool isStaked;
}
event RollupInitialized(bytes32 machineHash, uint256 chainId);
event NodeCreated(
uint64 indexed nodeNum,
bytes32 indexed parentNodeHash,
bytes32 indexed nodeHash,
bytes32 executionHash,
RollupLib.Assertion assertion,
bytes32 afterInboxBatchAcc,
bytes32 wasmModuleRoot,
uint256 inboxMaxCount
);
event NodeConfirmed(uint64 indexed nodeNum, bytes32 blockHash, bytes32 sendRoot);
event NodeRejected(uint64 indexed nodeNum);
event RollupChallengeStarted(
uint64 indexed challengeIndex,
address asserter,
address challenger,
uint64 challengedNode
);
event UserStakeUpdated(address indexed user, uint256 initialBalance, uint256 finalBalance);
event UserWithdrawableFundsUpdated(
address indexed user,
uint256 initialBalance,
uint256 finalBalance
);
function confirmPeriodBlocks() external view returns (uint64);
function extraChallengeTimeBlocks() external view returns (uint64);
function chainId() external view returns (uint256);
function baseStake() external view returns (uint256);
function wasmModuleRoot() external view returns (bytes32);
function bridge() external view returns (IBridge);
function sequencerInbox() external view returns (ISequencerInbox);
function outbox() external view returns (IOutbox);
function rollupEventInbox() external view returns (IRollupEventInbox);
function challengeManager() external view returns (IChallengeManager);
function loserStakeEscrow() external view returns (address);
function stakeToken() external view returns (address);
function minimumAssertionPeriod() external view returns (uint256);
function isValidator(address) external view returns (bool);
/**
* @notice Get the Node for the given index.
*/
function getNode(uint64 nodeNum) external view returns (Node memory);
/**
* @notice Check if the specified node has been staked on by the provided staker.
* Only accurate at the latest confirmed node and afterwards.
*/
function nodeHasStaker(uint64 nodeNum, address staker) external view returns (bool);
/**
* @notice Get the address of the staker at the given index
* @param stakerNum Index of the staker
* @return Address of the staker
*/
function getStakerAddress(uint64 stakerNum) external view returns (address);
/**
* @notice Check whether the given staker is staked
* @param staker Staker address to check
* @return True or False for whether the staker was staked
*/
function isStaked(address staker) external view returns (bool);
/**
* @notice Get the latest staked node of the given staker
* @param staker Staker address to lookup
* @return Latest node staked of the staker
*/
function latestStakedNode(address staker) external view returns (uint64);
/**
* @notice Get the current challenge of the given staker
* @param staker Staker address to lookup
* @return Current challenge of the staker
*/
function currentChallenge(address staker) external view returns (uint64);
/**
* @notice Get the amount staked of the given staker
* @param staker Staker address to lookup
* @return Amount staked of the staker
*/
function amountStaked(address staker) external view returns (uint256);
/**
* @notice Retrieves stored information about a requested staker
* @param staker Staker address to retrieve
* @return A structure with information about the requested staker
*/
function getStaker(address staker) external view returns (Staker memory);
/**
* @notice Get the original staker address of the zombie at the given index
* @param zombieNum Index of the zombie to lookup
* @return Original staker address of the zombie
*/
function zombieAddress(uint256 zombieNum) external view returns (address);
/**
* @notice Get Latest node that the given zombie at the given index is staked on
* @param zombieNum Index of the zombie to lookup
* @return Latest node that the given zombie is staked on
*/
function zombieLatestStakedNode(uint256 zombieNum) external view returns (uint64);
/// @return Current number of un-removed zombies
function zombieCount() external view returns (uint256);
function isZombie(address staker) external view returns (bool);
/**
* @notice Get the amount of funds withdrawable by the given address
* @param owner Address to check the funds of
* @return Amount of funds withdrawable by owner
*/
function withdrawableFunds(address owner) external view returns (uint256);
/**
* @return Index of the first unresolved node
* @dev If all nodes have been resolved, this will be latestNodeCreated + 1
*/
function firstUnresolvedNode() external view returns (uint64);
/// @return Index of the latest confirmed node
function latestConfirmed() external view returns (uint64);
/// @return Index of the latest rollup node created
function latestNodeCreated() external view returns (uint64);
/// @return Ethereum block that the most recent stake was created
function lastStakeBlock() external view returns (uint64);
/// @return Number of active stakers currently staked
function stakerCount() external view returns (uint64);
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
// solhint-disable-next-line compiler-version
pragma solidity >=0.6.9 <0.9.0;
import "./IBridge.sol";
interface IOutbox {
event SendRootUpdated(bytes32 indexed blockHash, bytes32 indexed outputRoot);
event OutBoxTransactionExecuted(
address indexed to,
address indexed l2Sender,
uint256 indexed zero,
uint256 transactionIndex
);
function rollup() external view returns (address); // the rollup contract
function bridge() external view returns (IBridge); // the bridge contract
function spent(uint256) external view returns (bytes32); // packed spent bitmap
function roots(bytes32) external view returns (bytes32); // maps root hashes => L2 block hash
// solhint-disable-next-line func-name-mixedcase
function OUTBOX_VERSION() external view returns (uint128); // the outbox version
function updateSendRoot(bytes32 sendRoot, bytes32 l2BlockHash) external;
/// @notice When l2ToL1Sender returns a nonzero address, the message was originated by an L2 account
/// When the return value is zero, that means this is a system message
/// @dev the l2ToL1Sender behaves as the tx.origin, the msg.sender should be validated to protect against reentrancies
function l2ToL1Sender() external view returns (address);
/// @return l2Block return L2 block when the L2 tx was initiated or 0 if no L2 to L1 transaction is active
function l2ToL1Block() external view returns (uint256);
/// @return l1Block return L1 block when the L2 tx was initiated or 0 if no L2 to L1 transaction is active
function l2ToL1EthBlock() external view returns (uint256);
/// @return timestamp return L2 timestamp when the L2 tx was initiated or 0 if no L2 to L1 transaction is active
function l2ToL1Timestamp() external view returns (uint256);
/// @return outputId returns the unique output identifier of the L2 to L1 tx or 0 if no L2 to L1 transaction is active
function l2ToL1OutputId() external view returns (bytes32);
/**
* @notice Executes a messages in an Outbox entry.
* @dev Reverts if dispute period hasn't expired, since the outbox entry
* is only created once the rollup confirms the respective assertion.
* @dev it is not possible to execute any L2-to-L1 transaction which contains data
* to a contract address without any code (as enforced by the Bridge contract).
* @param proof Merkle proof of message inclusion in send root
* @param index Merkle path to message
* @param l2Sender sender if original message (i.e., caller of ArbSys.sendTxToL1)
* @param to destination address for L1 contract call
* @param l2Block l2 block number at which sendTxToL1 call was made
* @param l1Block l1 block number at which sendTxToL1 call was made
* @param l2Timestamp l2 Timestamp at which sendTxToL1 call was made
* @param value wei in L1 message
* @param data abi-encoded L1 message data
*/
function executeTransaction(
bytes32[] calldata proof,
uint256 index,
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
uint256 value,
bytes calldata data
) external;
/**
* @dev function used to simulate the result of a particular function call from the outbox
* it is useful for things such as gas estimates. This function includes all costs except for
* proof validation (which can be considered offchain as a somewhat of a fixed cost - it's
* not really a fixed cost, but can be treated as so with a fixed overhead for gas estimation).
* We can't include the cost of proof validation since this is intended to be used to simulate txs
* that are included in yet-to-be confirmed merkle roots. The simulation entrypoint could instead pretend
* to confirm a pending merkle root, but that would be less pratical for integrating with tooling.
* It is only possible to trigger it when the msg sender is address zero, which should be impossible
* unless under simulation in an eth_call or eth_estimateGas
*/
function executeTransactionSimulation(
uint256 index,
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
uint256 value,
bytes calldata data
) external;
/**
* @param index Merkle path to message
* @return true if the message has been spent
*/
function isSpent(uint256 index) external view returns (bool);
function calculateItemHash(
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
uint256 value,
bytes calldata data
) external pure returns (bytes32);
function calculateMerkleRoot(
bytes32[] memory proof,
uint256 path,
bytes32 item
) external pure returns (bytes32);
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../state/Machine.sol";
import "../bridge/IBridge.sol";
import "../bridge/ISequencerInbox.sol";
import "../osp/IOneStepProofEntry.sol";
import "./IChallengeResultReceiver.sol";
import "./ChallengeLib.sol";
interface IChallengeManager {
enum ChallengeTerminationType {
TIMEOUT,
BLOCK_PROOF,
EXECUTION_PROOF,
CLEARED
}
event InitiatedChallenge(
uint64 indexed challengeIndex,
GlobalState startState,
GlobalState endState
);
event Bisected(
uint64 indexed challengeIndex,
bytes32 indexed challengeRoot,
uint256 challengedSegmentStart,
uint256 challengedSegmentLength,
bytes32[] chainHashes
);
event ExecutionChallengeBegun(uint64 indexed challengeIndex, uint256 blockSteps);
event OneStepProofCompleted(uint64 indexed challengeIndex);
event ChallengeEnded(uint64 indexed challengeIndex, ChallengeTerminationType kind);
function initialize(
IChallengeResultReceiver resultReceiver_,
ISequencerInbox sequencerInbox_,
IBridge bridge_,
IOneStepProofEntry osp_
) external;
function createChallenge(
bytes32 wasmModuleRoot_,
MachineStatus[2] calldata startAndEndMachineStatuses_,
GlobalState[2] calldata startAndEndGlobalStates_,
uint64 numBlocks,
address asserter_,
address challenger_,
uint256 asserterTimeLeft_,
uint256 challengerTimeLeft_
) external returns (uint64);
function challengeInfo(uint64 challengeIndex_)
external
view
returns (ChallengeLib.Challenge memory);
function currentResponder(uint64 challengeIndex) external view returns (address);
function isTimedOut(uint64 challengeIndex) external view returns (bool);
function currentResponderTimeLeft(uint64 challengeIndex_) external view returns (uint256);
function clearChallenge(uint64 challengeIndex_) external;
function timeout(uint64 challengeIndex_) external;
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../state/Machine.sol";
import "../state/GlobalState.sol";
library ChallengeLib {
using MachineLib for Machine;
using ChallengeLib for Challenge;
/// @dev It's assumed that that uninitialzed challenges have mode NONE
enum ChallengeMode {
NONE,
BLOCK,
EXECUTION
}
struct Participant {
address addr;
uint256 timeLeft;
}
struct Challenge {
Participant current;
Participant next;
uint256 lastMoveTimestamp;
bytes32 wasmModuleRoot;
bytes32 challengeStateHash;
uint64 maxInboxMessages;
ChallengeMode mode;
}
struct SegmentSelection {
uint256 oldSegmentsStart;
uint256 oldSegmentsLength;
bytes32[] oldSegments;
uint256 challengePosition;
}
function timeUsedSinceLastMove(Challenge storage challenge) internal view returns (uint256) {
return block.timestamp - challenge.lastMoveTimestamp;
}
function isTimedOut(Challenge storage challenge) internal view returns (bool) {
return challenge.timeUsedSinceLastMove() > challenge.current.timeLeft;
}
function getStartMachineHash(bytes32 globalStateHash, bytes32 wasmModuleRoot)
internal
pure
returns (bytes32)
{
// Start the value stack with the function call ABI for the entrypoint
Value[] memory startingValues = new Value[](3);
startingValues[0] = ValueLib.newRefNull();
startingValues[1] = ValueLib.newI32(0);
startingValues[2] = ValueLib.newI32(0);
ValueArray memory valuesArray = ValueArray({inner: startingValues});
ValueStack memory values = ValueStack({proved: valuesArray, remainingHash: 0});
ValueStack memory internalStack;
StackFrameWindow memory frameStack;
Machine memory mach = Machine({
status: MachineStatus.RUNNING,
valueStack: values,
internalStack: internalStack,
frameStack: frameStack,
globalStateHash: globalStateHash,
moduleIdx: 0,
functionIdx: 0,
functionPc: 0,
modulesRoot: wasmModuleRoot
});
return mach.hash();
}
function getEndMachineHash(MachineStatus status, bytes32 globalStateHash)
internal
pure
returns (bytes32)
{
if (status == MachineStatus.FINISHED) {
return keccak256(abi.encodePacked("Machine finished:", globalStateHash));
} else if (status == MachineStatus.ERRORED) {
return keccak256(abi.encodePacked("Machine errored:"));
} else if (status == MachineStatus.TOO_FAR) {
return keccak256(abi.encodePacked("Machine too far:"));
} else {
revert("BAD_BLOCK_STATUS");
}
}
function extractChallengeSegment(SegmentSelection calldata selection)
internal
pure
returns (uint256 segmentStart, uint256 segmentLength)
{
uint256 oldChallengeDegree = selection.oldSegments.length - 1;
segmentLength = selection.oldSegmentsLength / oldChallengeDegree;
// Intentionally done before challengeLength is potentially added to for the final segment
segmentStart = selection.oldSegmentsStart + segmentLength * selection.challengePosition;
if (selection.challengePosition == selection.oldSegments.length - 2) {
segmentLength += selection.oldSegmentsLength % oldChallengeDegree;
}
}
function hashChallengeState(
uint256 segmentsStart,
uint256 segmentsLength,
bytes32[] memory segments
) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(segmentsStart, segmentsLength, segments));
}
function blockStateHash(MachineStatus status, bytes32 globalStateHash)
internal
pure
returns (bytes32)
{
if (status == MachineStatus.FINISHED) {
return keccak256(abi.encodePacked("Block state:", globalStateHash));
} else if (status == MachineStatus.ERRORED) {
return keccak256(abi.encodePacked("Block state, errored:", globalStateHash));
} else if (status == MachineStatus.TOO_FAR) {
return keccak256(abi.encodePacked("Block state, too far:"));
} else {
revert("BAD_BLOCK_STATUS");
}
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
struct GlobalState {
bytes32[2] bytes32Vals;
uint64[2] u64Vals;
}
library GlobalStateLib {
uint16 internal constant BYTES32_VALS_NUM = 2;
uint16 internal constant U64_VALS_NUM = 2;
function hash(GlobalState memory state) internal pure returns (bytes32) {
return
keccak256(
abi.encodePacked(
"Global state:",
state.bytes32Vals[0],
state.bytes32Vals[1],
state.u64Vals[0],
state.u64Vals[1]
)
);
}
function getBlockHash(GlobalState memory state) internal pure returns (bytes32) {
return state.bytes32Vals[0];
}
function getSendRoot(GlobalState memory state) internal pure returns (bytes32) {
return state.bytes32Vals[1];
}
function getInboxPosition(GlobalState memory state) internal pure returns (uint64) {
return state.u64Vals[0];
}
function getPositionInMessage(GlobalState memory state) internal pure returns (uint64) {
return state.u64Vals[1];
}
function isEmpty(GlobalState calldata state) internal pure returns (bool) {
return (state.bytes32Vals[0] == bytes32(0) &&
state.bytes32Vals[1] == bytes32(0) &&
state.u64Vals[0] == 0 &&
state.u64Vals[1] == 0);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../bridge/IBridge.sol";
interface IRollupEventInbox {
function bridge() external view returns (IBridge);
function initialize(IBridge _bridge) external;
function rollup() external view returns (address);
function rollupInitialized(uint256 chainId) external;
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./ValueStack.sol";
import "./Instructions.sol";
import "./StackFrame.sol";
enum MachineStatus {
RUNNING,
FINISHED,
ERRORED,
TOO_FAR
}
struct Machine {
MachineStatus status;
ValueStack valueStack;
ValueStack internalStack;
StackFrameWindow frameStack;
bytes32 globalStateHash;
uint32 moduleIdx;
uint32 functionIdx;
uint32 functionPc;
bytes32 modulesRoot;
}
library MachineLib {
using StackFrameLib for StackFrameWindow;
using ValueStackLib for ValueStack;
function hash(Machine memory mach) internal pure returns (bytes32) {
// Warning: the non-running hashes are replicated in Challenge
if (mach.status == MachineStatus.RUNNING) {
return
keccak256(
abi.encodePacked(
"Machine running:",
mach.valueStack.hash(),
mach.internalStack.hash(),
mach.frameStack.hash(),
mach.globalStateHash,
mach.moduleIdx,
mach.functionIdx,
mach.functionPc,
mach.modulesRoot
)
);
} else if (mach.status == MachineStatus.FINISHED) {
return keccak256(abi.encodePacked("Machine finished:", mach.globalStateHash));
} else if (mach.status == MachineStatus.ERRORED) {
return keccak256(abi.encodePacked("Machine errored:"));
} else if (mach.status == MachineStatus.TOO_FAR) {
return keccak256(abi.encodePacked("Machine too far:"));
} else {
revert("BAD_MACH_STATUS");
}
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./IOneStepProver.sol";
library OneStepProofEntryLib {
uint256 internal constant MAX_STEPS = 1 << 43;
}
interface IOneStepProofEntry {
function proveOneStep(
ExecutionContext calldata execCtx,
uint256 machineStep,
bytes32 beforeHash,
bytes calldata proof
) external view returns (bytes32 afterHash);
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
interface IChallengeResultReceiver {
function completeChallenge(
uint256 challengeIndex,
address winner,
address loser
) external;
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./Value.sol";
import "./ValueArray.sol";
struct ValueStack {
ValueArray proved;
bytes32 remainingHash;
}
library ValueStackLib {
using ValueLib for Value;
using ValueArrayLib for ValueArray;
function hash(ValueStack memory stack) internal pure returns (bytes32 h) {
h = stack.remainingHash;
uint256 len = stack.proved.length();
for (uint256 i = 0; i < len; i++) {
h = keccak256(abi.encodePacked("Value stack:", stack.proved.get(i).hash(), h));
}
}
function peek(ValueStack memory stack) internal pure returns (Value memory) {
uint256 len = stack.proved.length();
return stack.proved.get(len - 1);
}
function pop(ValueStack memory stack) internal pure returns (Value memory) {
return stack.proved.pop();
}
function push(ValueStack memory stack, Value memory val) internal pure {
return stack.proved.push(val);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
struct Instruction {
uint16 opcode;
uint256 argumentData;
}
library Instructions {
uint16 internal constant UNREACHABLE = 0x00;
uint16 internal constant NOP = 0x01;
uint16 internal constant RETURN = 0x0F;
uint16 internal constant CALL = 0x10;
uint16 internal constant CALL_INDIRECT = 0x11;
uint16 internal constant LOCAL_GET = 0x20;
uint16 internal constant LOCAL_SET = 0x21;
uint16 internal constant GLOBAL_GET = 0x23;
uint16 internal constant GLOBAL_SET = 0x24;
uint16 internal constant I32_LOAD = 0x28;
uint16 internal constant I64_LOAD = 0x29;
uint16 internal constant F32_LOAD = 0x2A;
uint16 internal constant F64_LOAD = 0x2B;
uint16 internal constant I32_LOAD8_S = 0x2C;
uint16 internal constant I32_LOAD8_U = 0x2D;
uint16 internal constant I32_LOAD16_S = 0x2E;
uint16 internal constant I32_LOAD16_U = 0x2F;
uint16 internal constant I64_LOAD8_S = 0x30;
uint16 internal constant I64_LOAD8_U = 0x31;
uint16 internal constant I64_LOAD16_S = 0x32;
uint16 internal constant I64_LOAD16_U = 0x33;
uint16 internal constant I64_LOAD32_S = 0x34;
uint16 internal constant I64_LOAD32_U = 0x35;
uint16 internal constant I32_STORE = 0x36;
uint16 internal constant I64_STORE = 0x37;
uint16 internal constant F32_STORE = 0x38;
uint16 internal constant F64_STORE = 0x39;
uint16 internal constant I32_STORE8 = 0x3A;
uint16 internal constant I32_STORE16 = 0x3B;
uint16 internal constant I64_STORE8 = 0x3C;
uint16 internal constant I64_STORE16 = 0x3D;
uint16 internal constant I64_STORE32 = 0x3E;
uint16 internal constant MEMORY_SIZE = 0x3F;
uint16 internal constant MEMORY_GROW = 0x40;
uint16 internal constant DROP = 0x1A;
uint16 internal constant SELECT = 0x1B;
uint16 internal constant I32_CONST = 0x41;
uint16 internal constant I64_CONST = 0x42;
uint16 internal constant F32_CONST = 0x43;
uint16 internal constant F64_CONST = 0x44;
uint16 internal constant I32_EQZ = 0x45;
uint16 internal constant I32_RELOP_BASE = 0x46;
uint16 internal constant IRELOP_EQ = 0;
uint16 internal constant IRELOP_NE = 1;
uint16 internal constant IRELOP_LT_S = 2;
uint16 internal constant IRELOP_LT_U = 3;
uint16 internal constant IRELOP_GT_S = 4;
uint16 internal constant IRELOP_GT_U = 5;
uint16 internal constant IRELOP_LE_S = 6;
uint16 internal constant IRELOP_LE_U = 7;
uint16 internal constant IRELOP_GE_S = 8;
uint16 internal constant IRELOP_GE_U = 9;
uint16 internal constant IRELOP_LAST = IRELOP_GE_U;
uint16 internal constant I64_EQZ = 0x50;
uint16 internal constant I64_RELOP_BASE = 0x51;
uint16 internal constant I32_UNOP_BASE = 0x67;
uint16 internal constant IUNOP_CLZ = 0;
uint16 internal constant IUNOP_CTZ = 1;
uint16 internal constant IUNOP_POPCNT = 2;
uint16 internal constant IUNOP_LAST = IUNOP_POPCNT;
uint16 internal constant I32_ADD = 0x6A;
uint16 internal constant I32_SUB = 0x6B;
uint16 internal constant I32_MUL = 0x6C;
uint16 internal constant I32_DIV_S = 0x6D;
uint16 internal constant I32_DIV_U = 0x6E;
uint16 internal constant I32_REM_S = 0x6F;
uint16 internal constant I32_REM_U = 0x70;
uint16 internal constant I32_AND = 0x71;
uint16 internal constant I32_OR = 0x72;
uint16 internal constant I32_XOR = 0x73;
uint16 internal constant I32_SHL = 0x74;
uint16 internal constant I32_SHR_S = 0x75;
uint16 internal constant I32_SHR_U = 0x76;
uint16 internal constant I32_ROTL = 0x77;
uint16 internal constant I32_ROTR = 0x78;
uint16 internal constant I64_UNOP_BASE = 0x79;
uint16 internal constant I64_ADD = 0x7C;
uint16 internal constant I64_SUB = 0x7D;
uint16 internal constant I64_MUL = 0x7E;
uint16 internal constant I64_DIV_S = 0x7F;
uint16 internal constant I64_DIV_U = 0x80;
uint16 internal constant I64_REM_S = 0x81;
uint16 internal constant I64_REM_U = 0x82;
uint16 internal constant I64_AND = 0x83;
uint16 internal constant I64_OR = 0x84;
uint16 internal constant I64_XOR = 0x85;
uint16 internal constant I64_SHL = 0x86;
uint16 internal constant I64_SHR_S = 0x87;
uint16 internal constant I64_SHR_U = 0x88;
uint16 internal constant I64_ROTL = 0x89;
uint16 internal constant I64_ROTR = 0x8A;
uint16 internal constant I32_WRAP_I64 = 0xA7;
uint16 internal constant I64_EXTEND_I32_S = 0xAC;
uint16 internal constant I64_EXTEND_I32_U = 0xAD;
uint16 internal constant I32_REINTERPRET_F32 = 0xBC;
uint16 internal constant I64_REINTERPRET_F64 = 0xBD;
uint16 internal constant F32_REINTERPRET_I32 = 0xBE;
uint16 internal constant F64_REINTERPRET_I64 = 0xBF;
uint16 internal constant I32_EXTEND_8S = 0xC0;
uint16 internal constant I32_EXTEND_16S = 0xC1;
uint16 internal constant I64_EXTEND_8S = 0xC2;
uint16 internal constant I64_EXTEND_16S = 0xC3;
uint16 internal constant I64_EXTEND_32S = 0xC4;
uint16 internal constant INIT_FRAME = 0x8002;
uint16 internal constant ARBITRARY_JUMP = 0x8003;
uint16 internal constant ARBITRARY_JUMP_IF = 0x8004;
uint16 internal constant MOVE_FROM_STACK_TO_INTERNAL = 0x8005;
uint16 internal constant MOVE_FROM_INTERNAL_TO_STACK = 0x8006;
uint16 internal constant DUP = 0x8008;
uint16 internal constant CROSS_MODULE_CALL = 0x8009;
uint16 internal constant CALLER_MODULE_INTERNAL_CALL = 0x800A;
uint16 internal constant GET_GLOBAL_STATE_BYTES32 = 0x8010;
uint16 internal constant SET_GLOBAL_STATE_BYTES32 = 0x8011;
uint16 internal constant GET_GLOBAL_STATE_U64 = 0x8012;
uint16 internal constant SET_GLOBAL_STATE_U64 = 0x8013;
uint16 internal constant READ_PRE_IMAGE = 0x8020;
uint16 internal constant READ_INBOX_MESSAGE = 0x8021;
uint16 internal constant HALT_AND_SET_FINISHED = 0x8022;
uint256 internal constant INBOX_INDEX_SEQUENCER = 0;
uint256 internal constant INBOX_INDEX_DELAYED = 1;
function hash(Instruction memory inst) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("Instruction:", inst.opcode, inst.argumentData));
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./Value.sol";
struct StackFrame {
Value returnPc;
bytes32 localsMerkleRoot;
uint32 callerModule;
uint32 callerModuleInternals;
}
struct StackFrameWindow {
StackFrame[] proved;
bytes32 remainingHash;
}
library StackFrameLib {
using ValueLib for Value;
function hash(StackFrame memory frame) internal pure returns (bytes32) {
return
keccak256(
abi.encodePacked(
"Stack frame:",
frame.returnPc.hash(),
frame.localsMerkleRoot,
frame.callerModule,
frame.callerModuleInternals
)
);
}
function hash(StackFrameWindow memory window) internal pure returns (bytes32 h) {
h = window.remainingHash;
for (uint256 i = 0; i < window.proved.length; i++) {
h = keccak256(abi.encodePacked("Stack frame stack:", hash(window.proved[i]), h));
}
}
function peek(StackFrameWindow memory window) internal pure returns (StackFrame memory) {
require(window.proved.length == 1, "BAD_WINDOW_LENGTH");
return window.proved[0];
}
function pop(StackFrameWindow memory window) internal pure returns (StackFrame memory frame) {
require(window.proved.length == 1, "BAD_WINDOW_LENGTH");
frame = window.proved[0];
window.proved = new StackFrame[](0);
}
function push(StackFrameWindow memory window, StackFrame memory frame) internal pure {
StackFrame[] memory newProved = new StackFrame[](window.proved.length + 1);
for (uint256 i = 0; i < window.proved.length; i++) {
newProved[i] = window.proved[i];
}
newProved[window.proved.length] = frame;
window.proved = newProved;
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
enum ValueType {
I32,
I64,
F32,
F64,
REF_NULL,
FUNC_REF,
INTERNAL_REF
}
struct Value {
ValueType valueType;
uint256 contents;
}
library ValueLib {
function hash(Value memory val) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("Value:", val.valueType, val.contents));
}
function maxValueType() internal pure returns (ValueType) {
return ValueType.INTERNAL_REF;
}
function assumeI32(Value memory val) internal pure returns (uint32) {
uint256 uintval = uint256(val.contents);
require(val.valueType == ValueType.I32, "NOT_I32");
require(uintval < (1 << 32), "BAD_I32");
return uint32(uintval);
}
function assumeI64(Value memory val) internal pure returns (uint64) {
uint256 uintval = uint256(val.contents);
require(val.valueType == ValueType.I64, "NOT_I64");
require(uintval < (1 << 64), "BAD_I64");
return uint64(uintval);
}
function newRefNull() internal pure returns (Value memory) {
return Value({valueType: ValueType.REF_NULL, contents: 0});
}
function newI32(uint32 x) internal pure returns (Value memory) {
return Value({valueType: ValueType.I32, contents: uint256(x)});
}
function newI64(uint64 x) internal pure returns (Value memory) {
return Value({valueType: ValueType.I64, contents: uint256(x)});
}
function newBoolean(bool x) internal pure returns (Value memory) {
if (x) {
return newI32(uint32(1));
} else {
return newI32(uint32(0));
}
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./Value.sol";
struct ValueArray {
Value[] inner;
}
library ValueArrayLib {
function get(ValueArray memory arr, uint256 index) internal pure returns (Value memory) {
return arr.inner[index];
}
function set(
ValueArray memory arr,
uint256 index,
Value memory val
) internal pure {
arr.inner[index] = val;
}
function length(ValueArray memory arr) internal pure returns (uint256) {
return arr.inner.length;
}
function push(ValueArray memory arr, Value memory val) internal pure {
Value[] memory newInner = new Value[](arr.inner.length + 1);
for (uint256 i = 0; i < arr.inner.length; i++) {
newInner[i] = arr.inner[i];
}
newInner[arr.inner.length] = val;
arr.inner = newInner;
}
function pop(ValueArray memory arr) internal pure returns (Value memory popped) {
popped = arr.inner[arr.inner.length - 1];
Value[] memory newInner = new Value[](arr.inner.length - 1);
for (uint256 i = 0; i < newInner.length; i++) {
newInner[i] = arr.inner[i];
}
arr.inner = newInner;
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../state/Machine.sol";
import "../state/Module.sol";
import "../state/Instructions.sol";
import "../bridge/ISequencerInbox.sol";
import "../bridge/IBridge.sol";
struct ExecutionContext {
uint256 maxInboxMessagesRead;
IBridge bridge;
}
abstract contract IOneStepProver {
function executeOneStep(
ExecutionContext memory execCtx,
Machine calldata mach,
Module calldata mod,
Instruction calldata instruction,
bytes calldata proof
) external view virtual returns (Machine memory result, Module memory resultMod);
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./ModuleMemory.sol";
struct Module {
bytes32 globalsMerkleRoot;
ModuleMemory moduleMemory;
bytes32 tablesMerkleRoot;
bytes32 functionsMerkleRoot;
uint32 internalsOffset;
}
library ModuleLib {
using ModuleMemoryLib for ModuleMemory;
function hash(Module memory mod) internal pure returns (bytes32) {
return
keccak256(
abi.encodePacked(
"Module:",
mod.globalsMerkleRoot,
mod.moduleMemory.hash(),
mod.tablesMerkleRoot,
mod.functionsMerkleRoot,
mod.internalsOffset
)
);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./MerkleProof.sol";
import "./Deserialize.sol";
struct ModuleMemory {
uint64 size;
uint64 maxSize;
bytes32 merkleRoot;
}
library ModuleMemoryLib {
using MerkleProofLib for MerkleProof;
function hash(ModuleMemory memory mem) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("Memory:", mem.size, mem.maxSize, mem.merkleRoot));
}
function proveLeaf(
ModuleMemory memory mem,
uint256 leafIdx,
bytes calldata proof,
uint256 startOffset
)
internal
pure
returns (
bytes32 contents,
uint256 offset,
MerkleProof memory merkle
)
{
offset = startOffset;
(contents, offset) = Deserialize.b32(proof, offset);
(merkle, offset) = Deserialize.merkleProof(proof, offset);
bytes32 recomputedRoot = merkle.computeRootFromMemory(leafIdx, contents);
require(recomputedRoot == mem.merkleRoot, "WRONG_MEM_ROOT");
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./Value.sol";
import "./Instructions.sol";
import "./Module.sol";
struct MerkleProof {
bytes32[] counterparts;
}
library MerkleProofLib {
using ModuleLib for Module;
using ValueLib for Value;
function computeRootFromValue(
MerkleProof memory proof,
uint256 index,
Value memory leaf
) internal pure returns (bytes32) {
return computeRootUnsafe(proof, index, leaf.hash(), "Value merkle tree:");
}
function computeRootFromInstruction(
MerkleProof memory proof,
uint256 index,
Instruction memory inst
) internal pure returns (bytes32) {
return computeRootUnsafe(proof, index, Instructions.hash(inst), "Instruction merkle tree:");
}
function computeRootFromFunction(
MerkleProof memory proof,
uint256 index,
bytes32 codeRoot
) internal pure returns (bytes32) {
bytes32 h = keccak256(abi.encodePacked("Function:", codeRoot));
return computeRootUnsafe(proof, index, h, "Function merkle tree:");
}
function computeRootFromMemory(
MerkleProof memory proof,
uint256 index,
bytes32 contents
) internal pure returns (bytes32) {
bytes32 h = keccak256(abi.encodePacked("Memory leaf:", contents));
return computeRootUnsafe(proof, index, h, "Memory merkle tree:");
}
function computeRootFromElement(
MerkleProof memory proof,
uint256 index,
bytes32 funcTypeHash,
Value memory val
) internal pure returns (bytes32) {
bytes32 h = keccak256(abi.encodePacked("Table element:", funcTypeHash, val.hash()));
return computeRootUnsafe(proof, index, h, "Table element merkle tree:");
}
function computeRootFromTable(
MerkleProof memory proof,
uint256 index,
uint8 tableType,
uint64 tableSize,
bytes32 elementsRoot
) internal pure returns (bytes32) {
bytes32 h = keccak256(abi.encodePacked("Table:", tableType, tableSize, elementsRoot));
return computeRootUnsafe(proof, index, h, "Table merkle tree:");
}
function computeRootFromModule(
MerkleProof memory proof,
uint256 index,
Module memory mod
) internal pure returns (bytes32) {
return computeRootUnsafe(proof, index, mod.hash(), "Module merkle tree:");
}
// WARNING: leafHash must be computed in such a way that it cannot be a non-leaf hash.
function computeRootUnsafe(
MerkleProof memory proof,
uint256 index,
bytes32 leafHash,
string memory prefix
) internal pure returns (bytes32 h) {
h = leafHash;
for (uint256 layer = 0; layer < proof.counterparts.length; layer++) {
if (index & 1 == 0) {
h = keccak256(abi.encodePacked(prefix, h, proof.counterparts[layer]));
} else {
h = keccak256(abi.encodePacked(prefix, proof.counterparts[layer], h));
}
index >>= 1;
}
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./Value.sol";
import "./ValueStack.sol";
import "./Machine.sol";
import "./Instructions.sol";
import "./StackFrame.sol";
import "./MerkleProof.sol";
import "./ModuleMemory.sol";
import "./Module.sol";
import "./GlobalState.sol";
library Deserialize {
function u8(bytes calldata proof, uint256 startOffset)
internal
pure
returns (uint8 ret, uint256 offset)
{
offset = startOffset;
ret = uint8(proof[offset]);
offset++;
}
function u16(bytes calldata proof, uint256 startOffset)
internal
pure
returns (uint16 ret, uint256 offset)
{
offset = startOffset;
for (uint256 i = 0; i < 16 / 8; i++) {
ret <<= 8;
ret |= uint8(proof[offset]);
offset++;
}
}
function u32(bytes calldata proof, uint256 startOffset)
internal
pure
returns (uint32 ret, uint256 offset)
{
offset = startOffset;
for (uint256 i = 0; i < 32 / 8; i++) {
ret <<= 8;
ret |= uint8(proof[offset]);
offset++;
}
}
function u64(bytes calldata proof, uint256 startOffset)
internal
pure
returns (uint64 ret, uint256 offset)
{
offset = startOffset;
for (uint256 i = 0; i < 64 / 8; i++) {
ret <<= 8;
ret |= uint8(proof[offset]);
offset++;
}
}
function u256(bytes calldata proof, uint256 startOffset)
internal
pure
returns (uint256 ret, uint256 offset)
{
offset = startOffset;
for (uint256 i = 0; i < 256 / 8; i++) {
ret <<= 8;
ret |= uint8(proof[offset]);
offset++;
}
}
function b32(bytes calldata proof, uint256 startOffset)
internal
pure
returns (bytes32 ret, uint256 offset)
{
offset = startOffset;
uint256 retInt;
(retInt, offset) = u256(proof, offset);
ret = bytes32(retInt);
}
function value(bytes calldata proof, uint256 startOffset)
internal
pure
returns (Value memory val, uint256 offset)
{
offset = startOffset;
uint8 typeInt = uint8(proof[offset]);
offset++;
require(typeInt <= uint8(ValueLib.maxValueType()), "BAD_VALUE_TYPE");
uint256 contents;
(contents, offset) = u256(proof, offset);
val = Value({valueType: ValueType(typeInt), contents: contents});
}
function valueStack(bytes calldata proof, uint256 startOffset)
internal
pure
returns (ValueStack memory stack, uint256 offset)
{
offset = startOffset;
bytes32 remainingHash;
(remainingHash, offset) = b32(proof, offset);
uint256 provedLength;
(provedLength, offset) = u256(proof, offset);
Value[] memory proved = new Value[](provedLength);
for (uint256 i = 0; i < proved.length; i++) {
(proved[i], offset) = value(proof, offset);
}
stack = ValueStack({proved: ValueArray(proved), remainingHash: remainingHash});
}
function instruction(bytes calldata proof, uint256 startOffset)
internal
pure
returns (Instruction memory inst, uint256 offset)
{
offset = startOffset;
uint16 opcode;
uint256 data;
(opcode, offset) = u16(proof, offset);
(data, offset) = u256(proof, offset);
inst = Instruction({opcode: opcode, argumentData: data});
}
function stackFrame(bytes calldata proof, uint256 startOffset)
internal
pure
returns (StackFrame memory window, uint256 offset)
{
offset = startOffset;
Value memory returnPc;
bytes32 localsMerkleRoot;
uint32 callerModule;
uint32 callerModuleInternals;
(returnPc, offset) = value(proof, offset);
(localsMerkleRoot, offset) = b32(proof, offset);
(callerModule, offset) = u32(proof, offset);
(callerModuleInternals, offset) = u32(proof, offset);
window = StackFrame({
returnPc: returnPc,
localsMerkleRoot: localsMerkleRoot,
callerModule: callerModule,
callerModuleInternals: callerModuleInternals
});
}
function stackFrameWindow(bytes calldata proof, uint256 startOffset)
internal
pure
returns (StackFrameWindow memory window, uint256 offset)
{
offset = startOffset;
bytes32 remainingHash;
(remainingHash, offset) = b32(proof, offset);
StackFrame[] memory proved;
if (proof[offset] != 0) {
offset++;
proved = new StackFrame[](1);
(proved[0], offset) = stackFrame(proof, offset);
} else {
offset++;
proved = new StackFrame[](0);
}
window = StackFrameWindow({proved: proved, remainingHash: remainingHash});
}
function moduleMemory(bytes calldata proof, uint256 startOffset)
internal
pure
returns (ModuleMemory memory mem, uint256 offset)
{
offset = startOffset;
uint64 size;
uint64 maxSize;
bytes32 root;
(size, offset) = u64(proof, offset);
(maxSize, offset) = u64(proof, offset);
(root, offset) = b32(proof, offset);
mem = ModuleMemory({size: size, maxSize: maxSize, merkleRoot: root});
}
function module(bytes calldata proof, uint256 startOffset)
internal
pure
returns (Module memory mod, uint256 offset)
{
offset = startOffset;
bytes32 globalsMerkleRoot;
ModuleMemory memory mem;
bytes32 tablesMerkleRoot;
bytes32 functionsMerkleRoot;
uint32 internalsOffset;
(globalsMerkleRoot, offset) = b32(proof, offset);
(mem, offset) = moduleMemory(proof, offset);
(tablesMerkleRoot, offset) = b32(proof, offset);
(functionsMerkleRoot, offset) = b32(proof, offset);
(internalsOffset, offset) = u32(proof, offset);
mod = Module({
globalsMerkleRoot: globalsMerkleRoot,
moduleMemory: mem,
tablesMerkleRoot: tablesMerkleRoot,
functionsMerkleRoot: functionsMerkleRoot,
internalsOffset: internalsOffset
});
}
function globalState(bytes calldata proof, uint256 startOffset)
internal
pure
returns (GlobalState memory state, uint256 offset)
{
offset = startOffset;
// using constant ints for array size requires newer solidity
bytes32[2] memory bytes32Vals;
uint64[2] memory u64Vals;
for (uint8 i = 0; i < GlobalStateLib.BYTES32_VALS_NUM; i++) {
(bytes32Vals[i], offset) = b32(proof, offset);
}
for (uint8 i = 0; i < GlobalStateLib.U64_VALS_NUM; i++) {
(u64Vals[i], offset) = u64(proof, offset);
}
state = GlobalState({bytes32Vals: bytes32Vals, u64Vals: u64Vals});
}
function machine(bytes calldata proof, uint256 startOffset)
internal
pure
returns (Machine memory mach, uint256 offset)
{
offset = startOffset;
MachineStatus status;
{
uint8 statusU8;
(statusU8, offset) = u8(proof, offset);
if (statusU8 == 0) {
status = MachineStatus.RUNNING;
} else if (statusU8 == 1) {
status = MachineStatus.FINISHED;
} else if (statusU8 == 2) {
status = MachineStatus.ERRORED;
} else if (statusU8 == 3) {
status = MachineStatus.TOO_FAR;
} else {
revert("UNKNOWN_MACH_STATUS");
}
}
ValueStack memory values;
ValueStack memory internalStack;
bytes32 globalStateHash;
uint32 moduleIdx;
uint32 functionIdx;
uint32 functionPc;
StackFrameWindow memory frameStack;
bytes32 modulesRoot;
(values, offset) = valueStack(proof, offset);
(internalStack, offset) = valueStack(proof, offset);
(frameStack, offset) = stackFrameWindow(proof, offset);
(globalStateHash, offset) = b32(proof, offset);
(moduleIdx, offset) = u32(proof, offset);
(functionIdx, offset) = u32(proof, offset);
(functionPc, offset) = u32(proof, offset);
(modulesRoot, offset) = b32(proof, offset);
mach = Machine({
status: status,
valueStack: values,
internalStack: internalStack,
frameStack: frameStack,
globalStateHash: globalStateHash,
moduleIdx: moduleIdx,
functionIdx: functionIdx,
functionPc: functionPc,
modulesRoot: modulesRoot
});
}
function merkleProof(bytes calldata proof, uint256 startOffset)
internal
pure
returns (MerkleProof memory merkle, uint256 offset)
{
offset = startOffset;
uint8 length;
(length, offset) = u8(proof, offset);
bytes32[] memory counterparts = new bytes32[](length);
for (uint8 i = 0; i < length; i++) {
(counterparts[i], offset) = b32(proof, offset);
}
merkle = MerkleProof(counterparts);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
struct Node {
// Hash of the state of the chain as of this node
bytes32 stateHash;
// Hash of the data that can be challenged
bytes32 challengeHash;
// Hash of the data that will be committed if this node is confirmed
bytes32 confirmData;
// Index of the node previous to this one
uint64 prevNum;
// Deadline at which this node can be confirmed
uint64 deadlineBlock;
// Deadline at which a child of this node can be confirmed
uint64 noChildConfirmedBeforeBlock;
// Number of stakers staked on this node. This includes real stakers and zombies
uint64 stakerCount;
// Number of stakers staked on a child node. This includes real stakers and zombies
uint64 childStakerCount;
// This value starts at zero and is set to a value when the first child is created. After that it is constant until the node is destroyed or the owner destroys pending nodes
uint64 firstChildBlock;
// The number of the latest child of this node to be created
uint64 latestChildNumber;
// The block number when this node was created
uint64 createdAtBlock;
// A hash of all the data needed to determine this node's validity, to protect against reorgs
bytes32 nodeHash;
}
/**
* @notice Utility functions for Node
*/
library NodeLib {
/**
* @notice Initialize a Node
* @param _stateHash Initial value of stateHash
* @param _challengeHash Initial value of challengeHash
* @param _confirmData Initial value of confirmData
* @param _prevNum Initial value of prevNum
* @param _deadlineBlock Initial value of deadlineBlock
* @param _nodeHash Initial value of nodeHash
*/
function createNode(
bytes32 _stateHash,
bytes32 _challengeHash,
bytes32 _confirmData,
uint64 _prevNum,
uint64 _deadlineBlock,
bytes32 _nodeHash
) internal view returns (Node memory) {
Node memory node;
node.stateHash = _stateHash;
node.challengeHash = _challengeHash;
node.confirmData = _confirmData;
node.prevNum = _prevNum;
node.deadlineBlock = _deadlineBlock;
node.noChildConfirmedBeforeBlock = _deadlineBlock;
node.createdAtBlock = uint64(block.number);
node.nodeHash = _nodeHash;
return node;
}
/**
* @notice Update child properties
* @param number The child number to set
*/
function childCreated(Node storage self, uint64 number) internal {
if (self.firstChildBlock == 0) {
self.firstChildBlock = uint64(block.number);
}
self.latestChildNumber = number;
}
/**
* @notice Update the child confirmed deadline
* @param deadline The new deadline to set
*/
function newChildConfirmDeadline(Node storage self, uint64 deadline) internal {
self.noChildConfirmedBeforeBlock = deadline;
}
/**
* @notice Check whether the current block number has met or passed the node's deadline
*/
function requirePastDeadline(Node memory self) internal view {
require(block.number >= self.deadlineBlock, "BEFORE_DEADLINE");
}
/**
* @notice Check whether the current block number has met or passed deadline for children of this node to be confirmed
*/
function requirePastChildConfirmDeadline(Node memory self) internal view {
require(block.number >= self.noChildConfirmedBeforeBlock, "CHILD_TOO_RECENT");
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
library AddressAliasHelper {
uint160 internal constant OFFSET = uint160(0x1111000000000000000000000000000000001111);
/// @notice Utility function that converts the address in the L1 that submitted a tx to
/// the inbox to the msg.sender viewed in the L2
/// @param l1Address the address in the L1 that triggered the tx to L2
/// @return l2Address L2 address as viewed in msg.sender
function applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
unchecked {
l2Address = address(uint160(l1Address) + OFFSET);
}
}
/// @notice Utility function that converts the msg.sender viewed in the L2 to the
/// address in the L1 that submitted a tx to the inbox
/// @param l2Address L2 address as viewed in msg.sender
/// @return l1Address the address in the L1 that triggered the tx to L2
function undoL1ToL2Alias(address l2Address) internal pure returns (address l1Address) {
unchecked {
l1Address = address(uint160(l2Address) - OFFSET);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which allows children to implement an emergency stop
* mechanism that can be triggered by an authorized account.
*
* This module is used through inheritance. It will make available the
* modifiers `whenNotPaused` and `whenPaused`, which can be applied to
* the functions of your contract. Note that they will not be pausable by
* simply including this module, only once the modifiers are put in place.
*/
abstract contract PausableUpgradeable is Initializable, ContextUpgradeable {
/**
* @dev Emitted when the pause is triggered by `account`.
*/
event Paused(address account);
/**
* @dev Emitted when the pause is lifted by `account`.
*/
event Unpaused(address account);
bool private _paused;
/**
* @dev Initializes the contract in unpaused state.
*/
function __Pausable_init() internal onlyInitializing {
__Pausable_init_unchained();
}
function __Pausable_init_unchained() internal onlyInitializing {
_paused = false;
}
/**
* @dev Returns true if the contract is paused, and false otherwise.
*/
function paused() public view virtual returns (bool) {
return _paused;
}
/**
* @dev Modifier to make a function callable only when the contract is not paused.
*
* Requirements:
*
* - The contract must not be paused.
*/
modifier whenNotPaused() {
require(!paused(), "Pausable: paused");
_;
}
/**
* @dev Modifier to make a function callable only when the contract is paused.
*
* Requirements:
*
* - The contract must be paused.
*/
modifier whenPaused() {
require(paused(), "Pausable: not paused");
_;
}
/**
* @dev Triggers stopped state.
*
* Requirements:
*
* - The contract must not be paused.
*/
function _pause() internal virtual whenNotPaused {
_paused = true;
emit Paused(_msgSender());
}
/**
* @dev Returns to normal state.
*
* Requirements:
*
* - The contract must be paused.
*/
function _unpause() internal virtual whenPaused {
_paused = false;
emit Unpaused(_msgSender());
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;
import {MerkleProofTooLong} from "./Error.sol";
library MerkleLib {
function generateRoot(bytes32[] memory _hashes) internal pure returns (bytes32) {
bytes32[] memory prevLayer = _hashes;
while (prevLayer.length > 1) {
bytes32[] memory nextLayer = new bytes32[]((prevLayer.length + 1) / 2);
for (uint256 i = 0; i < nextLayer.length; i++) {
if (2 * i + 1 < prevLayer.length) {
nextLayer[i] = keccak256(
abi.encodePacked(prevLayer[2 * i], prevLayer[2 * i + 1])
);
} else {
nextLayer[i] = prevLayer[2 * i];
}
}
prevLayer = nextLayer;
}
return prevLayer[0];
}
function calculateRoot(
bytes32[] memory nodes,
uint256 route,
bytes32 item
) internal pure returns (bytes32) {
uint256 proofItems = nodes.length;
if (proofItems > 256) revert MerkleProofTooLong(proofItems, 256);
bytes32 h = item;
for (uint256 i = 0; i < proofItems; ) {
bytes32 node = nodes[i];
if ((route & (1 << i)) == 0) {
assembly {
mstore(0x00, h)
mstore(0x20, node)
h := keccak256(0x00, 0x40)
}
} else {
assembly {
mstore(0x00, node)
mstore(0x20, h)
h := keccak256(0x00, 0x40)
}
}
unchecked {
++i;
}
}
return h;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/transparent/TransparentUpgradeableProxy.sol)
pragma solidity ^0.8.0;
import "../ERC1967/ERC1967Proxy.sol";
/**
* @dev This contract implements a proxy that is upgradeable by an admin.
*
* To avoid https://medium.com/nomic-labs-blog/malicious-backdoors-in-ethereum-proxies-62629adf3357[proxy selector
* clashing], which can potentially be used in an attack, this contract uses the
* https://blog.openzeppelin.com/the-transparent-proxy-pattern/[transparent proxy pattern]. This pattern implies two
* things that go hand in hand:
*
* 1. If any account other than the admin calls the proxy, the call will be forwarded to the implementation, even if
* that call matches one of the admin functions exposed by the proxy itself.
* 2. If the admin calls the proxy, it can access the admin functions, but its calls will never be forwarded to the
* implementation. If the admin tries to call a function on the implementation it will fail with an error that says
* "admin cannot fallback to proxy target".
*
* These properties mean that the admin account can only be used for admin actions like upgrading the proxy or changing
* the admin, so it's best if it's a dedicated account that is not used for anything else. This will avoid headaches due
* to sudden errors when trying to call a function from the proxy implementation.
*
* Our recommendation is for the dedicated account to be an instance of the {ProxyAdmin} contract. If set up this way,
* you should think of the `ProxyAdmin` instance as the real administrative interface of your proxy.
*/
contract TransparentUpgradeableProxy is ERC1967Proxy {
/**
* @dev Initializes an upgradeable proxy managed by `_admin`, backed by the implementation at `_logic`, and
* optionally initialized with `_data` as explained in {ERC1967Proxy-constructor}.
*/
constructor(
address _logic,
address admin_,
bytes memory _data
) payable ERC1967Proxy(_logic, _data) {
assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
_changeAdmin(admin_);
}
/**
* @dev Modifier used internally that will delegate the call to the implementation unless the sender is the admin.
*/
modifier ifAdmin() {
if (msg.sender == _getAdmin()) {
_;
} else {
_fallback();
}
}
/**
* @dev Returns the current admin.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyAdmin}.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103`
*/
function admin() external ifAdmin returns (address admin_) {
admin_ = _getAdmin();
}
/**
* @dev Returns the current implementation.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-getProxyImplementation}.
*
* TIP: To get this value clients can read directly from the storage slot shown below (specified by EIP1967) using the
* https://eth.wiki/json-rpc/API#eth_getstorageat[`eth_getStorageAt`] RPC call.
* `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc`
*/
function implementation() external ifAdmin returns (address implementation_) {
implementation_ = _implementation();
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-changeProxyAdmin}.
*/
function changeAdmin(address newAdmin) external virtual ifAdmin {
_changeAdmin(newAdmin);
}
/**
* @dev Upgrade the implementation of the proxy.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-upgrade}.
*/
function upgradeTo(address newImplementation) external ifAdmin {
_upgradeToAndCall(newImplementation, bytes(""), false);
}
/**
* @dev Upgrade the implementation of the proxy, and then call a function from the new implementation as specified
* by `data`, which should be an encoded function call. This is useful to initialize new storage variables in the
* proxied contract.
*
* NOTE: Only the admin can call this function. See {ProxyAdmin-upgradeAndCall}.
*/
function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
_upgradeToAndCall(newImplementation, data, true);
}
/**
* @dev Returns the current admin.
*/
function _admin() internal view virtual returns (address) {
return _getAdmin();
}
/**
* @dev Makes sure the admin cannot access the fallback function. See {Proxy-_beforeFallback}.
*/
function _beforeFallback() internal virtual override {
require(msg.sender != _getAdmin(), "TransparentUpgradeableProxy: admin cannot fallback to proxy target");
super._beforeFallback();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/ERC1967/ERC1967Proxy.sol)
pragma solidity ^0.8.0;
import "../Proxy.sol";
import "./ERC1967Upgrade.sol";
/**
* @dev This contract implements an upgradeable proxy. It is upgradeable because calls are delegated to an
* implementation address that can be changed. This address is stored in storage in the location specified by
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967], so that it doesn't conflict with the storage layout of the
* implementation behind the proxy.
*/
contract ERC1967Proxy is Proxy, ERC1967Upgrade {
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `_logic`.
*
* If `_data` is nonempty, it's used as data in a delegate call to `_logic`. This will typically be an encoded
* function call, and allows initializating the storage of the proxy like a Solidity constructor.
*/
constructor(address _logic, bytes memory _data) payable {
assert(_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
_upgradeToAndCall(_logic, _data, false);
}
/**
* @dev Returns the current implementation address.
*/
function _implementation() internal view virtual override returns (address impl) {
return ERC1967Upgrade._getImplementation();
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/Proxy.sol)
pragma solidity ^0.8.0;
/**
* @dev This abstract contract provides a fallback function that delegates all calls to another contract using the EVM
* instruction `delegatecall`. We refer to the second contract as the _implementation_ behind the proxy, and it has to
* be specified by overriding the virtual {_implementation} function.
*
* Additionally, delegation to the implementation can be triggered manually through the {_fallback} function, or to a
* different contract through the {_delegate} function.
*
* The success and return data of the delegated call will be returned back to the caller of the proxy.
*/
abstract contract Proxy {
/**
* @dev Delegates the current call to `implementation`.
*
* This function does not return to its internal call site, it will return directly to the external caller.
*/
function _delegate(address implementation) internal virtual {
assembly {
// Copy msg.data. We take full control of memory in this inline assembly
// block because it will not return to Solidity code. We overwrite the
// Solidity scratch pad at memory position 0.
calldatacopy(0, 0, calldatasize())
// Call the implementation.
// out and outsize are 0 because we don't know the size yet.
let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0)
// Copy the returned data.
returndatacopy(0, 0, returndatasize())
switch result
// delegatecall returns 0 on error.
case 0 {
revert(0, returndatasize())
}
default {
return(0, returndatasize())
}
}
}
/**
* @dev This is a virtual function that should be overriden so it returns the address to which the fallback function
* and {_fallback} should delegate.
*/
function _implementation() internal view virtual returns (address);
/**
* @dev Delegates the current call to the address returned by `_implementation()`.
*
* This function does not return to its internall call site, it will return directly to the external caller.
*/
function _fallback() internal virtual {
_beforeFallback();
_delegate(_implementation());
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if no other
* function in the contract matches the call data.
*/
fallback() external payable virtual {
_fallback();
}
/**
* @dev Fallback function that delegates calls to the address returned by `_implementation()`. Will run if call data
* is empty.
*/
receive() external payable virtual {
_fallback();
}
/**
* @dev Hook that is called before falling back to the implementation. Can happen as part of a manual `_fallback`
* call, or as part of the Solidity `fallback` or `receive` functions.
*
* If overriden should call `super._beforeFallback()`.
*/
function _beforeFallback() internal virtual {}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/ERC1967/ERC1967Upgrade.sol)
pragma solidity ^0.8.2;
import "../beacon/IBeacon.sol";
import "../../interfaces/draft-IERC1822.sol";
import "../../utils/Address.sol";
import "../../utils/StorageSlot.sol";
/**
* @dev This abstract contract provides getters and event emitting update functions for
* https://eips.ethereum.org/EIPS/eip-1967[EIP1967] slots.
*
* _Available since v4.1._
*
* @custom:oz-upgrades-unsafe-allow delegatecall
*/
abstract contract ERC1967Upgrade {
// This is the keccak-256 hash of "eip1967.proxy.rollback" subtracted by 1
bytes32 private constant _ROLLBACK_SLOT = 0x4910fdfa16fed3260ed0e7147f7cc6da11a60208b5b9406d12a635614ffd9143;
/**
* @dev Storage slot with the address of the current implementation.
* This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
/**
* @dev Emitted when the implementation is upgraded.
*/
event Upgraded(address indexed implementation);
/**
* @dev Returns the current implementation address.
*/
function _getImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
}
/**
* @dev Perform implementation upgrade
*
* Emits an {Upgraded} event.
*/
function _upgradeTo(address newImplementation) internal {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Perform implementation upgrade with additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCall(
address newImplementation,
bytes memory data,
bool forceCall
) internal {
_upgradeTo(newImplementation);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(newImplementation, data);
}
}
/**
* @dev Perform implementation upgrade with security checks for UUPS proxies, and additional setup call.
*
* Emits an {Upgraded} event.
*/
function _upgradeToAndCallUUPS(
address newImplementation,
bytes memory data,
bool forceCall
) internal {
// Upgrades from old implementations will perform a rollback test. This test requires the new
// implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
// this special case will break upgrade paths from old UUPS implementation to new ones.
if (StorageSlot.getBooleanSlot(_ROLLBACK_SLOT).value) {
_setImplementation(newImplementation);
} else {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
require(slot == _IMPLEMENTATION_SLOT, "ERC1967Upgrade: unsupported proxiableUUID");
} catch {
revert("ERC1967Upgrade: new implementation is not UUPS");
}
_upgradeToAndCall(newImplementation, data, forceCall);
}
}
/**
* @dev Storage slot with the admin of the contract.
* This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
* validated in the constructor.
*/
bytes32 internal constant _ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
/**
* @dev Emitted when the admin account has changed.
*/
event AdminChanged(address previousAdmin, address newAdmin);
/**
* @dev Returns the current admin.
*/
function _getAdmin() internal view returns (address) {
return StorageSlot.getAddressSlot(_ADMIN_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 admin slot.
*/
function _setAdmin(address newAdmin) private {
require(newAdmin != address(0), "ERC1967: new admin is the zero address");
StorageSlot.getAddressSlot(_ADMIN_SLOT).value = newAdmin;
}
/**
* @dev Changes the admin of the proxy.
*
* Emits an {AdminChanged} event.
*/
function _changeAdmin(address newAdmin) internal {
emit AdminChanged(_getAdmin(), newAdmin);
_setAdmin(newAdmin);
}
/**
* @dev The storage slot of the UpgradeableBeacon contract which defines the implementation for this proxy.
* This is bytes32(uint256(keccak256('eip1967.proxy.beacon')) - 1)) and is validated in the constructor.
*/
bytes32 internal constant _BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
/**
* @dev Emitted when the beacon is upgraded.
*/
event BeaconUpgraded(address indexed beacon);
/**
* @dev Returns the current beacon.
*/
function _getBeacon() internal view returns (address) {
return StorageSlot.getAddressSlot(_BEACON_SLOT).value;
}
/**
* @dev Stores a new beacon in the EIP1967 beacon slot.
*/
function _setBeacon(address newBeacon) private {
require(Address.isContract(newBeacon), "ERC1967: new beacon is not a contract");
require(
Address.isContract(IBeacon(newBeacon).implementation()),
"ERC1967: beacon implementation is not a contract"
);
StorageSlot.getAddressSlot(_BEACON_SLOT).value = newBeacon;
}
/**
* @dev Perform beacon upgrade with additional setup call. Note: This upgrades the address of the beacon, it does
* not upgrade the implementation contained in the beacon (see {UpgradeableBeacon-_setImplementation} for that).
*
* Emits a {BeaconUpgraded} event.
*/
function _upgradeBeaconToAndCall(
address newBeacon,
bytes memory data,
bool forceCall
) internal {
_setBeacon(newBeacon);
emit BeaconUpgraded(newBeacon);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(IBeacon(newBeacon).implementation(), data);
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/IBeacon.sol)
pragma solidity ^0.8.0;
/**
* @dev This is the interface that {BeaconProxy} expects of its beacon.
*/
interface IBeacon {
/**
* @dev Must return an address that can be used as a delegate call target.
*
* {BeaconProxy} will check that this address is a contract.
*/
function implementation() external view returns (address);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (interfaces/draft-IERC1822.sol)
pragma solidity ^0.8.0;
/**
* @dev ERC1822: Universal Upgradeable Proxy Standard (UUPS) documents a method for upgradeability through a simplified
* proxy whose upgrades are fully controlled by the current implementation.
*/
interface IERC1822Proxiable {
/**
* @dev Returns the storage slot that the proxiable contract assumes is being used to store the implementation
* address.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy.
*/
function proxiableUUID() external view returns (bytes32);
}// 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);
}
}
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/StorageSlot.sol)
pragma solidity ^0.8.0;
/**
* @dev Library for reading and writing primitive types to specific storage slots.
*
* Storage slots are often used to avoid storage conflict when dealing with upgradeable contracts.
* This library helps with reading and writing to such slots without the need for inline assembly.
*
* The functions in this library return Slot structs that contain a `value` member that can be used to read or write.
*
* Example usage to set ERC1967 implementation slot:
* ```
* contract ERC1967 {
* bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
*
* function _getImplementation() internal view returns (address) {
* return StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value;
* }
*
* function _setImplementation(address newImplementation) internal {
* require(Address.isContract(newImplementation), "ERC1967: new implementation is not a contract");
* StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = newImplementation;
* }
* }
* ```
*
* _Available since v4.1 for `address`, `bool`, `bytes32`, and `uint256`._
*/
library StorageSlot {
struct AddressSlot {
address value;
}
struct BooleanSlot {
bool value;
}
struct Bytes32Slot {
bytes32 value;
}
struct Uint256Slot {
uint256 value;
}
/**
* @dev Returns an `AddressSlot` with member `value` located at `slot`.
*/
function getAddressSlot(bytes32 slot) internal pure returns (AddressSlot storage r) {
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `BooleanSlot` with member `value` located at `slot`.
*/
function getBooleanSlot(bytes32 slot) internal pure returns (BooleanSlot storage r) {
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Bytes32Slot` with member `value` located at `slot`.
*/
function getBytes32Slot(bytes32 slot) internal pure returns (Bytes32Slot storage r) {
assembly {
r.slot := slot
}
}
/**
* @dev Returns an `Uint256Slot` with member `value` located at `slot`.
*/
function getUint256Slot(bytes32 slot) internal pure returns (Uint256Slot storage r) {
assembly {
r.slot := slot
}
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./BridgeCreator.sol";
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./RollupProxy.sol";
contract RollupCreator is Ownable {
event RollupCreated(
address indexed rollupAddress,
address inboxAddress,
address adminProxy,
address sequencerInbox,
address bridge
);
event TemplatesUpdated();
BridgeCreator public bridgeCreator;
IOneStepProofEntry public osp;
IChallengeManager public challengeManagerTemplate;
IRollupAdmin public rollupAdminLogic;
IRollupUser public rollupUserLogic;
address public validatorUtils;
address public validatorWalletCreator;
constructor() Ownable() {}
function setTemplates(
BridgeCreator _bridgeCreator,
IOneStepProofEntry _osp,
IChallengeManager _challengeManagerLogic,
IRollupAdmin _rollupAdminLogic,
IRollupUser _rollupUserLogic,
address _validatorUtils,
address _validatorWalletCreator
) external onlyOwner {
bridgeCreator = _bridgeCreator;
osp = _osp;
challengeManagerTemplate = _challengeManagerLogic;
rollupAdminLogic = _rollupAdminLogic;
rollupUserLogic = _rollupUserLogic;
validatorUtils = _validatorUtils;
validatorWalletCreator = _validatorWalletCreator;
emit TemplatesUpdated();
}
struct CreateRollupFrame {
ProxyAdmin admin;
IBridge bridge;
ISequencerInbox sequencerInbox;
IInbox inbox;
IRollupEventInbox rollupEventInbox;
IOutbox outbox;
RollupProxy rollup;
}
// After this setup:
// Rollup should be the owner of bridge
// RollupOwner should be the owner of Rollup's ProxyAdmin
// RollupOwner should be the owner of Rollup
// Bridge should have a single inbox and outbox
function createRollup(Config memory config, address expectedRollupAddr)
external
returns (address)
{
CreateRollupFrame memory frame;
frame.admin = new ProxyAdmin();
(
frame.bridge,
frame.sequencerInbox,
frame.inbox,
frame.rollupEventInbox,
frame.outbox
) = bridgeCreator.createBridge(
address(frame.admin),
expectedRollupAddr,
config.sequencerInboxMaxTimeVariation
);
frame.admin.transferOwnership(config.owner);
IChallengeManager challengeManager = IChallengeManager(
address(
new TransparentUpgradeableProxy(
address(challengeManagerTemplate),
address(frame.admin),
""
)
)
);
challengeManager.initialize(
IChallengeResultReceiver(expectedRollupAddr),
frame.sequencerInbox,
frame.bridge,
osp
);
frame.rollup = new RollupProxy(
config,
ContractDependencies({
bridge: frame.bridge,
sequencerInbox: frame.sequencerInbox,
inbox: frame.inbox,
outbox: frame.outbox,
rollupEventInbox: frame.rollupEventInbox,
challengeManager: challengeManager,
rollupAdminLogic: rollupAdminLogic,
rollupUserLogic: rollupUserLogic,
validatorUtils: validatorUtils,
validatorWalletCreator: validatorWalletCreator
})
);
require(address(frame.rollup) == expectedRollupAddr, "WRONG_ROLLUP_ADDR");
emit RollupCreated(
address(frame.rollup),
address(frame.inbox),
address(frame.admin),
address(frame.sequencerInbox),
address(frame.bridge)
);
return address(frame.rollup);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/proxy/Proxy.sol";
import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/StorageSlot.sol";
/// @notice An extension to OZ's ERC1967Upgrade implementation to support two logic contracts
abstract contract DoubleLogicERC1967Upgrade is ERC1967Upgrade {
// This is the keccak-256 hash of "eip1967.proxy.implementation.secondary" subtracted by 1
bytes32 internal constant _IMPLEMENTATION_SECONDARY_SLOT =
0x2b1dbce74324248c222f0ec2d5ed7bd323cfc425b336f0253c5ccfda7265546d;
// This is the keccak-256 hash of "eip1967.proxy.rollback.secondary" subtracted by 1
bytes32 private constant _ROLLBACK_SECONDARY_SLOT =
0x49bd798cd84788856140a4cd5030756b4d08a9e4d55db725ec195f232d262a89;
/**
* @dev Emitted when the secondary implementation is upgraded.
*/
event UpgradedSecondary(address indexed implementation);
/**
* @dev Returns the current secondary implementation address.
*/
function _getSecondaryImplementation() internal view returns (address) {
return StorageSlot.getAddressSlot(_IMPLEMENTATION_SECONDARY_SLOT).value;
}
/**
* @dev Stores a new address in the EIP1967 implementation slot.
*/
function _setSecondaryImplementation(address newImplementation) private {
require(
Address.isContract(newImplementation),
"ERC1967: new secondary implementation is not a contract"
);
StorageSlot.getAddressSlot(_IMPLEMENTATION_SECONDARY_SLOT).value = newImplementation;
}
/**
* @dev Perform secondary implementation upgrade
*
* Emits an {UpgradedSecondary} event.
*/
function _upgradeSecondaryTo(address newImplementation) internal {
_setSecondaryImplementation(newImplementation);
emit UpgradedSecondary(newImplementation);
}
/**
* @dev Perform secondary implementation upgrade with additional setup call.
*
* Emits an {UpgradedSecondary} event.
*/
function _upgradeSecondaryToAndCall(
address newImplementation,
bytes memory data,
bool forceCall
) internal {
_upgradeSecondaryTo(newImplementation);
if (data.length > 0 || forceCall) {
Address.functionDelegateCall(newImplementation, data);
}
}
/**
* @dev Perform secondary implementation upgrade with security checks for UUPS proxies, and additional setup call.
*
* Emits an {UpgradedSecondary} event.
*/
function _upgradeSecondaryToAndCallUUPS(
address newImplementation,
bytes memory data,
bool forceCall
) internal {
// Upgrades from old implementations will perform a rollback test. This test requires the new
// implementation to upgrade back to the old, non-ERC1822 compliant, implementation. Removing
// this special case will break upgrade paths from old UUPS implementation to new ones.
if (StorageSlot.getBooleanSlot(_ROLLBACK_SECONDARY_SLOT).value) {
_setSecondaryImplementation(newImplementation);
} else {
try IERC1822Proxiable(newImplementation).proxiableUUID() returns (bytes32 slot) {
require(
slot == _IMPLEMENTATION_SECONDARY_SLOT,
"ERC1967Upgrade: unsupported secondary proxiableUUID"
);
} catch {
revert("ERC1967Upgrade: new secondary implementation is not UUPS");
}
_upgradeSecondaryToAndCall(newImplementation, data, forceCall);
}
}
}
/// @notice similar to TransparentUpgradeableProxy but allows the admin to fallback to a separate logic contract using DoubleLogicERC1967Upgrade
/// @dev this follows the UUPS pattern for upgradeability - read more at https://github.com/OpenZeppelin/openzeppelin-contracts/tree/v4.5.0/contracts/proxy#transparent-vs-uups-proxies
contract AdminFallbackProxy is Proxy, DoubleLogicERC1967Upgrade {
/**
* @dev Initializes the upgradeable proxy with an initial implementation specified by `adminLogic` and a secondary
* logic implementation specified by `userLogic`
*
* Only the `adminAddr` is able to use the `adminLogic` functions
* All other addresses can interact with the `userLogic` functions
*/
constructor(
address adminLogic,
bytes memory adminData,
address userLogic,
bytes memory userData,
address adminAddr
) payable {
assert(_ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
assert(
_IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1)
);
assert(
_IMPLEMENTATION_SECONDARY_SLOT ==
bytes32(uint256(keccak256("eip1967.proxy.implementation.secondary")) - 1)
);
_changeAdmin(adminAddr);
_upgradeToAndCall(adminLogic, adminData, false);
_upgradeSecondaryToAndCall(userLogic, userData, false);
}
/// @inheritdoc Proxy
function _implementation() internal view override returns (address) {
require(msg.data.length >= 4, "NO_FUNC_SIG");
// if the sender is the proxy's admin, delegate to admin logic
// if the admin is disabled, all calls will be forwarded to user logic
// admin affordances can be disabled by setting to a no-op smart contract
// since there is a check for contract code before updating the value
address target = _getAdmin() != msg.sender
? DoubleLogicERC1967Upgrade._getSecondaryImplementation()
: ERC1967Upgrade._getImplementation();
// implementation setters do an existence check, but we protect against selfdestructs this way
require(Address.isContract(target), "TARGET_NOT_CONTRACT");
return target;
}
/**
* @dev unlike transparent upgradeable proxies, this does allow the admin to fallback to a logic contract
* the admin is expected to interact only with the primary logic contract, which handles contract
* upgrades using the UUPS approach
*/
function _beforeFallback() internal override {
super._beforeFallback();
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol";
import {IRollupUser} from "./IRollupLogic.sol";
import "../libraries/UUPSNotUpgradeable.sol";
import "./RollupCore.sol";
abstract contract AbsRollupUserLogic is
RollupCore,
UUPSNotUpgradeable,
IRollupUserAbs,
IChallengeResultReceiver
{
using NodeLib for Node;
using GlobalStateLib for GlobalState;
modifier onlyValidator() {
require(isValidator[msg.sender], "NOT_VALIDATOR");
_;
}
function isERC20Enabled() public view override returns (bool) {
return stakeToken != address(0);
}
/**
* @notice Reject the next unresolved node
* @param stakerAddress Example staker staked on sibling, used to prove a node is on an unconfirmable branch and can be rejected
*/
function rejectNextNode(address stakerAddress) external onlyValidator whenNotPaused {
requireUnresolvedExists();
uint64 latestConfirmedNodeNum = latestConfirmed();
uint64 firstUnresolvedNodeNum = firstUnresolvedNode();
Node storage firstUnresolvedNode_ = getNodeStorage(firstUnresolvedNodeNum);
if (firstUnresolvedNode_.prevNum == latestConfirmedNodeNum) {
/**If the first unresolved node is a child of the latest confirmed node, to prove it can be rejected, we show:
* a) Its deadline has expired
* b) *Some* staker is staked on a sibling
* The following three checks are sufficient to prove b:
*/
// 1. StakerAddress is indeed a staker
require(isStakedOnLatestConfirmed(stakerAddress), "NOT_STAKED");
// 2. Staker's latest staked node hasn't been resolved; this proves that staker's latest staked node can't be a parent of firstUnresolvedNode
requireUnresolved(latestStakedNode(stakerAddress));
// 3. staker isn't staked on first unresolved node; this proves staker's latest staked can't be a child of firstUnresolvedNode (recall staking on node requires staking on all of its parents)
require(!nodeHasStaker(firstUnresolvedNodeNum, stakerAddress), "STAKED_ON_TARGET");
// If a staker is staked on a node that is neither a child nor a parent of firstUnresolvedNode, it must be a sibling, QED
// Verify the block's deadline has passed
firstUnresolvedNode_.requirePastDeadline();
getNodeStorage(latestConfirmedNodeNum).requirePastChildConfirmDeadline();
removeOldZombies(0);
// Verify that no staker is staked on this node
require(
firstUnresolvedNode_.stakerCount == countStakedZombies(firstUnresolvedNodeNum),
"HAS_STAKERS"
);
}
// Simpler case: if the first unreseolved node doesn't point to the last confirmed node, another branch was confirmed and can simply reject it outright
_rejectNextNode();
emit NodeRejected(firstUnresolvedNodeNum);
}
/**
* @notice Confirm the next unresolved node
* @param blockHash The block hash at the end of the assertion
* @param sendRoot The send root at the end of the assertion
*/
function confirmNextNode(bytes32 blockHash, bytes32 sendRoot)
external
onlyValidator
whenNotPaused
{
requireUnresolvedExists();
uint64 nodeNum = firstUnresolvedNode();
Node storage node = getNodeStorage(nodeNum);
// Verify the block's deadline has passed
node.requirePastDeadline();
// Check that prev is latest confirmed
assert(node.prevNum == latestConfirmed());
Node storage prevNode = getNodeStorage(node.prevNum);
prevNode.requirePastChildConfirmDeadline();
removeOldZombies(0);
// Require only zombies are staked on siblings to this node, and there's at least one non-zombie staked on this node
uint256 stakedZombies = countStakedZombies(nodeNum);
uint256 zombiesStakedOnOtherChildren = countZombiesStakedOnChildren(node.prevNum) -
stakedZombies;
require(node.stakerCount > stakedZombies, "NO_STAKERS");
require(
prevNode.childStakerCount == node.stakerCount + zombiesStakedOnOtherChildren,
"NOT_ALL_STAKED"
);
confirmNode(nodeNum, blockHash, sendRoot);
}
/**
* @notice Create a new stake
* @param depositAmount The amount of either eth or tokens staked
*/
function _newStake(uint256 depositAmount) internal onlyValidator whenNotPaused {
// Verify that sender is not already a staker
require(!isStaked(msg.sender), "ALREADY_STAKED");
require(!isZombie(msg.sender), "STAKER_IS_ZOMBIE");
require(depositAmount >= currentRequiredStake(), "NOT_ENOUGH_STAKE");
createNewStake(msg.sender, depositAmount);
}
/**
* @notice Move stake onto existing child node
* @param nodeNum Index of the node to move stake to. This must by a child of the node the staker is currently staked on
* @param nodeHash Node hash of nodeNum (protects against reorgs)
*/
function stakeOnExistingNode(uint64 nodeNum, bytes32 nodeHash)
public
onlyValidator
whenNotPaused
{
require(isStakedOnLatestConfirmed(msg.sender), "NOT_STAKED");
require(
nodeNum >= firstUnresolvedNode() && nodeNum <= latestNodeCreated(),
"NODE_NUM_OUT_OF_RANGE"
);
Node storage node = getNodeStorage(nodeNum);
require(node.nodeHash == nodeHash, "NODE_REORG");
require(latestStakedNode(msg.sender) == node.prevNum, "NOT_STAKED_PREV");
stakeOnNode(msg.sender, nodeNum);
}
/**
* @notice Create a new node and move stake onto it
* @param assertion The assertion data
* @param expectedNodeHash The hash of the node being created (protects against reorgs)
*/
function stakeOnNewNode(
RollupLib.Assertion calldata assertion,
bytes32 expectedNodeHash,
uint256 prevNodeInboxMaxCount
) public onlyValidator whenNotPaused {
require(isStakedOnLatestConfirmed(msg.sender), "NOT_STAKED");
// Ensure staker is staked on the previous node
uint64 prevNode = latestStakedNode(msg.sender);
{
uint256 timeSinceLastNode = block.number - getNode(prevNode).createdAtBlock;
// Verify that assertion meets the minimum Delta time requirement
require(timeSinceLastNode >= minimumAssertionPeriod, "TIME_DELTA");
// Minimum size requirement: any assertion must consume at least all inbox messages
// put into L1 inbox before the prev node’s L1 blocknum.
// We make an exception if the machine enters the errored state,
// as it can't consume future batches.
require(
assertion.afterState.machineStatus == MachineStatus.ERRORED ||
assertion.afterState.globalState.getInboxPosition() >= prevNodeInboxMaxCount,
"TOO_SMALL"
);
// Minimum size requirement: any assertion must contain at least one block
require(assertion.numBlocks > 0, "EMPTY_ASSERTION");
// The rollup cannot advance normally from an errored state
require(
assertion.beforeState.machineStatus == MachineStatus.FINISHED,
"BAD_PREV_STATUS"
);
}
createNewNode(assertion, prevNode, prevNodeInboxMaxCount, expectedNodeHash);
stakeOnNode(msg.sender, latestNodeCreated());
}
/**
* @notice Refund a staker that is currently staked on or before the latest confirmed node
* @dev Since a staker is initially placed in the latest confirmed node, if they don't move it
* a griefer can remove their stake. It is recomended to batch together the txs to place a stake
* and move it to the desired node.
* @param stakerAddress Address of the staker whose stake is refunded
*/
function returnOldDeposit(address stakerAddress) external override onlyValidator whenNotPaused {
require(latestStakedNode(stakerAddress) <= latestConfirmed(), "TOO_RECENT");
requireUnchallengedStaker(stakerAddress);
withdrawStaker(stakerAddress);
}
/**
* @notice Increase the amount staked for the given staker
* @param stakerAddress Address of the staker whose stake is increased
* @param depositAmount The amount of either eth or tokens deposited
*/
function _addToDeposit(address stakerAddress, uint256 depositAmount)
internal
onlyValidator
whenNotPaused
{
requireUnchallengedStaker(stakerAddress);
increaseStakeBy(stakerAddress, depositAmount);
}
/**
* @notice Reduce the amount staked for the sender (difference between initial amount staked and target is creditted back to the sender).
* @param target Target amount of stake for the staker. If this is below the current minimum, it will be set to minimum instead
*/
function reduceDeposit(uint256 target) external onlyValidator whenNotPaused {
requireUnchallengedStaker(msg.sender);
uint256 currentRequired = currentRequiredStake();
if (target < currentRequired) {
target = currentRequired;
}
reduceStakeTo(msg.sender, target);
}
/**
* @notice Start a challenge between the given stakers over the node created by the first staker assuming that the two are staked on conflicting nodes. N.B.: challenge creator does not necessarily need to be one of the two asserters.
* @param stakers Stakers engaged in the challenge. The first staker should be staked on the first node
* @param nodeNums Nodes of the stakers engaged in the challenge. The first node should be the earliest and is the one challenged
* @param machineStatuses The before and after machine status for the first assertion
* @param globalStates The before and after global state for the first assertion
* @param numBlocks The number of L2 blocks contained in the first assertion
* @param secondExecutionHash The execution hash of the second assertion
* @param proposedTimes Times that the two nodes were proposed
* @param wasmModuleRoots The wasm module roots at the time of the creation of each assertion
*/
function createChallenge(
address[2] calldata stakers,
uint64[2] calldata nodeNums,
MachineStatus[2] calldata machineStatuses,
GlobalState[2] calldata globalStates,
uint64 numBlocks,
bytes32 secondExecutionHash,
uint256[2] calldata proposedTimes,
bytes32[2] calldata wasmModuleRoots
) external onlyValidator whenNotPaused {
require(nodeNums[0] < nodeNums[1], "WRONG_ORDER");
require(nodeNums[1] <= latestNodeCreated(), "NOT_PROPOSED");
require(latestConfirmed() < nodeNums[0], "ALREADY_CONFIRMED");
Node storage node1 = getNodeStorage(nodeNums[0]);
Node storage node2 = getNodeStorage(nodeNums[1]);
// ensure nodes staked on the same parent (and thus in conflict)
require(node1.prevNum == node2.prevNum, "DIFF_PREV");
// ensure both stakers aren't currently in challenge
requireUnchallengedStaker(stakers[0]);
requireUnchallengedStaker(stakers[1]);
require(nodeHasStaker(nodeNums[0], stakers[0]), "STAKER1_NOT_STAKED");
require(nodeHasStaker(nodeNums[1], stakers[1]), "STAKER2_NOT_STAKED");
// Check param data against challenge hash
require(
node1.challengeHash ==
RollupLib.challengeRootHash(
RollupLib.executionHash(machineStatuses, globalStates, numBlocks),
proposedTimes[0],
wasmModuleRoots[0]
),
"CHAL_HASH1"
);
require(
node2.challengeHash ==
RollupLib.challengeRootHash(
secondExecutionHash,
proposedTimes[1],
wasmModuleRoots[1]
),
"CHAL_HASH2"
);
// Calculate upper limit for allowed node proposal time:
uint256 commonEndTime = getNodeStorage(node1.prevNum).firstChildBlock +
// Dispute start: dispute timer for a node starts when its first child is created
(node1.deadlineBlock - proposedTimes[0]) +
extraChallengeTimeBlocks; // add dispute window to dispute start time
if (commonEndTime < proposedTimes[1]) {
// The 2nd node was created too late; loses challenge automatically.
completeChallengeImpl(stakers[0], stakers[1]);
return;
}
// Start a challenge between staker1 and staker2. Staker1 will defend the correctness of node1, and staker2 will challenge it.
uint64 challengeIndex = createChallengeHelper(
stakers,
machineStatuses,
globalStates,
numBlocks,
wasmModuleRoots,
commonEndTime - proposedTimes[0],
commonEndTime - proposedTimes[1]
); // trusted external call
challengeStarted(stakers[0], stakers[1], challengeIndex);
emit RollupChallengeStarted(challengeIndex, stakers[0], stakers[1], nodeNums[0]);
}
function createChallengeHelper(
address[2] calldata stakers,
MachineStatus[2] calldata machineStatuses,
GlobalState[2] calldata globalStates,
uint64 numBlocks,
bytes32[2] calldata wasmModuleRoots,
uint256 asserterTimeLeft,
uint256 challengerTimeLeft
) internal returns (uint64) {
return
challengeManager.createChallenge(
wasmModuleRoots[0],
machineStatuses,
globalStates,
numBlocks,
stakers[0],
stakers[1],
asserterTimeLeft,
challengerTimeLeft
);
}
/**
* @notice Inform the rollup that the challenge between the given stakers is completed
* @param winningStaker Address of the winning staker
* @param losingStaker Address of the losing staker
*/
function completeChallenge(
uint256 challengeIndex,
address winningStaker,
address losingStaker
) external override whenNotPaused {
// Only the challenge manager contract can call this to declare the winner and loser
require(msg.sender == address(challengeManager), "WRONG_SENDER");
require(challengeIndex == inChallenge(winningStaker, losingStaker), "NOT_IN_CHAL");
completeChallengeImpl(winningStaker, losingStaker);
}
function completeChallengeImpl(address winningStaker, address losingStaker) private {
uint256 remainingLoserStake = amountStaked(losingStaker);
uint256 winnerStake = amountStaked(winningStaker);
if (remainingLoserStake > winnerStake) {
// If loser has a higher stake than the winner, refund the difference
remainingLoserStake -= reduceStakeTo(losingStaker, winnerStake);
}
// Reward the winner with half the remaining stake
uint256 amountWon = remainingLoserStake / 2;
increaseStakeBy(winningStaker, amountWon);
remainingLoserStake -= amountWon;
// We deliberately leave loser in challenge state to prevent them from
// doing certain thing that are allowed only to parties not in a challenge
clearChallenge(winningStaker);
// Credit the other half to the loserStakeEscrow address
increaseWithdrawableFunds(loserStakeEscrow, remainingLoserStake);
// Turning loser into zombie renders the loser's remaining stake inaccessible
turnIntoZombie(losingStaker);
}
/**
* @notice Remove the given zombie from nodes it is staked on, moving backwords from the latest node it is staked on
* @param zombieNum Index of the zombie to remove
* @param maxNodes Maximum number of nodes to remove the zombie from (to limit the cost of this transaction)
*/
function removeZombie(uint256 zombieNum, uint256 maxNodes)
external
onlyValidator
whenNotPaused
{
require(zombieNum < zombieCount(), "NO_SUCH_ZOMBIE");
address zombieStakerAddress = zombieAddress(zombieNum);
uint64 latestNodeStaked = zombieLatestStakedNode(zombieNum);
uint256 nodesRemoved = 0;
uint256 latestConfirmedNum = latestConfirmed();
while (latestNodeStaked >= latestConfirmedNum && nodesRemoved < maxNodes) {
Node storage node = getNodeStorage(latestNodeStaked);
removeStaker(latestNodeStaked, zombieStakerAddress);
latestNodeStaked = node.prevNum;
nodesRemoved++;
}
if (latestNodeStaked < latestConfirmedNum) {
removeZombie(zombieNum);
} else {
zombieUpdateLatestStakedNode(zombieNum, latestNodeStaked);
}
}
/**
* @notice Remove any zombies whose latest stake is earlier than the latest confirmed node
* @param startIndex Index in the zombie list to start removing zombies from (to limit the cost of this transaction)
*/
function removeOldZombies(uint256 startIndex) public onlyValidator whenNotPaused {
uint256 currentZombieCount = zombieCount();
uint256 latestConfirmedNum = latestConfirmed();
for (uint256 i = startIndex; i < currentZombieCount; i++) {
while (zombieLatestStakedNode(i) < latestConfirmedNum) {
removeZombie(i);
currentZombieCount--;
if (i >= currentZombieCount) {
return;
}
}
}
}
/**
* @notice Calculate the current amount of funds required to place a new stake in the rollup
* @dev If the stake requirement get's too high, this function may start reverting due to overflow, but
* that only blocks operations that should be blocked anyway
* @return The current minimum stake requirement
*/
function currentRequiredStake(
uint256 _blockNumber,
uint64 _firstUnresolvedNodeNum,
uint256 _latestCreatedNode
) internal view returns (uint256) {
// If there are no unresolved nodes, then you can use the base stake
if (_firstUnresolvedNodeNum - 1 == _latestCreatedNode) {
return baseStake;
}
uint256 firstUnresolvedDeadline = getNodeStorage(_firstUnresolvedNodeNum).deadlineBlock;
if (_blockNumber < firstUnresolvedDeadline) {
return baseStake;
}
uint24[10] memory numerators = [
1,
122971,
128977,
80017,
207329,
114243,
314252,
129988,
224562,
162163
];
uint24[10] memory denominators = [
1,
114736,
112281,
64994,
157126,
80782,
207329,
80017,
128977,
86901
];
uint256 firstUnresolvedAge = _blockNumber - firstUnresolvedDeadline;
uint256 periodsPassed = (firstUnresolvedAge * 10) / confirmPeriodBlocks;
uint256 baseMultiplier = 2**(periodsPassed / 10);
uint256 withNumerator = baseMultiplier * numerators[periodsPassed % 10];
uint256 multiplier = withNumerator / denominators[periodsPassed % 10];
if (multiplier == 0) {
multiplier = 1;
}
return baseStake * multiplier;
}
/**
* @notice Calculate the current amount of funds required to place a new stake in the rollup
* @dev If the stake requirement get's too high, this function may start reverting due to overflow, but
* that only blocks operations that should be blocked anyway
* @return The current minimum stake requirement
*/
function requiredStake(
uint256 blockNumber,
uint64 firstUnresolvedNodeNum,
uint64 latestCreatedNode
) external view returns (uint256) {
return currentRequiredStake(blockNumber, firstUnresolvedNodeNum, latestCreatedNode);
}
function owner() external view returns (address) {
return _getAdmin();
}
function currentRequiredStake() public view returns (uint256) {
uint64 firstUnresolvedNodeNum = firstUnresolvedNode();
return currentRequiredStake(block.number, firstUnresolvedNodeNum, latestNodeCreated());
}
/**
* @notice Calculate the number of zombies staked on the given node
*
* @dev This function could be uncallable if there are too many zombies. However,
* removeZombie and removeOldZombies can be used to remove any zombies that exist
* so that this will then be callable
*
* @param nodeNum The node on which to count staked zombies
* @return The number of zombies staked on the node
*/
function countStakedZombies(uint64 nodeNum) public view override returns (uint256) {
uint256 currentZombieCount = zombieCount();
uint256 stakedZombieCount = 0;
for (uint256 i = 0; i < currentZombieCount; i++) {
if (nodeHasStaker(nodeNum, zombieAddress(i))) {
stakedZombieCount++;
}
}
return stakedZombieCount;
}
/**
* @notice Calculate the number of zombies staked on a child of the given node
*
* @dev This function could be uncallable if there are too many zombies. However,
* removeZombie and removeOldZombies can be used to remove any zombies that exist
* so that this will then be callable
*
* @param nodeNum The parent node on which to count zombies staked on children
* @return The number of zombies staked on children of the node
*/
function countZombiesStakedOnChildren(uint64 nodeNum) public view override returns (uint256) {
uint256 currentZombieCount = zombieCount();
uint256 stakedZombieCount = 0;
for (uint256 i = 0; i < currentZombieCount; i++) {
Zombie storage zombie = getZombieStorage(i);
// If this zombie is staked on this node, but its _latest_ staked node isn't this node,
// then it must be staked on a child of this node.
if (
zombie.latestStakedNode != nodeNum && nodeHasStaker(nodeNum, zombie.stakerAddress)
) {
stakedZombieCount++;
}
}
return stakedZombieCount;
}
/**
* @notice Verify that there are some number of nodes still unresolved
*/
function requireUnresolvedExists() public view override {
uint256 firstUnresolved = firstUnresolvedNode();
require(
firstUnresolved > latestConfirmed() && firstUnresolved <= latestNodeCreated(),
"NO_UNRESOLVED"
);
}
function requireUnresolved(uint256 nodeNum) public view override {
require(nodeNum >= firstUnresolvedNode(), "ALREADY_DECIDED");
require(nodeNum <= latestNodeCreated(), "DOESNT_EXIST");
}
/**
* @notice Verify that the given address is staked and not actively in a challenge
* @param stakerAddress Address to check
*/
function requireUnchallengedStaker(address stakerAddress) private view {
require(isStaked(stakerAddress), "NOT_STAKED");
require(currentChallenge(stakerAddress) == NO_CHAL_INDEX, "IN_CHAL");
}
}
contract RollupUserLogic is AbsRollupUserLogic, IRollupUser {
/// @dev the user logic just validated configuration and shouldn't write to state during init
/// this allows the admin logic to ensure consistency on parameters.
function initialize(address _stakeToken) external view override onlyProxy {
require(_stakeToken == address(0), "NO_TOKEN_ALLOWED");
require(!isERC20Enabled(), "FACET_NOT_ERC20");
}
/**
* @notice Create a new stake on an existing node
* @param nodeNum Number of the node your stake will be place one
* @param nodeHash Node hash of the node with the given nodeNum
*/
function newStakeOnExistingNode(uint64 nodeNum, bytes32 nodeHash) external payable override {
_newStake(msg.value);
stakeOnExistingNode(nodeNum, nodeHash);
}
/**
* @notice Create a new stake on a new node
* @param assertion Assertion describing the state change between the old node and the new one
* @param expectedNodeHash Node hash of the node that will be created
* @param prevNodeInboxMaxCount Total of messages in the inbox as of the previous node
*/
function newStakeOnNewNode(
RollupLib.Assertion calldata assertion,
bytes32 expectedNodeHash,
uint256 prevNodeInboxMaxCount
) external payable override {
_newStake(msg.value);
stakeOnNewNode(assertion, expectedNodeHash, prevNodeInboxMaxCount);
}
/**
* @notice Increase the amount staked eth for the given staker
* @param stakerAddress Address of the staker whose stake is increased
*/
function addToDeposit(address stakerAddress)
external
payable
override
onlyValidator
whenNotPaused
{
_addToDeposit(stakerAddress, msg.value);
}
/**
* @notice Withdraw uncommitted funds owned by sender from the rollup chain
*/
function withdrawStakerFunds() external override onlyValidator whenNotPaused returns (uint256) {
uint256 amount = withdrawFunds(msg.sender);
// This is safe because it occurs after all checks and effects
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "TRANSFER_FAILED");
return amount;
}
}
contract ERC20RollupUserLogic is AbsRollupUserLogic, IRollupUserERC20 {
/// @dev the user logic just validated configuration and shouldn't write to state during init
/// this allows the admin logic to ensure consistency on parameters.
function initialize(address _stakeToken) external view override onlyProxy {
require(_stakeToken != address(0), "NEED_STAKE_TOKEN");
require(isERC20Enabled(), "FACET_NOT_ERC20");
}
/**
* @notice Create a new stake on an existing node
* @param tokenAmount Amount of the rollups staking token to stake
* @param nodeNum Number of the node your stake will be place one
* @param nodeHash Node hash of the node with the given nodeNum
*/
function newStakeOnExistingNode(
uint256 tokenAmount,
uint64 nodeNum,
bytes32 nodeHash
) external override {
_newStake(tokenAmount);
stakeOnExistingNode(nodeNum, nodeHash);
/// @dev This is an external call, safe because it's at the end of the function
receiveTokens(tokenAmount);
}
/**
* @notice Create a new stake on a new node
* @param tokenAmount Amount of the rollups staking token to stake
* @param assertion Assertion describing the state change between the old node and the new one
* @param expectedNodeHash Node hash of the node that will be created
* @param prevNodeInboxMaxCount Total of messages in the inbox as of the previous node
*/
function newStakeOnNewNode(
uint256 tokenAmount,
RollupLib.Assertion calldata assertion,
bytes32 expectedNodeHash,
uint256 prevNodeInboxMaxCount
) external override {
_newStake(tokenAmount);
stakeOnNewNode(assertion, expectedNodeHash, prevNodeInboxMaxCount);
/// @dev This is an external call, safe because it's at the end of the function
receiveTokens(tokenAmount);
}
/**
* @notice Increase the amount staked tokens for the given staker
* @param stakerAddress Address of the staker whose stake is increased
* @param tokenAmount the amount of tokens staked
*/
function addToDeposit(address stakerAddress, uint256 tokenAmount)
external
onlyValidator
whenNotPaused
{
_addToDeposit(stakerAddress, tokenAmount);
/// @dev This is an external call, safe because it's at the end of the function
receiveTokens(tokenAmount);
}
/**
* @notice Withdraw uncommitted funds owned by sender from the rollup chain
*/
function withdrawStakerFunds() external override onlyValidator whenNotPaused returns (uint256) {
uint256 amount = withdrawFunds(msg.sender);
// This is safe because it occurs after all checks and effects
require(IERC20Upgradeable(stakeToken).transfer(msg.sender, amount), "TRANSFER_FAILED");
return amount;
}
function receiveTokens(uint256 tokenAmount) private {
require(
IERC20Upgradeable(stakeToken).transferFrom(msg.sender, address(this), tokenAmount),
"TRANSFER_FAIL"
);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20Upgradeable {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {DoubleLogicERC1967Upgrade} from "./AdminFallbackProxy.sol";
/**
* @dev UUPSUpgradeable by OpenZeppelin but not upgradeable. This is expected to be used on the secondary
* logic slot behind a DoubleLogicERC1967Upgrade proxy
*/
abstract contract UUPSNotUpgradeable is IERC1822Proxiable, DoubleLogicERC1967Upgrade {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
address private immutable __self = address(this);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
require(address(this) != __self, "Function must be called through delegatecall");
require(
_getSecondaryImplementation() == __self,
"Function must be called through active proxy"
);
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
require(
address(this) == __self,
"UUPSNotUpgradeable: must not be called through delegatecall"
);
_;
}
/**
* @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate that the this implementation remains valid after an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual override notDelegated returns (bytes32) {
return _IMPLEMENTATION_SECONDARY_SLOT;
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "./Node.sol";
import "./IRollupCore.sol";
import "./RollupLib.sol";
import "./IRollupEventInbox.sol";
import "./IRollupCore.sol";
import "../challenge/IChallengeManager.sol";
import "../bridge/ISequencerInbox.sol";
import "../bridge/IBridge.sol";
import "../bridge/IOutbox.sol";
import {NO_CHAL_INDEX} from "../libraries/Constants.sol";
abstract contract RollupCore is IRollupCore, PausableUpgradeable {
using NodeLib for Node;
using GlobalStateLib for GlobalState;
// Rollup Config
uint64 public confirmPeriodBlocks;
uint64 public extraChallengeTimeBlocks;
uint256 public chainId;
uint256 public baseStake;
bytes32 public wasmModuleRoot;
IInbox public inbox;
IBridge public bridge;
IOutbox public outbox;
ISequencerInbox public sequencerInbox;
IRollupEventInbox public rollupEventInbox;
IChallengeManager public override challengeManager;
// misc useful contracts when interacting with the rollup
address public validatorUtils;
address public validatorWalletCreator;
// when a staker loses a challenge, half of their funds get escrowed in this address
address public loserStakeEscrow;
address public stakeToken;
uint256 public minimumAssertionPeriod;
mapping(address => bool) public isValidator;
// Stakers become Zombies after losing a challenge
struct Zombie {
address stakerAddress;
uint64 latestStakedNode;
}
uint64 private _latestConfirmed;
uint64 private _firstUnresolvedNode;
uint64 private _latestNodeCreated;
uint64 private _lastStakeBlock;
mapping(uint64 => Node) private _nodes;
mapping(uint64 => mapping(address => bool)) private _nodeStakers;
address[] private _stakerList;
mapping(address => Staker) public _stakerMap;
Zombie[] private _zombies;
mapping(address => uint256) private _withdrawableFunds;
uint256 public totalWithdrawableFunds;
uint256 public rollupDeploymentBlock;
// The node number of the initial node
uint64 internal constant GENESIS_NODE = 0;
/**
* @notice Get a storage reference to the Node for the given node index
* @param nodeNum Index of the node
* @return Node struct
*/
function getNodeStorage(uint64 nodeNum) internal view returns (Node storage) {
return _nodes[nodeNum];
}
/**
* @notice Get the Node for the given index.
*/
function getNode(uint64 nodeNum) public view override returns (Node memory) {
return getNodeStorage(nodeNum);
}
/**
* @notice Check if the specified node has been staked on by the provided staker.
* Only accurate at the latest confirmed node and afterwards.
*/
function nodeHasStaker(uint64 nodeNum, address staker) public view override returns (bool) {
return _nodeStakers[nodeNum][staker];
}
/**
* @notice Get the address of the staker at the given index
* @param stakerNum Index of the staker
* @return Address of the staker
*/
function getStakerAddress(uint64 stakerNum) external view override returns (address) {
return _stakerList[stakerNum];
}
/**
* @notice Check whether the given staker is staked
* @param staker Staker address to check
* @return True or False for whether the staker was staked
*/
function isStaked(address staker) public view override returns (bool) {
return _stakerMap[staker].isStaked;
}
/**
* @notice Check whether the given staker is staked on the latest confirmed node,
* which includes if the staker is staked on a descendent of the latest confirmed node.
* @param staker Staker address to check
* @return True or False for whether the staker was staked
*/
function isStakedOnLatestConfirmed(address staker) public view returns (bool) {
return _stakerMap[staker].isStaked && nodeHasStaker(_latestConfirmed, staker);
}
/**
* @notice Get the latest staked node of the given staker
* @param staker Staker address to lookup
* @return Latest node staked of the staker
*/
function latestStakedNode(address staker) public view override returns (uint64) {
return _stakerMap[staker].latestStakedNode;
}
/**
* @notice Get the current challenge of the given staker
* @param staker Staker address to lookup
* @return Current challenge of the staker
*/
function currentChallenge(address staker) public view override returns (uint64) {
return _stakerMap[staker].currentChallenge;
}
/**
* @notice Get the amount staked of the given staker
* @param staker Staker address to lookup
* @return Amount staked of the staker
*/
function amountStaked(address staker) public view override returns (uint256) {
return _stakerMap[staker].amountStaked;
}
/**
* @notice Retrieves stored information about a requested staker
* @param staker Staker address to retrieve
* @return A structure with information about the requested staker
*/
function getStaker(address staker) external view override returns (Staker memory) {
return _stakerMap[staker];
}
/**
* @notice Get the original staker address of the zombie at the given index
* @param zombieNum Index of the zombie to lookup
* @return Original staker address of the zombie
*/
function zombieAddress(uint256 zombieNum) public view override returns (address) {
return _zombies[zombieNum].stakerAddress;
}
/**
* @notice Get Latest node that the given zombie at the given index is staked on
* @param zombieNum Index of the zombie to lookup
* @return Latest node that the given zombie is staked on
*/
function zombieLatestStakedNode(uint256 zombieNum) public view override returns (uint64) {
return _zombies[zombieNum].latestStakedNode;
}
/**
* @notice Retrieves stored information about a requested zombie
* @param zombieNum Index of the zombie to lookup
* @return A structure with information about the requested staker
*/
function getZombieStorage(uint256 zombieNum) internal view returns (Zombie storage) {
return _zombies[zombieNum];
}
/// @return Current number of un-removed zombies
function zombieCount() public view override returns (uint256) {
return _zombies.length;
}
function isZombie(address staker) public view override returns (bool) {
for (uint256 i = 0; i < _zombies.length; i++) {
if (staker == _zombies[i].stakerAddress) {
return true;
}
}
return false;
}
/**
* @notice Get the amount of funds withdrawable by the given address
* @param user Address to check the funds of
* @return Amount of funds withdrawable by user
*/
function withdrawableFunds(address user) external view override returns (uint256) {
return _withdrawableFunds[user];
}
/**
* @return Index of the first unresolved node
* @dev If all nodes have been resolved, this will be latestNodeCreated + 1
*/
function firstUnresolvedNode() public view override returns (uint64) {
return _firstUnresolvedNode;
}
/// @return Index of the latest confirmed node
function latestConfirmed() public view override returns (uint64) {
return _latestConfirmed;
}
/// @return Index of the latest rollup node created
function latestNodeCreated() public view override returns (uint64) {
return _latestNodeCreated;
}
/// @return Ethereum block that the most recent stake was created
function lastStakeBlock() external view override returns (uint64) {
return _lastStakeBlock;
}
/// @return Number of active stakers currently staked
function stakerCount() public view override returns (uint64) {
return uint64(_stakerList.length);
}
/**
* @notice Initialize the core with an initial node
* @param initialNode Initial node to start the chain with
*/
function initializeCore(Node memory initialNode) internal {
__Pausable_init();
_nodes[GENESIS_NODE] = initialNode;
_firstUnresolvedNode = GENESIS_NODE + 1;
}
/**
* @notice React to a new node being created by storing it an incrementing the latest node counter
* @param node Node that was newly created
*/
function nodeCreated(Node memory node) internal {
_latestNodeCreated++;
_nodes[_latestNodeCreated] = node;
}
/// @notice Reject the next unresolved node
function _rejectNextNode() internal {
_firstUnresolvedNode++;
}
function confirmNode(
uint64 nodeNum,
bytes32 blockHash,
bytes32 sendRoot
) internal {
Node storage node = getNodeStorage(nodeNum);
// Authenticate data against node's confirm data pre-image
require(node.confirmData == RollupLib.confirmHash(blockHash, sendRoot), "CONFIRM_DATA");
// trusted external call to outbox
outbox.updateSendRoot(sendRoot, blockHash);
_latestConfirmed = nodeNum;
_firstUnresolvedNode = nodeNum + 1;
emit NodeConfirmed(nodeNum, blockHash, sendRoot);
}
/**
* @notice Create a new stake at latest confirmed node
* @param stakerAddress Address of the new staker
* @param depositAmount Stake amount of the new staker
*/
function createNewStake(address stakerAddress, uint256 depositAmount) internal {
uint64 stakerIndex = uint64(_stakerList.length);
_stakerList.push(stakerAddress);
_stakerMap[stakerAddress] = Staker(
depositAmount,
stakerIndex,
_latestConfirmed,
NO_CHAL_INDEX, // new staker is not in challenge
true
);
_nodeStakers[_latestConfirmed][stakerAddress] = true;
_lastStakeBlock = uint64(block.number);
emit UserStakeUpdated(stakerAddress, 0, depositAmount);
}
/**
* @notice Check to see whether the two stakers are in the same challenge
* @param stakerAddress1 Address of the first staker
* @param stakerAddress2 Address of the second staker
* @return Address of the challenge that the two stakers are in
*/
function inChallenge(address stakerAddress1, address stakerAddress2)
internal
view
returns (uint64)
{
Staker storage staker1 = _stakerMap[stakerAddress1];
Staker storage staker2 = _stakerMap[stakerAddress2];
uint64 challenge = staker1.currentChallenge;
require(challenge != NO_CHAL_INDEX, "NO_CHAL");
require(challenge == staker2.currentChallenge, "DIFF_IN_CHAL");
return challenge;
}
/**
* @notice Make the given staker as not being in a challenge
* @param stakerAddress Address of the staker to remove from a challenge
*/
function clearChallenge(address stakerAddress) internal {
Staker storage staker = _stakerMap[stakerAddress];
staker.currentChallenge = NO_CHAL_INDEX;
}
/**
* @notice Mark both the given stakers as engaged in the challenge
* @param staker1 Address of the first staker
* @param staker2 Address of the second staker
* @param challenge Address of the challenge both stakers are now in
*/
function challengeStarted(
address staker1,
address staker2,
uint64 challenge
) internal {
_stakerMap[staker1].currentChallenge = challenge;
_stakerMap[staker2].currentChallenge = challenge;
}
/**
* @notice Add to the stake of the given staker by the given amount
* @param stakerAddress Address of the staker to increase the stake of
* @param amountAdded Amount of stake to add to the staker
*/
function increaseStakeBy(address stakerAddress, uint256 amountAdded) internal {
Staker storage staker = _stakerMap[stakerAddress];
uint256 initialStaked = staker.amountStaked;
uint256 finalStaked = initialStaked + amountAdded;
staker.amountStaked = finalStaked;
emit UserStakeUpdated(stakerAddress, initialStaked, finalStaked);
}
/**
* @notice Reduce the stake of the given staker to the given target
* @param stakerAddress Address of the staker to reduce the stake of
* @param target Amount of stake to leave with the staker
* @return Amount of value released from the stake
*/
function reduceStakeTo(address stakerAddress, uint256 target) internal returns (uint256) {
Staker storage staker = _stakerMap[stakerAddress];
uint256 current = staker.amountStaked;
require(target <= current, "TOO_LITTLE_STAKE");
uint256 amountWithdrawn = current - target;
staker.amountStaked = target;
increaseWithdrawableFunds(stakerAddress, amountWithdrawn);
emit UserStakeUpdated(stakerAddress, current, target);
return amountWithdrawn;
}
/**
* @notice Remove the given staker and turn them into a zombie
* @param stakerAddress Address of the staker to remove
*/
function turnIntoZombie(address stakerAddress) internal {
Staker storage staker = _stakerMap[stakerAddress];
_zombies.push(Zombie(stakerAddress, staker.latestStakedNode));
deleteStaker(stakerAddress);
}
/**
* @notice Update the latest staked node of the zombie at the given index
* @param zombieNum Index of the zombie to move
* @param latest New latest node the zombie is staked on
*/
function zombieUpdateLatestStakedNode(uint256 zombieNum, uint64 latest) internal {
_zombies[zombieNum].latestStakedNode = latest;
}
/**
* @notice Remove the zombie at the given index
* @param zombieNum Index of the zombie to remove
*/
function removeZombie(uint256 zombieNum) internal {
_zombies[zombieNum] = _zombies[_zombies.length - 1];
_zombies.pop();
}
/**
* @notice Mark the given staker as staked on this node
* @param staker Address of the staker to mark
*/
function addStaker(uint64 nodeNum, address staker) internal {
require(!_nodeStakers[nodeNum][staker], "ALREADY_STAKED");
_nodeStakers[nodeNum][staker] = true;
Node storage node = getNodeStorage(nodeNum);
require(node.deadlineBlock != 0, "NO_NODE");
uint64 prevCount = node.stakerCount;
node.stakerCount = prevCount + 1;
if (nodeNum > GENESIS_NODE) {
Node storage parent = getNodeStorage(node.prevNum);
parent.childStakerCount++;
if (prevCount == 0) {
parent.newChildConfirmDeadline(uint64(block.number) + confirmPeriodBlocks);
}
}
}
/**
* @notice Remove the given staker from this node
* @param staker Address of the staker to remove
*/
function removeStaker(uint64 nodeNum, address staker) internal {
require(_nodeStakers[nodeNum][staker], "NOT_STAKED");
_nodeStakers[nodeNum][staker] = false;
Node storage node = getNodeStorage(nodeNum);
node.stakerCount--;
if (nodeNum > GENESIS_NODE) {
getNodeStorage(node.prevNum).childStakerCount--;
}
}
/**
* @notice Remove the given staker and return their stake
* This should not be called if the staker is staked on a descendent of the latest confirmed node
* @param stakerAddress Address of the staker withdrawing their stake
*/
function withdrawStaker(address stakerAddress) internal {
Staker storage staker = _stakerMap[stakerAddress];
uint64 latestConfirmedNum = latestConfirmed();
if (nodeHasStaker(latestConfirmedNum, stakerAddress)) {
// Withdrawing a staker whose latest staked node isn't resolved should be impossible
assert(staker.latestStakedNode == latestConfirmedNum);
removeStaker(latestConfirmedNum, stakerAddress);
}
uint256 initialStaked = staker.amountStaked;
increaseWithdrawableFunds(stakerAddress, initialStaked);
deleteStaker(stakerAddress);
emit UserStakeUpdated(stakerAddress, initialStaked, 0);
}
/**
* @notice Advance the given staker to the given node
* @param stakerAddress Address of the staker adding their stake
* @param nodeNum Index of the node to stake on
*/
function stakeOnNode(address stakerAddress, uint64 nodeNum) internal {
Staker storage staker = _stakerMap[stakerAddress];
addStaker(nodeNum, stakerAddress);
staker.latestStakedNode = nodeNum;
}
/**
* @notice Clear the withdrawable funds for the given address
* @param account Address of the account to remove funds from
* @return Amount of funds removed from account
*/
function withdrawFunds(address account) internal returns (uint256) {
uint256 amount = _withdrawableFunds[account];
_withdrawableFunds[account] = 0;
totalWithdrawableFunds -= amount;
emit UserWithdrawableFundsUpdated(account, amount, 0);
return amount;
}
/**
* @notice Increase the withdrawable funds for the given address
* @param account Address of the account to add withdrawable funds to
*/
function increaseWithdrawableFunds(address account, uint256 amount) internal {
uint256 initialWithdrawable = _withdrawableFunds[account];
uint256 finalWithdrawable = initialWithdrawable + amount;
_withdrawableFunds[account] = finalWithdrawable;
totalWithdrawableFunds += amount;
emit UserWithdrawableFundsUpdated(account, initialWithdrawable, finalWithdrawable);
}
/**
* @notice Remove the given staker
* @param stakerAddress Address of the staker to remove
*/
function deleteStaker(address stakerAddress) private {
Staker storage staker = _stakerMap[stakerAddress];
require(staker.isStaked, "NOT_STAKED");
uint64 stakerIndex = staker.index;
_stakerList[stakerIndex] = _stakerList[_stakerList.length - 1];
_stakerMap[_stakerList[stakerIndex]].index = stakerIndex;
_stakerList.pop();
delete _stakerMap[stakerAddress];
}
struct StakeOnNewNodeFrame {
uint256 currentInboxSize;
Node node;
bytes32 executionHash;
Node prevNode;
bytes32 lastHash;
bool hasSibling;
uint64 deadlineBlock;
bytes32 sequencerBatchAcc;
}
function createNewNode(
RollupLib.Assertion calldata assertion,
uint64 prevNodeNum,
uint256 prevNodeInboxMaxCount,
bytes32 expectedNodeHash
) internal returns (bytes32 newNodeHash) {
require(
assertion.afterState.machineStatus == MachineStatus.FINISHED ||
assertion.afterState.machineStatus == MachineStatus.ERRORED,
"BAD_AFTER_STATUS"
);
StakeOnNewNodeFrame memory memoryFrame;
{
// validate data
memoryFrame.prevNode = getNode(prevNodeNum);
memoryFrame.currentInboxSize = bridge.sequencerMessageCount();
// Make sure the previous state is correct against the node being built on
require(
RollupLib.stateHash(assertion.beforeState, prevNodeInboxMaxCount) ==
memoryFrame.prevNode.stateHash,
"PREV_STATE_HASH"
);
// Ensure that the assertion doesn't read past the end of the current inbox
uint64 afterInboxCount = assertion.afterState.globalState.getInboxPosition();
uint64 prevInboxPosition = assertion.beforeState.globalState.getInboxPosition();
require(afterInboxCount >= prevInboxPosition, "INBOX_BACKWARDS");
if (afterInboxCount == prevInboxPosition) {
require(
assertion.afterState.globalState.getPositionInMessage() >=
assertion.beforeState.globalState.getPositionInMessage(),
"INBOX_POS_IN_MSG_BACKWARDS"
);
}
// See validator/assertion.go ExecutionState RequiredBatches() for reasoning
if (
assertion.afterState.machineStatus == MachineStatus.ERRORED ||
assertion.afterState.globalState.getPositionInMessage() > 0
) {
// The current inbox message was read
afterInboxCount++;
}
require(afterInboxCount <= memoryFrame.currentInboxSize, "INBOX_PAST_END");
// This gives replay protection against the state of the inbox
if (afterInboxCount > 0) {
memoryFrame.sequencerBatchAcc = bridge.sequencerInboxAccs(afterInboxCount - 1);
}
}
{
memoryFrame.executionHash = RollupLib.executionHash(assertion);
memoryFrame.deadlineBlock = uint64(block.number) + confirmPeriodBlocks;
memoryFrame.hasSibling = memoryFrame.prevNode.latestChildNumber > 0;
// here we don't use ternacy operator to remain compatible with slither
if (memoryFrame.hasSibling) {
memoryFrame.lastHash = getNodeStorage(memoryFrame.prevNode.latestChildNumber)
.nodeHash;
} else {
memoryFrame.lastHash = memoryFrame.prevNode.nodeHash;
}
newNodeHash = RollupLib.nodeHash(
memoryFrame.hasSibling,
memoryFrame.lastHash,
memoryFrame.executionHash,
memoryFrame.sequencerBatchAcc,
wasmModuleRoot
);
require(
newNodeHash == expectedNodeHash || expectedNodeHash == bytes32(0),
"UNEXPECTED_NODE_HASH"
);
memoryFrame.node = NodeLib.createNode(
RollupLib.stateHash(assertion.afterState, memoryFrame.currentInboxSize),
RollupLib.challengeRootHash(
memoryFrame.executionHash,
block.number,
wasmModuleRoot
),
RollupLib.confirmHash(assertion),
prevNodeNum,
memoryFrame.deadlineBlock,
newNodeHash
);
}
{
uint64 nodeNum = latestNodeCreated() + 1;
// Fetch a storage reference to prevNode since we copied our other one into memory
// and we don't have enough stack available to keep to keep the previous storage reference around
Node storage prevNode = getNodeStorage(prevNodeNum);
prevNode.childCreated(nodeNum);
nodeCreated(memoryFrame.node);
}
emit NodeCreated(
latestNodeCreated(),
memoryFrame.prevNode.nodeHash,
newNodeHash,
memoryFrame.executionHash,
assertion,
memoryFrame.sequencerBatchAcc,
wasmModuleRoot,
memoryFrame.currentInboxSize
);
return newNodeHash;
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
pragma experimental ABIEncoderV2;
import "../rollup/IRollupCore.sol";
import "../challenge/IChallengeManager.sol";
import {NO_CHAL_INDEX} from "../libraries/Constants.sol";
contract ValidatorUtils {
using NodeLib for Node;
enum ConfirmType {
NONE,
VALID,
INVALID
}
enum NodeConflictType {
NONE,
FOUND,
INDETERMINATE,
INCOMPLETE
}
struct NodeConflict {
NodeConflictType ty;
uint64 node1;
uint64 node2;
}
function findStakerConflict(
IRollupCore rollup,
address staker1,
address staker2,
uint256 maxDepth
) external view returns (NodeConflict memory) {
uint64 staker1NodeNum = rollup.latestStakedNode(staker1);
uint64 staker2NodeNum = rollup.latestStakedNode(staker2);
return findNodeConflict(rollup, staker1NodeNum, staker2NodeNum, maxDepth);
}
function checkDecidableNextNode(IRollupUserAbs rollup) external view returns (ConfirmType) {
try ValidatorUtils(address(this)).requireConfirmable(rollup) {
return ConfirmType.VALID;
} catch {}
try ValidatorUtils(address(this)).requireRejectable(rollup) {
return ConfirmType.INVALID;
} catch {
return ConfirmType.NONE;
}
}
function requireRejectable(IRollupCore rollup) external view {
IRollupUser(address(rollup)).requireUnresolvedExists();
uint64 firstUnresolvedNode = rollup.firstUnresolvedNode();
Node memory node = rollup.getNode(firstUnresolvedNode);
if (node.prevNum == rollup.latestConfirmed()) {
// Verify the block's deadline has passed
require(block.number >= node.deadlineBlock, "BEFORE_DEADLINE");
rollup.getNode(node.prevNum).requirePastChildConfirmDeadline();
// Verify that no staker is staked on this node
require(
node.stakerCount ==
IRollupUser(address(rollup)).countStakedZombies(firstUnresolvedNode),
"HAS_STAKERS"
);
}
}
function requireConfirmable(IRollupUserAbs rollup) external view {
rollup.requireUnresolvedExists();
uint256 stakerCount = rollup.stakerCount();
// There is at least one non-zombie staker
require(stakerCount > 0, "NO_STAKERS");
uint64 firstUnresolved = rollup.firstUnresolvedNode();
Node memory node = rollup.getNode(firstUnresolved);
// Verify the block's deadline has passed
node.requirePastDeadline();
// Check that prev is latest confirmed
assert(node.prevNum == rollup.latestConfirmed());
Node memory prevNode = rollup.getNode(node.prevNum);
prevNode.requirePastChildConfirmDeadline();
uint256 zombiesStakedOnOtherChildren = rollup.countZombiesStakedOnChildren(node.prevNum) -
rollup.countStakedZombies(firstUnresolved);
require(
prevNode.childStakerCount == node.stakerCount + zombiesStakedOnOtherChildren,
"NOT_ALL_STAKED"
);
}
function refundableStakers(IRollupCore rollup) external view returns (address[] memory) {
uint256 stakerCount = rollup.stakerCount();
address[] memory stakers = new address[](stakerCount);
uint256 latestConfirmed = rollup.latestConfirmed();
uint256 index = 0;
for (uint64 i = 0; i < stakerCount; i++) {
address staker = rollup.getStakerAddress(i);
uint256 latestStakedNode = rollup.latestStakedNode(staker);
if (latestStakedNode <= latestConfirmed && rollup.currentChallenge(staker) == 0) {
stakers[index] = staker;
index++;
}
}
assembly {
mstore(stakers, index)
}
return stakers;
}
function latestStaked(IRollupCore rollup, address staker)
external
view
returns (uint64, Node memory)
{
uint64 num = rollup.latestStakedNode(staker);
if (num == 0) {
num = rollup.latestConfirmed();
}
Node memory node = rollup.getNode(num);
return (num, node);
}
function stakedNodes(IRollupCore rollup, address staker)
external
view
returns (uint64[] memory)
{
uint64[] memory nodes = new uint64[](100000);
uint256 index = 0;
for (uint64 i = rollup.latestConfirmed(); i <= rollup.latestNodeCreated(); i++) {
if (rollup.nodeHasStaker(i, staker)) {
nodes[index] = i;
index++;
}
}
// Shrink array down to real size
assembly {
mstore(nodes, index)
}
return nodes;
}
function findNodeConflict(
IRollupCore rollup,
uint64 node1,
uint64 node2,
uint256 maxDepth
) public view returns (NodeConflict memory) {
uint64 firstUnresolvedNode = rollup.firstUnresolvedNode();
uint64 node1Prev = rollup.getNode(node1).prevNum;
uint64 node2Prev = rollup.getNode(node2).prevNum;
for (uint256 i = 0; i < maxDepth; i++) {
if (node1 == node2) {
return NodeConflict(NodeConflictType.NONE, node1, node2);
}
if (node1Prev == node2Prev) {
return NodeConflict(NodeConflictType.FOUND, node1, node2);
}
if (node1Prev < firstUnresolvedNode && node2Prev < firstUnresolvedNode) {
return NodeConflict(NodeConflictType.INDETERMINATE, 0, 0);
}
if (node1Prev < node2Prev) {
node2 = node2Prev;
node2Prev = rollup.getNode(node2).prevNum;
} else {
node1 = node1Prev;
node1Prev = rollup.getNode(node1).prevNum;
}
}
return NodeConflict(NodeConflictType.INCOMPLETE, 0, 0);
}
function getStakers(
IRollupCore rollup,
uint64 startIndex,
uint64 max
) public view returns (address[] memory, bool hasMore) {
uint256 maxStakers = rollup.stakerCount();
if (startIndex + max <= maxStakers) {
maxStakers = startIndex + max;
hasMore = true;
}
address[] memory stakers = new address[](maxStakers);
for (uint64 i = 0; i < maxStakers; i++) {
stakers[i] = rollup.getStakerAddress(startIndex + i);
}
return (stakers, hasMore);
}
function timedOutChallenges(
IRollupCore rollup,
uint64 startIndex,
uint64 max
) external view returns (uint64[] memory, bool hasMore) {
(address[] memory stakers, bool hasMoreStakers) = getStakers(rollup, startIndex, max);
uint64[] memory challenges = new uint64[](stakers.length);
uint256 index = 0;
IChallengeManager challengeManager = rollup.challengeManager();
for (uint256 i = 0; i < stakers.length; i++) {
address staker = stakers[i];
uint64 challengeIndex = rollup.currentChallenge(staker);
if (
challengeIndex != NO_CHAL_INDEX &&
challengeManager.isTimedOut(challengeIndex) &&
challengeManager.currentResponder(challengeIndex) == staker
) {
challenges[index++] = challengeIndex;
}
}
// Shrink array down to real size
assembly {
mstore(challenges, index)
}
return (challenges, hasMoreStakers);
}
// Worst case runtime of O(depth), as it terminates if it switches paths.
function areUnresolvedNodesLinear(IRollupCore rollup) external view returns (bool) {
uint256 end = rollup.latestNodeCreated();
for (uint64 i = rollup.firstUnresolvedNode(); i <= end; i++) {
if (i > 0 && rollup.getNode(i).prevNum != i - 1) {
return false;
}
}
return true;
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../challenge/IChallengeManager.sol";
import "../libraries/DelegateCallAware.sol";
import "../libraries/IGasRefunder.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/// @dev thrown when arrays provided don't have the expected length
error BadArrayLength(uint256 expected, uint256 actual);
/// @dev thrown when a function is called by an address that isn't the owner nor a executor
error NotExecutorOrOwner(address actual);
/// @dev thrown when the particular address can't be called by an executor
error OnlyOwnerDestination(address expected, address actual, address destination);
/// @dev thrown when eth withdrawal tx fails
error WithdrawEthFail(address destination);
contract ValidatorWallet is OwnableUpgradeable, DelegateCallAware, GasRefundEnabled {
using Address for address;
/// @dev a executor is allowed to call only certain contracts
mapping(address => bool) public executors;
/// @dev allowed addresses which can be called by an executor
mapping(address => bool) public allowedExecutorDestinations;
modifier onlyExecutorOrOwner() {
if (!executors[_msgSender()] && owner() != _msgSender())
revert NotExecutorOrOwner(_msgSender());
_;
}
event ExecutorUpdated(address indexed executor, bool isExecutor);
/// @dev updates the executor addresses
function setExecutor(address[] calldata newExecutors, bool[] calldata isExecutor)
external
onlyOwner
{
if (newExecutors.length != isExecutor.length)
revert BadArrayLength(newExecutors.length, isExecutor.length);
unchecked {
for (uint64 i = 0; i < newExecutors.length; ++i) {
executors[newExecutors[i]] = isExecutor[i];
emit ExecutorUpdated(newExecutors[i], isExecutor[i]);
}
}
}
function initialize(
address _executor,
address _owner,
address[] calldata initialExecutorAllowedDests
) external initializer onlyDelegated {
__Ownable_init();
transferOwnership(_owner);
executors[_executor] = true;
emit ExecutorUpdated(_executor, true);
unchecked {
for (uint64 i = 0; i < initialExecutorAllowedDests.length; ++i) {
allowedExecutorDestinations[initialExecutorAllowedDests[i]] = true;
emit AllowedExecutorDestinationsUpdated(initialExecutorAllowedDests[i], true);
}
}
}
event AllowedExecutorDestinationsUpdated(address indexed destination, bool isSet);
/// @notice updates the destination addresses which executors are allowed to call
function setAllowedExecutorDestinations(address[] calldata destinations, bool[] calldata isSet)
external
onlyOwner
{
if (destinations.length != isSet.length)
revert BadArrayLength(destinations.length, isSet.length);
unchecked {
for (uint256 i = 0; i < destinations.length; ++i) {
allowedExecutorDestinations[destinations[i]] = isSet[i];
emit AllowedExecutorDestinationsUpdated(destinations[i], isSet[i]);
}
}
}
/// @dev reverts if the current function can't be called
function validateExecuteTransaction(address destination) public view {
if (!allowedExecutorDestinations[destination] && owner() != _msgSender())
revert OnlyOwnerDestination(owner(), _msgSender(), destination);
}
function executeTransactions(
bytes[] calldata data,
address[] calldata destination,
uint256[] calldata amount
) external payable {
executeTransactionsWithGasRefunder(IGasRefunder(address(0)), data, destination, amount);
}
function executeTransactionsWithGasRefunder(
IGasRefunder gasRefunder,
bytes[] calldata data,
address[] calldata destination,
uint256[] calldata amount
) public payable onlyExecutorOrOwner refundsGas(gasRefunder) {
uint256 numTxes = data.length;
if (numTxes != destination.length) revert BadArrayLength(numTxes, destination.length);
if (numTxes != amount.length) revert BadArrayLength(numTxes, amount.length);
for (uint256 i = 0; i < numTxes; i++) {
if (data[i].length > 0) require(destination[i].isContract(), "NO_CODE_AT_ADDR");
validateExecuteTransaction(destination[i]);
// We use a low level call here to allow for contract and non-contract calls
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = address(destination[i]).call{value: amount[i]}(data[i]);
if (!success) {
assembly {
let ptr := mload(0x40)
let size := returndatasize()
returndatacopy(ptr, 0, size)
revert(ptr, size)
}
}
}
}
function executeTransaction(
bytes calldata data,
address destination,
uint256 amount
) external payable {
executeTransactionWithGasRefunder(IGasRefunder(address(0)), data, destination, amount);
}
function executeTransactionWithGasRefunder(
IGasRefunder gasRefunder,
bytes calldata data,
address destination,
uint256 amount
) public payable onlyExecutorOrOwner refundsGas(gasRefunder) {
if (data.length > 0) require(destination.isContract(), "NO_CODE_AT_ADDR");
validateExecuteTransaction(destination);
// We use a low level call here to allow for contract and non-contract calls
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = destination.call{value: amount}(data);
if (!success) {
assembly {
let ptr := mload(0x40)
let size := returndatasize()
returndatacopy(ptr, 0, size)
revert(ptr, size)
}
}
}
function timeoutChallenges(IChallengeManager manager, uint64[] calldata challenges) external {
timeoutChallengesWithGasRefunder(IGasRefunder(address(0)), manager, challenges);
}
function timeoutChallengesWithGasRefunder(
IGasRefunder gasRefunder,
IChallengeManager manager,
uint64[] calldata challenges
) public onlyExecutorOrOwner refundsGas(gasRefunder) {
uint256 challengesCount = challenges.length;
for (uint256 i = 0; i < challengesCount; i++) {
try manager.timeout(challenges[i]) {} catch (bytes memory error) {
if (error.length == 0) {
// Assume out of gas
// We need to revert here so gas estimation works
require(false, "GAS");
}
}
}
}
receive() external payable {}
/// @dev allows the owner to withdraw eth held by this contract
function withdrawEth(uint256 amount, address destination) external onlyOwner {
// solhint-disable-next-line avoid-low-level-calls
(bool success, ) = destination.call{value: amount}("");
if (!success) revert WithdrawEthFail(destination);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal onlyInitializing {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./ValidatorWallet.sol";
contract ValidatorWalletCreator is Ownable {
event WalletCreated(
address indexed walletAddress,
address indexed executorAddress,
address indexed ownerAddress,
address adminProxy
);
event TemplateUpdated();
address public template;
constructor() Ownable() {
template = address(new ValidatorWallet());
}
function setTemplate(address _template) external onlyOwner {
template = _template;
emit TemplateUpdated();
}
function createWallet(address[] calldata initialExecutorAllowedDests)
external
returns (address)
{
address _executor = msg.sender;
address _owner = msg.sender;
ProxyAdmin admin = new ProxyAdmin();
address proxy = address(
new TransparentUpgradeableProxy(address(template), address(admin), "")
);
admin.transferOwnership(_owner);
ValidatorWallet(payable(proxy)).initialize(_executor, _owner, initialExecutorAllowedDests);
emit WalletCreated(proxy, _executor, _owner, address(admin));
return proxy;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/beacon/UpgradeableBeacon.sol)
pragma solidity ^0.8.0;
import "./IBeacon.sol";
import "../../access/Ownable.sol";
import "../../utils/Address.sol";
/**
* @dev This contract is used in conjunction with one or more instances of {BeaconProxy} to determine their
* implementation contract, which is where they will delegate all function calls.
*
* An owner is able to change the implementation the beacon points to, thus upgrading the proxies that use this beacon.
*/
contract UpgradeableBeacon is IBeacon, Ownable {
address private _implementation;
/**
* @dev Emitted when the implementation returned by the beacon is changed.
*/
event Upgraded(address indexed implementation);
/**
* @dev Sets the address of the initial implementation, and the deployer account as the owner who can upgrade the
* beacon.
*/
constructor(address implementation_) {
_setImplementation(implementation_);
}
/**
* @dev Returns the current implementation address.
*/
function implementation() public view virtual override returns (address) {
return _implementation;
}
/**
* @dev Upgrades the beacon to a new implementation.
*
* Emits an {Upgraded} event.
*
* Requirements:
*
* - msg.sender must be the owner of the contract.
* - `newImplementation` must be a contract.
*/
function upgradeTo(address newImplementation) public virtual onlyOwner {
_setImplementation(newImplementation);
emit Upgraded(newImplementation);
}
/**
* @dev Sets the implementation contract address for this beacon
*
* Requirements:
*
* - `newImplementation` must be a contract.
*/
function _setImplementation(address newImplementation) private {
require(Address.isContract(newImplementation), "UpgradeableBeacon: implementation is not a contract");
_implementation = newImplementation;
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/proxy/Proxy.sol";
contract SimpleProxy is Proxy {
address private immutable impl;
constructor(address impl_) {
impl = impl_;
}
function _implementation() internal view override returns (address) {
return impl;
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.0;
import "../../interfaces/draft-IERC1822.sol";
import "../ERC1967/ERC1967Upgrade.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*
* _Available since v4.1._
*/
abstract contract UUPSUpgradeable is IERC1822Proxiable, ERC1967Upgrade {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable state-variable-assignment
address private immutable __self = address(this);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. Execution of a
* function through ERC1167 minimal proxies (clones) would not normally pass this test, but is not guaranteed to
* fail.
*/
modifier onlyProxy() {
require(address(this) != __self, "Function must be called through delegatecall");
require(_getImplementation() == __self, "Function must be called through active proxy");
_;
}
/**
* @dev Check that the execution is not being performed through a delegate call. This allows a function to be
* callable on the implementing contract but not through proxies.
*/
modifier notDelegated() {
require(address(this) == __self, "UUPSUpgradeable: must not be called through delegatecall");
_;
}
/**
* @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the
* implementation. It is used to validate that the this implementation remains valid after an upgrade.
*
* IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks
* bricking a proxy that upgrades to it, by delegating to itself until out of gas. Thus it is critical that this
* function revert if invoked through a proxy. This is guaranteed by the `notDelegated` modifier.
*/
function proxiableUUID() external view virtual override notDelegated returns (bytes32) {
return _IMPLEMENTATION_SLOT;
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*/
function upgradeTo(address newImplementation) external virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, new bytes(0), false);
}
/**
* @dev Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeUpgrade}.
*
* Emits an {Upgraded} event.
*/
function upgradeToAndCall(address newImplementation, bytes memory data) external payable virtual onlyProxy {
_authorizeUpgrade(newImplementation);
_upgradeToAndCallUUPS(newImplementation, data, true);
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the contract. Called by
* {upgradeTo} and {upgradeToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeUpgrade(address) internal override onlyOwner {}
* ```
*/
function _authorizeUpgrade(address newImplementation) internal virtual;
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import {DoubleLogicERC1967Upgrade} from "./AdminFallbackProxy.sol";
import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol";
/// @notice An extension to OZ's UUPSUpgradeable contract to be used for handling UUPS upgrades with a DoubleLogicERC1967Upgrade proxy
/// The should be used in the primary implementation slot of the DoubleLogicUUPS proxy
/// @dev upgrades should be handles by the primary logic contract in order to pass the `onlyProxy` check
abstract contract DoubleLogicUUPSUpgradeable is UUPSUpgradeable, DoubleLogicERC1967Upgrade {
/// @inheritdoc UUPSUpgradeable
function proxiableUUID() external view override notDelegated returns (bytes32) {
return _IMPLEMENTATION_SLOT;
}
/**
* @dev Function that should revert when `msg.sender` is not authorized to upgrade the secondary contract. Called by
* {upgradeSecondaryTo} and {upgradeSecondaryToAndCall}.
*
* Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.
*
* ```solidity
* function _authorizeSecondaryUpgrade(address) internal override onlyOwner {}
* ```
*/
function _authorizeSecondaryUpgrade(address newImplementation) internal virtual;
/**
* @dev Upgrade the secondary implementation of the proxy to `newImplementation`.
*
* Calls {_authorizeSecondaryUpgrade}.
*
* Emits an {UpgradedSecondary} event.
*/
function upgradeSecondaryTo(address newImplementation) external onlyProxy {
_authorizeSecondaryUpgrade(newImplementation);
_upgradeSecondaryToAndCallUUPS(newImplementation, new bytes(0), false);
}
/**
* @dev Upgrade the secondary implementation of the proxy to `newImplementation`, and subsequently execute the function call
* encoded in `data`.
*
* Calls {_authorizeSecondaryUpgrade}.
*
* Emits an {UpgradedSecondary} event.
*/
function upgradeSecondaryToAndCall(address newImplementation, bytes memory data)
external
payable
onlyProxy
{
_authorizeSecondaryUpgrade(newImplementation);
_upgradeSecondaryToAndCallUUPS(newImplementation, data, true);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import {IRollupAdmin, IRollupUser} from "./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);
connectedContracts.sequencerInbox.addSequencerL2Batch(0, "", 1, IGasRefunder(address(0)));
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(
RollupLib.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,
RollupLib.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(IInbox newInbox) external {
inbox = newInbox;
emit OwnerFunctionCalled(28);
}
function createNitroMigrationGenesis(RollupLib.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);
}
}// Copyright 2021-2022, Offchain Labs, Inc. // For license information, see https://github.com/nitro/blob/master/LICENSE // SPDX-License-Identifier: BUSL-1.1 // solhint-disable-next-line compiler-version pragma solidity >=0.6.9 <0.9.0; import "../bridge/IBridge.sol"; import "../bridge/IOutbox.sol"; import "../bridge/IInbox.sol"; import "../bridge/ISequencerInbox.sol";
// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../bridge/IInbox.sol";
import "../bridge/IBridge.sol";
import "../bridge/Messages.sol";
import "./BridgeStub.sol";
import {
L2_MSG,
L1MessageType_L2FundedByL1,
L1MessageType_submitRetryableTx,
L2MessageType_unsignedEOATx,
L2MessageType_unsignedContractTx
} from "../libraries/MessageTypes.sol";
contract InboxStub is IInbox {
IBridge public override bridge;
ISequencerInbox public override sequencerInbox;
bool public paused;
function pause() external pure {
revert("NOT IMPLEMENTED");
}
function unpause() external pure {
revert("NOT IMPLEMENTED");
}
function initialize(IBridge _bridge, ISequencerInbox) external {
require(address(bridge) == address(0), "ALREADY_INIT");
bridge = _bridge;
}
/**
* @notice Send a generic L2 message to the chain
* @dev This method is an optimization to avoid having to emit the entirety of the messageData in a log. Instead validators are expected to be able to parse the data from the transaction's input
* @param messageData Data of the message being sent
*/
function sendL2MessageFromOrigin(bytes calldata messageData) external returns (uint256) {
// solhint-disable-next-line avoid-tx-origin
require(msg.sender == tx.origin, "origin only");
uint256 msgNum = deliverToBridge(L2_MSG, msg.sender, keccak256(messageData));
emit InboxMessageDeliveredFromOrigin(msgNum);
return msgNum;
}
/**
* @notice Send a generic L2 message to the chain
* @dev This method can be used to send any type of message that doesn't require L1 validation
* @param messageData Data of the message being sent
*/
function sendL2Message(bytes calldata messageData) external override returns (uint256) {
uint256 msgNum = deliverToBridge(L2_MSG, msg.sender, keccak256(messageData));
emit InboxMessageDelivered(msgNum, messageData);
return msgNum;
}
function deliverToBridge(
uint8 kind,
address sender,
bytes32 messageDataHash
) internal returns (uint256) {
return bridge.enqueueDelayedMessage{value: msg.value}(kind, sender, messageDataHash);
}
function sendUnsignedTransaction(
uint256,
uint256,
uint256,
address,
uint256,
bytes calldata
) external pure override returns (uint256) {
revert("NOT_IMPLEMENTED");
}
function sendContractTransaction(
uint256,
uint256,
address,
uint256,
bytes calldata
) external pure override returns (uint256) {
revert("NOT_IMPLEMENTED");
}
function sendL1FundedUnsignedTransaction(
uint256,
uint256,
uint256,
address,
bytes calldata
) external payable override returns (uint256) {
revert("NOT_IMPLEMENTED");
}
function sendL1FundedContractTransaction(
uint256,
uint256,
address,
bytes calldata
) external payable override returns (uint256) {
revert("NOT_IMPLEMENTED");
}
function createRetryableTicket(
address,
uint256,
uint256,
address,
address,
uint256,
uint256,
bytes calldata
) external payable override returns (uint256) {
revert("NOT_IMPLEMENTED");
}
function unsafeCreateRetryableTicket(
address,
uint256,
uint256,
address,
address,
uint256,
uint256,
bytes calldata
) external payable override returns (uint256) {
revert("NOT_IMPLEMENTED");
}
function depositEth() external payable override returns (uint256) {
revert("NOT_IMPLEMENTED");
}
function postUpgradeInit(IBridge _bridge) external {}
function calculateRetryableSubmissionFee(uint256, uint256)
external
pure
override
returns (uint256)
{
revert("NOT_IMPLEMENTED");
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "./InboxStub.sol";
import "../bridge/IBridge.sol";
contract BridgeStub is IBridge {
struct InOutInfo {
uint256 index;
bool allowed;
}
mapping(address => InOutInfo) private allowedDelayedInboxesMap;
//mapping(address => InOutInfo) private allowedOutboxesMap;
address[] public allowedDelayedInboxList;
address[] public allowedOutboxList;
address public override activeOutbox;
// Accumulator for delayed inbox; tail represents hash of the current state; each element represents the inclusion of a new message.
bytes32[] public override delayedInboxAccs;
bytes32[] public override sequencerInboxAccs;
address public sequencerInbox;
function setSequencerInbox(address _sequencerInbox) external override {
sequencerInbox = _sequencerInbox;
emit SequencerInboxUpdated(_sequencerInbox);
}
function allowedDelayedInboxes(address inbox) external view override returns (bool) {
return allowedDelayedInboxesMap[inbox].allowed;
}
function allowedOutboxes(address) external pure override returns (bool) {
revert("NOT_IMPLEMENTED");
}
function enqueueDelayedMessage(
uint8 kind,
address sender,
bytes32 messageDataHash
) external payable override returns (uint256) {
require(allowedDelayedInboxesMap[msg.sender].allowed, "NOT_FROM_INBOX");
return
addMessageToDelayedAccumulator(
kind,
sender,
block.number,
block.timestamp, // solhint-disable-line not-rely-on-time
block.basefee,
messageDataHash
);
}
function enqueueSequencerMessage(bytes32 dataHash, uint256 afterDelayedMessagesRead)
external
returns (
uint256 seqMessageIndex,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 acc
)
{
seqMessageIndex = sequencerInboxAccs.length;
if (sequencerInboxAccs.length > 0) {
beforeAcc = sequencerInboxAccs[sequencerInboxAccs.length - 1];
}
if (afterDelayedMessagesRead > 0) {
delayedAcc = delayedInboxAccs[afterDelayedMessagesRead - 1];
}
acc = keccak256(abi.encodePacked(beforeAcc, dataHash, delayedAcc));
sequencerInboxAccs.push(acc);
}
function submitBatchSpendingReport(address batchPoster, bytes32 dataHash)
external
returns (uint256)
{
// TODO: implement stub
}
function addMessageToDelayedAccumulator(
uint8,
address,
uint256,
uint256,
uint256,
bytes32 messageDataHash
) internal returns (uint256) {
uint256 count = delayedInboxAccs.length;
bytes32 messageHash = Messages.messageHash(
0,
address(uint160(0)),
0,
0,
0,
0,
messageDataHash
);
bytes32 prevAcc = 0;
if (count > 0) {
prevAcc = delayedInboxAccs[count - 1];
}
delayedInboxAccs.push(Messages.accumulateInboxMessage(prevAcc, messageHash));
return count;
}
function executeCall(
address,
uint256,
bytes calldata
) external pure override returns (bool, bytes memory) {
revert("NOT_IMPLEMENTED");
}
function setDelayedInbox(address inbox, bool enabled) external override {
InOutInfo storage info = allowedDelayedInboxesMap[inbox];
bool alreadyEnabled = info.allowed;
emit InboxToggle(inbox, enabled);
if ((alreadyEnabled && enabled) || (!alreadyEnabled && !enabled)) {
return;
}
if (enabled) {
allowedDelayedInboxesMap[inbox] = InOutInfo(allowedDelayedInboxList.length, true);
allowedDelayedInboxList.push(inbox);
} else {
allowedDelayedInboxList[info.index] = allowedDelayedInboxList[
allowedDelayedInboxList.length - 1
];
allowedDelayedInboxesMap[allowedDelayedInboxList[info.index]].index = info.index;
allowedDelayedInboxList.pop();
delete allowedDelayedInboxesMap[inbox];
}
}
function setOutbox(
address, /* outbox */
bool /* enabled*/
) external pure override {
revert("NOT_IMPLEMENTED");
}
function delayedMessageCount() external view override returns (uint256) {
return delayedInboxAccs.length;
}
function sequencerMessageCount() external view override returns (uint256) {
return sequencerInboxAccs.length;
}
function rollup() external pure override returns (IOwnable) {
revert("NOT_IMPLEMENTED");
}
function acceptFundsFromOldBridge() external payable {}
function initialize(IOwnable) external pure {
revert("NOT_IMPLEMENTED");
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;
import {
AlreadyInit,
NotRollup,
ProofTooLong,
PathNotMinimal,
UnknownRoot,
AlreadySpent,
BridgeCallFailed
} from "../libraries/Error.sol";
import "../bridge/IBridge.sol";
import "../bridge/IOutbox.sol";
import "../libraries/MerkleLib.sol";
import "../libraries/DelegateCallAware.sol";
contract OutboxWithoutOptTester is DelegateCallAware, IOutbox {
address public rollup; // the rollup contract
IBridge public bridge; // the bridge contract
function spent(uint256) external pure override returns (bytes32) {
revert("NOT_IMPLEMETED");
}
mapping(uint256 => bool) public isSpent; // maps leaf number => if spent
mapping(bytes32 => bytes32) public roots; // maps root hashes => L2 block hash
struct L2ToL1Context {
uint128 l2Block;
uint128 l1Block;
uint128 timestamp;
bytes32 outputId;
address sender;
}
// Note, these variables are set and then wiped during a single transaction.
// Therefore their values don't need to be maintained, and their slots will
// be empty outside of transactions
L2ToL1Context internal context;
uint128 public constant OUTBOX_VERSION = 2;
function initialize(IBridge _bridge) external {
if (address(bridge) != address(0)) revert AlreadyInit();
bridge = _bridge;
rollup = address(_bridge.rollup());
}
function updateSendRoot(bytes32 root, bytes32 l2BlockHash) external override {
//if (msg.sender != rollup) revert NotRollup(msg.sender, rollup); //test only!!!
roots[root] = l2BlockHash;
emit SendRootUpdated(root, l2BlockHash);
}
/// @notice When l2ToL1Sender returns a nonzero address, the message was originated by an L2 account
/// When the return value is zero, that means this is a system message
/// @dev the l2ToL1Sender behaves as the tx.origin, the msg.sender should be validated to protect against reentrancies
function l2ToL1Sender() external view override returns (address) {
return context.sender;
}
function l2ToL1Block() external view override returns (uint256) {
return uint256(context.l2Block);
}
function l2ToL1EthBlock() external view override returns (uint256) {
return uint256(context.l1Block);
}
function l2ToL1Timestamp() external view override returns (uint256) {
return uint256(context.timestamp);
}
// @deprecated batch number is now always 0
function l2ToL1BatchNum() external pure returns (uint256) {
return 0;
}
function l2ToL1OutputId() external view override returns (bytes32) {
return context.outputId;
}
/**
* @notice Executes a messages in an Outbox entry.
* @dev Reverts if dispute period hasn't expired, since the outbox entry
* is only created once the rollup confirms the respective assertion.
* @param proof Merkle proof of message inclusion in send root
* @param index Merkle path to message
* @param l2Sender sender if original message (i.e., caller of ArbSys.sendTxToL1)
* @param to destination address for L1 contract call
* @param l2Block l2 block number at which sendTxToL1 call was made
* @param l1Block l1 block number at which sendTxToL1 call was made
* @param l2Timestamp l2 Timestamp at which sendTxToL1 call was made
* @param value wei in L1 message
* @param data abi-encoded L1 message data
*/
function executeTransaction(
bytes32[] calldata proof,
uint256 index,
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
uint256 value,
bytes calldata data
) external virtual override {
bytes32 outputId;
{
bytes32 userTx = calculateItemHash(
l2Sender,
to,
l2Block,
l1Block,
l2Timestamp,
value,
data
);
outputId = recordOutputAsSpent(proof, index, userTx);
emit OutBoxTransactionExecuted(to, l2Sender, 0, index);
}
// we temporarily store the previous values so the outbox can naturally
// unwind itself when there are nested calls to `executeTransaction`
L2ToL1Context memory prevContext = context;
context = L2ToL1Context({
sender: l2Sender,
l2Block: uint128(l2Block),
l1Block: uint128(l1Block),
timestamp: uint128(l2Timestamp),
outputId: outputId
});
// set and reset vars around execution so they remain valid during call
executeBridgeCall(to, value, data);
context = prevContext;
}
function executeTransactionSimulation(
uint256,
address,
address,
uint256,
uint256,
uint256,
uint256,
bytes calldata
) external pure override {
revert("Not implemented");
}
function recordOutputAsSpent(
bytes32[] memory proof,
uint256 index,
bytes32 item
) internal returns (bytes32) {
if (proof.length >= 256) revert ProofTooLong(proof.length);
if (index >= 2**proof.length) revert PathNotMinimal(index, 2**proof.length);
// Hash the leaf an extra time to prove it's a leaf
bytes32 calcRoot = calculateMerkleRoot(proof, index, item);
if (roots[calcRoot] == bytes32(0)) revert UnknownRoot(calcRoot);
if (isSpent[index]) revert AlreadySpent(index);
isSpent[index] = true;
return bytes32(index);
}
function executeBridgeCall(
address to,
uint256 value,
bytes memory data
) internal {
(bool success, bytes memory returndata) = bridge.executeCall(to, value, data);
if (!success) {
if (returndata.length > 0) {
// solhint-disable-next-line no-inline-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert BridgeCallFailed();
}
}
}
function calculateItemHash(
address l2Sender,
address to,
uint256 l2Block,
uint256 l1Block,
uint256 l2Timestamp,
uint256 value,
bytes calldata data
) public pure override returns (bytes32) {
return
keccak256(abi.encodePacked(l2Sender, to, l2Block, l1Block, l2Timestamp, value, data));
}
function calculateMerkleRoot(
bytes32[] memory proof,
uint256 path,
bytes32 item
) public pure override returns (bytes32) {
return MerkleLib.calculateRoot(proof, path, keccak256(abi.encodePacked(item)));
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.4;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
import {
NotContract,
NotRollupOrOwner,
NotDelayedInbox,
NotSequencerInbox,
NotOutbox,
InvalidOutboxSet
} from "../libraries/Error.sol";
import "../bridge/IBridge.sol";
import "../bridge/Messages.sol";
import "../libraries/DelegateCallAware.sol";
/**
* @title Staging ground for incoming and outgoing messages
* @notice Holds the inbox accumulator for delayed messages, and is the ETH escrow
* for value sent with these messages.
* Since the escrow is held here, this contract also contains a list of allowed
* outboxes that can make calls from here and withdraw this escrow.
*/
contract BridgeTester is Initializable, DelegateCallAware, IBridge {
using AddressUpgradeable for address;
struct InOutInfo {
uint256 index;
bool allowed;
}
mapping(address => InOutInfo) private allowedInboxesMap;
mapping(address => InOutInfo) private allowedOutboxesMap;
address[] public allowedDelayedInboxList;
address[] public allowedOutboxList;
address private _activeOutbox;
IOwnable public rollup;
address public sequencerInbox;
modifier onlyRollupOrOwner() {
if (msg.sender != address(rollup)) {
address rollupOwner = rollup.owner();
if (msg.sender != rollupOwner) {
revert NotRollupOrOwner(msg.sender, address(rollup), rollupOwner);
}
}
_;
}
function setSequencerInbox(address _sequencerInbox) external override onlyRollupOrOwner {
sequencerInbox = _sequencerInbox;
emit SequencerInboxUpdated(_sequencerInbox);
}
/// @dev Accumulator for delayed inbox messages; tail represents hash of the current state; each element represents the inclusion of a new message.
bytes32[] public override delayedInboxAccs;
bytes32[] public override sequencerInboxAccs;
address private constant EMPTY_ACTIVEOUTBOX = address(type(uint160).max);
function initialize(IOwnable rollup_) external initializer {
_activeOutbox = EMPTY_ACTIVEOUTBOX;
rollup = rollup_;
}
function activeOutbox() public view returns (address) {
if (_activeOutbox == EMPTY_ACTIVEOUTBOX) return address(uint160(0));
return _activeOutbox;
}
function allowedDelayedInboxes(address inbox) external view override returns (bool) {
return allowedInboxesMap[inbox].allowed;
}
function allowedOutboxes(address outbox) external view override returns (bool) {
return allowedOutboxesMap[outbox].allowed;
}
function enqueueSequencerMessage(bytes32 dataHash, uint256 afterDelayedMessagesRead)
external
returns (
uint256 seqMessageIndex,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 acc
)
{
// TODO: implement stub logic
}
function submitBatchSpendingReport(address batchPoster, bytes32 dataHash)
external
returns (uint256)
{
// TODO: implement stub
}
/**
* @dev Enqueue a message in the delayed inbox accumulator.
* These messages are later sequenced in the SequencerInbox, either by the sequencer as
* part of a normal batch, or by force inclusion.
*/
function enqueueDelayedMessage(
uint8 kind,
address sender,
bytes32 messageDataHash
) external payable override returns (uint256) {
if (!allowedInboxesMap[msg.sender].allowed) revert NotDelayedInbox(msg.sender);
return
addMessageToDelayedAccumulator(
kind,
sender,
uint64(block.number),
uint64(block.timestamp), // solhint-disable-line not-rely-on-time
block.basefee,
messageDataHash
);
}
function addMessageToDelayedAccumulator(
uint8 kind,
address sender,
uint64 blockNumber,
uint64 blockTimestamp,
uint256 baseFeeL1,
bytes32 messageDataHash
) internal returns (uint256) {
uint256 count = delayedInboxAccs.length;
bytes32 messageHash = Messages.messageHash(
kind,
sender,
blockNumber,
blockTimestamp,
count,
baseFeeL1,
messageDataHash
);
bytes32 prevAcc = 0;
if (count > 0) {
prevAcc = delayedInboxAccs[count - 1];
}
delayedInboxAccs.push(Messages.accumulateInboxMessage(prevAcc, messageHash));
emit MessageDelivered(
count,
prevAcc,
msg.sender,
kind,
sender,
messageDataHash,
baseFeeL1,
blockTimestamp
);
return count;
}
function executeCall(
address to,
uint256 value,
bytes calldata data
) external override returns (bool success, bytes memory returnData) {
if (!allowedOutboxesMap[msg.sender].allowed) revert NotOutbox(msg.sender);
if (data.length > 0 && !to.isContract()) revert NotContract(to);
address prevOutbox = _activeOutbox;
_activeOutbox = msg.sender;
// We set and reset active outbox around external call so activeOutbox remains valid during call
// We use a low level call here since we want to bubble up whether it succeeded or failed to the caller
// rather than reverting on failure as well as allow contract and non-contract calls
// solhint-disable-next-line avoid-low-level-calls
(success, returnData) = to.call{value: value}(data);
_activeOutbox = prevOutbox;
emit BridgeCallTriggered(msg.sender, to, value, data);
}
function setDelayedInbox(address inbox, bool enabled) external override onlyRollupOrOwner {
InOutInfo storage info = allowedInboxesMap[inbox];
bool alreadyEnabled = info.allowed;
emit InboxToggle(inbox, enabled);
if ((alreadyEnabled && enabled) || (!alreadyEnabled && !enabled)) {
return;
}
if (enabled) {
allowedInboxesMap[inbox] = InOutInfo(allowedDelayedInboxList.length, true);
allowedDelayedInboxList.push(inbox);
} else {
allowedDelayedInboxList[info.index] = allowedDelayedInboxList[
allowedDelayedInboxList.length - 1
];
allowedInboxesMap[allowedDelayedInboxList[info.index]].index = info.index;
allowedDelayedInboxList.pop();
delete allowedInboxesMap[inbox];
}
}
function setOutbox(address outbox, bool enabled) external override onlyRollupOrOwner {
InOutInfo storage info = allowedOutboxesMap[outbox];
bool alreadyEnabled = info.allowed;
emit OutboxToggle(outbox, enabled);
if ((alreadyEnabled && enabled) || (!alreadyEnabled && !enabled)) {
return;
}
if (enabled) {
allowedOutboxesMap[outbox] = InOutInfo(allowedOutboxList.length, true);
allowedOutboxList.push(outbox);
} else {
allowedOutboxList[info.index] = allowedOutboxList[allowedOutboxList.length - 1];
allowedOutboxesMap[allowedOutboxList[info.index]].index = info.index;
allowedOutboxList.pop();
delete allowedOutboxesMap[outbox];
}
}
function delayedMessageCount() external view override returns (uint256) {
return delayedInboxAccs.length;
}
function sequencerMessageCount() external view override returns (uint256) {
return sequencerInboxAccs.length;
}
receive() external payable {}
function acceptFundsFromOldBridge() external payable {}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../bridge/Messages.sol";
contract MessageTester {
function messageHash(
uint8 messageType,
address sender,
uint64 blockNumber,
uint64 timestamp,
uint256 inboxSeqNum,
uint256 gasPriceL1,
bytes32 messageDataHash
) public pure returns (bytes32) {
return
Messages.messageHash(
messageType,
sender,
blockNumber,
timestamp,
inboxSeqNum,
gasPriceL1,
messageDataHash
);
}
function accumulateInboxMessage(bytes32 inbox, bytes32 message) public pure returns (bytes32) {
return Messages.accumulateInboxMessage(inbox, message);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../state/Value.sol";
import "../state/Machine.sol";
import "../state/Deserialize.sol";
import "./IOneStepProver.sol";
import "../bridge/Messages.sol";
import "../bridge/IBridge.sol";
contract OneStepProverHostIo is IOneStepProver {
using GlobalStateLib for GlobalState;
using MerkleProofLib for MerkleProof;
using ModuleMemoryLib for ModuleMemory;
using ValueLib for Value;
using ValueStackLib for ValueStack;
uint256 private constant LEAF_SIZE = 32;
uint256 private constant INBOX_NUM = 2;
uint64 private constant INBOX_HEADER_LEN = 40;
uint64 private constant DELAYED_HEADER_LEN = 112 + 1;
function setLeafByte(
bytes32 oldLeaf,
uint256 idx,
uint8 val
) internal pure returns (bytes32) {
require(idx < LEAF_SIZE, "BAD_SET_LEAF_BYTE_IDX");
// Take into account that we are casting the leaf to a big-endian integer
uint256 leafShift = (LEAF_SIZE - 1 - idx) * 8;
uint256 newLeaf = uint256(oldLeaf);
newLeaf &= ~(0xFF << leafShift);
newLeaf |= uint256(val) << leafShift;
return bytes32(newLeaf);
}
function executeGetOrSetBytes32(
Machine memory mach,
Module memory mod,
GlobalState memory state,
Instruction calldata inst,
bytes calldata proof
) internal pure {
uint256 ptr = mach.valueStack.pop().assumeI32();
uint32 idx = mach.valueStack.pop().assumeI32();
if (idx >= GlobalStateLib.BYTES32_VALS_NUM) {
mach.status = MachineStatus.ERRORED;
return;
}
if (ptr + 32 > mod.moduleMemory.size || ptr % LEAF_SIZE != 0) {
mach.status = MachineStatus.ERRORED;
return;
}
uint256 leafIdx = ptr / LEAF_SIZE;
uint256 proofOffset = 0;
bytes32 startLeafContents;
MerkleProof memory merkleProof;
(startLeafContents, proofOffset, merkleProof) = mod.moduleMemory.proveLeaf(
leafIdx,
proof,
proofOffset
);
if (inst.opcode == Instructions.GET_GLOBAL_STATE_BYTES32) {
mod.moduleMemory.merkleRoot = merkleProof.computeRootFromMemory(
leafIdx,
state.bytes32Vals[idx]
);
} else if (inst.opcode == Instructions.SET_GLOBAL_STATE_BYTES32) {
state.bytes32Vals[idx] = startLeafContents;
} else {
revert("BAD_GLOBAL_STATE_OPCODE");
}
}
function executeGetU64(Machine memory mach, GlobalState memory state) internal pure {
uint32 idx = mach.valueStack.pop().assumeI32();
if (idx >= GlobalStateLib.U64_VALS_NUM) {
mach.status = MachineStatus.ERRORED;
return;
}
mach.valueStack.push(ValueLib.newI64(state.u64Vals[idx]));
}
function executeSetU64(Machine memory mach, GlobalState memory state) internal pure {
uint64 val = mach.valueStack.pop().assumeI64();
uint32 idx = mach.valueStack.pop().assumeI32();
if (idx >= GlobalStateLib.U64_VALS_NUM) {
mach.status = MachineStatus.ERRORED;
return;
}
state.u64Vals[idx] = val;
}
function executeReadPreImage(
ExecutionContext calldata,
Machine memory mach,
Module memory mod,
Instruction calldata,
bytes calldata proof
) internal pure {
uint256 preimageOffset = mach.valueStack.pop().assumeI32();
uint256 ptr = mach.valueStack.pop().assumeI32();
if (ptr + 32 > mod.moduleMemory.size || ptr % LEAF_SIZE != 0) {
mach.status = MachineStatus.ERRORED;
return;
}
uint256 leafIdx = ptr / LEAF_SIZE;
uint256 proofOffset = 0;
bytes32 leafContents;
MerkleProof memory merkleProof;
(leafContents, proofOffset, merkleProof) = mod.moduleMemory.proveLeaf(
leafIdx,
proof,
proofOffset
);
bytes memory extracted;
uint8 proofType = uint8(proof[proofOffset]);
proofOffset++;
if (proofType == 0) {
bytes calldata preimage = proof[proofOffset:];
require(keccak256(preimage) == leafContents, "BAD_PREIMAGE");
uint256 preimageEnd = preimageOffset + 32;
if (preimageEnd > preimage.length) {
preimageEnd = preimage.length;
}
extracted = preimage[preimageOffset:preimageEnd];
} else {
// TODO: support proving via an authenticated contract
revert("UNKNOWN_PREIMAGE_PROOF");
}
for (uint256 i = 0; i < extracted.length; i++) {
leafContents = setLeafByte(leafContents, i, uint8(extracted[i]));
}
mod.moduleMemory.merkleRoot = merkleProof.computeRootFromMemory(leafIdx, leafContents);
mach.valueStack.push(ValueLib.newI32(uint32(extracted.length)));
}
function validateSequencerInbox(
ExecutionContext calldata execCtx,
uint64 msgIndex,
bytes calldata message
) internal view returns (bool) {
require(message.length >= INBOX_HEADER_LEN, "BAD_SEQINBOX_PROOF");
uint64 afterDelayedMsg;
(afterDelayedMsg, ) = Deserialize.u64(message, 32);
bytes32 messageHash = keccak256(message);
bytes32 beforeAcc;
bytes32 delayedAcc;
if (msgIndex > 0) {
beforeAcc = execCtx.bridge.sequencerInboxAccs(msgIndex - 1);
}
if (afterDelayedMsg > 0) {
delayedAcc = execCtx.bridge.delayedInboxAccs(afterDelayedMsg - 1);
}
bytes32 acc = keccak256(abi.encodePacked(beforeAcc, messageHash, delayedAcc));
require(acc == execCtx.bridge.sequencerInboxAccs(msgIndex), "BAD_SEQINBOX_MESSAGE");
return true;
}
function validateDelayedInbox(
ExecutionContext calldata execCtx,
uint64 msgIndex,
bytes calldata message
) internal view returns (bool) {
require(message.length >= DELAYED_HEADER_LEN, "BAD_DELAYED_PROOF");
bytes32 beforeAcc;
if (msgIndex > 0) {
beforeAcc = execCtx.bridge.delayedInboxAccs(msgIndex - 1);
}
bytes32 messageDataHash = keccak256(message[DELAYED_HEADER_LEN:]);
bytes1 kind = message[0];
uint256 sender;
(sender, ) = Deserialize.u256(message, 1);
bytes32 messageHash = keccak256(
abi.encodePacked(kind, uint160(sender), message[33:DELAYED_HEADER_LEN], messageDataHash)
);
bytes32 acc = Messages.accumulateInboxMessage(beforeAcc, messageHash);
require(acc == execCtx.bridge.delayedInboxAccs(msgIndex), "BAD_DELAYED_MESSAGE");
return true;
}
function executeReadInboxMessage(
ExecutionContext calldata execCtx,
Machine memory mach,
Module memory mod,
Instruction calldata inst,
bytes calldata proof
) internal view {
uint256 messageOffset = mach.valueStack.pop().assumeI32();
uint256 ptr = mach.valueStack.pop().assumeI32();
uint256 msgIndex = mach.valueStack.pop().assumeI64();
if (
inst.argumentData == Instructions.INBOX_INDEX_SEQUENCER &&
msgIndex >= execCtx.maxInboxMessagesRead
) {
mach.status = MachineStatus.TOO_FAR;
return;
}
if (ptr + 32 > mod.moduleMemory.size || ptr % LEAF_SIZE != 0) {
mach.status = MachineStatus.ERRORED;
return;
}
uint256 leafIdx = ptr / LEAF_SIZE;
uint256 proofOffset = 0;
bytes32 leafContents;
MerkleProof memory merkleProof;
(leafContents, proofOffset, merkleProof) = mod.moduleMemory.proveLeaf(
leafIdx,
proof,
proofOffset
);
{
// TODO: support proving via an authenticated contract
require(proof[proofOffset] == 0, "UNKNOWN_INBOX_PROOF");
proofOffset++;
function(ExecutionContext calldata, uint64, bytes calldata)
internal
view
returns (bool) inboxValidate;
bool success;
if (inst.argumentData == Instructions.INBOX_INDEX_SEQUENCER) {
inboxValidate = validateSequencerInbox;
} else if (inst.argumentData == Instructions.INBOX_INDEX_DELAYED) {
inboxValidate = validateDelayedInbox;
} else {
mach.status = MachineStatus.ERRORED;
return;
}
success = inboxValidate(execCtx, uint64(msgIndex), proof[proofOffset:]);
if (!success) {
mach.status = MachineStatus.ERRORED;
return;
}
}
require(proof.length >= proofOffset, "BAD_MESSAGE_PROOF");
uint256 messageLength = proof.length - proofOffset;
uint32 i = 0;
for (; i < 32 && messageOffset + i < messageLength; i++) {
leafContents = setLeafByte(
leafContents,
i,
uint8(proof[proofOffset + messageOffset + i])
);
}
mod.moduleMemory.merkleRoot = merkleProof.computeRootFromMemory(leafIdx, leafContents);
mach.valueStack.push(ValueLib.newI32(i));
}
function executeHaltAndSetFinished(
ExecutionContext calldata,
Machine memory mach,
Module memory,
Instruction calldata,
bytes calldata
) internal pure {
mach.status = MachineStatus.FINISHED;
}
function executeGlobalStateAccess(
ExecutionContext calldata,
Machine memory mach,
Module memory mod,
Instruction calldata inst,
bytes calldata proof
) internal pure {
uint16 opcode = inst.opcode;
GlobalState memory state;
uint256 proofOffset = 0;
(state, proofOffset) = Deserialize.globalState(proof, proofOffset);
require(state.hash() == mach.globalStateHash, "BAD_GLOBAL_STATE");
if (
opcode == Instructions.GET_GLOBAL_STATE_BYTES32 ||
opcode == Instructions.SET_GLOBAL_STATE_BYTES32
) {
executeGetOrSetBytes32(mach, mod, state, inst, proof[proofOffset:]);
} else if (opcode == Instructions.GET_GLOBAL_STATE_U64) {
executeGetU64(mach, state);
} else if (opcode == Instructions.SET_GLOBAL_STATE_U64) {
executeSetU64(mach, state);
} else {
revert("INVALID_GLOBALSTATE_OPCODE");
}
mach.globalStateHash = state.hash();
}
function executeOneStep(
ExecutionContext calldata execCtx,
Machine calldata startMach,
Module calldata startMod,
Instruction calldata inst,
bytes calldata proof
) external view override returns (Machine memory mach, Module memory mod) {
mach = startMach;
mod = startMod;
uint16 opcode = inst.opcode;
function(
ExecutionContext calldata,
Machine memory,
Module memory,
Instruction calldata,
bytes calldata
) internal view impl;
if (
opcode >= Instructions.GET_GLOBAL_STATE_BYTES32 &&
opcode <= Instructions.SET_GLOBAL_STATE_U64
) {
impl = executeGlobalStateAccess;
} else if (opcode == Instructions.READ_PRE_IMAGE) {
impl = executeReadPreImage;
} else if (opcode == Instructions.READ_INBOX_MESSAGE) {
impl = executeReadInboxMessage;
} else if (opcode == Instructions.HALT_AND_SET_FINISHED) {
impl = executeHaltAndSetFinished;
} else {
revert("INVALID_MEMORY_OPCODE");
}
impl(execCtx, mach, mod, inst, proof);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../libraries/DelegateCallAware.sol";
import "../osp/IOneStepProofEntry.sol";
import "../state/GlobalState.sol";
import "./IChallengeResultReceiver.sol";
import "./ChallengeLib.sol";
import "./IChallengeManager.sol";
import {NO_CHAL_INDEX} from "../libraries/Constants.sol";
contract ChallengeManager is DelegateCallAware, IChallengeManager {
using GlobalStateLib for GlobalState;
using MachineLib for Machine;
using ChallengeLib for ChallengeLib.Challenge;
enum ChallengeModeRequirement {
ANY,
BLOCK,
EXECUTION
}
string private constant NO_CHAL = "NO_CHAL";
uint256 private constant MAX_CHALLENGE_DEGREE = 40;
uint64 public totalChallengesCreated;
mapping(uint256 => ChallengeLib.Challenge) public challenges;
IChallengeResultReceiver public resultReceiver;
ISequencerInbox public sequencerInbox;
IBridge public bridge;
IOneStepProofEntry public osp;
function challengeInfo(uint64 challengeIndex)
external
view
override
returns (ChallengeLib.Challenge memory)
{
return challenges[challengeIndex];
}
modifier takeTurn(
uint64 challengeIndex,
ChallengeLib.SegmentSelection calldata selection,
ChallengeModeRequirement expectedMode
) {
ChallengeLib.Challenge storage challenge = challenges[challengeIndex];
require(msg.sender == currentResponder(challengeIndex), "CHAL_SENDER");
require(!isTimedOut(challengeIndex), "CHAL_DEADLINE");
if (expectedMode == ChallengeModeRequirement.ANY) {
require(challenge.mode != ChallengeLib.ChallengeMode.NONE, NO_CHAL);
} else if (expectedMode == ChallengeModeRequirement.BLOCK) {
require(challenge.mode == ChallengeLib.ChallengeMode.BLOCK, "CHAL_NOT_BLOCK");
} else if (expectedMode == ChallengeModeRequirement.EXECUTION) {
require(challenge.mode == ChallengeLib.ChallengeMode.EXECUTION, "CHAL_NOT_EXECUTION");
} else {
assert(false);
}
require(
challenge.challengeStateHash ==
ChallengeLib.hashChallengeState(
selection.oldSegmentsStart,
selection.oldSegmentsLength,
selection.oldSegments
),
"BIS_STATE"
);
if (
selection.oldSegments.length < 2 ||
selection.challengePosition >= selection.oldSegments.length - 1
) {
revert("BAD_CHALLENGE_POS");
}
_;
if (challenge.mode == ChallengeLib.ChallengeMode.NONE) {
// Early return since challenge must have terminated
return;
}
ChallengeLib.Participant memory current = challenge.current;
current.timeLeft -= block.timestamp - challenge.lastMoveTimestamp;
challenge.current = challenge.next;
challenge.next = current;
challenge.lastMoveTimestamp = block.timestamp;
}
function initialize(
IChallengeResultReceiver resultReceiver_,
ISequencerInbox sequencerInbox_,
IBridge bridge_,
IOneStepProofEntry osp_
) external override onlyDelegated {
require(address(resultReceiver) == address(0), "ALREADY_INIT");
require(address(resultReceiver_) != address(0), "NO_RESULT_RECEIVER");
resultReceiver = resultReceiver_;
sequencerInbox = sequencerInbox_;
bridge = bridge_;
osp = osp_;
}
function createChallenge(
bytes32 wasmModuleRoot_,
MachineStatus[2] calldata startAndEndMachineStatuses_,
GlobalState[2] calldata startAndEndGlobalStates_,
uint64 numBlocks,
address asserter_,
address challenger_,
uint256 asserterTimeLeft_,
uint256 challengerTimeLeft_
) external override returns (uint64) {
require(msg.sender == address(resultReceiver), "ONLY_ROLLUP_CHAL");
bytes32[] memory segments = new bytes32[](2);
segments[0] = ChallengeLib.blockStateHash(
startAndEndMachineStatuses_[0],
startAndEndGlobalStates_[0].hash()
);
segments[1] = ChallengeLib.blockStateHash(
startAndEndMachineStatuses_[1],
startAndEndGlobalStates_[1].hash()
);
uint64 challengeIndex = ++totalChallengesCreated;
// The following is an assertion since it should never be possible, but it's an important invariant
assert(challengeIndex != NO_CHAL_INDEX);
ChallengeLib.Challenge storage challenge = challenges[challengeIndex];
challenge.wasmModuleRoot = wasmModuleRoot_;
// See validator/assertion.go ExecutionState RequiredBatches() for reasoning
uint64 maxInboxMessagesRead = startAndEndGlobalStates_[1].getInboxPosition();
if (
startAndEndMachineStatuses_[1] == MachineStatus.ERRORED ||
startAndEndGlobalStates_[1].getPositionInMessage() > 0
) {
maxInboxMessagesRead++;
}
challenge.maxInboxMessages = maxInboxMessagesRead;
challenge.next = ChallengeLib.Participant({addr: asserter_, timeLeft: asserterTimeLeft_});
challenge.current = ChallengeLib.Participant({
addr: challenger_,
timeLeft: challengerTimeLeft_
});
challenge.lastMoveTimestamp = block.timestamp;
challenge.mode = ChallengeLib.ChallengeMode.BLOCK;
emit InitiatedChallenge(
challengeIndex,
startAndEndGlobalStates_[0],
startAndEndGlobalStates_[1]
);
completeBisection(challengeIndex, 0, numBlocks, segments);
return challengeIndex;
}
/**
* @notice Initiate the next round in the bisection by objecting to execution correctness with a bisection
* of an execution segment with the same length but a different endpoint. This is either the initial move
* or follows another execution objection
*/
function bisectExecution(
uint64 challengeIndex,
ChallengeLib.SegmentSelection calldata selection,
bytes32[] calldata newSegments
) external takeTurn(challengeIndex, selection, ChallengeModeRequirement.ANY) {
(uint256 challengeStart, uint256 challengeLength) = ChallengeLib.extractChallengeSegment(
selection
);
require(challengeLength > 1, "TOO_SHORT");
{
uint256 expectedDegree = challengeLength;
if (expectedDegree > MAX_CHALLENGE_DEGREE) {
expectedDegree = MAX_CHALLENGE_DEGREE;
}
require(newSegments.length == expectedDegree + 1, "WRONG_DEGREE");
}
requireValidBisection(selection, newSegments[0], newSegments[newSegments.length - 1]);
completeBisection(challengeIndex, challengeStart, challengeLength, newSegments);
}
function challengeExecution(
uint64 challengeIndex,
ChallengeLib.SegmentSelection calldata selection,
MachineStatus[2] calldata machineStatuses,
bytes32[2] calldata globalStateHashes,
uint256 numSteps
) external takeTurn(challengeIndex, selection, ChallengeModeRequirement.BLOCK) {
require(numSteps >= 1, "CHALLENGE_TOO_SHORT");
require(numSteps <= OneStepProofEntryLib.MAX_STEPS, "CHALLENGE_TOO_LONG");
requireValidBisection(
selection,
ChallengeLib.blockStateHash(machineStatuses[0], globalStateHashes[0]),
ChallengeLib.blockStateHash(machineStatuses[1], globalStateHashes[1])
);
ChallengeLib.Challenge storage challenge = challenges[challengeIndex];
(uint256 executionChallengeAtSteps, uint256 challengeLength) = ChallengeLib
.extractChallengeSegment(selection);
require(challengeLength == 1, "TOO_LONG");
if (machineStatuses[0] != MachineStatus.FINISHED) {
// If the machine is in a halted state, it can't change
require(
machineStatuses[0] == machineStatuses[1] &&
globalStateHashes[0] == globalStateHashes[1],
"HALTED_CHANGE"
);
_currentWin(challengeIndex, ChallengeTerminationType.BLOCK_PROOF);
return;
}
if (machineStatuses[1] == MachineStatus.ERRORED) {
// If the machine errors, it must return to the previous global state
require(globalStateHashes[0] == globalStateHashes[1], "ERROR_CHANGE");
}
bytes32[] memory segments = new bytes32[](2);
segments[0] = ChallengeLib.getStartMachineHash(
globalStateHashes[0],
challenge.wasmModuleRoot
);
segments[1] = ChallengeLib.getEndMachineHash(machineStatuses[1], globalStateHashes[1]);
challenge.mode = ChallengeLib.ChallengeMode.EXECUTION;
completeBisection(challengeIndex, 0, numSteps, segments);
emit ExecutionChallengeBegun(challengeIndex, executionChallengeAtSteps);
}
function oneStepProveExecution(
uint64 challengeIndex,
ChallengeLib.SegmentSelection calldata selection,
bytes calldata proof
) external takeTurn(challengeIndex, selection, ChallengeModeRequirement.EXECUTION) {
ChallengeLib.Challenge storage challenge = challenges[challengeIndex];
uint256 challengeStart;
{
uint256 challengeLength;
(challengeStart, challengeLength) = ChallengeLib.extractChallengeSegment(selection);
require(challengeLength == 1, "TOO_LONG");
}
bytes32 afterHash = osp.proveOneStep(
ExecutionContext({maxInboxMessagesRead: challenge.maxInboxMessages, bridge: bridge}),
challengeStart,
selection.oldSegments[selection.challengePosition],
proof
);
require(
afterHash != selection.oldSegments[selection.challengePosition + 1],
"SAME_OSP_END"
);
emit OneStepProofCompleted(challengeIndex);
_currentWin(challengeIndex, ChallengeTerminationType.EXECUTION_PROOF);
}
function timeout(uint64 challengeIndex) external override {
require(challenges[challengeIndex].mode != ChallengeLib.ChallengeMode.NONE, NO_CHAL);
require(isTimedOut(challengeIndex), "TIMEOUT_DEADLINE");
_nextWin(challengeIndex, ChallengeTerminationType.TIMEOUT);
}
function clearChallenge(uint64 challengeIndex) external override {
require(msg.sender == address(resultReceiver), "NOT_RES_RECEIVER");
require(challenges[challengeIndex].mode != ChallengeLib.ChallengeMode.NONE, NO_CHAL);
delete challenges[challengeIndex];
emit ChallengeEnded(challengeIndex, ChallengeTerminationType.CLEARED);
}
function currentResponder(uint64 challengeIndex) public view override returns (address) {
return challenges[challengeIndex].current.addr;
}
function currentResponderTimeLeft(uint64 challengeIndex)
public
view
override
returns (uint256)
{
return challenges[challengeIndex].current.timeLeft;
}
function isTimedOut(uint64 challengeIndex) public view override returns (bool) {
return challenges[challengeIndex].isTimedOut();
}
function requireValidBisection(
ChallengeLib.SegmentSelection calldata selection,
bytes32 startHash,
bytes32 endHash
) private pure {
require(selection.oldSegments[selection.challengePosition] == startHash, "WRONG_START");
require(selection.oldSegments[selection.challengePosition + 1] != endHash, "SAME_END");
}
function completeBisection(
uint64 challengeIndex,
uint256 challengeStart,
uint256 challengeLength,
bytes32[] memory newSegments
) private {
assert(challengeLength >= 1);
assert(newSegments.length >= 2);
bytes32 challengeStateHash = ChallengeLib.hashChallengeState(
challengeStart,
challengeLength,
newSegments
);
challenges[challengeIndex].challengeStateHash = challengeStateHash;
emit Bisected(
challengeIndex,
challengeStateHash,
challengeStart,
challengeLength,
newSegments
);
}
/// @dev This function causes the mode of the challenge to be set to NONE by deleting the challenge
function _nextWin(uint64 challengeIndex, ChallengeTerminationType reason) private {
ChallengeLib.Challenge storage challenge = challenges[challengeIndex];
address next = challenge.next.addr;
address current = challenge.current.addr;
delete challenges[challengeIndex];
resultReceiver.completeChallenge(challengeIndex, next, current);
emit ChallengeEnded(challengeIndex, reason);
}
/**
* @dev this currently sets a challenge hash of 0 - no move is possible for the next participant to progress the
* state. It is assumed that wherever this function is consumed, the turn is then adjusted for the opposite party
* to timeout. This is done as a safety measure so challenges can only be resolved by timeouts during mainnet beta.
*/
function _currentWin(
uint64 challengeIndex,
ChallengeTerminationType /* reason */
) private {
ChallengeLib.Challenge storage challenge = challenges[challengeIndex];
challenge.challengeStateHash = bytes32(0);
// address next = challenge.next.addr;
// address current = challenge.current.addr;
// delete challenges[challengeIndex];
// resultReceiver.completeChallenge(challengeIndex, current, next);
// emit ChallengeEnded(challengeIndex, reason);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../challenge/ChallengeManager.sol";
contract SingleExecutionChallenge is ChallengeManager {
constructor(
IOneStepProofEntry osp_,
IChallengeResultReceiver resultReceiver_,
uint64 maxInboxMessagesRead_,
bytes32[2] memory startAndEndHashes,
uint256 numSteps_,
address asserter_,
address challenger_,
uint256 asserterTimeLeft_,
uint256 challengerTimeLeft_
) {
osp = osp_;
resultReceiver = resultReceiver_;
uint64 challengeIndex = ++totalChallengesCreated;
ChallengeLib.Challenge storage challenge = challenges[challengeIndex];
challenge.maxInboxMessages = maxInboxMessagesRead_;
bytes32[] memory segments = new bytes32[](2);
segments[0] = startAndEndHashes[0];
segments[1] = startAndEndHashes[1];
bytes32 challengeStateHash = ChallengeLib.hashChallengeState(0, numSteps_, segments);
challenge.challengeStateHash = challengeStateHash;
challenge.next = ChallengeLib.Participant({addr: asserter_, timeLeft: asserterTimeLeft_});
challenge.current = ChallengeLib.Participant({
addr: challenger_,
timeLeft: challengerTimeLeft_
});
challenge.lastMoveTimestamp = block.timestamp;
challenge.mode = ChallengeLib.ChallengeMode.EXECUTION;
emit Bisected(challengeIndex, challengeStateHash, 0, numSteps_, segments);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../state/Value.sol";
import "../state/Machine.sol";
import "../state/Deserialize.sol";
import "./IOneStepProver.sol";
contract OneStepProverMemory is IOneStepProver {
using MerkleProofLib for MerkleProof;
using ModuleMemoryLib for ModuleMemory;
using ValueLib for Value;
using ValueStackLib for ValueStack;
uint256 private constant LEAF_SIZE = 32;
uint64 private constant PAGE_SIZE = 65536;
function pullLeafByte(bytes32 leaf, uint256 idx) internal pure returns (uint8) {
require(idx < LEAF_SIZE, "BAD_PULL_LEAF_BYTE_IDX");
// Take into account that we are casting the leaf to a big-endian integer
uint256 leafShift = (LEAF_SIZE - 1 - idx) * 8;
return uint8(uint256(leaf) >> leafShift);
}
function setLeafByte(
bytes32 oldLeaf,
uint256 idx,
uint8 val
) internal pure returns (bytes32) {
require(idx < LEAF_SIZE, "BAD_SET_LEAF_BYTE_IDX");
// Take into account that we are casting the leaf to a big-endian integer
uint256 leafShift = (LEAF_SIZE - 1 - idx) * 8;
uint256 newLeaf = uint256(oldLeaf);
newLeaf &= ~(0xFF << leafShift);
newLeaf |= uint256(val) << leafShift;
return bytes32(newLeaf);
}
function executeMemoryLoad(
Machine memory mach,
Module memory mod,
Instruction calldata inst,
bytes calldata proof
) internal pure {
ValueType ty;
uint256 readBytes;
bool signed;
if (inst.opcode == Instructions.I32_LOAD) {
ty = ValueType.I32;
readBytes = 4;
signed = false;
} else if (inst.opcode == Instructions.I64_LOAD) {
ty = ValueType.I64;
readBytes = 8;
signed = false;
} else if (inst.opcode == Instructions.F32_LOAD) {
ty = ValueType.F32;
readBytes = 4;
signed = false;
} else if (inst.opcode == Instructions.F64_LOAD) {
ty = ValueType.F64;
readBytes = 8;
signed = false;
} else if (inst.opcode == Instructions.I32_LOAD8_S) {
ty = ValueType.I32;
readBytes = 1;
signed = true;
} else if (inst.opcode == Instructions.I32_LOAD8_U) {
ty = ValueType.I32;
readBytes = 1;
signed = false;
} else if (inst.opcode == Instructions.I32_LOAD16_S) {
ty = ValueType.I32;
readBytes = 2;
signed = true;
} else if (inst.opcode == Instructions.I32_LOAD16_U) {
ty = ValueType.I32;
readBytes = 2;
signed = false;
} else if (inst.opcode == Instructions.I64_LOAD8_S) {
ty = ValueType.I64;
readBytes = 1;
signed = true;
} else if (inst.opcode == Instructions.I64_LOAD8_U) {
ty = ValueType.I64;
readBytes = 1;
signed = false;
} else if (inst.opcode == Instructions.I64_LOAD16_S) {
ty = ValueType.I64;
readBytes = 2;
signed = true;
} else if (inst.opcode == Instructions.I64_LOAD16_U) {
ty = ValueType.I64;
readBytes = 2;
signed = false;
} else if (inst.opcode == Instructions.I64_LOAD32_S) {
ty = ValueType.I64;
readBytes = 4;
signed = true;
} else if (inst.opcode == Instructions.I64_LOAD32_U) {
ty = ValueType.I64;
readBytes = 4;
signed = false;
} else {
revert("INVALID_MEMORY_LOAD_OPCODE");
}
// Neither of these can overflow as they're computed with much less than 256 bit integers.
uint256 startIdx = inst.argumentData + mach.valueStack.pop().assumeI32();
if (startIdx + readBytes > mod.moduleMemory.size) {
mach.status = MachineStatus.ERRORED;
return;
}
uint256 proofOffset = 0;
uint256 lastProvedLeafIdx = ~uint256(0);
bytes32 lastProvedLeafContents;
uint64 readValue;
for (uint256 i = 0; i < readBytes; i++) {
uint256 idx = startIdx + i;
uint256 leafIdx = idx / LEAF_SIZE;
if (leafIdx != lastProvedLeafIdx) {
// This hits the stack size if we phrase it as mod.moduleMemory.proveLeaf(...)
(lastProvedLeafContents, proofOffset, ) = ModuleMemoryLib.proveLeaf(
mod.moduleMemory,
leafIdx,
proof,
proofOffset
);
lastProvedLeafIdx = leafIdx;
}
uint256 indexWithinLeaf = idx % LEAF_SIZE;
readValue |=
uint64(pullLeafByte(lastProvedLeafContents, indexWithinLeaf)) <<
uint64(i * 8);
}
if (signed) {
// Go down to the original uint size, change to signed, go up to correct size, convert back to unsigned
if (readBytes == 1 && ty == ValueType.I32) {
readValue = uint32(int32(int8(uint8(readValue))));
} else if (readBytes == 1 && ty == ValueType.I64) {
readValue = uint64(int64(int8(uint8(readValue))));
} else if (readBytes == 2 && ty == ValueType.I32) {
readValue = uint32(int32(int16(uint16(readValue))));
} else if (readBytes == 2 && ty == ValueType.I64) {
readValue = uint64(int64(int16(uint16(readValue))));
} else if (readBytes == 4 && ty == ValueType.I64) {
readValue = uint64(int64(int32(uint32(readValue))));
} else {
revert("BAD_READ_BYTES_SIGNED");
}
}
mach.valueStack.push(Value({valueType: ty, contents: readValue}));
}
function executeMemoryStore(
Machine memory mach,
Module memory mod,
Instruction calldata inst,
bytes calldata proof
) internal pure {
uint64 writeBytes;
uint64 toWrite;
{
ValueType ty;
if (inst.opcode == Instructions.I32_STORE) {
ty = ValueType.I32;
writeBytes = 4;
} else if (inst.opcode == Instructions.I64_STORE) {
ty = ValueType.I64;
writeBytes = 8;
} else if (inst.opcode == Instructions.F32_STORE) {
ty = ValueType.F32;
writeBytes = 4;
} else if (inst.opcode == Instructions.F64_STORE) {
ty = ValueType.F64;
writeBytes = 8;
} else if (inst.opcode == Instructions.I32_STORE8) {
ty = ValueType.I32;
writeBytes = 1;
} else if (inst.opcode == Instructions.I32_STORE16) {
ty = ValueType.I32;
writeBytes = 2;
} else if (inst.opcode == Instructions.I64_STORE8) {
ty = ValueType.I64;
writeBytes = 1;
} else if (inst.opcode == Instructions.I64_STORE16) {
ty = ValueType.I64;
writeBytes = 2;
} else if (inst.opcode == Instructions.I64_STORE32) {
ty = ValueType.I64;
writeBytes = 4;
} else {
revert("INVALID_MEMORY_STORE_OPCODE");
}
Value memory writingVal = mach.valueStack.pop();
require(writingVal.valueType == ty, "BAD_STORE_TYPE");
toWrite = uint64(writingVal.contents);
if (writeBytes < 8) {
toWrite &= (uint64(1) << (writeBytes * 8)) - 1;
}
}
// Neither of these can overflow as they're computed with much less than 256 bit integers.
uint256 startIdx = inst.argumentData + mach.valueStack.pop().assumeI32();
if (startIdx + writeBytes > mod.moduleMemory.size) {
mach.status = MachineStatus.ERRORED;
return;
}
uint256 proofOffset = 0;
uint256 lastProvedLeafIdx = ~uint256(0);
MerkleProof memory lastProvedMerkle;
bytes32 lastProvedLeafContents;
for (uint256 i = 0; i < writeBytes; i++) {
uint256 idx = startIdx + i;
uint256 leafIdx = idx / LEAF_SIZE;
if (leafIdx != lastProvedLeafIdx) {
if (lastProvedLeafIdx != ~uint256(0)) {
// Apply the last leaf update
mod.moduleMemory.merkleRoot = lastProvedMerkle.computeRootFromMemory(
lastProvedLeafIdx,
lastProvedLeafContents
);
}
// This hits the stack size if we phrase it as mod.moduleMemory.proveLeaf(...)
(lastProvedLeafContents, proofOffset, lastProvedMerkle) = ModuleMemoryLib.proveLeaf(
mod.moduleMemory,
leafIdx,
proof,
proofOffset
);
lastProvedLeafIdx = leafIdx;
}
uint256 indexWithinLeaf = idx % LEAF_SIZE;
lastProvedLeafContents = setLeafByte(
lastProvedLeafContents,
indexWithinLeaf,
uint8(toWrite)
);
toWrite >>= 8;
}
mod.moduleMemory.merkleRoot = lastProvedMerkle.computeRootFromMemory(
lastProvedLeafIdx,
lastProvedLeafContents
);
}
function executeMemorySize(
Machine memory mach,
Module memory mod,
Instruction calldata,
bytes calldata
) internal pure {
uint32 pages = uint32(mod.moduleMemory.size / PAGE_SIZE);
mach.valueStack.push(ValueLib.newI32(pages));
}
function executeMemoryGrow(
Machine memory mach,
Module memory mod,
Instruction calldata,
bytes calldata
) internal pure {
uint32 oldPages = uint32(mod.moduleMemory.size / PAGE_SIZE);
uint32 growingPages = mach.valueStack.pop().assumeI32();
// Safe as the input integers are too small to overflow a uint256
uint256 newSize = uint256(oldPages) + uint256(growingPages);
if (newSize <= mod.moduleMemory.maxSize) {
mod.moduleMemory.size = uint64(newSize * PAGE_SIZE);
mach.valueStack.push(ValueLib.newI32(oldPages));
} else {
mach.valueStack.push(ValueLib.newI32(~uint32(0)));
}
}
function executeOneStep(
ExecutionContext calldata,
Machine calldata startMach,
Module calldata startMod,
Instruction calldata inst,
bytes calldata proof
) external pure override returns (Machine memory mach, Module memory mod) {
mach = startMach;
mod = startMod;
uint16 opcode = inst.opcode;
function(Machine memory, Module memory, Instruction calldata, bytes calldata)
internal
pure impl;
if (opcode >= Instructions.I32_LOAD && opcode <= Instructions.I64_LOAD32_U) {
impl = executeMemoryLoad;
} else if (opcode >= Instructions.I32_STORE && opcode <= Instructions.I64_STORE32) {
impl = executeMemoryStore;
} else if (opcode == Instructions.MEMORY_SIZE) {
impl = executeMemorySize;
} else if (opcode == Instructions.MEMORY_GROW) {
impl = executeMemoryGrow;
} else {
revert("INVALID_MEMORY_OPCODE");
}
impl(mach, mod, inst, proof);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../state/Value.sol";
import "../state/Machine.sol";
import "../state/Module.sol";
import "../state/Deserialize.sol";
import "./IOneStepProver.sol";
contract OneStepProverMath is IOneStepProver {
using ValueLib for Value;
using ValueStackLib for ValueStack;
function executeEqz(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
Value memory v = mach.valueStack.pop();
if (inst.opcode == Instructions.I32_EQZ) {
require(v.valueType == ValueType.I32, "NOT_I32");
} else if (inst.opcode == Instructions.I64_EQZ) {
require(v.valueType == ValueType.I64, "NOT_I64");
} else {
revert("BAD_EQZ");
}
uint32 output;
if (v.contents == 0) {
output = 1;
} else {
output = 0;
}
mach.valueStack.push(ValueLib.newI32(output));
}
function signExtend(uint32 a) internal pure returns (uint64) {
if (a & (1 << 31) != 0) {
return uint64(a) | uint64(0xffffffff00000000);
}
return uint64(a);
}
function i64RelOp(
uint64 a,
uint64 b,
uint16 relop
) internal pure returns (bool) {
if (relop == Instructions.IRELOP_EQ) {
return (a == b);
} else if (relop == Instructions.IRELOP_NE) {
return (a != b);
} else if (relop == Instructions.IRELOP_LT_S) {
return (int64(a) < int64(b));
} else if (relop == Instructions.IRELOP_LT_U) {
return (a < b);
} else if (relop == Instructions.IRELOP_GT_S) {
return (int64(a) > int64(b));
} else if (relop == Instructions.IRELOP_GT_U) {
return (a > b);
} else if (relop == Instructions.IRELOP_LE_S) {
return (int64(a) <= int64(b));
} else if (relop == Instructions.IRELOP_LE_U) {
return (a <= b);
} else if (relop == Instructions.IRELOP_GE_S) {
return (int64(a) >= int64(b));
} else if (relop == Instructions.IRELOP_GE_U) {
return (a >= b);
} else {
revert("BAD IRELOP");
}
}
function executeI32RelOp(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
uint32 b = mach.valueStack.pop().assumeI32();
uint32 a = mach.valueStack.pop().assumeI32();
uint16 relop = inst.opcode - Instructions.I32_RELOP_BASE;
uint64 a64;
uint64 b64;
if (
relop == Instructions.IRELOP_LT_S ||
relop == Instructions.IRELOP_GT_S ||
relop == Instructions.IRELOP_LE_S ||
relop == Instructions.IRELOP_GE_S
) {
a64 = signExtend(a);
b64 = signExtend(b);
} else {
a64 = uint64(a);
b64 = uint64(b);
}
bool res = i64RelOp(a64, b64, relop);
mach.valueStack.push(ValueLib.newBoolean(res));
}
function executeI64RelOp(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
uint64 b = mach.valueStack.pop().assumeI64();
uint64 a = mach.valueStack.pop().assumeI64();
uint16 relop = inst.opcode - Instructions.I64_RELOP_BASE;
bool res = i64RelOp(a, b, relop);
mach.valueStack.push(ValueLib.newBoolean(res));
}
function genericIUnOp(
uint64 a,
uint16 unop,
uint16 bits
) internal pure returns (uint32) {
require(bits == 32 || bits == 64, "WRONG USE OF genericUnOp");
if (unop == Instructions.IUNOP_CLZ) {
/* curbits is one-based to keep with unsigned mathematics */
uint32 curbit = bits;
while (curbit > 0 && (a & (1 << (curbit - 1)) == 0)) {
curbit -= 1;
}
return (bits - curbit);
} else if (unop == Instructions.IUNOP_CTZ) {
uint32 curbit = 0;
while (curbit < bits && ((a & (1 << curbit)) == 0)) {
curbit += 1;
}
return curbit;
} else if (unop == Instructions.IUNOP_POPCNT) {
uint32 curbit = 0;
uint32 res = 0;
while (curbit < bits) {
if ((a & (1 << curbit)) != 0) {
res += 1;
}
curbit++;
}
return res;
}
revert("BAD IUnOp");
}
function executeI32UnOp(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
uint32 a = mach.valueStack.pop().assumeI32();
uint16 unop = inst.opcode - Instructions.I32_UNOP_BASE;
uint32 res = genericIUnOp(a, unop, 32);
mach.valueStack.push(ValueLib.newI32(res));
}
function executeI64UnOp(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
uint64 a = mach.valueStack.pop().assumeI64();
uint16 unop = inst.opcode - Instructions.I64_UNOP_BASE;
uint64 res = uint64(genericIUnOp(a, unop, 64));
mach.valueStack.push(ValueLib.newI64(res));
}
function rotl32(uint32 a, uint32 b) internal pure returns (uint32) {
b %= 32;
return (a << b) | (a >> (32 - b));
}
function rotl64(uint64 a, uint64 b) internal pure returns (uint64) {
b %= 64;
return (a << b) | (a >> (64 - b));
}
function rotr32(uint32 a, uint32 b) internal pure returns (uint32) {
b %= 32;
return (a >> b) | (a << (32 - b));
}
function rotr64(uint64 a, uint64 b) internal pure returns (uint64) {
b %= 64;
return (a >> b) | (a << (64 - b));
}
function genericBinOp(
uint64 a,
uint64 b,
uint16 opcodeOffset
) internal pure returns (uint64, bool) {
unchecked {
if (opcodeOffset == 0) {
// add
return (a + b, false);
} else if (opcodeOffset == 1) {
// sub
return (a - b, false);
} else if (opcodeOffset == 2) {
// mul
return (a * b, false);
} else if (opcodeOffset == 4) {
// div_u
if (b == 0) {
return (0, true);
}
return (a / b, false);
} else if (opcodeOffset == 6) {
// rem_u
if (b == 0) {
return (0, true);
}
return (a % b, false);
} else if (opcodeOffset == 7) {
// and
return (a & b, false);
} else if (opcodeOffset == 8) {
// or
return (a | b, false);
} else if (opcodeOffset == 9) {
// xor
return (a ^ b, false);
} else {
revert("INVALID_GENERIC_BIN_OP");
}
}
}
function executeI32BinOp(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
uint32 b = mach.valueStack.pop().assumeI32();
uint32 a = mach.valueStack.pop().assumeI32();
uint32 res;
uint16 opcodeOffset = inst.opcode - Instructions.I32_ADD;
unchecked {
if (opcodeOffset == 3) {
// div_s
if (b == 0 || (int32(a) == -2147483648 && int32(b) == -1)) {
mach.status = MachineStatus.ERRORED;
return;
}
res = uint32(int32(a) / int32(b));
} else if (opcodeOffset == 5) {
// rem_s
if (b == 0) {
mach.status = MachineStatus.ERRORED;
return;
}
res = uint32(int32(a) % int32(b));
} else if (opcodeOffset == 10) {
// shl
res = a << (b % 32);
} else if (opcodeOffset == 12) {
// shr_u
res = a >> (b % 32);
} else if (opcodeOffset == 11) {
// shr_s
res = uint32(int32(a) >> (b % 32));
} else if (opcodeOffset == 13) {
// rotl
res = rotl32(a, b);
} else if (opcodeOffset == 14) {
// rotr
res = rotr32(a, b);
} else {
(uint64 computed, bool err) = genericBinOp(a, b, opcodeOffset);
if (err) {
mach.status = MachineStatus.ERRORED;
return;
}
res = uint32(computed);
}
}
mach.valueStack.push(ValueLib.newI32(res));
}
function executeI64BinOp(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
uint64 b = mach.valueStack.pop().assumeI64();
uint64 a = mach.valueStack.pop().assumeI64();
uint64 res;
uint16 opcodeOffset = inst.opcode - Instructions.I64_ADD;
unchecked {
if (opcodeOffset == 3) {
// div_s
if (b == 0 || (int64(a) == -9223372036854775808 && int64(b) == -1)) {
mach.status = MachineStatus.ERRORED;
return;
}
res = uint64(int64(a) / int64(b));
} else if (opcodeOffset == 5) {
// rem_s
if (b == 0) {
mach.status = MachineStatus.ERRORED;
return;
}
res = uint64(int64(a) % int64(b));
} else if (opcodeOffset == 10) {
// shl
res = a << (b % 64);
} else if (opcodeOffset == 12) {
// shr_u
res = a >> (b % 64);
} else if (opcodeOffset == 11) {
// shr_s
res = uint64(int64(a) >> (b % 64));
} else if (opcodeOffset == 13) {
// rotl
res = rotl64(a, b);
} else if (opcodeOffset == 14) {
// rotr
res = rotr64(a, b);
} else {
bool err;
(res, err) = genericBinOp(a, b, opcodeOffset);
if (err) {
mach.status = MachineStatus.ERRORED;
return;
}
}
}
mach.valueStack.push(ValueLib.newI64(res));
}
function executeI32WrapI64(
Machine memory mach,
Module memory,
Instruction calldata,
bytes calldata
) internal pure {
uint64 a = mach.valueStack.pop().assumeI64();
uint32 a32 = uint32(a);
mach.valueStack.push(ValueLib.newI32(a32));
}
function executeI64ExtendI32(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
uint32 a = mach.valueStack.pop().assumeI32();
uint64 a64;
if (inst.opcode == Instructions.I64_EXTEND_I32_S) {
a64 = signExtend(a);
} else {
a64 = uint64(a);
}
mach.valueStack.push(ValueLib.newI64(a64));
}
function executeExtendSameType(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
ValueType ty;
uint8 sourceBits;
if (inst.opcode == Instructions.I32_EXTEND_8S) {
ty = ValueType.I32;
sourceBits = 8;
} else if (inst.opcode == Instructions.I32_EXTEND_16S) {
ty = ValueType.I32;
sourceBits = 16;
} else if (inst.opcode == Instructions.I64_EXTEND_8S) {
ty = ValueType.I64;
sourceBits = 8;
} else if (inst.opcode == Instructions.I64_EXTEND_16S) {
ty = ValueType.I64;
sourceBits = 16;
} else if (inst.opcode == Instructions.I64_EXTEND_32S) {
ty = ValueType.I64;
sourceBits = 32;
} else {
revert("INVALID_EXTEND_SAME_TYPE");
}
uint256 resultMask;
if (ty == ValueType.I32) {
resultMask = (1 << 32) - 1;
} else {
resultMask = (1 << 64) - 1;
}
Value memory val = mach.valueStack.pop();
require(val.valueType == ty, "BAD_EXTEND_SAME_TYPE_TYPE");
uint256 sourceMask = (1 << sourceBits) - 1;
val.contents &= sourceMask;
if (val.contents & (1 << (sourceBits - 1)) != 0) {
// Extend sign flag
val.contents |= resultMask & ~sourceMask;
}
mach.valueStack.push(val);
}
function executeReinterpret(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
ValueType destTy;
ValueType sourceTy;
if (inst.opcode == Instructions.I32_REINTERPRET_F32) {
destTy = ValueType.I32;
sourceTy = ValueType.F32;
} else if (inst.opcode == Instructions.I64_REINTERPRET_F64) {
destTy = ValueType.I64;
sourceTy = ValueType.F64;
} else if (inst.opcode == Instructions.F32_REINTERPRET_I32) {
destTy = ValueType.F32;
sourceTy = ValueType.I32;
} else if (inst.opcode == Instructions.F64_REINTERPRET_I64) {
destTy = ValueType.F64;
sourceTy = ValueType.I64;
} else {
revert("INVALID_REINTERPRET");
}
Value memory val = mach.valueStack.pop();
require(val.valueType == sourceTy, "INVALID_REINTERPRET_TYPE");
val.valueType = destTy;
mach.valueStack.push(val);
}
function executeOneStep(
ExecutionContext calldata,
Machine calldata startMach,
Module calldata startMod,
Instruction calldata inst,
bytes calldata proof
) external pure override returns (Machine memory mach, Module memory mod) {
mach = startMach;
mod = startMod;
uint16 opcode = inst.opcode;
function(Machine memory, Module memory, Instruction calldata, bytes calldata)
internal
pure impl;
if (opcode == Instructions.I32_EQZ || opcode == Instructions.I64_EQZ) {
impl = executeEqz;
} else if (
opcode >= Instructions.I32_RELOP_BASE &&
opcode <= Instructions.I32_RELOP_BASE + Instructions.IRELOP_LAST
) {
impl = executeI32RelOp;
} else if (
opcode >= Instructions.I32_UNOP_BASE &&
opcode <= Instructions.I32_UNOP_BASE + Instructions.IUNOP_LAST
) {
impl = executeI32UnOp;
} else if (opcode >= Instructions.I32_ADD && opcode <= Instructions.I32_ROTR) {
impl = executeI32BinOp;
} else if (
opcode >= Instructions.I64_RELOP_BASE &&
opcode <= Instructions.I64_RELOP_BASE + Instructions.IRELOP_LAST
) {
impl = executeI64RelOp;
} else if (
opcode >= Instructions.I64_UNOP_BASE &&
opcode <= Instructions.I64_UNOP_BASE + Instructions.IUNOP_LAST
) {
impl = executeI64UnOp;
} else if (opcode >= Instructions.I64_ADD && opcode <= Instructions.I64_ROTR) {
impl = executeI64BinOp;
} else if (opcode == Instructions.I32_WRAP_I64) {
impl = executeI32WrapI64;
} else if (
opcode == Instructions.I64_EXTEND_I32_S || opcode == Instructions.I64_EXTEND_I32_U
) {
impl = executeI64ExtendI32;
} else if (opcode >= Instructions.I32_EXTEND_8S && opcode <= Instructions.I64_EXTEND_32S) {
impl = executeExtendSameType;
} else if (
opcode >= Instructions.I32_REINTERPRET_F32 && opcode <= Instructions.F64_REINTERPRET_I64
) {
impl = executeReinterpret;
} else {
revert("INVALID_OPCODE");
}
impl(mach, mod, inst, proof);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../state/Value.sol";
import "../state/Machine.sol";
import "../state/Module.sol";
import "../state/Deserialize.sol";
import "./IOneStepProver.sol";
contract OneStepProver0 is IOneStepProver {
using MerkleProofLib for MerkleProof;
using StackFrameLib for StackFrameWindow;
using ValueLib for Value;
using ValueStackLib for ValueStack;
function executeUnreachable(
Machine memory mach,
Module memory,
Instruction calldata,
bytes calldata
) internal pure {
mach.status = MachineStatus.ERRORED;
}
function executeNop(
Machine memory mach,
Module memory,
Instruction calldata,
bytes calldata
) internal pure {
// :)
}
function executeConstPush(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
uint16 opcode = inst.opcode;
ValueType ty;
if (opcode == Instructions.I32_CONST) {
ty = ValueType.I32;
} else if (opcode == Instructions.I64_CONST) {
ty = ValueType.I64;
} else if (opcode == Instructions.F32_CONST) {
ty = ValueType.F32;
} else if (opcode == Instructions.F64_CONST) {
ty = ValueType.F64;
} else {
revert("CONST_PUSH_INVALID_OPCODE");
}
mach.valueStack.push(Value({valueType: ty, contents: uint64(inst.argumentData)}));
}
function executeDrop(
Machine memory mach,
Module memory,
Instruction calldata,
bytes calldata
) internal pure {
mach.valueStack.pop();
}
function executeSelect(
Machine memory mach,
Module memory,
Instruction calldata,
bytes calldata
) internal pure {
uint32 selector = mach.valueStack.pop().assumeI32();
Value memory b = mach.valueStack.pop();
Value memory a = mach.valueStack.pop();
if (selector != 0) {
mach.valueStack.push(a);
} else {
mach.valueStack.push(b);
}
}
function executeReturn(
Machine memory mach,
Module memory,
Instruction calldata,
bytes calldata
) internal pure {
StackFrame memory frame = mach.frameStack.pop();
if (frame.returnPc.valueType == ValueType.REF_NULL) {
mach.status = MachineStatus.ERRORED;
return;
} else if (frame.returnPc.valueType != ValueType.INTERNAL_REF) {
revert("INVALID_RETURN_PC_TYPE");
}
uint256 data = frame.returnPc.contents;
uint32 pc = uint32(data);
uint32 func = uint32(data >> 32);
uint32 mod = uint32(data >> 64);
require(data >> 96 == 0, "INVALID_RETURN_PC_DATA");
mach.functionPc = pc;
mach.functionIdx = func;
mach.moduleIdx = mod;
}
function createReturnValue(Machine memory mach) internal pure returns (Value memory) {
uint256 returnData = 0;
returnData |= mach.functionPc;
returnData |= uint256(mach.functionIdx) << 32;
returnData |= uint256(mach.moduleIdx) << 64;
return Value({valueType: ValueType.INTERNAL_REF, contents: returnData});
}
function executeCall(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
// Push the return pc to the stack
mach.valueStack.push(createReturnValue(mach));
// Push caller module info to the stack
StackFrame memory frame = mach.frameStack.peek();
mach.valueStack.push(ValueLib.newI32(frame.callerModule));
mach.valueStack.push(ValueLib.newI32(frame.callerModuleInternals));
// Jump to the target
uint32 idx = uint32(inst.argumentData);
require(idx == inst.argumentData, "BAD_CALL_DATA");
mach.functionIdx = idx;
mach.functionPc = 0;
}
function executeCrossModuleCall(
Machine memory mach,
Module memory mod,
Instruction calldata inst,
bytes calldata
) internal pure {
// Push the return pc to the stack
mach.valueStack.push(createReturnValue(mach));
// Push caller module info to the stack
mach.valueStack.push(ValueLib.newI32(mach.moduleIdx));
mach.valueStack.push(ValueLib.newI32(mod.internalsOffset));
// Jump to the target
uint32 func = uint32(inst.argumentData);
uint32 module = uint32(inst.argumentData >> 32);
require(inst.argumentData >> 64 == 0, "BAD_CROSS_MODULE_CALL_DATA");
mach.moduleIdx = module;
mach.functionIdx = func;
mach.functionPc = 0;
}
function executeCallerModuleInternalCall(
Machine memory mach,
Module memory mod,
Instruction calldata inst,
bytes calldata
) internal pure {
// Push the return pc to the stack
mach.valueStack.push(createReturnValue(mach));
// Push caller module info to the stack
mach.valueStack.push(ValueLib.newI32(mach.moduleIdx));
mach.valueStack.push(ValueLib.newI32(mod.internalsOffset));
StackFrame memory frame = mach.frameStack.peek();
if (frame.callerModuleInternals == 0) {
// The caller module has no internals
mach.status = MachineStatus.ERRORED;
return;
}
// Jump to the target
uint32 offset = uint32(inst.argumentData);
require(offset == inst.argumentData, "BAD_CALLER_INTERNAL_CALL_DATA");
mach.moduleIdx = frame.callerModule;
mach.functionIdx = frame.callerModuleInternals + offset;
mach.functionPc = 0;
}
function executeCallIndirect(
Machine memory mach,
Module memory mod,
Instruction calldata inst,
bytes calldata proof
) internal pure {
uint32 funcIdx;
{
uint32 elementIdx = mach.valueStack.pop().assumeI32();
// Prove metadata about the instruction and tables
bytes32 elemsRoot;
bytes32 wantedFuncTypeHash;
uint256 offset = 0;
{
uint64 tableIdx;
uint8 tableType;
uint64 tableSize;
MerkleProof memory tableMerkleProof;
(tableIdx, offset) = Deserialize.u64(proof, offset);
(wantedFuncTypeHash, offset) = Deserialize.b32(proof, offset);
(tableType, offset) = Deserialize.u8(proof, offset);
(tableSize, offset) = Deserialize.u64(proof, offset);
(elemsRoot, offset) = Deserialize.b32(proof, offset);
(tableMerkleProof, offset) = Deserialize.merkleProof(proof, offset);
// Validate the information by recomputing known hashes
bytes32 recomputed = keccak256(
abi.encodePacked("Call indirect:", tableIdx, wantedFuncTypeHash)
);
require(recomputed == bytes32(inst.argumentData), "BAD_CALL_INDIRECT_DATA");
recomputed = tableMerkleProof.computeRootFromTable(
tableIdx,
tableType,
tableSize,
elemsRoot
);
require(recomputed == mod.tablesMerkleRoot, "BAD_TABLES_ROOT");
// Check if the table access is out of bounds
if (elementIdx >= tableSize) {
mach.status = MachineStatus.ERRORED;
return;
}
}
bytes32 elemFuncTypeHash;
Value memory functionPointer;
MerkleProof memory elementMerkleProof;
(elemFuncTypeHash, offset) = Deserialize.b32(proof, offset);
(functionPointer, offset) = Deserialize.value(proof, offset);
(elementMerkleProof, offset) = Deserialize.merkleProof(proof, offset);
bytes32 recomputedElemRoot = elementMerkleProof.computeRootFromElement(
elementIdx,
elemFuncTypeHash,
functionPointer
);
require(recomputedElemRoot == elemsRoot, "BAD_ELEMENTS_ROOT");
if (elemFuncTypeHash != wantedFuncTypeHash) {
mach.status = MachineStatus.ERRORED;
return;
}
if (functionPointer.valueType == ValueType.REF_NULL) {
mach.status = MachineStatus.ERRORED;
return;
} else if (functionPointer.valueType == ValueType.FUNC_REF) {
funcIdx = uint32(functionPointer.contents);
require(funcIdx == functionPointer.contents, "BAD_FUNC_REF_CONTENTS");
} else {
revert("BAD_ELEM_TYPE");
}
}
// Push the return pc to the stack
mach.valueStack.push(createReturnValue(mach));
// Push caller module info to the stack
StackFrame memory frame = mach.frameStack.peek();
mach.valueStack.push(ValueLib.newI32(frame.callerModule));
mach.valueStack.push(ValueLib.newI32(frame.callerModuleInternals));
// Jump to the target
mach.functionIdx = funcIdx;
mach.functionPc = 0;
}
function executeArbitraryJump(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
// Jump to target
uint32 pc = uint32(inst.argumentData);
require(pc == inst.argumentData, "BAD_CALL_DATA");
mach.functionPc = pc;
}
function executeArbitraryJumpIf(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
uint32 cond = mach.valueStack.pop().assumeI32();
if (cond != 0) {
// Jump to target
uint32 pc = uint32(inst.argumentData);
require(pc == inst.argumentData, "BAD_CALL_DATA");
mach.functionPc = pc;
}
}
function merkleProveGetValue(
bytes32 merkleRoot,
uint256 index,
bytes calldata proof
) internal pure returns (Value memory) {
uint256 offset = 0;
Value memory proposedVal;
MerkleProof memory merkle;
(proposedVal, offset) = Deserialize.value(proof, offset);
(merkle, offset) = Deserialize.merkleProof(proof, offset);
bytes32 recomputedRoot = merkle.computeRootFromValue(index, proposedVal);
require(recomputedRoot == merkleRoot, "WRONG_MERKLE_ROOT");
return proposedVal;
}
function merkleProveSetValue(
bytes32 merkleRoot,
uint256 index,
Value memory newVal,
bytes calldata proof
) internal pure returns (bytes32) {
Value memory oldVal;
uint256 offset = 0;
MerkleProof memory merkle;
(oldVal, offset) = Deserialize.value(proof, offset);
(merkle, offset) = Deserialize.merkleProof(proof, offset);
bytes32 recomputedRoot = merkle.computeRootFromValue(index, oldVal);
require(recomputedRoot == merkleRoot, "WRONG_MERKLE_ROOT");
return merkle.computeRootFromValue(index, newVal);
}
function executeLocalGet(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata proof
) internal pure {
StackFrame memory frame = mach.frameStack.peek();
Value memory val = merkleProveGetValue(frame.localsMerkleRoot, inst.argumentData, proof);
mach.valueStack.push(val);
}
function executeLocalSet(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata proof
) internal pure {
Value memory newVal = mach.valueStack.pop();
StackFrame memory frame = mach.frameStack.peek();
frame.localsMerkleRoot = merkleProveSetValue(
frame.localsMerkleRoot,
inst.argumentData,
newVal,
proof
);
}
function executeGlobalGet(
Machine memory mach,
Module memory mod,
Instruction calldata inst,
bytes calldata proof
) internal pure {
Value memory val = merkleProveGetValue(mod.globalsMerkleRoot, inst.argumentData, proof);
mach.valueStack.push(val);
}
function executeGlobalSet(
Machine memory mach,
Module memory mod,
Instruction calldata inst,
bytes calldata proof
) internal pure {
Value memory newVal = mach.valueStack.pop();
mod.globalsMerkleRoot = merkleProveSetValue(
mod.globalsMerkleRoot,
inst.argumentData,
newVal,
proof
);
}
function executeInitFrame(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
Value memory callerModuleInternals = mach.valueStack.pop();
Value memory callerModule = mach.valueStack.pop();
Value memory returnPc = mach.valueStack.pop();
StackFrame memory newFrame = StackFrame({
returnPc: returnPc,
localsMerkleRoot: bytes32(inst.argumentData),
callerModule: callerModule.assumeI32(),
callerModuleInternals: callerModuleInternals.assumeI32()
});
mach.frameStack.push(newFrame);
}
function executeMoveInternal(
Machine memory mach,
Module memory,
Instruction calldata inst,
bytes calldata
) internal pure {
Value memory val;
if (inst.opcode == Instructions.MOVE_FROM_STACK_TO_INTERNAL) {
val = mach.valueStack.pop();
mach.internalStack.push(val);
} else if (inst.opcode == Instructions.MOVE_FROM_INTERNAL_TO_STACK) {
val = mach.internalStack.pop();
mach.valueStack.push(val);
} else {
revert("MOVE_INTERNAL_INVALID_OPCODE");
}
}
function executeDup(
Machine memory mach,
Module memory,
Instruction calldata,
bytes calldata
) internal pure {
Value memory val = mach.valueStack.peek();
mach.valueStack.push(val);
}
function executeOneStep(
ExecutionContext calldata,
Machine calldata startMach,
Module calldata startMod,
Instruction calldata inst,
bytes calldata proof
) external pure override returns (Machine memory mach, Module memory mod) {
mach = startMach;
mod = startMod;
uint16 opcode = inst.opcode;
function(Machine memory, Module memory, Instruction calldata, bytes calldata)
internal
pure impl;
if (opcode == Instructions.UNREACHABLE) {
impl = executeUnreachable;
} else if (opcode == Instructions.NOP) {
impl = executeNop;
} else if (opcode == Instructions.RETURN) {
impl = executeReturn;
} else if (opcode == Instructions.CALL) {
impl = executeCall;
} else if (opcode == Instructions.CROSS_MODULE_CALL) {
impl = executeCrossModuleCall;
} else if (opcode == Instructions.CALLER_MODULE_INTERNAL_CALL) {
impl = executeCallerModuleInternalCall;
} else if (opcode == Instructions.CALL_INDIRECT) {
impl = executeCallIndirect;
} else if (opcode == Instructions.ARBITRARY_JUMP) {
impl = executeArbitraryJump;
} else if (opcode == Instructions.ARBITRARY_JUMP_IF) {
impl = executeArbitraryJumpIf;
} else if (opcode == Instructions.LOCAL_GET) {
impl = executeLocalGet;
} else if (opcode == Instructions.LOCAL_SET) {
impl = executeLocalSet;
} else if (opcode == Instructions.GLOBAL_GET) {
impl = executeGlobalGet;
} else if (opcode == Instructions.GLOBAL_SET) {
impl = executeGlobalSet;
} else if (opcode == Instructions.INIT_FRAME) {
impl = executeInitFrame;
} else if (opcode == Instructions.DROP) {
impl = executeDrop;
} else if (opcode == Instructions.SELECT) {
impl = executeSelect;
} else if (opcode >= Instructions.I32_CONST && opcode <= Instructions.F64_CONST) {
impl = executeConstPush;
} else if (
opcode == Instructions.MOVE_FROM_STACK_TO_INTERNAL ||
opcode == Instructions.MOVE_FROM_INTERNAL_TO_STACK
) {
impl = executeMoveInternal;
} else if (opcode == Instructions.DUP) {
impl = executeDup;
} else {
revert("INVALID_OPCODE");
}
impl(mach, mod, inst, proof);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../state/Deserialize.sol";
import "../state/Machine.sol";
import "../state/MerkleProof.sol";
import "./IOneStepProver.sol";
import "./IOneStepProofEntry.sol";
contract OneStepProofEntry is IOneStepProofEntry {
using MerkleProofLib for MerkleProof;
using MachineLib for Machine;
IOneStepProver public prover0;
IOneStepProver public proverMem;
IOneStepProver public proverMath;
IOneStepProver public proverHostIo;
constructor(
IOneStepProver prover0_,
IOneStepProver proverMem_,
IOneStepProver proverMath_,
IOneStepProver proverHostIo_
) {
prover0 = prover0_;
proverMem = proverMem_;
proverMath = proverMath_;
proverHostIo = proverHostIo_;
}
function proveOneStep(
ExecutionContext calldata execCtx,
uint256 machineStep,
bytes32 beforeHash,
bytes calldata proof
) external view override returns (bytes32 afterHash) {
Machine memory mach;
Module memory mod;
MerkleProof memory modProof;
Instruction memory inst;
{
uint256 offset = 0;
(mach, offset) = Deserialize.machine(proof, offset);
require(mach.hash() == beforeHash, "MACHINE_BEFORE_HASH");
if (mach.status != MachineStatus.RUNNING) {
// Machine is halted.
// WARNING: at this point, most machine fields are unconstrained.
return mach.hash();
}
if (machineStep + 1 == OneStepProofEntryLib.MAX_STEPS) {
mach.status = MachineStatus.ERRORED;
return mach.hash();
}
(mod, offset) = Deserialize.module(proof, offset);
(modProof, offset) = Deserialize.merkleProof(proof, offset);
require(
modProof.computeRootFromModule(mach.moduleIdx, mod) == mach.modulesRoot,
"MODULES_ROOT"
);
{
MerkleProof memory instProof;
MerkleProof memory funcProof;
(inst, offset) = Deserialize.instruction(proof, offset);
(instProof, offset) = Deserialize.merkleProof(proof, offset);
(funcProof, offset) = Deserialize.merkleProof(proof, offset);
bytes32 codeHash = instProof.computeRootFromInstruction(mach.functionPc, inst);
bytes32 recomputedRoot = funcProof.computeRootFromFunction(
mach.functionIdx,
codeHash
);
require(recomputedRoot == mod.functionsMerkleRoot, "BAD_FUNCTIONS_ROOT");
}
proof = proof[offset:];
}
uint256 oldModIdx = mach.moduleIdx;
mach.functionPc += 1;
uint16 opcode = inst.opcode;
IOneStepProver prover;
if (
(opcode >= Instructions.I32_LOAD && opcode <= Instructions.I64_LOAD32_U) ||
(opcode >= Instructions.I32_STORE && opcode <= Instructions.I64_STORE32) ||
opcode == Instructions.MEMORY_SIZE ||
opcode == Instructions.MEMORY_GROW
) {
prover = proverMem;
} else if (
(opcode == Instructions.I32_EQZ || opcode == Instructions.I64_EQZ) ||
(opcode >= Instructions.I32_RELOP_BASE &&
opcode <= Instructions.I32_RELOP_BASE + Instructions.IRELOP_LAST) ||
(opcode >= Instructions.I32_UNOP_BASE &&
opcode <= Instructions.I32_UNOP_BASE + Instructions.IUNOP_LAST) ||
(opcode >= Instructions.I32_ADD && opcode <= Instructions.I32_ROTR) ||
(opcode >= Instructions.I64_RELOP_BASE &&
opcode <= Instructions.I64_RELOP_BASE + Instructions.IRELOP_LAST) ||
(opcode >= Instructions.I64_UNOP_BASE &&
opcode <= Instructions.I64_UNOP_BASE + Instructions.IUNOP_LAST) ||
(opcode >= Instructions.I64_ADD && opcode <= Instructions.I64_ROTR) ||
(opcode == Instructions.I32_WRAP_I64) ||
(opcode == Instructions.I64_EXTEND_I32_S || opcode == Instructions.I64_EXTEND_I32_U) ||
(opcode >= Instructions.I32_EXTEND_8S && opcode <= Instructions.I64_EXTEND_32S) ||
(opcode >= Instructions.I32_REINTERPRET_F32 &&
opcode <= Instructions.F64_REINTERPRET_I64)
) {
prover = proverMath;
} else if (
(opcode >= Instructions.GET_GLOBAL_STATE_BYTES32 &&
opcode <= Instructions.SET_GLOBAL_STATE_U64) ||
(opcode >= Instructions.READ_PRE_IMAGE && opcode <= Instructions.HALT_AND_SET_FINISHED)
) {
prover = proverHostIo;
} else {
prover = prover0;
}
(mach, mod) = prover.executeOneStep(execCtx, mach, mod, inst, proof);
mach.modulesRoot = modProof.computeRootFromModule(oldModIdx, mod);
return mach.hash();
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../state/ValueArray.sol";
contract ValueArrayTester {
using ValueArrayLib for ValueArray;
function test() external pure {
ValueArray memory arr = ValueArray(new Value[](2));
require(arr.length() == 2, "START_LEN");
arr.set(0, ValueLib.newI32(1));
arr.set(1, ValueLib.newI32(2));
arr.push(ValueLib.newI32(3));
require(arr.length() == 3, "PUSH_LEN");
for (uint256 i = 0; i < arr.length(); i++) {
Value memory val = arr.get(i);
require(val.valueType == ValueType.I32, "PUSH_VAL_TYPE");
require(val.contents == i + 1, "PUSH_VAL_CONTENTS");
}
Value memory popped = arr.pop();
require(popped.valueType == ValueType.I32, "POP_RET_TYPE");
require(popped.contents == 3, "POP_RET_CONTENTS");
require(arr.length() == 2, "POP_LEN");
for (uint256 i = 0; i < arr.length(); i++) {
Value memory val = arr.get(i);
require(val.valueType == ValueType.I32, "POP_VAL_TYPE");
require(val.contents == i + 1, "POP_VAL_CONTENTS");
}
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../challenge/IChallengeResultReceiver.sol";
import "../challenge/IChallengeManager.sol";
contract MockResultReceiver is IChallengeResultReceiver {
IChallengeManager public manager;
address public winner;
address public loser;
uint256 public challengeIndex;
event ChallengeCompleted(
uint256 indexed challengeIndex,
address indexed winner,
address indexed loser
);
constructor(IChallengeManager manager_) {
manager = manager_;
}
function createChallenge(
bytes32 wasmModuleRoot_,
MachineStatus[2] calldata startAndEndMachineStatuses_,
GlobalState[2] calldata startAndEndGlobalStates_,
uint64 numBlocks,
address asserter_,
address challenger_,
uint256 asserterTimeLeft_,
uint256 challengerTimeLeft_
) external returns (uint64) {
return
manager.createChallenge(
wasmModuleRoot_,
startAndEndMachineStatuses_,
startAndEndGlobalStates_,
numBlocks,
asserter_,
challenger_,
asserterTimeLeft_,
challengerTimeLeft_
);
}
function completeChallenge(
uint256 challengeIndex_,
address winner_,
address loser_
) external override {
winner = winner_;
loser = loser_;
challengeIndex = challengeIndex_;
emit ChallengeCompleted(challengeIndex, winner_, loser_);
}
}// Copyright 2021-2022, Offchain Labs, Inc.
// For license information, see https://github.com/nitro/blob/master/LICENSE
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;
import "../bridge/SequencerInbox.sol";
contract SequencerInboxStub is SequencerInbox {
constructor(
IBridge bridge_,
address sequencer_,
ISequencerInbox.MaxTimeVariation memory maxTimeVariation_
) {
bridge = bridge_;
rollup = IOwnable(msg.sender);
maxTimeVariation = maxTimeVariation_;
isBatchPoster[sequencer_] = true;
}
function addInitMessage() external {
(bytes32 dataHash, TimeBounds memory timeBounds) = formEmptyDataHash(0);
(
uint256 sequencerMessageCount,
bytes32 beforeAcc,
bytes32 delayedAcc,
bytes32 afterAcc
) = addSequencerL2BatchImpl(dataHash, 0, 0);
emit SequencerBatchDelivered(
sequencerMessageCount,
beforeAcc,
afterAcc,
delayedAcc,
totalDelayedMessagesRead,
timeBounds,
BatchDataLocation.NoData
);
}
function getTimeBounds() internal view override returns (TimeBounds memory bounds) {
this; // silence warning about function not being view
return bounds;
}
}{
"optimizer": {
"enabled": true,
"runs": 100
},
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
},
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"components":[{"internalType":"uint64","name":"confirmPeriodBlocks","type":"uint64"},{"internalType":"uint64","name":"extraChallengeTimeBlocks","type":"uint64"},{"internalType":"address","name":"stakeToken","type":"address"},{"internalType":"uint256","name":"baseStake","type":"uint256"},{"internalType":"bytes32","name":"wasmModuleRoot","type":"bytes32"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"loserStakeEscrow","type":"address"},{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"uint64","name":"genesisBlockNum","type":"uint64"},{"components":[{"internalType":"uint256","name":"delayBlocks","type":"uint256"},{"internalType":"uint256","name":"futureBlocks","type":"uint256"},{"internalType":"uint256","name":"delaySeconds","type":"uint256"},{"internalType":"uint256","name":"futureSeconds","type":"uint256"}],"internalType":"struct ISequencerInbox.MaxTimeVariation","name":"sequencerInboxMaxTimeVariation","type":"tuple"}],"internalType":"struct Config","name":"config","type":"tuple"},{"components":[{"internalType":"contract IBridge","name":"bridge","type":"address"},{"internalType":"contract ISequencerInbox","name":"sequencerInbox","type":"address"},{"internalType":"contract IInbox","name":"inbox","type":"address"},{"internalType":"contract IOutbox","name":"outbox","type":"address"},{"internalType":"contract IRollupEventInbox","name":"rollupEventInbox","type":"address"},{"internalType":"contract IChallengeManager","name":"challengeManager","type":"address"},{"internalType":"contract IRollupAdmin","name":"rollupAdminLogic","type":"address"},{"internalType":"contract IRollupUser","name":"rollupUserLogic","type":"address"},{"internalType":"address","name":"validatorUtils","type":"address"},{"internalType":"address","name":"validatorWalletCreator","type":"address"}],"internalType":"struct ContractDependencies","name":"connectedContracts","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousAdmin","type":"address"},{"indexed":false,"internalType":"address","name":"newAdmin","type":"address"}],"name":"AdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"beacon","type":"address"}],"name":"BeaconUpgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"Upgraded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"implementation","type":"address"}],"name":"UpgradedSecondary","type":"event"},{"stateMutability":"payable","type":"fallback"},{"stateMutability":"payable","type":"receive"}]Contract Creation Code
60806040523480156200001157600080fd5b50604051620010a1380380620010a1833981016040819052620000349162000896565b60c08101516040516329ce5f2b60e01b9062000057908590859060240162000a89565b60408051601f198184030181529181526020820180516001600160e01b03166001600160e01b03199094169390931790925260e08401518583015192516001600160a01b039093166024840152909163189acdbd60e31b9060440160408051601f198184030181529190526020810180516001600160e01b03166001600160e01b03199093169290921790915260a08601516200011660017fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d610462000b87565b6000805160206200101a8339815191521462000136576200013662000bad565b6200016360017f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbd62000b87565b6000805160206200103a8339815191521462000183576200018362000bad565b620001b060017f2b1dbce74324248c222f0ec2d5ed7bd323cfc425b336f0253c5ccfda7265546e62000b87565b6000805160206200108183398151915214620001d057620001d062000bad565b620001db8162000204565b620001e9858560006200025f565b620001f7838360006200029c565b5050505050505062000c45565b7f7e644d79422f17c01e4894b5f4f588d331ebfa28653d42ae832dc59e38c9798f6200022f620002a7565b604080516001600160a01b03928316815291841660208301520160405180910390a16200025c81620002e0565b50565b6200026a8362000395565b600082511180620002785750805b156200029757620002958383620003d760201b620000291760201c565b505b505050565b6200026a8362000406565b6000620002d16000805160206200101a83398151915260001b6200044860201b620000551760201c565b546001600160a01b0316919050565b6001600160a01b0381166200034b5760405162461bcd60e51b815260206004820152602660248201527f455243313936373a206e65772061646d696e20697320746865207a65726f206160448201526564647265737360d01b60648201526084015b60405180910390fd5b80620003746000805160206200101a83398151915260001b6200044860201b620000551760201c565b80546001600160a01b0319166001600160a01b039290921691909117905550565b620003a0816200044b565b6040516001600160a01b038216907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b90600090a250565b6060620003ff83836040518060600160405280602781526020016200105a60279139620004ee565b9392505050565b6200041181620005d4565b6040516001600160a01b038216907ff7eed2a7fabbf1bec8d55ed5e785cc76622376dde5df4ff15470551e030b813490600090a250565b90565b62000461816200068760201b620000581760201c565b620004c55760405162461bcd60e51b815260206004820152602d60248201527f455243313936373a206e657720696d706c656d656e746174696f6e206973206e60448201526c1bdd08184818dbdb9d1c9858dd609a1b606482015260840162000342565b80620003746000805160206200103a83398151915260001b6200044860201b620000551760201c565b60606001600160a01b0384163b620005585760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b606482015260840162000342565b600080856001600160a01b03168560405162000575919062000bf2565b600060405180830381855af49150503d8060008114620005b2576040519150601f19603f3d011682016040523d82523d6000602084013e620005b7565b606091505b509092509050620005ca82828662000696565b9695505050505050565b620005ea816200068760201b620000581760201c565b6200065e5760405162461bcd60e51b815260206004820152603760248201527f455243313936373a206e6577207365636f6e6461727920696d706c656d656e7460448201527f6174696f6e206973206e6f74206120636f6e7472616374000000000000000000606482015260840162000342565b80620003746000805160206200108183398151915260001b6200044860201b620000551760201c565b6001600160a01b03163b151590565b60608315620006a7575081620003ff565b825115620006b85782518084602001fd5b8160405162461bcd60e51b815260040162000342919062000c10565b60405161014081016001600160401b03811182821017156200070657634e487b7160e01b600052604160045260246000fd5b60405290565b80516001600160401b03811681146200072457600080fd5b919050565b80516001600160a01b03811681146200072457600080fd5b6000608082840312156200075457600080fd5b604051608081016001600160401b03811182821017156200078557634e487b7160e01b600052604160045260246000fd5b8060405250809150825181526020830151602082015260408301516040820152606083015160608201525092915050565b60006101408284031215620007ca57600080fd5b620007d4620006d4565b9050620007e18262000729565b8152620007f16020830162000729565b6020820152620008046040830162000729565b6040820152620008176060830162000729565b60608201526200082a6080830162000729565b60808201526200083d60a0830162000729565b60a08201526200085060c0830162000729565b60c08201526200086360e0830162000729565b60e08201526101006200087881840162000729565b908201526101206200088c83820162000729565b9082015292915050565b6000808284036102e0811215620008ac57600080fd5b6101a080821215620008bd57600080fd5b620008c7620006d4565b9150620008d4856200070c565b8252620008e4602086016200070c565b6020830152620008f76040860162000729565b604083015260608501516060830152608085015160808301526200091e60a0860162000729565b60a08301526200093160c0860162000729565b60c083015260e085015160e0830152610100620009508187016200070c565b90830152610120620009658787830162000741565b81840152508193506200097b86828701620007b6565b925050509250929050565b80516001600160a01b031682526020810151620009ae60208401826001600160a01b03169052565b506040810151620009ca60408401826001600160a01b03169052565b506060810151620009e660608401826001600160a01b03169052565b50608081015162000a0260808401826001600160a01b03169052565b5060a081015162000a1e60a08401826001600160a01b03169052565b5060c081015162000a3a60c08401826001600160a01b03169052565b5060e081015162000a5660e08401826001600160a01b03169052565b50610100818101516001600160a01b038116848301525050610120818101516001600160a01b0381168483015262000295565b82516001600160401b031681526102e08101602084015162000ab660208401826001600160401b03169052565b50604084015162000ad260408401826001600160a01b03169052565b50606084015160608301526080840151608083015260a084015162000b0260a08401826001600160a01b03169052565b5060c084015162000b1e60c08401826001600160a01b03169052565b5060e084015160e08301526101008085015162000b45828501826001600160401b03169052565b5050610120848101518051848301526020810151610140850152604081015161016085015260608101516101808501525050620003ff6101a083018462000986565b60008282101562000ba857634e487b7160e01b600052601160045260246000fd5b500390565b634e487b7160e01b600052600160045260246000fd5b60005b8381101562000be057818101518382015260200162000bc6565b83811115620002955750506000910152565b6000825162000c0681846020870162000bc3565b9190910192915050565b602081526000825180602084015262000c3181604085016020870162000bc3565b601f01601f19169190910160400192915050565b6103c58062000c556000396000f3fe60806040523661001357610011610017565b005b6100115b610027610022610067565b61012e565b565b606061004e838360405180606001604052806027815260200161036960279139610152565b9392505050565b90565b6001600160a01b03163b151590565b600060043610156100ad5760405162461bcd60e51b815260206004820152600b60248201526a4e4f5f46554e435f53494760a81b60448201526064015b60405180910390fd5b6000336100b861022d565b6001600160a01b031614156100d4576100cf610260565b6100dc565b6100dc610288565b90506100e781610058565b6101295760405162461bcd60e51b815260206004820152601360248201527215105491d15517d393d517d0d3d395149050d5606a1b60448201526064016100a4565b919050565b3660008037600080366000845af43d6000803e80801561014d573d6000f35b3d6000fd5b606061015d84610058565b6101b85760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b60648201526084016100a4565b600080856001600160a01b0316856040516101d39190610319565b600060405180830381855af49150503d806000811461020e576040519150601f19603f3d011682016040523d82523d6000602084013e610213565b606091505b50915091506102238282866102b0565b9695505050505050565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610251565b60007f2b1dbce74324248c222f0ec2d5ed7bd323cfc425b336f0253c5ccfda7265546d610251565b606083156102bf57508161004e565b8251156102cf5782518084602001fd5b8160405162461bcd60e51b81526004016100a49190610335565b60005b838110156103045781810151838201526020016102ec565b83811115610313576000848401525b50505050565b6000825161032b8184602087016102e9565b9190910192915050565b60208152600082518060208401526103548160408501602087016102e9565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212209c785474fdcd1ba13f5d8ab2e33cef4b0c8685545bba065effad674acb96aefe64736f6c63430008090033b53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c65642b1dbce74324248c222f0ec2d5ed7bd323cfc425b336f0253c5ccfda7265546d000000000000000000000000000000000000000000000000000000000000b2fa00000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400003848eff5e0356faf1fc9cafecb789584c5e7f4f8f817694d842ada96613d8bab00000000000000000000000080420b3216e87e4ed25489ef392901aafc10951b0000000000000000000000005b11bdc6ef32ce261a39f58122e301d59fc05677000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001680000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e100000000000000000000000008315177ab297ba92a06054ce80a67ed4dbd7ed3a0000000000000000000000001c479675ad559dc151f6ec7ed3fbf8cee79582b60000000000000000000000001c9dbddc9c2f1b29d4613e45bd5f35c0b1fba8d60000000000000000000000000b9857ae2d4a3dbe74ffe1d7df045bb7f96e484000000000000000000000000057bd336d579a51938619271a7cc137a46d0501b1000000000000000000000000e5896783a2f463446e1f624e64aa6836be4c6f5800000000000000000000000075fc5465c4bad74b367ac917f7298ad66c308fb80000000000000000000000004c5960936f1635765e37ff1a220d7344b27d70460000000000000000000000009e40625f52829cf04bc4839f186d621ee33b0e67000000000000000000000000960953f7c69cd2bc2322db9223a815c680ccc7ea
Deployed Bytecode
0x60806040523661001357610011610017565b005b6100115b610027610022610067565b61012e565b565b606061004e838360405180606001604052806027815260200161036960279139610152565b9392505050565b90565b6001600160a01b03163b151590565b600060043610156100ad5760405162461bcd60e51b815260206004820152600b60248201526a4e4f5f46554e435f53494760a81b60448201526064015b60405180910390fd5b6000336100b861022d565b6001600160a01b031614156100d4576100cf610260565b6100dc565b6100dc610288565b90506100e781610058565b6101295760405162461bcd60e51b815260206004820152601360248201527215105491d15517d393d517d0d3d395149050d5606a1b60448201526064016100a4565b919050565b3660008037600080366000845af43d6000803e80801561014d573d6000f35b3d6000fd5b606061015d84610058565b6101b85760405162461bcd60e51b815260206004820152602660248201527f416464726573733a2064656c65676174652063616c6c20746f206e6f6e2d636f6044820152651b9d1c9858dd60d21b60648201526084016100a4565b600080856001600160a01b0316856040516101d39190610319565b600060405180830381855af49150503d806000811461020e576040519150601f19603f3d011682016040523d82523d6000602084013e610213565b606091505b50915091506102238282866102b0565b9695505050505050565b60007fb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d61035b546001600160a01b0316919050565b60007f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc610251565b60007f2b1dbce74324248c222f0ec2d5ed7bd323cfc425b336f0253c5ccfda7265546d610251565b606083156102bf57508161004e565b8251156102cf5782518084602001fd5b8160405162461bcd60e51b81526004016100a49190610335565b60005b838110156103045781810151838201526020016102ec565b83811115610313576000848401525b50505050565b6000825161032b8184602087016102e9565b9190910192915050565b60208152600082518060208401526103548160408501602087016102e9565b601f01601f1916919091016040019291505056fe416464726573733a206c6f772d6c6576656c2064656c65676174652063616c6c206661696c6564a26469706673582212209c785474fdcd1ba13f5d8ab2e33cef4b0c8685545bba065effad674acb96aefe64736f6c63430008090033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
000000000000000000000000000000000000000000000000000000000000b2fa00000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000de0b6b3a76400003848eff5e0356faf1fc9cafecb789584c5e7f4f8f817694d842ada96613d8bab00000000000000000000000080420b3216e87e4ed25489ef392901aafc10951b0000000000000000000000005b11bdc6ef32ce261a39f58122e301d59fc05677000000000000000000000000000000000000000000000000000000000000a4b100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001680000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000151800000000000000000000000000000000000000000000000000000000000000e100000000000000000000000008315177ab297ba92a06054ce80a67ed4dbd7ed3a0000000000000000000000001c479675ad559dc151f6ec7ed3fbf8cee79582b60000000000000000000000001c9dbddc9c2f1b29d4613e45bd5f35c0b1fba8d60000000000000000000000000b9857ae2d4a3dbe74ffe1d7df045bb7f96e484000000000000000000000000057bd336d579a51938619271a7cc137a46d0501b1000000000000000000000000e5896783a2f463446e1f624e64aa6836be4c6f5800000000000000000000000075fc5465c4bad74b367ac917f7298ad66c308fb80000000000000000000000004c5960936f1635765e37ff1a220d7344b27d70460000000000000000000000009e40625f52829cf04bc4839f186d621ee33b0e67000000000000000000000000960953f7c69cd2bc2322db9223a815c680ccc7ea
-----Decoded View---------------
Arg [0] : config (tuple):
Arg [1] : confirmPeriodBlocks (uint64): 45818
Arg [2] : extraChallengeTimeBlocks (uint64): 200
Arg [3] : stakeToken (address): 0x0000000000000000000000000000000000000000
Arg [4] : baseStake (uint256): 1000000000000000000
Arg [5] : wasmModuleRoot (bytes32): 0x3848eff5e0356faf1fc9cafecb789584c5e7f4f8f817694d842ada96613d8bab
Arg [6] : owner (address): 0x80420B3216E87e4ed25489ef392901Aafc10951B
Arg [7] : loserStakeEscrow (address): 0x5b11BDC6eF32cE261A39f58122E301D59FC05677
Arg [8] : chainId (uint256): 42161
Arg [9] : genesisBlockNum (uint64): 0
Arg [10] : sequencerInboxMaxTimeVariation (tuple):
Arg [1] : delayBlocks (uint256): 5760
Arg [2] : futureBlocks (uint256): 12
Arg [3] : delaySeconds (uint256): 86400
Arg [4] : futureSeconds (uint256): 3600
Arg [1] : connectedContracts (tuple):
Arg [1] : bridge (address): 0x8315177aB297bA92A06054cE80a67Ed4DBd7ed3a
Arg [2] : sequencerInbox (address): 0x1c479675ad559DC151F6Ec7ed3FbF8ceE79582B6
Arg [3] : inbox (address): 0x1c9DbddC9C2f1B29d4613E45BD5F35C0b1FBA8d6
Arg [4] : outbox (address): 0x0B9857ae2D4A3DBe74ffE1d7DF045bb7F96E4840
Arg [5] : rollupEventInbox (address): 0x57Bd336d579A51938619271a7Cc137a46D0501B1
Arg [6] : challengeManager (address): 0xe5896783a2F463446E1f624e64Aa6836BE4C6f58
Arg [7] : rollupAdminLogic (address): 0x75fc5465c4BaD74B367ac917f7298aD66c308Fb8
Arg [8] : rollupUserLogic (address): 0x4C5960936f1635765e37Ff1a220D7344b27D7046
Arg [9] : validatorUtils (address): 0x9E40625F52829Cf04bC4839F186D621ee33b0E67
Arg [10] : validatorWalletCreator (address): 0x960953f7c69cd2BC2322Db9223A815C680ccc7ea
-----Encoded View---------------
23 Constructor Arguments found :
Arg [0] : 000000000000000000000000000000000000000000000000000000000000b2fa
Arg [1] : 00000000000000000000000000000000000000000000000000000000000000c8
Arg [2] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [3] : 0000000000000000000000000000000000000000000000000de0b6b3a7640000
Arg [4] : 3848eff5e0356faf1fc9cafecb789584c5e7f4f8f817694d842ada96613d8bab
Arg [5] : 00000000000000000000000080420b3216e87e4ed25489ef392901aafc10951b
Arg [6] : 0000000000000000000000005b11bdc6ef32ce261a39f58122e301d59fc05677
Arg [7] : 000000000000000000000000000000000000000000000000000000000000a4b1
Arg [8] : 0000000000000000000000000000000000000000000000000000000000000000
Arg [9] : 0000000000000000000000000000000000000000000000000000000000001680
Arg [10] : 000000000000000000000000000000000000000000000000000000000000000c
Arg [11] : 0000000000000000000000000000000000000000000000000000000000015180
Arg [12] : 0000000000000000000000000000000000000000000000000000000000000e10
Arg [13] : 0000000000000000000000008315177ab297ba92a06054ce80a67ed4dbd7ed3a
Arg [14] : 0000000000000000000000001c479675ad559dc151f6ec7ed3fbf8cee79582b6
Arg [15] : 0000000000000000000000001c9dbddc9c2f1b29d4613e45bd5f35c0b1fba8d6
Arg [16] : 0000000000000000000000000b9857ae2d4a3dbe74ffe1d7df045bb7f96e4840
Arg [17] : 00000000000000000000000057bd336d579a51938619271a7cc137a46d0501b1
Arg [18] : 000000000000000000000000e5896783a2f463446e1f624e64aa6836be4c6f58
Arg [19] : 00000000000000000000000075fc5465c4bad74b367ac917f7298ad66c308fb8
Arg [20] : 0000000000000000000000004c5960936f1635765e37ff1a220d7344b27d7046
Arg [21] : 0000000000000000000000009e40625f52829cf04bc4839f186d621ee33b0e67
Arg [22] : 000000000000000000000000960953f7c69cd2bc2322db9223a815c680ccc7ea
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 34 Chains
| Chain | Token | Portfolio % | Price | Amount | Value |
|---|---|---|---|---|---|
| ETH | 100.00% | $3,381.7 | 2 | $6,763.4 |
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.