|
| 1 | +// Copyright 2021-2022, Offchain Labs, Inc. |
| 2 | +// For license information, see https://github.com/nitro/blob/master/LICENSE |
| 3 | +// SPDX-License-Identifier: BUSL-1.1 |
| 4 | + |
| 5 | +pragma solidity ^0.8.4; |
| 6 | + |
| 7 | +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; |
| 8 | +import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol"; |
| 9 | + |
| 10 | +import { |
| 11 | + NotContract, |
| 12 | + NotRollupOrOwner, |
| 13 | + NotDelayedInbox, |
| 14 | + NotSequencerInbox, |
| 15 | + NotOutbox, |
| 16 | + InvalidOutboxSet, |
| 17 | + BadSequencerMessageNumber |
| 18 | +} from "../libraries/Error.sol"; |
| 19 | +import "./IBridge.sol"; |
| 20 | +import "./Messages.sol"; |
| 21 | +import "../libraries/DelegateCallAware.sol"; |
| 22 | + |
| 23 | +import {L1MessageType_batchPostingReport} from "../libraries/MessageTypes.sol"; |
| 24 | + |
| 25 | +/** |
| 26 | + * @title Staging ground for incoming and outgoing messages |
| 27 | + * @notice Holds the inbox accumulator for sequenced and delayed messages. |
| 28 | + * It is also the ETH escrow for value sent with these messages. |
| 29 | + * Since the escrow is held here, this contract also contains a list of allowed |
| 30 | + * outboxes that can make calls from here and withdraw this escrow. |
| 31 | + */ |
| 32 | +contract Bridge is Initializable, DelegateCallAware, IBridge { |
| 33 | + using AddressUpgradeable for address; |
| 34 | + |
| 35 | + struct InOutInfo { |
| 36 | + uint256 index; |
| 37 | + bool allowed; |
| 38 | + } |
| 39 | + |
| 40 | + mapping(address => InOutInfo) private allowedDelayedInboxesMap; |
| 41 | + mapping(address => InOutInfo) private allowedOutboxesMap; |
| 42 | + |
| 43 | + address[] public allowedDelayedInboxList; |
| 44 | + address[] public allowedOutboxList; |
| 45 | + |
| 46 | + address internal _activeOutbox; |
| 47 | + |
| 48 | + /// @inheritdoc IBridge |
| 49 | + bytes32[] public delayedInboxAccs; |
| 50 | + |
| 51 | + /// @inheritdoc IBridge |
| 52 | + bytes32[] public sequencerInboxAccs; |
| 53 | + |
| 54 | + IOwnable public rollup; |
| 55 | + address public sequencerInbox; |
| 56 | + |
| 57 | + uint256 public override sequencerReportedSubMessageCount; |
| 58 | + |
| 59 | + address internal constant EMPTY_ACTIVEOUTBOX = address(type(uint160).max); |
| 60 | + |
| 61 | + function initialize(IOwnable rollup_) external initializer onlyDelegated { |
| 62 | + _activeOutbox = EMPTY_ACTIVEOUTBOX; |
| 63 | + rollup = rollup_; |
| 64 | + } |
| 65 | + |
| 66 | + modifier onlyRollupOrOwner() { |
| 67 | + if (msg.sender != address(rollup)) { |
| 68 | + address rollupOwner = rollup.owner(); |
| 69 | + if (msg.sender != rollupOwner) { |
| 70 | + revert NotRollupOrOwner(msg.sender, address(rollup), rollupOwner); |
| 71 | + } |
| 72 | + } |
| 73 | + _; |
| 74 | + } |
| 75 | + |
| 76 | + /// @dev returns the address of current active Outbox, or zero if no outbox is active |
| 77 | + function activeOutbox() public view returns (address) { |
| 78 | + address outbox = _activeOutbox; |
| 79 | + // address zero is returned if no outbox is set, but the value used in storage |
| 80 | + // is non-zero to save users some gas (as storage refunds are usually maxed out) |
| 81 | + // EIP-1153 would help here. |
| 82 | + // we don't return `EMPTY_ACTIVEOUTBOX` to avoid a breaking change on the current api |
| 83 | + if (outbox == EMPTY_ACTIVEOUTBOX) return address(0); |
| 84 | + return outbox; |
| 85 | + } |
| 86 | + |
| 87 | + function allowedDelayedInboxes(address inbox) external view returns (bool) { |
| 88 | + return allowedDelayedInboxesMap[inbox].allowed; |
| 89 | + } |
| 90 | + |
| 91 | + function allowedOutboxes(address outbox) external view returns (bool) { |
| 92 | + return allowedOutboxesMap[outbox].allowed; |
| 93 | + } |
| 94 | + |
| 95 | + modifier onlySequencerInbox() { |
| 96 | + if (msg.sender != sequencerInbox) revert NotSequencerInbox(msg.sender); |
| 97 | + _; |
| 98 | + } |
| 99 | + |
| 100 | + function enqueueSequencerMessage( |
| 101 | + bytes32 dataHash, |
| 102 | + uint256 afterDelayedMessagesRead, |
| 103 | + uint256 prevMessageCount, |
| 104 | + uint256 newMessageCount |
| 105 | + ) |
| 106 | + external |
| 107 | + onlySequencerInbox |
| 108 | + returns ( |
| 109 | + uint256 seqMessageIndex, |
| 110 | + bytes32 beforeAcc, |
| 111 | + bytes32 delayedAcc, |
| 112 | + bytes32 acc |
| 113 | + ) |
| 114 | + { |
| 115 | + if ( |
| 116 | + sequencerReportedSubMessageCount != prevMessageCount && |
| 117 | + prevMessageCount != 0 && |
| 118 | + sequencerReportedSubMessageCount != 0 |
| 119 | + ) { |
| 120 | + revert BadSequencerMessageNumber(sequencerReportedSubMessageCount, prevMessageCount); |
| 121 | + } |
| 122 | + sequencerReportedSubMessageCount = newMessageCount; |
| 123 | + seqMessageIndex = sequencerInboxAccs.length; |
| 124 | + if (sequencerInboxAccs.length > 0) { |
| 125 | + beforeAcc = sequencerInboxAccs[sequencerInboxAccs.length - 1]; |
| 126 | + } |
| 127 | + if (afterDelayedMessagesRead > 0) { |
| 128 | + delayedAcc = delayedInboxAccs[afterDelayedMessagesRead - 1]; |
| 129 | + } |
| 130 | + acc = keccak256(abi.encodePacked(beforeAcc, dataHash, delayedAcc)); |
| 131 | + sequencerInboxAccs.push(acc); |
| 132 | + } |
| 133 | + |
| 134 | + /// @inheritdoc IBridge |
| 135 | + function submitBatchSpendingReport(address sender, bytes32 messageDataHash) |
| 136 | + external |
| 137 | + onlySequencerInbox |
| 138 | + returns (uint256) |
| 139 | + { |
| 140 | + return |
| 141 | + addMessageToDelayedAccumulator( |
| 142 | + L1MessageType_batchPostingReport, |
| 143 | + sender, |
| 144 | + uint64(block.number), |
| 145 | + uint64(block.timestamp), // solhint-disable-line not-rely-on-time, |
| 146 | + block.basefee, |
| 147 | + messageDataHash |
| 148 | + ); |
| 149 | + } |
| 150 | + |
| 151 | + /// @inheritdoc IBridge |
| 152 | + function enqueueDelayedMessage( |
| 153 | + uint8 kind, |
| 154 | + address sender, |
| 155 | + bytes32 messageDataHash |
| 156 | + ) external payable returns (uint256) { |
| 157 | + if (!allowedDelayedInboxesMap[msg.sender].allowed) revert NotDelayedInbox(msg.sender); |
| 158 | + return |
| 159 | + addMessageToDelayedAccumulator( |
| 160 | + kind, |
| 161 | + sender, |
| 162 | + uint64(block.number), |
| 163 | + uint64(block.timestamp), // solhint-disable-line not-rely-on-time |
| 164 | + block.basefee, |
| 165 | + messageDataHash |
| 166 | + ); |
| 167 | + } |
| 168 | + |
| 169 | + function addMessageToDelayedAccumulator( |
| 170 | + uint8 kind, |
| 171 | + address sender, |
| 172 | + uint64 blockNumber, |
| 173 | + uint64 blockTimestamp, |
| 174 | + uint256 baseFeeL1, |
| 175 | + bytes32 messageDataHash |
| 176 | + ) internal returns (uint256) { |
| 177 | + uint256 count = delayedInboxAccs.length; |
| 178 | + bytes32 messageHash = Messages.messageHash( |
| 179 | + kind, |
| 180 | + sender, |
| 181 | + blockNumber, |
| 182 | + blockTimestamp, |
| 183 | + count, |
| 184 | + baseFeeL1, |
| 185 | + messageDataHash |
| 186 | + ); |
| 187 | + bytes32 prevAcc = 0; |
| 188 | + if (count > 0) { |
| 189 | + prevAcc = delayedInboxAccs[count - 1]; |
| 190 | + } |
| 191 | + delayedInboxAccs.push(Messages.accumulateInboxMessage(prevAcc, messageHash)); |
| 192 | + emit MessageDelivered( |
| 193 | + count, |
| 194 | + prevAcc, |
| 195 | + msg.sender, |
| 196 | + kind, |
| 197 | + sender, |
| 198 | + messageDataHash, |
| 199 | + baseFeeL1, |
| 200 | + blockTimestamp |
| 201 | + ); |
| 202 | + return count; |
| 203 | + } |
| 204 | + |
| 205 | + function executeCall( |
| 206 | + address to, |
| 207 | + uint256 value, |
| 208 | + bytes calldata data |
| 209 | + ) external returns (bool success, bytes memory returnData) { |
| 210 | + if (!allowedOutboxesMap[msg.sender].allowed) revert NotOutbox(msg.sender); |
| 211 | + if (data.length > 0 && !to.isContract()) revert NotContract(to); |
| 212 | + address prevOutbox = _activeOutbox; |
| 213 | + _activeOutbox = msg.sender; |
| 214 | + // We set and reset active outbox around external call so activeOutbox remains valid during call |
| 215 | + |
| 216 | + // We use a low level call here since we want to bubble up whether it succeeded or failed to the caller |
| 217 | + // rather than reverting on failure as well as allow contract and non-contract calls |
| 218 | + // solhint-disable-next-line avoid-low-level-calls |
| 219 | + (success, returnData) = to.call{value: value}(data); |
| 220 | + _activeOutbox = prevOutbox; |
| 221 | + emit BridgeCallTriggered(msg.sender, to, value, data); |
| 222 | + } |
| 223 | + |
| 224 | + function setSequencerInbox(address _sequencerInbox) external onlyRollupOrOwner { |
| 225 | + sequencerInbox = _sequencerInbox; |
| 226 | + emit SequencerInboxUpdated(_sequencerInbox); |
| 227 | + } |
| 228 | + |
| 229 | + function setDelayedInbox(address inbox, bool enabled) external onlyRollupOrOwner { |
| 230 | + InOutInfo storage info = allowedDelayedInboxesMap[inbox]; |
| 231 | + bool alreadyEnabled = info.allowed; |
| 232 | + emit InboxToggle(inbox, enabled); |
| 233 | + if (alreadyEnabled == enabled) { |
| 234 | + return; |
| 235 | + } |
| 236 | + if (enabled) { |
| 237 | + allowedDelayedInboxesMap[inbox] = InOutInfo(allowedDelayedInboxList.length, true); |
| 238 | + allowedDelayedInboxList.push(inbox); |
| 239 | + } else { |
| 240 | + allowedDelayedInboxList[info.index] = allowedDelayedInboxList[ |
| 241 | + allowedDelayedInboxList.length - 1 |
| 242 | + ]; |
| 243 | + allowedDelayedInboxesMap[allowedDelayedInboxList[info.index]].index = info.index; |
| 244 | + allowedDelayedInboxList.pop(); |
| 245 | + delete allowedDelayedInboxesMap[inbox]; |
| 246 | + } |
| 247 | + } |
| 248 | + |
| 249 | + function setOutbox(address outbox, bool enabled) external onlyRollupOrOwner { |
| 250 | + if (outbox == EMPTY_ACTIVEOUTBOX) revert InvalidOutboxSet(outbox); |
| 251 | + |
| 252 | + InOutInfo storage info = allowedOutboxesMap[outbox]; |
| 253 | + bool alreadyEnabled = info.allowed; |
| 254 | + emit OutboxToggle(outbox, enabled); |
| 255 | + if (alreadyEnabled == enabled) { |
| 256 | + return; |
| 257 | + } |
| 258 | + if (enabled) { |
| 259 | + allowedOutboxesMap[outbox] = InOutInfo(allowedOutboxList.length, true); |
| 260 | + allowedOutboxList.push(outbox); |
| 261 | + } else { |
| 262 | + allowedOutboxList[info.index] = allowedOutboxList[allowedOutboxList.length - 1]; |
| 263 | + allowedOutboxesMap[allowedOutboxList[info.index]].index = info.index; |
| 264 | + allowedOutboxList.pop(); |
| 265 | + delete allowedOutboxesMap[outbox]; |
| 266 | + } |
| 267 | + } |
| 268 | + |
| 269 | + function setSequencerReportedSubMessageCount(uint256 newMsgCount) external onlyRollupOrOwner { |
| 270 | + sequencerReportedSubMessageCount = newMsgCount; |
| 271 | + } |
| 272 | + |
| 273 | + function delayedMessageCount() external view override returns (uint256) { |
| 274 | + return delayedInboxAccs.length; |
| 275 | + } |
| 276 | + |
| 277 | + function sequencerMessageCount() external view returns (uint256) { |
| 278 | + return sequencerInboxAccs.length; |
| 279 | + } |
| 280 | + |
| 281 | + /// @dev For the classic -> nitro migration. TODO: remove post-migration. |
| 282 | + function acceptFundsFromOldBridge() external payable {} |
| 283 | +} |
0 commit comments