diff --git a/.prettierrc.js b/.prettierrc.js index b08bd49d3..5392936bc 100644 --- a/.prettierrc.js +++ b/.prettierrc.js @@ -14,7 +14,7 @@ module.exports = { printWidth: 100, singleQuote: false, bracketSpacing: false, - compiler: '0.8.6', + compiler: '0.8.9', }, }, ], diff --git a/LICENSE b/LICENSE.md similarity index 80% rename from LICENSE rename to LICENSE.md index 56b5f0425..a4e5a7f61 100644 --- a/LICENSE +++ b/LICENSE.md @@ -16,17 +16,19 @@ Additional Use Grant: You may use the Licensed Work in a production environment to provide a point of interface to permit end users or applications utilizing the Covered Arbitrum Chains to interact and query the state of a Covered Arbitrum Chain, including without limitation - validating the correctness of the posted chain state. For purposes - of this Additional Use Grant, the "Covered Arbitrum Chains" are - means (a) Arbitrum One (chainid:42161), Arbitrum Nova (chainid:42170), - Arbitrum Rinkeby testnet/Rinkarby (chainid:421611), and - Arbitrum Nitro Goerli testnet (chainid:421613) (b) any future - blockchains authorized to be designated as Covered Arbitrum Chains - by the decentralized autonomous organization governing the Arbitrum - network; and (c) any “Layer 3” Arbitrum-based blockchain that is built - on and settles to another Covered Arbitrum Chain. - - + validating the correctness of the posted chain state, or to deploy + and operate (x) a blockchain that settles to a Covered Arbitrum Chain + or (y) a blockchain in accordance with, and subject to, the [Arbitrum + Expansion Program Term of Use](https://docs.arbitrum.foundation/assets/files/Arbitrum%20Expansion%20Program%20Jan182024-4f08b0c2cb476a55dc153380fa3e64b0.pdf). For purposes of this + Additional Use Grant, the "Covered Arbitrum Chains" are + (a) Arbitrum One (chainid:42161), Arbitrum Nova (chainid:42170), + rbitrum Rinkeby testnet/Rinkarby (chainid:421611),Arbitrum Nitro + Goerli testnet (chainid:421613), and Arbitrum Sepolia Testnet + (chainid:421614); (b) any future blockchains authorized to be + designated as Covered Arbitrum Chains by the decentralized autonomous + organization governing the Arbitrum network; and (c) any “Layer 3” + Arbitrum-based blockchain that is built on and settles to another + Covered Arbitrum Chain. diff --git a/README.md b/README.md index 8fc3d98cc..9d4812d7f 100644 --- a/README.md +++ b/README.md @@ -17,3 +17,19 @@ cd nitro-contracts yarn install yarn build ``` + +## License + +Nitro is currently licensed under a [Business Source License](./LICENSE), similar to our friends at Uniswap and Aave, with an "Additional Use Grant" to ensure that everyone can have full comfort using and running nodes on all public Arbitrum chains. + +The Additional Use Grant also permits the deployment of the Nitro software, in a permissionless fashion and without cost, as a new blockchain provided that the chain settles to either Arbitrum One or Arbitrum Nova. + +For those that prefer to deploy the Nitro software either directly on Ethereum (i.e. an L2) or have it settle to another Layer-2 on top of Ethereum, the [Arbitrum Expansion Program (the "AEP")](https://docs.arbitrum.foundation/assets/files/Arbitrum%20Expansion%20Program%20Jan182024-4f08b0c2cb476a55dc153380fa3e64b0.pdf) was recently established. The AEP allows for the permissionless deployment in the aforementioned fashion provided that 10% of net revenue is contributed back to the Arbitrum community in accordance with the requirements of the AEP. + +## Contact + +Discord - [Arbitrum](https://discord.com/invite/5KE54JwyTs) + +Twitter: [Arbitrum](https://twitter.com/arbitrum) + + diff --git a/deploy/BridgeStubCreator.js b/deploy/BridgeStubCreator.js index 9fe34dde4..99e524af0 100644 --- a/deploy/BridgeStubCreator.js +++ b/deploy/BridgeStubCreator.js @@ -3,7 +3,7 @@ module.exports = async hre => { const { deploy } = deployments const { deployer } = await getNamedAccounts() - await deploy('BridgeStub', { from: deployer, args: [] }) + await deploy('BridgeStub', { from: deployer, args: [deployer] }) } module.exports.tags = ['BridgeStub', 'test'] diff --git a/deploy/SequencerInboxStubCreator.js b/deploy/SequencerInboxStubCreator.js index b0adea5e5..e61a227ca 100644 --- a/deploy/SequencerInboxStubCreator.js +++ b/deploy/SequencerInboxStubCreator.js @@ -1,11 +1,14 @@ +import { Toolkit4844 } from '../test/contract/toolkit4844' + module.exports = async hre => { - const { deployments, getNamedAccounts, ethers } = hre + const { deployments, getSigners, getNamedAccounts, ethers } = hre const { deploy } = deployments const { deployer } = await getNamedAccounts() const bridge = await ethers.getContract('BridgeStub') - const blobBasefeeReader = await ethers.getContract('BlobBasefeeReader') - const dataHashReader = await ethers.getContract('DataHashReader') + const reader4844 = await Toolkit4844.deployReader4844( + await ethers.getSigner(deployer) + ) const maxTime = { delayBlocks: 10000, futureBlocks: 10000, @@ -19,8 +22,8 @@ module.exports = async hre => { deployer, maxTime, 117964, - dataHashReader, - blobBasefeeReader, + reader4844.address, + false, ], }) } diff --git a/package.json b/package.json index 9af3259af..96781783a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "build:all": "yarn build && yarn build:forge", "build": "hardhat compile", "build:forge:sol": "forge build --skip *.yul", - "build:forge:yul": "FOUNDRY_PROFILE=yul forge build --skip *.sol --skip *BlobBasefeeReader.yul && FOUNDRY_PROFILE=yul forge build --skip *.sol --skip *DataHashesReader.yul", + "build:forge:yul": "FOUNDRY_PROFILE=yul forge build --skip *.sol", "build:forge": "yarn build:forge:sol && yarn build:forge:yul", "lint:test": "eslint ./test", "solhint": "solhint -f table src/**/*.sol", @@ -36,6 +36,7 @@ "test:e2e": "hardhat test test/e2e/*.ts", "postinstall": "patch-package", "deploy-factory": "hardhat run scripts/deployment.ts", + "deploy-rollup": "hardhat run scripts/rollupCreation.ts", "deploy-eth-rollup": "hardhat run scripts/createEthRollup.ts", "deploy-erc20-rollup": "hardhat run scripts/createERC20Rollup.ts" }, diff --git a/scripts/config.ts.example b/scripts/config.ts.example index cf5d8704c..cb557aa1c 100644 --- a/scripts/config.ts.example +++ b/scripts/config.ts.example @@ -19,10 +19,10 @@ export const config = { '{"chainId":13331370,"homesteadBlock":0,"daoForkBlock":null,"daoForkSupport":true,"eip150Block":0,"eip150Hash":"0x0000000000000000000000000000000000000000000000000000000000000000","eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"muirGlacierBlock":0,"berlinBlock":0,"londonBlock":0,"clique":{"period":0,"epoch":0},"arbitrum":{"EnableArbOS":true,"AllowDebugPrecompiles":false,"DataAvailabilityCommittee":false,"InitialArbOSVersion":10,"InitialChainOwner":"0x1234123412341234123412341234123412341234","GenesisBlockNum":0}}', genesisBlockNum: ethers.BigNumber.from('0'), sequencerInboxMaxTimeVariation: { - delayBlocks: ethers.BigNumber.from('5760'), - futureBlocks: ethers.BigNumber.from('12'), - delaySeconds: ethers.BigNumber.from('86400'), - futureSeconds: ethers.BigNumber.from('3600'), + delayBlocks: 5760, + futureBlocks: 12, + delaySeconds: 86400, + futureSeconds: 3600, }, }, validators: [ diff --git a/scripts/deployment.ts b/scripts/deployment.ts index 93c091142..01162c552 100644 --- a/scripts/deployment.ts +++ b/scripts/deployment.ts @@ -81,6 +81,7 @@ async function deployAllContracts( const ethBridge = await deployContract('Bridge', signer, []) const ethSequencerInbox = await deployContract('SequencerInbox', signer, [ maxDataSize, + false, ]) const ethInbox = await deployContract('Inbox', signer, [maxDataSize]) const ethRollupEventInbox = await deployContract( @@ -91,7 +92,10 @@ async function deployAllContracts( const ethOutbox = await deployContract('Outbox', signer, []) const erc20Bridge = await deployContract('ERC20Bridge', signer, []) - const erc20SequencerInbox = ethSequencerInbox + const erc20SequencerInbox = await deployContract('SequencerInbox', signer, [ + maxDataSize, + true, + ]) const erc20Inbox = await deployContract('ERC20Inbox', signer, [maxDataSize]) const erc20RollupEventInbox = await deployContract( 'ERC20RollupEventInbox', diff --git a/scripts/testSetup.ts b/scripts/testSetup.ts index 2c11e7694..73f0f4fe9 100644 --- a/scripts/testSetup.ts +++ b/scripts/testSetup.ts @@ -30,6 +30,8 @@ export const getCustomNetworks = async ( `docker exec ${sequencerContainer} cat /config/deployment.json` ).toString() + console.log('deploymentData', deploymentData) + const parsedDeploymentData = JSON.parse(deploymentData) as { bridge: string inbox: string diff --git a/src/bridge/AbsBridge.sol b/src/bridge/AbsBridge.sol index bafd56f95..471c4a2bd 100644 --- a/src/bridge/AbsBridge.sol +++ b/src/bridge/AbsBridge.sol @@ -14,11 +14,15 @@ import { NotSequencerInbox, NotOutbox, InvalidOutboxSet, - BadSequencerMessageNumber + BadSequencerMessageNumber, + EmptyDelayedMessagesRead, + DelayedBackwards, + DelayedTooFar } from "../libraries/Error.sol"; import "./IBridge.sol"; import "./Messages.sol"; import "../libraries/DelegateCallAware.sol"; +import "./ISequencerInbox.sol"; import {L1MessageType_batchPostingReport} from "../libraries/MessageTypes.sol"; @@ -55,8 +59,15 @@ abstract contract AbsBridge is Initializable, DelegateCallAware, IBridge { uint256 public override sequencerReportedSubMessageCount; + uint256 public totalDelayedMessagesRead; + address internal constant EMPTY_ACTIVEOUTBOX = address(type(uint160).max); + function postUpgradeInit() external onlyDelegated onlyProxyOwner { + totalDelayedMessagesRead = ISequencerInbox(sequencerInbox).totalDelayedMessagesRead(); + if (totalDelayedMessagesRead == 0) revert EmptyDelayedMessagesRead(); + } + modifier onlyRollupOrOwner() { if (msg.sender != address(rollup)) { address rollupOwner = rollup.owner(); @@ -101,7 +112,9 @@ abstract contract AbsBridge is Initializable, DelegateCallAware, IBridge { bytes32 dataHash, uint256 afterDelayedMessagesRead, uint256 prevMessageCount, - uint256 newMessageCount + uint256 newMessageCount, + TimeBounds memory timeBounds, + BatchDataLocation batchDataLocation ) external onlySequencerInbox @@ -119,6 +132,9 @@ abstract contract AbsBridge is Initializable, DelegateCallAware, IBridge { ) { revert BadSequencerMessageNumber(sequencerReportedSubMessageCount, prevMessageCount); } + if (afterDelayedMessagesRead > delayedInboxAccs.length) revert DelayedTooFar(); + if (afterDelayedMessagesRead < totalDelayedMessagesRead) revert DelayedBackwards(); + sequencerReportedSubMessageCount = newMessageCount; seqMessageIndex = sequencerInboxAccs.length; if (sequencerInboxAccs.length > 0) { @@ -129,6 +145,18 @@ abstract contract AbsBridge is Initializable, DelegateCallAware, IBridge { } acc = keccak256(abi.encodePacked(beforeAcc, dataHash, delayedAcc)); sequencerInboxAccs.push(acc); + totalDelayedMessagesRead = afterDelayedMessagesRead; + + emit SequencerBatchDelivered( + seqMessageIndex, + beforeAcc, + acc, + delayedAcc, + afterDelayedMessagesRead, + timeBounds, + batchDataLocation, + msg.sender + ); } /// @inheritdoc IBridge @@ -304,5 +332,5 @@ abstract contract AbsBridge is Initializable, DelegateCallAware, IBridge { * variables without shifting down storage in the inheritance chain. * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ - uint256[40] private __gap; + uint256[39] private __gap; } diff --git a/src/bridge/AbsOutbox.sol b/src/bridge/AbsOutbox.sol index 4dabf9ff7..c73f4213c 100644 --- a/src/bridge/AbsOutbox.sol +++ b/src/bridge/AbsOutbox.sol @@ -76,6 +76,7 @@ abstract contract AbsOutbox is DelegateCallAware, IOutbox { }); bridge = _bridge; rollup = address(_bridge.rollup()); + if (address(rollup) == address(0)) revert RollupNotChanged(); } function postUpgradeInit() external onlyDelegated onlyProxyOwner { diff --git a/src/bridge/IBridge.sol b/src/bridge/IBridge.sol index 1137fcd36..5c1ef4775 100644 --- a/src/bridge/IBridge.sol +++ b/src/bridge/IBridge.sol @@ -4,6 +4,7 @@ // solhint-disable-next-line compiler-version pragma solidity >=0.6.9 <0.9.0; +pragma experimental ABIEncoderV2; import "./IOwnable.sol"; @@ -48,6 +49,17 @@ interface IBridge { bytes data ); + event SequencerBatchDelivered( + uint256 indexed batchSequenceNumber, + bytes32 indexed beforeAcc, + bytes32 indexed afterAcc, + bytes32 delayedAcc, + uint256 afterDelayedMessagesRead, + TimeBounds timeBounds, + BatchDataLocation dataLocation, + address sequencerInbox + ); + event InboxToggle(address indexed inbox, bool enabled); event OutboxToggle(address indexed outbox, bool enabled); @@ -88,13 +100,17 @@ interface IBridge { function sequencerMessageCount() external view returns (uint256); + function totalDelayedMessagesRead() external view returns (uint256); + // ---------- onlySequencerInbox functions ---------- function enqueueSequencerMessage( bytes32 dataHash, uint256 afterDelayedMessagesRead, uint256 prevMessageCount, - uint256 newMessageCount + uint256 newMessageCount, + TimeBounds memory timeBounds, + BatchDataLocation dataLocation ) external returns ( diff --git a/src/bridge/ISequencerInbox.sol b/src/bridge/ISequencerInbox.sol index 077729a93..e95c64117 100644 --- a/src/bridge/ISequencerInbox.sol +++ b/src/bridge/ISequencerInbox.sol @@ -11,6 +11,11 @@ import "./IDelayedMessageProvider.sol"; import "./IBridge.sol"; interface ISequencerInbox is IDelayedMessageProvider { + /// @notice The maximum amount of time variatin between a message being posted on the L1 and being executed on the L2 + /// @param delayBlocks The max amount of blocks in the past that a message can be received on L2 + /// @param futureBlocks The max amount of blocks in the future that a message can be received on L2 + /// @param delaySeconds The max amount of seconds in the past that a message can be received on L2 + /// @param futureSeconds The max amount of seconds in the future that a message can be received on L2 struct MaxTimeVariation { uint64 delayBlocks; uint64 futureBlocks; @@ -18,16 +23,6 @@ interface ISequencerInbox is IDelayedMessageProvider { uint64 futureSeconds; } - event SequencerBatchDelivered( - uint256 indexed batchSequenceNumber, - bytes32 indexed beforeAcc, - bytes32 indexed afterAcc, - bytes32 delayedAcc, - uint256 afterDelayedMessagesRead, - IBridge.TimeBounds timeBounds, - IBridge.BatchDataLocation dataLocation - ); - event OwnerFunctionCalled(uint256 indexed id); /// @dev a separate event that emits batch data when this isn't easily accessible in the tx.input @@ -39,6 +34,8 @@ interface ISequencerInbox is IDelayedMessageProvider { /// @dev a keyset was invalidated event InvalidateKeyset(bytes32 indexed keysetHash); + /// @notice The total number of delated messages read in the bridge + /// @dev We surface this here for backwards compatibility function totalDelayedMessagesRead() external view returns (uint256); function bridge() external view returns (IBridge); @@ -47,6 +44,12 @@ interface ISequencerInbox is IDelayedMessageProvider { // solhint-disable-next-line func-name-mixedcase function HEADER_LENGTH() external view returns (uint256); + /// @dev If the first batch data byte after the header has this bit set, + /// the sequencer inbox has authenticated the data. Currently only used for 4844 blob support. + /// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go + // solhint-disable-next-line func-name-mixedcase + function DATA_AUTHENTICATED_FLAG() external view returns (bytes1); + /// @dev If the first data byte after the header has this bit set, /// then the batch data is to be found in 4844 data blobs /// See: https://github.com/OffchainLabs/nitro/blob/69de0603abf6f900a4128cab7933df60cad54ded/arbstate/das_reader.go @@ -85,6 +88,10 @@ interface ISequencerInbox is IDelayedMessageProvider { function maxDataSize() external view returns (uint256); + /// @notice The batch poster manager has the ability to change the batch poster addresses + /// This enables the batch poster to do key rotation + function batchPosterManager() external view returns (address); + struct DasKeySetInfo { bool isValidKeyset; uint64 creationBlock; @@ -103,9 +110,6 @@ interface ISequencerInbox is IDelayedMessageProvider { function dasKeySetInfo(bytes32) external view returns (bool, uint64); - /// @notice Remove force inclusion delay after a L1 chainId fork - function removeDelayAfterFork() external; - /// @notice Force messages from the delayed inbox to be included in the chain /// Callable by any address, but message can only be force-included after maxTimeVariation.delayBlocks and /// maxTimeVariation.delaySeconds has elapsed. As part of normal behaviour the sequencer will include these @@ -140,7 +144,9 @@ interface ISequencerInbox is IDelayedMessageProvider { uint256 sequenceNumber, bytes calldata data, uint256 afterDelayedMessagesRead, - IGasRefunder gasRefunder + IGasRefunder gasRefunder, + uint256 prevMessageCount, + uint256 newMessageCount ) external; function addSequencerL2Batch( @@ -152,13 +158,15 @@ interface ISequencerInbox is IDelayedMessageProvider { uint256 newMessageCount ) external; - // ---------- onlyRollupOrOwner functions ---------- + function addSequencerL2BatchFromBlobs( + uint256 sequenceNumber, + uint256 afterDelayedMessagesRead, + IGasRefunder gasRefunder, + uint256 prevMessageCount, + uint256 newMessageCount + ) external; - /** - * @notice Set max delay for sequencer inbox - * @param maxTimeVariation_ the maximum time variation parameters - */ - function setMaxTimeVariation(MaxTimeVariation memory maxTimeVariation_) external; + // ---------- onlyRollupOrOwner functions ---------- /** * @notice Updates whether an address is authorized to be a batch poster at the sequencer inbox @@ -187,19 +195,12 @@ interface ISequencerInbox is IDelayedMessageProvider { */ function setIsSequencer(address addr, bool isSequencer_) external; - // ---------- initializer ---------- - - function initialize(IBridge bridge_, MaxTimeVariation calldata maxTimeVariation_) external; + /** + * @notice Updates the batch poster manager, the address which has the ability to rotate batch poster keys + * @param newBatchPosterManager The new batch poster manager to be set + */ + function setBatchPosterManager(address newBatchPosterManager) external; + /// @notice Allows the rollup owner to sync the rollup address function updateRollupAddress() external; } - -interface IDataHashReader { - /// @notice Returns all the data hashes of all the blobs on the current transaction - function getDataHashes() external view returns (bytes32[] memory); -} - -interface IBlobBasefeeReader { - /// @notice Returns the current BLOBBASEFEE - function getBlobBaseFee() external view returns (uint256); -} diff --git a/src/bridge/SequencerInbox.sol b/src/bridge/SequencerInbox.sol index 7cecec64b..65c2760a2 100644 --- a/src/bridge/SequencerInbox.sol +++ b/src/bridge/SequencerInbox.sol @@ -7,7 +7,6 @@ pragma solidity ^0.8.0; import { AlreadyInit, HadZeroInit, - BadPostUpgradeInit, NotOrigin, DataTooLarge, NotRollup, @@ -22,6 +21,8 @@ import { AlreadyValidDASKeyset, NoSuchKeyset, NotForked, + NotBatchPosterManager, + RollupNotChanged, DataBlobsNotSupported, InitParamZero, MissingDataHashes, @@ -29,7 +30,9 @@ import { NotOwner, RollupNotChanged, EmptyBatchData, - InvalidHeaderFlag + InvalidHeaderFlag, + NativeTokenMismatch, + Deprecated } from "../libraries/Error.sol"; import "./IBridge.sol"; import "./IInboxBase.sol"; @@ -38,11 +41,14 @@ import "../rollup/IRollupLogic.sol"; import "./Messages.sol"; import "../precompiles/ArbGasInfo.sol"; import "../precompiles/ArbSys.sol"; +import "../libraries/IReader4844.sol"; import {L1MessageType_batchPostingReport} from "../libraries/MessageTypes.sol"; -import {GasRefundEnabled, IGasRefunder} from "../libraries/IGasRefunder.sol"; import "../libraries/DelegateCallAware.sol"; +import {IGasRefunder} from "../libraries/IGasRefunder.sol"; +import {GasRefundEnabled} from "../libraries/GasRefundEnabled.sol"; import "../libraries/ArbitrumChecker.sol"; +import {IERC20Bridge} from "./IERC20Bridge.sol"; /** * @title Accepts batches from the sequencer and adds them to the rollup inbox. @@ -51,16 +57,17 @@ import "../libraries/ArbitrumChecker.sol"; * 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; +contract SequencerInbox is GasRefundEnabled, ISequencerInbox { + IBridge public immutable bridge; /// @inheritdoc ISequencerInbox uint256 public constant HEADER_LENGTH = 40; /// @inheritdoc ISequencerInbox - bytes1 public constant DATA_BLOB_HEADER_FLAG = 0x40; + bytes1 public constant DATA_AUTHENTICATED_FLAG = 0x40; + + /// @inheritdoc ISequencerInbox + bytes1 public constant DATA_BLOB_HEADER_FLAG = DATA_AUTHENTICATED_FLAG | 0x10; /// @inheritdoc ISequencerInbox bytes1 public constant DAS_MESSAGE_HEADER_FLAG = 0x80; @@ -74,11 +81,17 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox /// @inheritdoc ISequencerInbox bytes1 public constant ZERO_HEAVY_MESSAGE_HEADER_FLAG = 0x20; + // GAS_PER_BLOB from EIP-4844 + uint256 internal constant GAS_PER_BLOB = 1 << 17; + IOwnable public rollup; - mapping(address => bool) public isBatchPoster; - // we previously stored the max time variation in a (uint,uint,uint,uint) struct here - uint256[4] private __LEGACY_MAX_TIME_VARIATION; + mapping(address => bool) public isBatchPoster; + // see ISequencerInbox.MaxTimeVariation + uint64 internal immutable delayBlocks; + uint64 internal immutable futureBlocks; + uint64 internal immutable delaySeconds; + uint64 internal immutable futureSeconds; mapping(bytes32 => DasKeySetInfo) public dasKeySetInfo; @@ -87,93 +100,57 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox _; } + modifier onlyRollupOwnerOrBatchPosterManager() { + if (msg.sender != rollup.owner() && msg.sender != batchPosterManager) { + revert NotBatchPosterManager(msg.sender); + } + _; + } + mapping(address => bool) public isSequencer; - IDataHashReader immutable dataHashReader; - IBlobBasefeeReader immutable blobBasefeeReader; + IReader4844 public immutable reader4844; - // see ISequencerInbox.MaxTimeVariation - uint64 internal delayBlocks; - uint64 internal futureBlocks; - uint64 internal delaySeconds; - uint64 internal futureSeconds; + /// @inheritdoc ISequencerInbox + address public batchPosterManager; // On L1 this should be set to 117964: 90% of Geth's 128KB tx size limit, leaving ~13KB for proving uint256 public immutable maxDataSize; uint256 internal immutable deployTimeChainId = block.chainid; // If the chain this SequencerInbox is deployed on is an Arbitrum chain. bool internal immutable hostChainIsArbitrum = ArbitrumChecker.runningOnArbitrum(); + // True if the chain this SequencerInbox is deployed on uses custom fee token + bool public immutable isUsingFeeToken; constructor( + IBridge bridge_, + ISequencerInbox.MaxTimeVariation memory maxTimeVariation_, uint256 _maxDataSize, - IDataHashReader dataHashReader_, - IBlobBasefeeReader blobBasefeeReader_ + IReader4844 reader4844_, + bool _isUsingFeeToken ) { + if (bridge_ == IBridge(address(0))) revert HadZeroInit(); + bridge = bridge_; + rollup = bridge_.rollup(); + if (address(rollup) == address(0)) revert RollupNotChanged(); + delayBlocks = maxTimeVariation_.delayBlocks; + futureBlocks = maxTimeVariation_.futureBlocks; + delaySeconds = maxTimeVariation_.delaySeconds; + futureSeconds = maxTimeVariation_.futureSeconds; maxDataSize = _maxDataSize; if (hostChainIsArbitrum) { - if (dataHashReader_ != IDataHashReader(address(0))) revert DataBlobsNotSupported(); - if (blobBasefeeReader_ != IBlobBasefeeReader(address(0))) - revert DataBlobsNotSupported(); + if (reader4844_ != IReader4844(address(0))) revert DataBlobsNotSupported(); } else { - if (dataHashReader_ == IDataHashReader(address(0))) - revert InitParamZero("DataHashReader"); - if (blobBasefeeReader_ == IBlobBasefeeReader(address(0))) - revert InitParamZero("BlobBasefeeReader"); + if (reader4844_ == IReader4844(address(0))) revert InitParamZero("Reader4844"); } - dataHashReader = dataHashReader_; - blobBasefeeReader = blobBasefeeReader_; + reader4844 = reader4844_; + isUsingFeeToken = _isUsingFeeToken; } function _chainIdChanged() internal view returns (bool) { return deployTimeChainId != block.chainid; } - function postUpgradeInit() external onlyDelegated onlyProxyOwner { - // Assuming we would not upgrade from a version that have MaxTimeVariation all set to zero - // If that is the case, postUpgradeInit do not need to be called - if ( - __LEGACY_MAX_TIME_VARIATION[0] == 0 && - __LEGACY_MAX_TIME_VARIATION[1] == 0 && - __LEGACY_MAX_TIME_VARIATION[2] == 0 && - __LEGACY_MAX_TIME_VARIATION[3] == 0 - ) { - revert AlreadyInit(); - } - - if ( - __LEGACY_MAX_TIME_VARIATION[0] > type(uint64).max || - __LEGACY_MAX_TIME_VARIATION[1] > type(uint64).max || - __LEGACY_MAX_TIME_VARIATION[2] > type(uint64).max || - __LEGACY_MAX_TIME_VARIATION[3] > type(uint64).max - ) { - revert BadPostUpgradeInit(); - } - - delayBlocks = uint64(__LEGACY_MAX_TIME_VARIATION[0]); - futureBlocks = uint64(__LEGACY_MAX_TIME_VARIATION[1]); - delaySeconds = uint64(__LEGACY_MAX_TIME_VARIATION[2]); - futureSeconds = uint64(__LEGACY_MAX_TIME_VARIATION[3]); - - __LEGACY_MAX_TIME_VARIATION[0] = 0; - __LEGACY_MAX_TIME_VARIATION[1] = 0; - __LEGACY_MAX_TIME_VARIATION[2] = 0; - __LEGACY_MAX_TIME_VARIATION[3] = 0; - } - - 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(); - delayBlocks = maxTimeVariation_.delayBlocks; - futureBlocks = maxTimeVariation_.futureBlocks; - delaySeconds = maxTimeVariation_.delaySeconds; - futureSeconds = maxTimeVariation_.futureSeconds; - } - - /// @notice Allows the rollup owner to sync the rollup address + /// @inheritdoc ISequencerInbox function updateRollupAddress() external { if (msg.sender != IOwnable(rollup).owner()) revert NotOwner(msg.sender, IOwnable(rollup).owner()); @@ -182,6 +159,11 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox rollup = newRollup; } + /// @inheritdoc ISequencerInbox + function totalDelayedMessagesRead() public view returns (uint256) { + return bridge.totalDelayedMessagesRead(); + } + function getTimeBounds() internal view virtual returns (IBridge.TimeBounds memory) { IBridge.TimeBounds memory bounds; ( @@ -201,15 +183,6 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox return bounds; } - /// @inheritdoc ISequencerInbox - function removeDelayAfterFork() external { - if (!_chainIdChanged()) revert NotForked(); - delayBlocks = 1; - futureBlocks = 1; - delaySeconds = 1; - futureSeconds = 1; - } - function maxTimeVariation() external view @@ -261,7 +234,7 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox address sender, bytes32 messageDataHash ) external { - if (_totalDelayedMessagesRead <= totalDelayedMessagesRead) revert DelayedBackwards(); + if (_totalDelayedMessagesRead <= totalDelayedMessagesRead()) revert DelayedBackwards(); bytes32 messageHash = Messages.messageHash( kind, sender, @@ -271,192 +244,119 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox baseFeeL1, messageDataHash ); + (uint256 delayBlocks_, , uint256 delaySeconds_, ) = maxTimeVariationInternal(); // Can only force-include after the Sequencer-only window has expired. - if (l1BlockAndTime[0] + delayBlocks >= block.number) revert ForceIncludeBlockTooSoon(); - if (l1BlockAndTime[1] + delaySeconds >= block.timestamp) revert ForceIncludeTimeTooSoon(); + if (l1BlockAndTime[0] + delayBlocks_ >= block.number) revert ForceIncludeBlockTooSoon(); + if (l1BlockAndTime[1] + delaySeconds_ >= block.timestamp) revert ForceIncludeTimeTooSoon(); // Verify that message hash represents the last message sequence of delayed message to be included - bytes32 prevDelayedAcc = 0; - if (_totalDelayedMessagesRead > 1) { - prevDelayedAcc = bridge.delayedInboxAccs(_totalDelayedMessagesRead - 2); + { + bytes32 prevDelayedAcc = 0; + if (_totalDelayedMessagesRead > 1) { + prevDelayedAcc = bridge.delayedInboxAccs(_totalDelayedMessagesRead - 2); + } + if ( + bridge.delayedInboxAccs(_totalDelayedMessagesRead - 1) != + Messages.accumulateInboxMessage(prevDelayedAcc, messageHash) + ) revert IncorrectMessagePreimage(); } - if ( - bridge.delayedInboxAccs(_totalDelayedMessagesRead - 1) != - Messages.accumulateInboxMessage(prevDelayedAcc, messageHash) - ) revert IncorrectMessagePreimage(); (bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formEmptyDataHash( _totalDelayedMessagesRead ); - uint256 __totalDelayedMessagesRead = _totalDelayedMessagesRead; uint256 prevSeqMsgCount = bridge.sequencerReportedSubMessageCount(); uint256 newSeqMsgCount = prevSeqMsgCount + _totalDelayedMessagesRead - - totalDelayedMessagesRead; - ( - uint256 seqMessageIndex, - bytes32 beforeAcc, - bytes32 delayedAcc, - bytes32 afterAcc - ) = addSequencerL2BatchImpl( - dataHash, - __totalDelayedMessagesRead, - 0, - prevSeqMsgCount, - newSeqMsgCount - ); - emit SequencerBatchDelivered( - seqMessageIndex, - beforeAcc, - afterAcc, - delayedAcc, - totalDelayedMessagesRead, + totalDelayedMessagesRead(); + + bridge.enqueueSequencerMessage( + dataHash, + _totalDelayedMessagesRead, + prevSeqMsgCount, + newSeqMsgCount, timeBounds, IBridge.BatchDataLocation.NoData ); } - /// @dev Deprecated in favor of the variant specifying message counts for consistency function addSequencerL2BatchFromOrigin( uint256 sequenceNumber, bytes calldata data, uint256 afterDelayedMessagesRead, - IGasRefunder gasRefunder - ) external refundsGas(gasRefunder) { + IGasRefunder gasRefunder, + uint256 prevMessageCount, + uint256 newMessageCount + ) external refundsGas(gasRefunder, IReader4844(address(0))) { // solhint-disable-next-line avoid-tx-origin if (msg.sender != tx.origin) revert NotOrigin(); if (!isBatchPoster[msg.sender]) revert NotBatchPoster(); - (bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formCallDataHash( data, afterDelayedMessagesRead ); - ( - uint256 seqMessageIndex, - bytes32 beforeAcc, - bytes32 delayedAcc, - bytes32 afterAcc - ) = addSequencerL2BatchImpl(dataHash, afterDelayedMessagesRead, data.length, 0, 0); - - // ~uint256(0) is type(uint256).max, but ever so slightly cheaper - if (seqMessageIndex != sequenceNumber && sequenceNumber != ~uint256(0)) { - revert BadSequencerNumber(seqMessageIndex, sequenceNumber); - } - emit SequencerBatchDelivered( - sequenceNumber, - beforeAcc, - afterAcc, - delayedAcc, - totalDelayedMessagesRead, + (uint256 seqMessageIndex, , , ) = bridge.enqueueSequencerMessage( + dataHash, + afterDelayedMessagesRead, + prevMessageCount, + newMessageCount, timeBounds, IBridge.BatchDataLocation.TxInput ); - } - - function addSequencerL2BatchFromOrigin( - uint256 sequenceNumber, - bytes calldata data, - uint256 afterDelayedMessagesRead, - IGasRefunder gasRefunder, - uint256 prevMessageCount, - uint256 newMessageCount - ) 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, IBridge.TimeBounds memory timeBounds) = formCallDataHash( - data, - afterDelayedMessagesRead - ); - // Reformat the stack to prevent "Stack too deep" - uint256 sequenceNumber_ = sequenceNumber; - IBridge.TimeBounds memory timeBounds_ = timeBounds; - bytes32 dataHash_ = dataHash; - uint256 dataLength = data.length; - uint256 afterDelayedMessagesRead_ = afterDelayedMessagesRead; - uint256 prevMessageCount_ = prevMessageCount; - uint256 newMessageCount_ = newMessageCount; - ( - uint256 seqMessageIndex, - bytes32 beforeAcc, - bytes32 delayedAcc, - bytes32 afterAcc - ) = addSequencerL2BatchImpl( - dataHash_, - afterDelayedMessagesRead_, - dataLength, - prevMessageCount_, - newMessageCount_ - ); // ~uint256(0) is type(uint256).max, but ever so slightly cheaper - if (seqMessageIndex != sequenceNumber_ && sequenceNumber_ != ~uint256(0)) { - revert BadSequencerNumber(seqMessageIndex, sequenceNumber_); + if (seqMessageIndex != sequenceNumber && sequenceNumber != ~uint256(0)) { + revert BadSequencerNumber(seqMessageIndex, sequenceNumber); } - emit SequencerBatchDelivered( - seqMessageIndex, - beforeAcc, - afterAcc, - delayedAcc, - totalDelayedMessagesRead, - timeBounds_, - IBridge.BatchDataLocation.TxInput - ); + if (!isUsingFeeToken) { + // only report batch poster spendings if chain is using ETH as native currency + submitBatchSpendingReport(dataHash, seqMessageIndex, block.basefee, 0); + } } - function addSequencerL2BatchFromBlob( + function addSequencerL2BatchFromBlobs( uint256 sequenceNumber, uint256 afterDelayedMessagesRead, IGasRefunder gasRefunder, uint256 prevMessageCount, uint256 newMessageCount - ) external refundsGas(gasRefunder) { + ) external refundsGas(gasRefunder, reader4844) { if (!isBatchPoster[msg.sender]) revert NotBatchPoster(); - (bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formBlobDataHash( - afterDelayedMessagesRead - ); - - // we use addSequencerL2BatchImpl for submitting the message - // normally this would also submit a batch spending report but that is skipped if we pass - // an empty call data size, then we submit a separate batch spending report later ( - uint256 seqMessageIndex, - bytes32 beforeAcc, - bytes32 delayedAcc, - bytes32 afterAcc - ) = addSequencerL2BatchImpl( - dataHash, - afterDelayedMessagesRead, - 0, - prevMessageCount, - newMessageCount - ); - - // ~uint256(0) is type(uint256).max, but ever so slightly cheaper - if (seqMessageIndex != sequenceNumber && sequenceNumber != ~uint256(0)) { - revert BadSequencerNumber(seqMessageIndex, sequenceNumber); - } + bytes32 dataHash, + IBridge.TimeBounds memory timeBounds, + uint256 blobGas + ) = formBlobDataHash(afterDelayedMessagesRead); - emit SequencerBatchDelivered( - sequenceNumber, - beforeAcc, - afterAcc, - delayedAcc, - totalDelayedMessagesRead, + (uint256 seqMessageIndex, , , ) = bridge.enqueueSequencerMessage( + dataHash, + afterDelayedMessagesRead, + prevMessageCount, + newMessageCount, timeBounds, IBridge.BatchDataLocation.Blob ); + uint256 _sequenceNumber = sequenceNumber; // stack workaround + + // ~uint256(0) is type(uint256).max, but ever so slightly cheaper + if (seqMessageIndex != _sequenceNumber && _sequenceNumber != ~uint256(0)) { + revert BadSequencerNumber(seqMessageIndex, _sequenceNumber); + } + // blobs are currently not supported on host arbitrum chains, when support is added it may // consume gas in a different way to L1, so explicitly block host arb chains so that if support for blobs // on arb is added it will need to explicitly turned on in the sequencer inbox if (hostChainIsArbitrum) revert DataBlobsNotSupported(); // submit a batch spending report to refund the entity that produced the blob batch data - uint256 blobBasefee = blobBasefeeReader.getBlobBaseFee(); - submitBatchSpendingReport(dataHash, seqMessageIndex, block.basefee, blobBasefee); + // same as using calldata, we only submit spending report if the caller is the origin of the tx + // such that one cannot "double-claim" batch posting refund in the same tx + // solhint-disable-next-line avoid-tx-origin + if (msg.sender == tx.origin && !isUsingFeeToken) { + submitBatchSpendingReport(dataHash, seqMessageIndex, block.basefee, blobGas); + } } function addSequencerL2Batch( @@ -466,50 +366,28 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox IGasRefunder gasRefunder, uint256 prevMessageCount, uint256 newMessageCount - ) external override refundsGas(gasRefunder) { + ) external override refundsGas(gasRefunder, IReader4844(address(0))) { if (!isBatchPoster[msg.sender] && msg.sender != address(rollup)) revert NotBatchPoster(); (bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formCallDataHash( data, afterDelayedMessagesRead ); - uint256 seqMessageIndex; - { - // Reformat the stack to prevent "Stack too deep" - uint256 sequenceNumber_ = sequenceNumber; - IBridge.TimeBounds memory timeBounds_ = timeBounds; - bytes32 dataHash_ = dataHash; - uint256 afterDelayedMessagesRead_ = afterDelayedMessagesRead; - uint256 prevMessageCount_ = prevMessageCount; - uint256 newMessageCount_ = newMessageCount; - // we set the calldata length posted to 0 here since the caller isn't the origin - // of the tx, so they might have not paid tx input cost for the calldata - bytes32 beforeAcc; - bytes32 delayedAcc; - bytes32 afterAcc; - (seqMessageIndex, beforeAcc, delayedAcc, afterAcc) = addSequencerL2BatchImpl( - dataHash_, - afterDelayedMessagesRead_, - 0, - prevMessageCount_, - newMessageCount_ - ); - - // ~uint256(0) is type(uint256).max, but ever so slightly cheaper - if (seqMessageIndex != sequenceNumber_ && sequenceNumber_ != ~uint256(0)) { - revert BadSequencerNumber(seqMessageIndex, sequenceNumber_); - } - emit SequencerBatchDelivered( - seqMessageIndex, - beforeAcc, - afterAcc, - delayedAcc, - totalDelayedMessagesRead, - timeBounds_, - IBridge.BatchDataLocation.SeparateBatchEvent - ); + (uint256 seqMessageIndex, , , ) = bridge.enqueueSequencerMessage( + dataHash, + afterDelayedMessagesRead, + prevMessageCount, + newMessageCount, + timeBounds, + IBridge.BatchDataLocation.SeparateBatchEvent + ); + + // ~uint256(0) is type(uint256).max, but ever so slightly cheaper + if (seqMessageIndex != sequenceNumber && sequenceNumber != ~uint256(0)) { + revert BadSequencerNumber(seqMessageIndex, sequenceNumber); } - emit SequencerBatchData(seqMessageIndex, data); + + emit SequencerBatchData(sequenceNumber, data); } function packHeader(uint256 afterDelayedMessagesRead) @@ -599,25 +477,33 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox /// @param afterDelayedMessagesRead The delayed messages count read up to /// @return The data hash /// @return The timebounds within which the message should be processed + /// @return The normalized amount of gas used for blob posting function formBlobDataHash(uint256 afterDelayedMessagesRead) internal view - returns (bytes32, IBridge.TimeBounds memory) + returns ( + bytes32, + IBridge.TimeBounds memory, + uint256 + ) { - bytes32[] memory dataHashes = dataHashReader.getDataHashes(); + bytes32[] memory dataHashes = reader4844.getDataHashes(); if (dataHashes.length == 0) revert MissingDataHashes(); (bytes memory header, IBridge.TimeBounds memory timeBounds) = packHeader( afterDelayedMessagesRead ); + uint256 blobCost = reader4844.getBlobBaseFee() * GAS_PER_BLOB * dataHashes.length; return ( keccak256(bytes.concat(header, DATA_BLOB_HEADER_FLAG, abi.encodePacked(dataHashes))), - timeBounds + timeBounds, + block.basefee > 0 ? blobCost / block.basefee : 0 ); } /// @dev Submit a batch spending report message so that the batch poster can be reimbursed on the rollup + /// This function expect msg.sender is tx.origin, and will always record tx.origin as the spender /// @param dataHash The hash of the message the spending report is being submitted for /// @param seqMessageIndex The index of the message to submit the spending report for /// @param gasPrice The gas price that was paid for the data (standard gas or data gas) @@ -625,39 +511,29 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox bytes32 dataHash, uint256 seqMessageIndex, uint256 gasPrice, - uint256 blobBaseFeePrice + uint256 extraGas ) internal { - bytes memory spendingReportMsg; - address batchPoster = msg.sender; + // report the account who paid the gas (tx.origin) for the tx as batch poster + // if msg.sender is used and is a contract, it might not be able to spend the refund on l2 + // solhint-disable-next-line avoid-tx-origin + address batchPoster = tx.origin; // this msg isn't included in the current sequencer batch, but instead added to // the delayed messages queue that is yet to be included if (hostChainIsArbitrum) { // Include extra gas for the host chain's L1 gas charging uint256 l1Fees = ArbGasInfo(address(0x6c)).getCurrentTxL1GasFees(); - uint256 extraGas = l1Fees / block.basefee; - require(extraGas <= type(uint64).max, "L1_GAS_NOT_UINT64"); - spendingReportMsg = abi.encodePacked( - block.timestamp, - batchPoster, - dataHash, - seqMessageIndex, - gasPrice, - uint64(extraGas) - ); - } else { - // when a blob base fee is supplied we include it into the batch spending report - spendingReportMsg = abi.encodePacked( - block.timestamp, - batchPoster, - dataHash, - seqMessageIndex, - gasPrice, - // we add an empty extraGas since the parsing code expects a value here - uint64(0), - blobBaseFeePrice - ); + extraGas += l1Fees / block.basefee; } + require(extraGas <= type(uint64).max, "EXTRA_GAS_NOT_UINT64"); + bytes memory spendingReportMsg = abi.encodePacked( + block.timestamp, + batchPoster, + dataHash, + seqMessageIndex, + gasPrice, + uint64(extraGas) + ); uint256 msgNum = bridge.submitBatchSpendingReport( batchPoster, @@ -667,38 +543,6 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox emit InboxMessageDelivered(msgNum, spendingReportMsg); } - function addSequencerL2BatchImpl( - bytes32 dataHash, - uint256 afterDelayedMessagesRead, - uint256 calldataLengthPosted, - uint256 prevMessageCount, - uint256 newMessageCount - ) - internal - returns ( - uint256 seqMessageIndex, - bytes32 beforeAcc, - bytes32 delayedAcc, - bytes32 acc - ) - { - if (afterDelayedMessagesRead < totalDelayedMessagesRead) revert DelayedBackwards(); - if (afterDelayedMessagesRead > bridge.delayedMessageCount()) revert DelayedTooFar(); - - (seqMessageIndex, beforeAcc, delayedAcc, acc) = bridge.enqueueSequencerMessage( - dataHash, - afterDelayedMessagesRead, - prevMessageCount, - newMessageCount - ); - - totalDelayedMessagesRead = afterDelayedMessagesRead; - - if (calldataLengthPosted > 0) { - submitBatchSpendingReport(dataHash, seqMessageIndex, block.basefee, 0); - } - } - function inboxAccs(uint256 index) external view returns (bytes32) { return bridge.sequencerInboxAccs(index); } @@ -708,20 +552,14 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox } /// @inheritdoc ISequencerInbox - function setMaxTimeVariation(ISequencerInbox.MaxTimeVariation memory maxTimeVariation_) + function setIsBatchPoster(address addr, bool isBatchPoster_) external - onlyRollupOwner + onlyRollupOwnerOrBatchPosterManager { - delayBlocks = maxTimeVariation_.delayBlocks; - futureBlocks = maxTimeVariation_.futureBlocks; - delaySeconds = maxTimeVariation_.delaySeconds; - futureSeconds = maxTimeVariation_.futureSeconds; - emit OwnerFunctionCalled(0); - } - - /// @inheritdoc ISequencerInbox - function setIsBatchPoster(address addr, bool isBatchPoster_) external onlyRollupOwner { isBatchPoster[addr] = isBatchPoster_; + // we used to have OwnerFunctionCalled(0) for setting the maxTimeVariation + // so we dont use index = 0 here, even though this is the first owner function + // to stay compatible with legacy events emit OwnerFunctionCalled(1); } @@ -756,9 +594,18 @@ contract SequencerInbox is DelegateCallAware, GasRefundEnabled, ISequencerInbox } /// @inheritdoc ISequencerInbox - function setIsSequencer(address addr, bool isSequencer_) external onlyRollupOwner { + function setIsSequencer(address addr, bool isSequencer_) + external + onlyRollupOwnerOrBatchPosterManager + { isSequencer[addr] = isSequencer_; - emit OwnerFunctionCalled(4); + emit OwnerFunctionCalled(4); // Owner in this context can also be batch poster manager + } + + /// @inheritdoc ISequencerInbox + function setBatchPosterManager(address newBatchPosterManager) external onlyRollupOwner { + batchPosterManager = newBatchPosterManager; + emit OwnerFunctionCalled(5); } function isValidKeysetHash(bytes32 ksHash) external view returns (bool) { diff --git a/src/challenge/ChallengeManager.sol b/src/challenge/ChallengeManager.sol index 12cad0852..c5427e7ea 100644 --- a/src/challenge/ChallengeManager.sol +++ b/src/challenge/ChallengeManager.sol @@ -110,6 +110,12 @@ contract ChallengeManager is DelegateCallAware, IChallengeManager { osp = osp_; } + function postUpgradeInit(IOneStepProofEntry osp_) external onlyDelegated onlyProxyOwner { + // when updating to 4844 we need to create new osp contracts and set them here + // on the challenge manager + osp = osp_; + } + function createChallenge( bytes32 wasmModuleRoot_, MachineStatus[2] calldata startAndEndMachineStatuses_, diff --git a/src/libraries/Error.sol b/src/libraries/Error.sol index 5d83ca104..03e3fbd04 100644 --- a/src/libraries/Error.sol +++ b/src/libraries/Error.sol @@ -176,6 +176,9 @@ error AlreadyValidDASKeyset(bytes32); /// @dev Tried to use or invalidate an already invalid Data Availability Service keyset error NoSuchKeyset(bytes32); +/// @dev Thrown when the provided address is not the designated batch poster manager +error NotBatchPosterManager(address); + /// @dev Thrown when a data blob feature is attempted to be used on a chain that doesnt support it error DataBlobsNotSupported(); @@ -191,8 +194,17 @@ error InvalidBlobMetadata(); /// @dev Thrown when rollup is not updated with updateRollupAddress error RollupNotChanged(); +/// @dev Thrown when reading the total delayed messages and getting 0 when a non zero value is expected +error EmptyDelayedMessagesRead(); + /// @dev Batch data was empty when non empty was expected error EmptyBatchData(); /// @dev Unsupported header flag was provided error InvalidHeaderFlag(bytes1); + +/// @dev SequencerInbox and Bridge are not in the same feeToken/ETH mode +error NativeTokenMismatch(); + +/// @dev Thrown when a deprecated function is called +error Deprecated(); diff --git a/src/libraries/GasRefundEnabled.sol b/src/libraries/GasRefundEnabled.sol new file mode 100644 index 000000000..60287ee0b --- /dev/null +++ b/src/libraries/GasRefundEnabled.sol @@ -0,0 +1,52 @@ +// Copyright 2021-2022, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro-contracts/blob/main/LICENSE +// SPDX-License-Identifier: BUSL-1.1 + +// solhint-disable-next-line compiler-version +pragma solidity ^0.8.0; + +import "./IReader4844.sol"; +import "./IGasRefunder.sol"; + +abstract contract GasRefundEnabled { + uint256 immutable gasPerBlob = 2**17; + + /// @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, IReader4844 reader4844) { + uint256 startGasLeft = gasleft(); + _; + if (address(gasRefunder) != address(0)) { + uint256 calldataSize = msg.data.length; + uint256 calldataWords = (calldataSize + 31) / 32; + // account for the CALLDATACOPY cost of the proxy contract, including the memory expansion cost + startGasLeft += calldataWords * 6 + (calldataWords**2) / 512; + // 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) { + // We can't be sure if this calldata came from the top level tx, + // so to be safe we tell the gas refunder there was no calldata. + calldataSize = 0; + } else { + // for similar reasons to above we only refund blob gas when the tx.origin is the msg.sender + // this avoids the caller being able to send blobs to other contracts and still get refunded here + if (address(reader4844) != address(0)) { + // add any cost for 4844 data, the data hash reader throws an error prior to 4844 being activated + // we do this addition here rather in the GasRefunder so that we can check the msg.sender is the tx.origin + try reader4844.getDataHashes() returns (bytes32[] memory dataHashes) { + if (dataHashes.length != 0) { + uint256 blobBasefee = reader4844.getBlobBaseFee(); + startGasLeft += + (dataHashes.length * gasPerBlob * blobBasefee) / + block.basefee; + } + } catch {} + } + } + + gasRefunder.onGasSpent(payable(msg.sender), startGasLeft - gasleft(), calldataSize); + } + } +} diff --git a/src/libraries/IGasRefunder.sol b/src/libraries/IGasRefunder.sol index e7b086565..f80ac3b2b 100644 --- a/src/libraries/IGasRefunder.sol +++ b/src/libraries/IGasRefunder.sol @@ -12,28 +12,3 @@ interface IGasRefunder { 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 = msg.data.length; - uint256 calldataWords = (calldataSize + 31) / 32; - // account for the CALLDATACOPY cost of the proxy contract, including the memory expansion cost - startGasLeft += calldataWords * 6 + (calldataWords**2) / 512; - // 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) { - // We can't be sure if this calldata came from the top level tx, - // so to be safe we tell the gas refunder there was no calldata. - calldataSize = 0; - } - gasRefunder.onGasSpent(payable(msg.sender), startGasLeft - gasleft(), calldataSize); - } - } -} diff --git a/src/libraries/IReader4844.sol b/src/libraries/IReader4844.sol new file mode 100644 index 000000000..a66ebae58 --- /dev/null +++ b/src/libraries/IReader4844.sol @@ -0,0 +1,9 @@ +pragma solidity >=0.6.9 <0.9.0; + +interface IReader4844 { + /// @notice Returns the current BLOBBASEFEE + function getBlobBaseFee() external view returns (uint256); + + /// @notice Returns all the data hashes of all the blobs on the current transaction + function getDataHashes() external view returns (bytes32[] memory); +} diff --git a/src/mocks/BridgeStub.sol b/src/mocks/BridgeStub.sol index 2e2dee05e..4277f6fd2 100644 --- a/src/mocks/BridgeStub.sol +++ b/src/mocks/BridgeStub.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; import "./InboxStub.sol"; -import {BadSequencerMessageNumber} from "../libraries/Error.sol"; +import {BadSequencerMessageNumber, DelayedTooFar, DelayedBackwards} from "../libraries/Error.sol"; import "../bridge/IBridge.sol"; import "../bridge/IEthBridge.sol"; @@ -31,6 +31,12 @@ contract BridgeStub is IBridge, IEthBridge { address public sequencerInbox; uint256 public override sequencerReportedSubMessageCount; + IOwnable public rollup; + uint256 public totalDelayedMessagesRead; + + constructor(IOwnable rollup_) { + rollup = rollup_; + } function setSequencerInbox(address _sequencerInbox) external override { sequencerInbox = _sequencerInbox; @@ -70,7 +76,9 @@ contract BridgeStub is IBridge, IEthBridge { bytes32 dataHash, uint256 afterDelayedMessagesRead, uint256 prevMessageCount, - uint256 newMessageCount + uint256 newMessageCount, + TimeBounds memory timeBounds, + BatchDataLocation batchDataLocation ) external returns ( @@ -87,6 +95,9 @@ contract BridgeStub is IBridge, IEthBridge { ) { revert BadSequencerMessageNumber(sequencerReportedSubMessageCount, prevMessageCount); } + if (afterDelayedMessagesRead > delayedInboxAccs.length) revert DelayedTooFar(); + if (afterDelayedMessagesRead < totalDelayedMessagesRead) revert DelayedBackwards(); + sequencerReportedSubMessageCount = newMessageCount; seqMessageIndex = sequencerInboxAccs.length; if (sequencerInboxAccs.length > 0) { @@ -97,6 +108,18 @@ contract BridgeStub is IBridge, IEthBridge { } acc = keccak256(abi.encodePacked(beforeAcc, dataHash, delayedAcc)); sequencerInboxAccs.push(acc); + totalDelayedMessagesRead = afterDelayedMessagesRead; + + emit SequencerBatchDelivered( + seqMessageIndex, + beforeAcc, + acc, + delayedAcc, + afterDelayedMessagesRead, + timeBounds, + batchDataLocation, + msg.sender + ); } function submitBatchSpendingReport(address batchPoster, bytes32 dataHash) @@ -175,10 +198,6 @@ contract BridgeStub is IBridge, IEthBridge { return sequencerInboxAccs.length; } - function rollup() external pure override returns (IOwnable) { - revert("NOT_IMPLEMENTED"); - } - function acceptFundsFromOldBridge() external payable {} function initialize(IOwnable) external pure { diff --git a/src/mocks/SequencerInboxStub.sol b/src/mocks/SequencerInboxStub.sol index f06393d4b..34cc7952e 100644 --- a/src/mocks/SequencerInboxStub.sol +++ b/src/mocks/SequencerInboxStub.sol @@ -10,11 +10,13 @@ import {INITIALIZATION_MSG_TYPE} from "../libraries/MessageTypes.sol"; contract SequencerInboxStub is SequencerInbox { constructor( + IBridge bridge_, address sequencer_, + ISequencerInbox.MaxTimeVariation memory maxTimeVariation_, uint256 maxDataSize_, - IDataHashReader dataHashReader_, - IBlobBasefeeReader blobBasefeeReader_ - ) SequencerInbox(maxDataSize_, dataHashReader_, blobBasefeeReader_) { + IReader4844 reader4844_, + bool isUsingFeeToken_ + ) SequencerInbox(bridge, maxTimeVariation_, maxDataSize_, reader4844_, isUsingFeeToken_) { isBatchPoster[sequencer_] = true; } @@ -28,22 +30,15 @@ contract SequencerInboxStub is SequencerInbox { require(num == 0, "ALREADY_DELAYED_INIT"); emit InboxMessageDelivered(num, initMsg); (bytes32 dataHash, IBridge.TimeBounds memory timeBounds) = formEmptyDataHash(1); - ( - uint256 sequencerMessageCount, - bytes32 beforeAcc, - bytes32 delayedAcc, - bytes32 afterAcc - ) = addSequencerL2BatchImpl(dataHash, 1, 0, 0, 1); - require(sequencerMessageCount == 0, "ALREADY_SEQ_INIT"); - emit SequencerBatchDelivered( - sequencerMessageCount, - beforeAcc, - afterAcc, - delayedAcc, - totalDelayedMessagesRead, + (uint256 sequencerMessageCount, , , ) = bridge.enqueueSequencerMessage( + dataHash, + 1, + 0, + 0, timeBounds, IBridge.BatchDataLocation.NoData ); + require(sequencerMessageCount == 0, "ALREADY_SEQ_INIT"); } function getTimeBounds() internal view override returns (IBridge.TimeBounds memory bounds) { diff --git a/src/osp/OneStepProverHostIo.sol b/src/osp/OneStepProverHostIo.sol index ec0ed85ed..0206572f4 100644 --- a/src/osp/OneStepProverHostIo.sol +++ b/src/osp/OneStepProverHostIo.sol @@ -101,35 +101,16 @@ contract OneStepProverHostIo is IOneStepProver { state.u64Vals[idx] = val; } - uint256 internal constant BLS_MODULUS = - 52435875175126190479447740508185965837690552500527637822603658699938581184513; - uint256 internal constant PRIMITIVE_ROOT_OF_UNITY = - 10238227357739495823651030575849232062558860180284477541189508159991286009131; - - // Computes b**e % m - // Really pure but the Solidity compiler sees the staticcall and requires view - function modExp256( - uint256 b, - uint256 e, - uint256 m - ) internal view returns (uint256) { - bytes memory modExpInput = abi.encode(32, 32, 32, b, e, m); - (bool modexpSuccess, bytes memory modExpOutput) = address(0x05).staticcall(modExpInput); - require(modexpSuccess, "MODEXP_FAILED"); - require(modExpOutput.length == 32, "MODEXP_WRONG_LENGTH"); - return uint256(bytes32(modExpOutput)); - } - function executeReadPreImage( ExecutionContext calldata, Machine memory mach, Module memory mod, Instruction calldata inst, bytes calldata proof - ) internal view { + ) internal pure { uint256 preimageOffset = mach.valueStack.pop().assumeI32(); uint256 ptr = mach.valueStack.pop().assumeI32(); - if (preimageOffset % 32 != 0 || ptr + 32 > mod.moduleMemory.size || ptr % LEAF_SIZE != 0) { + if (ptr + 32 > mod.moduleMemory.size || ptr % LEAF_SIZE != 0) { mach.status = MachineStatus.ERRORED; return; } @@ -177,60 +158,6 @@ contract OneStepProverHostIo is IOneStepProver { preimageEnd = preimage.length; } extracted = preimage[preimageOffset:preimageEnd]; - } else if (inst.argumentData == 2) { - // The machine is asking for an Ethereum versioned hash preimage - - require(proofType == 0, "UNKNOWN_PREIMAGE_PROOF"); - - // kzgProof should be a valid input to the EIP-4844 point evaluation precompile at address 0x0A. - // It should prove the preimageOffset/32'th word of the machine's requested KZG commitment. - bytes calldata kzgProof = proof[proofOffset:]; - - require(bytes32(kzgProof[:32]) == leafContents, "KZG_PROOF_WRONG_HASH"); - - uint256 fieldElementsPerBlob; - uint256 blsModulus; - { - (bool success, bytes memory kzgParams) = address(0x0A).staticcall(kzgProof); - require(success, "INVALID_KZG_PROOF"); - require(kzgParams.length > 0, "KZG_PRECOMPILE_MISSING"); - (fieldElementsPerBlob, blsModulus) = abi.decode(kzgParams, (uint256, uint256)); - } - - // With a hardcoded PRIMITIVE_ROOT_OF_UNITY, we can only support this BLS modulus. - // It may be worth in the future supporting arbitrary BLS moduli, but we would likely need to - // validate a user-supplied root of unity. - require(blsModulus == BLS_MODULUS, "UNKNOWN_BLS_MODULUS"); - - // If preimageOffset is greater than or equal to the blob size, leave extracted empty and call it here. - if (preimageOffset < fieldElementsPerBlob * 32) { - // We need to compute what point the polynomial should be evaluated at to get the right part of the preimage. - // KZG commitments use a bit reversal permutation to order the roots of unity. - // To account for that, we reverse the bit order of the index. - uint256 bitReversedIndex = 0; - // preimageOffset was required to be 32 byte aligned above - uint256 tmp = preimageOffset / 32; - for (uint256 i = 1; i < fieldElementsPerBlob; i <<= 1) { - bitReversedIndex <<= 1; - if (tmp & 1 == 1) { - bitReversedIndex |= 1; - } - tmp >>= 1; - } - - // First, we get the root of unity of order 2**fieldElementsPerBlob. - // We start with a root of unity of order 2**32 and then raise it to - // the power of (2**32)/fieldElementsPerBlob to get root of unity we need. - uint256 rootOfUnityPower = (1 << 32) / fieldElementsPerBlob; - // Then, we raise the root of unity to the power of bitReversedIndex, - // to retrieve this word of the KZG commitment. - rootOfUnityPower *= bitReversedIndex; - // z is the point the polynomial is evaluated at to retrieve this word of data - uint256 z = modExp256(PRIMITIVE_ROOT_OF_UNITY, rootOfUnityPower, blsModulus); - require(bytes32(kzgProof[32:64]) == bytes32(z), "KZG_PROOF_WRONG_Z"); - - extracted = kzgProof[64:96]; - } } else { revert("UNKNOWN_PREIMAGE_TYPE"); } diff --git a/src/precompiles/ArbGasInfo.sol b/src/precompiles/ArbGasInfo.sol index 31dd70eab..b4c210960 100644 --- a/src/precompiles/ArbGasInfo.sol +++ b/src/precompiles/ArbGasInfo.sol @@ -129,4 +129,24 @@ interface ArbGasInfo { /// @notice Returns the available funds from L1 fees function getL1FeesAvailable() external view returns (uint256); + + /// @notice Returns the equilibration units parameter for L1 price adjustment algorithm + /// Available in ArbOS version 20 + function getL1PricingEquilibrationUnits() external view returns (uint256); + + /// @notice Returns the last time the L1 calldata pricer was updated. + /// Available in ArbOS version 20 + function getLastL1PricingUpdateTime() external view returns (uint64); + + /// @notice Returns the amount of L1 calldata payments due for rewards (per the L1 reward rate) + /// Available in ArbOS version 20 + function getL1PricingFundsDueForRewards() external view returns (uint256); + + /// @notice Returns the amount of L1 calldata posted since the last update. + /// Available in ArbOS version 20 + function getL1PricingUnitsSinceUpdate() external view returns (uint64); + + /// @notice Returns the L1 pricing surplus as of the last update (may be negative). + /// Available in ArbOS version 20 + function getLastL1PricingSurplus() external view returns (int256); } diff --git a/src/precompiles/ArbOwnerPublic.sol b/src/precompiles/ArbOwnerPublic.sol index ee9e23470..0de57ce62 100644 --- a/src/precompiles/ArbOwnerPublic.sol +++ b/src/precompiles/ArbOwnerPublic.sol @@ -29,5 +29,13 @@ interface ArbOwnerPublic { /// @notice Get the Brotli compression level used for fast compression function getBrotliCompressionLevel() external view returns (uint64); + /// @notice Get the next scheduled ArbOS version upgrade and its activation timestamp. + /// Returns (0, 0) if no ArbOS upgrade is scheduled. + /// Available in ArbOS version 20. + function getScheduledUpgrade() + external + view + returns (uint64 arbosVersion, uint64 scheduledForTimestamp); + event ChainOwnerRectified(address rectifiedOwner); } diff --git a/src/rollup/AbsRollupEventInbox.sol b/src/rollup/AbsRollupEventInbox.sol index c0866d9e1..4887f050e 100644 --- a/src/rollup/AbsRollupEventInbox.sol +++ b/src/rollup/AbsRollupEventInbox.sol @@ -35,6 +35,7 @@ abstract contract AbsRollupEventInbox is if (address(_bridge) == address(0)) revert HadZeroInit(); bridge = _bridge; rollup = address(_bridge.rollup()); + if (address(rollup) == address(0)) revert RollupNotChanged(); } /// @notice Allows the rollup owner to sync the rollup address diff --git a/src/rollup/BridgeCreator.sol b/src/rollup/BridgeCreator.sol index 0e45f8152..91d3f045b 100644 --- a/src/rollup/BridgeCreator.sol +++ b/src/rollup/BridgeCreator.sol @@ -19,12 +19,19 @@ import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; contract BridgeCreator is Ownable { - BridgeContracts public ethBasedTemplates; - BridgeContracts public erc20BasedTemplates; + BridgeTemplates public ethBasedTemplates; + BridgeTemplates public erc20BasedTemplates; event TemplatesUpdated(); event ERC20TemplatesUpdated(); + struct BridgeTemplates { + IBridge bridge; + IInboxBase inbox; + IRollupEventInbox rollupEventInbox; + IOutbox outbox; + } + struct BridgeContracts { IBridge bridge; ISequencerInbox sequencerInbox; @@ -34,24 +41,24 @@ contract BridgeCreator is Ownable { } constructor( - BridgeContracts memory _ethBasedTemplates, - BridgeContracts memory _erc20BasedTemplates + BridgeTemplates memory _ethBasedTemplates, + BridgeTemplates memory _erc20BasedTemplates ) Ownable() { ethBasedTemplates = _ethBasedTemplates; erc20BasedTemplates = _erc20BasedTemplates; } - function updateTemplates(BridgeContracts calldata _newTemplates) external onlyOwner { + function updateTemplates(BridgeTemplates calldata _newTemplates) external onlyOwner { ethBasedTemplates = _newTemplates; emit TemplatesUpdated(); } - function updateERC20Templates(BridgeContracts calldata _newTemplates) external onlyOwner { + function updateERC20Templates(BridgeTemplates calldata _newTemplates) external onlyOwner { erc20BasedTemplates = _newTemplates; emit ERC20TemplatesUpdated(); } - function _createBridge(address adminProxy, BridgeContracts storage templates) + function _createBridge(address adminProxy, BridgeTemplates storage templates) internal returns (BridgeContracts memory) { @@ -59,11 +66,6 @@ contract BridgeCreator is Ownable { frame.bridge = IBridge( address(new TransparentUpgradeableProxy(address(templates.bridge), adminProxy, "")) ); - frame.sequencerInbox = ISequencerInbox( - address( - new TransparentUpgradeableProxy(address(templates.sequencerInbox), adminProxy, "") - ) - ); frame.inbox = IInboxBase( address(new TransparentUpgradeableProxy(address(templates.inbox), adminProxy, "")) ); @@ -82,7 +84,9 @@ contract BridgeCreator is Ownable { address adminProxy, address rollup, address nativeToken, - ISequencerInbox.MaxTimeVariation calldata maxTimeVariation + ISequencerInbox.MaxTimeVariation calldata maxTimeVariation, + uint256 maxDataSize, + IReader4844 reader4844 ) external returns (BridgeContracts memory) { // create ETH-based bridge if address zero is provided for native token, otherwise create ERC20-based bridge BridgeContracts memory frame = _createBridge( @@ -96,7 +100,13 @@ contract BridgeCreator is Ownable { } else { IERC20Bridge(address(frame.bridge)).initialize(IOwnable(rollup), nativeToken); } - frame.sequencerInbox.initialize(IBridge(frame.bridge), maxTimeVariation); + frame.sequencerInbox = new SequencerInbox( + IBridge(frame.bridge), + maxTimeVariation, + maxDataSize, + reader4844, + nativeToken != address(0) + ); frame.inbox.initialize(frame.bridge, frame.sequencerInbox); frame.rollupEventInbox.initialize(frame.bridge); frame.outbox.initialize(frame.bridge); diff --git a/src/rollup/RollupCreator.sol b/src/rollup/RollupCreator.sol index 5812143c3..dbd9128b8 100644 --- a/src/rollup/RollupCreator.sol +++ b/src/rollup/RollupCreator.sol @@ -35,12 +35,15 @@ contract RollupCreator is Ownable { struct RollupDeploymentParams { Config config; - address batchPoster; address[] validators; uint256 maxDataSize; address nativeToken; bool deployFactoriesToL2; uint256 maxFeePerGasForRetryables; + //// @dev The address of the batch poster, not used when set to zero address + address[] batchPosters; + address batchPosterManager; + IReader4844 reader4844; } BridgeCreator public bridgeCreator; @@ -110,27 +113,12 @@ contract RollupCreator is Ownable { payable returns (address) { - { - // Make sure the immutable maxDataSize is as expected - (, ISequencerInbox ethSequencerInbox, IInboxBase ethInbox, , ) = bridgeCreator - .ethBasedTemplates(); - require( - deployParams.maxDataSize == ethSequencerInbox.maxDataSize(), - "SI_MAX_DATA_SIZE_MISMATCH" - ); - require(deployParams.maxDataSize == ethInbox.maxDataSize(), "I_MAX_DATA_SIZE_MISMATCH"); + // Make sure the immutable maxDataSize is as expected + (, IInboxBase ethInbox, , ) = bridgeCreator.ethBasedTemplates(); + require(deployParams.maxDataSize == ethInbox.maxDataSize(), "I_MAX_DATA_SIZE_MISMATCH"); - (, ISequencerInbox erc20SequencerInbox, IInboxBase erc20Inbox, , ) = bridgeCreator - .erc20BasedTemplates(); - require( - deployParams.maxDataSize == erc20SequencerInbox.maxDataSize(), - "SI_MAX_DATA_SIZE_MISMATCH" - ); - require( - deployParams.maxDataSize == erc20Inbox.maxDataSize(), - "I_MAX_DATA_SIZE_MISMATCH" - ); - } + (, IInboxBase erc20Inbox, , ) = bridgeCreator.erc20BasedTemplates(); + require(deployParams.maxDataSize == erc20Inbox.maxDataSize(), "I_MAX_DATA_SIZE_MISMATCH"); // create proxy admin which will manage bridge contracts ProxyAdmin proxyAdmin = new ProxyAdmin(); @@ -142,7 +130,9 @@ contract RollupCreator is Ownable { address(proxyAdmin), address(rollup), deployParams.nativeToken, - deployParams.config.sequencerInboxMaxTimeVariation + deployParams.config.sequencerInboxMaxTimeVariation, + deployParams.maxDataSize, + deployParams.reader4844 ); IChallengeManager challengeManager = IChallengeManager( @@ -186,9 +176,12 @@ contract RollupCreator is Ownable { }) ); - // setting batch poster, if the address provided is not zero address - if (deployParams.batchPoster != address(0)) { - bridgeContracts.sequencerInbox.setIsBatchPoster(deployParams.batchPoster, true); + // Setting batch posters and batch poster manager + for (uint256 i = 0; i < deployParams.batchPosters.length; i++) { + bridgeContracts.sequencerInbox.setIsBatchPoster(deployParams.batchPosters[i], true); + } + if (deployParams.batchPosterManager != address(0)) { + bridgeContracts.sequencerInbox.setBatchPosterManager(deployParams.batchPosterManager); } // Call setValidator on the newly created rollup contract just if validator set is not empty diff --git a/src/rollup/ValidatorWallet.sol b/src/rollup/ValidatorWallet.sol index 9b8c2296c..0d7cbaceb 100644 --- a/src/rollup/ValidatorWallet.sol +++ b/src/rollup/ValidatorWallet.sol @@ -7,6 +7,7 @@ pragma solidity ^0.8.0; import "../challenge/IChallengeManager.sol"; import "../libraries/DelegateCallAware.sol"; import "../libraries/IGasRefunder.sol"; +import "../libraries/GasRefundEnabled.sol"; import "@openzeppelin/contracts/utils/Address.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; @@ -109,7 +110,7 @@ contract ValidatorWallet is OwnableUpgradeable, DelegateCallAware, GasRefundEnab bytes[] calldata data, address[] calldata destination, uint256[] calldata amount - ) public payable onlyExecutorOrOwner refundsGas(gasRefunder) { + ) public payable onlyExecutorOrOwner refundsGas(gasRefunder, IReader4844(address(0))) { uint256 numTxes = data.length; if (numTxes != destination.length) revert BadArrayLength(numTxes, destination.length); if (numTxes != amount.length) revert BadArrayLength(numTxes, amount.length); @@ -144,7 +145,7 @@ contract ValidatorWallet is OwnableUpgradeable, DelegateCallAware, GasRefundEnab bytes calldata data, address destination, uint256 amount - ) public payable onlyExecutorOrOwner refundsGas(gasRefunder) { + ) public payable onlyExecutorOrOwner refundsGas(gasRefunder, IReader4844(address(0))) { 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 @@ -168,7 +169,7 @@ contract ValidatorWallet is OwnableUpgradeable, DelegateCallAware, GasRefundEnab IGasRefunder gasRefunder, IChallengeManager manager, uint64[] calldata challenges - ) public onlyExecutorOrOwner refundsGas(gasRefunder) { + ) public onlyExecutorOrOwner refundsGas(gasRefunder, IReader4844(address(0))) { uint256 challengesCount = challenges.length; for (uint256 i = 0; i < challengesCount; i++) { try manager.timeout(challenges[i]) {} catch (bytes memory error) { diff --git a/src/test-helpers/BridgeTester.sol b/src/test-helpers/BridgeTester.sol index 0bcbe00fc..38954d960 100644 --- a/src/test-helpers/BridgeTester.sol +++ b/src/test-helpers/BridgeTester.sol @@ -67,6 +67,8 @@ contract BridgeTester is Initializable, DelegateCallAware, IBridge, IEthBridge { bytes32[] public override sequencerInboxAccs; uint256 public override sequencerReportedSubMessageCount; + uint256 public totalDelayedMessagesRead; + address private constant EMPTY_ACTIVEOUTBOX = address(type(uint160).max); function initialize(IOwnable rollup_) external initializer { @@ -95,7 +97,9 @@ contract BridgeTester is Initializable, DelegateCallAware, IBridge, IEthBridge { bytes32 dataHash, uint256 afterDelayedMessagesRead, uint256 prevMessageCount, - uint256 newMessageCount + uint256 newMessageCount, + TimeBounds memory timeBounds, + BatchDataLocation batchDataLocation ) external returns ( diff --git a/test/contract/arbRollup.spec.ts b/test/contract/arbRollup.spec.ts index 1d657765f..4593607d7 100644 --- a/test/contract/arbRollup.spec.ts +++ b/test/contract/arbRollup.spec.ts @@ -82,11 +82,11 @@ const ZERO_ADDR = ethers.constants.AddressZero const extraChallengeTimeBlocks = 20 const wasmModuleRoot = '0x9900000000000000000000000000000000000000000000000000000000000010' -const dummyDataHashReader = '0x0000000000000000000000000000000000000089' -const dummyBlobBasefeeReader = '0x0000000000000000000000000000000000000090' +const dummy4844Reader = '0x0000000000000000000000000000000000000089' // let rollup: RollupContract let rollup: RollupContract +let batchPosterManager: Signer let rollupUser: RollupUserLogic let rollupAdmin: RollupAdminLogic let bridge: Bridge @@ -133,6 +133,7 @@ const setup = async () => { const val3 = accounts[4] const val4 = accounts[5] sequencer = accounts[6] + const batchPosterManager = accounts[7] const oneStep0Fac = (await ethers.getContractFactory( 'OneStepProver0' @@ -187,15 +188,6 @@ const setup = async () => { )) as Bridge__factory const ethBridge = await ethBridgeFac.deploy() - const ethSequencerInboxFac = (await ethers.getContractFactory( - 'SequencerInbox' - )) as SequencerInbox__factory - const ethSequencerInbox = await ethSequencerInboxFac.deploy( - 117964, - dummyDataHashReader, - dummyBlobBasefeeReader - ) - const ethInboxFac = (await ethers.getContractFactory( 'Inbox' )) as Inbox__factory @@ -216,8 +208,6 @@ const setup = async () => { )) as ERC20Bridge__factory const erc20Bridge = await erc20BridgeFac.deploy() - const erc20SequencerInbox = ethSequencerInbox - const erc20InboxFac = (await ethers.getContractFactory( 'ERC20Inbox' )) as ERC20Inbox__factory @@ -239,14 +229,12 @@ const setup = async () => { const bridgeCreator = await bridgeCreatorFac.deploy( { bridge: ethBridge.address, - sequencerInbox: ethSequencerInbox.address, inbox: ethInbox.address, rollupEventInbox: ethRollupEventInbox.address, outbox: ethOutbox.address, }, { bridge: erc20Bridge.address, - sequencerInbox: erc20SequencerInbox.address, inbox: erc20Inbox.address, rollupEventInbox: erc20RollupEventInbox.address, outbox: erc20Outbox.address, @@ -277,9 +265,11 @@ const setup = async () => { const maxFeePerGas = BigNumber.from('1000000000') + const dummyDataHashReader = '0x0000000000000000000000000000000000000089' + const dummyBlobBasefeeReader = '0x0000000000000000000000000000000000000090' const deployParams = { config: await getDefaultConfig(), - batchPoster: await sequencer.getAddress(), + batchPosters: [await sequencer.getAddress()], validators: [ await val1.getAddress(), await val2.getAddress(), @@ -290,6 +280,8 @@ const setup = async () => { nativeToken: ethers.constants.AddressZero, deployFactoriesToL2: true, maxFeePerGasForRetryables: maxFeePerGas, + batchPosterManager: await batchPosterManager.getAddress(), + reader4844: dummy4844Reader, } const response = await rollupCreator.createRollup(deployParams, { @@ -316,6 +308,10 @@ const setup = async () => { )) as SequencerInbox__factory ).attach(rollupCreatedEvent.sequencerInbox) + await sequencerInbox + .connect(await impersonateAccount(rollupCreatedEvent.upgradeExecutor)) + .setBatchPosterManager(await batchPosterManager.getAddress()) + challengeManager = ( (await ethers.getContractFactory( 'ChallengeManager' @@ -340,6 +336,7 @@ const setup = async () => { delayedBridge: rollupCreatedEvent.bridge, delayedInbox: rollupCreatedEvent.inboxAddress, bridge, + batchPosterManager, upgradeExecutorAddress: rollupCreatedEvent.upgradeExecutor, adminproxy: rollupCreatedEvent.adminProxy, } @@ -511,6 +508,10 @@ const impersonateAccount = (address: string) => .then(() => ethers.getSigner(address)) describe('ArbRollup', () => { + it('should initialize contracts', async function () { + accounts = await initializeAccounts() + }) + it('should initialize', async function () { const { rollupAdmin: rollupAdminContract, @@ -518,6 +519,7 @@ describe('ArbRollup', () => { bridge: bridgeContract, admin: adminI, validators: validatorsI, + batchPosterManager: batchPosterManagerI, upgradeExecutorAddress, adminproxy: adminproxyAddress, } = await setup() @@ -529,6 +531,7 @@ describe('ArbRollup', () => { upgradeExecutor = upgradeExecutorAddress adminproxy = adminproxyAddress rollup = new RollupContract(rollupUser.connect(validators[0])) + batchPosterManager = batchPosterManagerI }) it('should only initialize once', async function () { @@ -1119,6 +1122,7 @@ describe('ArbRollup', () => { rollupUser: rollupUserContract, admin: adminI, validators: validatorsI, + batchPosterManager: batchPosterManagerI, upgradeExecutorAddress, } = await setup() rollupAdmin = rollupAdminContract @@ -1127,6 +1131,7 @@ describe('ArbRollup', () => { validators = validatorsI upgradeExecutor = upgradeExecutorAddress rollup = new RollupContract(rollupUser.connect(validators[0])) + batchPosterManager = batchPosterManagerI }) it('should stake on initial node again', async function () { @@ -1377,10 +1382,79 @@ describe('ArbRollup', () => { ).to.eq('view') }) - it('should fail the chainid fork check', async function () { - await expect(sequencerInbox.removeDelayAfterFork()).to.revertedWith( - 'NotForked()' + it('can set is sequencer', async function () { + const testAddress = await accounts[9].getAddress() + expect(await sequencerInbox.isSequencer(testAddress)).to.be.false + await expect( + sequencerInbox.setIsSequencer(testAddress, true) + ).to.revertedWith( + `NotBatchPosterManager("${await sequencerInbox.signer.getAddress()}")` ) + expect(await sequencerInbox.isSequencer(testAddress)).to.be.false + + await ( + await sequencerInbox + .connect(batchPosterManager) + .setIsSequencer(testAddress, true) + ).wait() + + expect(await sequencerInbox.isSequencer(testAddress)).to.be.true + + await ( + await sequencerInbox + .connect(batchPosterManager) + .setIsSequencer(testAddress, false) + ).wait() + + expect(await sequencerInbox.isSequencer(testAddress)).to.be.false + }) + + it('can set a batch poster', async function () { + const testAddress = await accounts[9].getAddress() + expect(await sequencerInbox.isBatchPoster(testAddress)).to.be.false + await expect( + sequencerInbox.setIsBatchPoster(testAddress, true) + ).to.revertedWith( + `NotBatchPosterManager("${await sequencerInbox.signer.getAddress()}")` + ) + expect(await sequencerInbox.isBatchPoster(testAddress)).to.be.false + + await ( + await sequencerInbox + .connect(batchPosterManager) + .setIsBatchPoster(testAddress, true) + ).wait() + + expect(await sequencerInbox.isBatchPoster(testAddress)).to.be.true + + await ( + await sequencerInbox + .connect(batchPosterManager) + .setIsBatchPoster(testAddress, false) + ).wait() + + expect(await sequencerInbox.isBatchPoster(testAddress)).to.be.false + }) + + it('can set batch poster manager', async function () { + const testManager = await accounts[8].getAddress() + expect(await sequencerInbox.batchPosterManager()).to.eq( + await batchPosterManager.getAddress() + ) + await expect( + sequencerInbox.connect(accounts[8]).setBatchPosterManager(testManager) + ).to.revertedWith(`NotOwner("${testManager}", "${upgradeExecutor}")`) + expect(await sequencerInbox.batchPosterManager()).to.eq( + await batchPosterManager.getAddress() + ) + + await ( + await sequencerInbox + .connect(await impersonateAccount(upgradeExecutor)) + .setBatchPosterManager(testManager) + ).wait() + + expect(await sequencerInbox.batchPosterManager()).to.eq(testManager) }) it('should fail the batch poster check', async function () { diff --git a/test/contract/sequencerInbox.spec.4844.ts b/test/contract/sequencerInbox.spec.4844.ts index 681e78dd9..bf3cdc088 100644 --- a/test/contract/sequencerInbox.spec.4844.ts +++ b/test/contract/sequencerInbox.spec.4844.ts @@ -23,10 +23,11 @@ import { JsonRpcProvider, TransactionReceipt, } from '@ethersproject/providers' -import { expect, util } from 'chai' +import { expect } from 'chai' import { Bridge, Bridge__factory, + GasRefunder__factory, Inbox, Inbox__factory, MessageTester, @@ -43,13 +44,16 @@ import { MessageDeliveredEvent, } from '../../build/types/src/bridge/Bridge' import { Signer, Wallet, constants, utils } from 'ethers' -import { keccak256, solidityKeccak256, solidityPack } from 'ethers/lib/utils' +import { + keccak256, + parseEther, + solidityKeccak256, + solidityPack, +} from 'ethers/lib/utils' import { Toolkit4844 } from './toolkit4844' import { SequencerInbox } from '../../build/types/src/bridge/SequencerInbox' -import { execSync } from 'child_process' -import { wait } from '@arbitrum/sdk/dist/lib/utils/lib' import { InboxMessageDeliveredEvent } from '../../build/types/src/bridge/AbsInbox' -import { SequencerBatchDeliveredEvent } from '../../build/types/src/bridge/ISequencerInbox' +import { SequencerBatchDeliveredEvent } from '../../build/types/src/bridge/AbsBridge' const mineBlocks = async ( wallet: Wallet, @@ -225,6 +229,7 @@ describe('SequencerInbox', async () => { sequencerInbox: string messageTester: string batchPoster: string + gasRefunder: string } ) => { return { @@ -240,13 +245,18 @@ describe('SequencerInbox', async () => { addresses.messageTester, deployer ), + gasRefunder: GasRefunder__factory.connect( + addresses.gasRefunder, + deployer + ), } } const setupSequencerInbox = async ( fundingWallet: Wallet, maxDelayBlocks = 10, - maxDelayTime = 0 + maxDelayTime = 0, + isUsingFeeToken = false ) => { const accounts = await fundAccounts(fundingWallet, 5, utils.parseEther('1')) @@ -258,13 +268,14 @@ describe('SequencerInbox', async () => { const batchPoster = accounts[4] // update the addresses below and uncomment to avoid redeploying - // return connectAddreses(user, deployer, batchPoster, { + // return connectAddreses(user, deployer, batchPoster, { // user: '0x870204e93ca485a6676E264EB0d7df4cD0246203', - // bridge: '0x00eb941BD8B89E0396A983c870fa74DA4aC5ecFB', - // inbox: '0x68BCf73c6b36ae3f20b2fD06c2d4651538Ae02a6', + // bridge: '0x95491D63100cc7a21155247329007ca294fC752B', + // inbox: '0x00eb941BD8B89E0396A983c870fa74DA4aC5ecFB', // sequencerInbox: '0x87fEe873425A65Bb2A11dFf6E15B4Ce25e7AFccD', - // messageTester: '0x33B1355B2F3BE116eB1c8226CF3B0a433259459C', + // messageTester: '0x68BCf73c6b36ae3f20b2fD06c2d4651538Ae02a6', // batchPoster: '0x328375c90F01Dcb114888DA36e3832F69Ad0BB57', + // gasRefunder: '0x33B1355B2F3BE116eB1c8226CF3B0a433259459C' // }) const rollupMockFac = new RollupMock__factory(deployer) @@ -272,16 +283,6 @@ describe('SequencerInbox', async () => { await rollupOwner.getAddress() ) - const dataHashReader = await Toolkit4844.deployDataHashReader(fundingWallet) - const blobBasefeeReader = await Toolkit4844.deployBlobBasefeeReader( - fundingWallet - ) - const sequencerInboxFac = new SequencerInbox__factory(deployer) - const seqInboxTemplate = await sequencerInboxFac.deploy( - 117964, - dataHashReader.address, - blobBasefeeReader.address - ) const inboxFac = new Inbox__factory(deployer) const inboxTemplate = await inboxFac.deploy(117964) @@ -290,7 +291,6 @@ describe('SequencerInbox', async () => { await rollupMock.deployed() await inboxTemplate.deployed() await bridgeTemplate.deployed() - await seqInboxTemplate.deployed() const transparentUpgradeableProxyFac = new TransparentUpgradeableProxy__factory(deployer) @@ -300,11 +300,7 @@ describe('SequencerInbox', async () => { adminAddr, '0x' ) - const sequencerInboxProxy = await transparentUpgradeableProxyFac.deploy( - seqInboxTemplate.address, - adminAddr, - '0x' - ) + const inboxProxy = await transparentUpgradeableProxyFac.deploy( inboxTemplate.address, adminAddr, @@ -312,24 +308,29 @@ describe('SequencerInbox', async () => { ) await bridgeProxy.deployed() await inboxProxy.deployed() - await sequencerInboxProxy.deployed() + const reader4844 = await Toolkit4844.deployReader4844(fundingWallet) const bridge = await bridgeFac.attach(bridgeProxy.address).connect(user) const bridgeAdmin = await bridgeFac .attach(bridgeProxy.address) .connect(rollupOwner) - const sequencerInbox = await sequencerInboxFac - .attach(sequencerInboxProxy.address) - .connect(user) await (await bridgeAdmin.initialize(rollupMock.address)).wait() - await ( - await sequencerInbox.initialize(bridgeProxy.address, { + + const sequencerInboxFac = new SequencerInbox__factory(deployer) + const sequencerInbox = await sequencerInboxFac.deploy( + bridge.address, + { delayBlocks: maxDelayBlocks, - delaySeconds: maxDelayTime, futureBlocks: 10, + delaySeconds: maxDelayTime, futureSeconds: 3000, - }) - ).wait() + }, + 117964, + reader4844.address, + isUsingFeeToken, + { gasLimit: 15000000 } + ) + await sequencerInbox.deployed() const inbox = await inboxFac.attach(inboxProxy.address).connect(user) @@ -346,6 +347,21 @@ describe('SequencerInbox', async () => { await (await bridgeAdmin.setSequencerInbox(sequencerInbox.address)).wait() const messageTester = await new MessageTester__factory(deployer).deploy() await messageTester.deployed() + + const gasRefunderFac = new GasRefunder__factory(deployer) + const gasRefunder = await gasRefunderFac.deploy() + await gasRefunder.deployed() + // fund the gas refunder + await ( + await deployer.sendTransaction({ + to: gasRefunder.address, + value: parseEther('0.2'), + }) + ).wait() + await (await gasRefunder.allowContracts([sequencerInbox.address])).wait() + await (await gasRefunder.allowRefundees([batchPoster.address])).wait() + await (await gasRefunder.setExtraGasMargin(35000)).wait() + const res = { user, bridge: bridge, @@ -353,6 +369,7 @@ describe('SequencerInbox', async () => { sequencerInbox: sequencerInbox, messageTester, batchPoster, + gasRefunder, } // comment this in to print the addresses that can then be re-used to avoid redeployment @@ -369,8 +386,15 @@ describe('SequencerInbox', async () => { const prov = new JsonRpcProvider('http://127.0.0.1:8545') const wallet = new Wallet(privKey).connect(prov) - const { user, inbox, bridge, messageTester, sequencerInbox, batchPoster } = - await setupSequencerInbox(wallet) + const { + user, + inbox, + bridge, + messageTester, + sequencerInbox, + batchPoster, + gasRefunder, + } = await setupSequencerInbox(wallet) await sendDelayedTx( user, @@ -386,20 +410,20 @@ describe('SequencerInbox', async () => { ) const subMessageCount = await bridge.sequencerReportedSubMessageCount() - const batchSendTx = await sequencerInbox - .connect(batchPoster) - .functions[ - 'addSequencerL2BatchFromOrigin(uint256,bytes,uint256,address,uint256,uint256)' - ]( - await bridge.sequencerMessageCount(), - '0x0042', - await bridge.delayedMessageCount(), - constants.AddressZero, - subMessageCount, - subMessageCount.add(1) - ) - - await batchSendTx.wait() + const balBefore = await batchPoster.getBalance() + await ( + await sequencerInbox + .connect(batchPoster) + .functions['addSequencerL2BatchFromOrigin']( + await bridge.sequencerMessageCount(), + '0x0042', + await bridge.delayedMessageCount(), + gasRefunder.address, + subMessageCount, + subMessageCount.add(1) + ) + ).wait() + expect((await batchPoster.getBalance()).gt(balBefore), 'Refund not enough') }) it('can send blob batch', async () => { @@ -408,8 +432,15 @@ describe('SequencerInbox', async () => { const prov = new JsonRpcProvider('http://127.0.0.1:8545') const wallet = new Wallet(privKey).connect(prov) - const { user, inbox, bridge, messageTester, sequencerInbox, batchPoster } = - await setupSequencerInbox(wallet) + const { + user, + inbox, + bridge, + messageTester, + sequencerInbox, + batchPoster, + gasRefunder, + } = await setupSequencerInbox(wallet) await sendDelayedTx( user, @@ -427,21 +458,25 @@ describe('SequencerInbox', async () => { const afterDelayedMessagesRead = await bridge.delayedMessageCount() const sequenceNumber = await bridge.sequencerMessageCount() + const balBefore = await batchPoster.getBalance() const txHash = await Toolkit4844.sendBlobTx( batchPoster.privateKey.substring(2), sequencerInbox.address, ['0x0142', '0x0143'], sequencerInbox.interface.encodeFunctionData( - 'addSequencerL2BatchFromBlob', + 'addSequencerL2BatchFromBlobs', [ sequenceNumber, afterDelayedMessagesRead, - constants.AddressZero, + gasRefunder.address, subMessageCount, subMessageCount.add(1), ] ) ) + + expect((await batchPoster.getBalance()).gt(balBefore), 'Refund not enough') + const batchSendTx = await Toolkit4844.getTx(txHash) const blobHashes = (batchSendTx as any)['blobVersionedHashes'] as string[] const batchSendReceipt = await Toolkit4844.getTxReceipt(txHash) @@ -461,16 +496,15 @@ describe('SequencerInbox', async () => { afterDelayedMessagesRead.toNumber(), blobHashes ) - const batchDeliveredEvent = batchSendReceipt.logs .filter( (b: any) => - b.address.toLowerCase() === sequencerInbox.address.toLowerCase() && + b.address.toLowerCase() === bridge.address.toLowerCase() && b.topics[0] === - sequencerInbox.interface.getEventTopic('SequencerBatchDelivered') + bridge.interface.getEventTopic('SequencerBatchDelivered') ) .map( - (l: any) => sequencerInbox.interface.parseLog(l).args + (l: any) => bridge.interface.parseLog(l).args )[0] as SequencerBatchDeliveredEvent['args'] if (!batchDeliveredEvent) throw new Error('missing batch event') @@ -523,8 +557,6 @@ describe('SequencerInbox', async () => { '0x' + inboxMsgDeliveredEvent.data.substring(234, 298) const spendingExtraGas = '0x' + inboxMsgDeliveredEvent.data.substring(298, 314) - const spendingBlobBasefee = - '0x' + inboxMsgDeliveredEvent.data.substring(314, 378) expect( BigNumber.from(spendingTimestamp).eq(blockTimestamp), @@ -547,14 +579,9 @@ describe('SequencerInbox', async () => { `spending basefee: ${BigNumber.from(spendingBlockBaseFee).toString()}` ).to.eq(true) expect( - BigNumber.from(spendingExtraGas).eq(0), + BigNumber.from(spendingExtraGas).gt(0), // blob spending is normalized into extra gas `spending extra gas: ${BigNumber.from(spendingExtraGas).toString()}` ).to.eq(true) - // we expect a very low - 1 - basefee since we havent sent many blobs - expect( - BigNumber.from(spendingBlobBasefee).eq(1), - `spending blob basefee: ${BigNumber.from(spendingBlobBasefee).toString()}` - ).to.eq(true) }) const getTimeBounds = async ( @@ -567,19 +594,18 @@ describe('SequencerInbox', async () => { minTimestamp: number maxTimestamp: number }> => { - const [delayBlocks, futureBlocks, delaySeconds, futureSeconds] = - await sequencerInbox.maxTimeVariation() + const maxTimeVariation = await sequencerInbox.maxTimeVariation() return { minBlocks: - blockNumber > delayBlocks.toNumber() - ? blockNumber - delayBlocks.toNumber() + blockNumber > maxTimeVariation[0].toNumber() + ? blockNumber - maxTimeVariation[0].toNumber() : 0, - maxBlock: blockNumber + futureBlocks.toNumber(), + maxBlock: blockNumber + maxTimeVariation[1].toNumber(), minTimestamp: - blockTimestamp > delaySeconds.toNumber() - ? blockTimestamp - delaySeconds.toNumber() + blockTimestamp > maxTimeVariation[2].toNumber() + ? blockTimestamp - maxTimeVariation[2].toNumber() : 0, - maxTimestamp: blockTimestamp + futureSeconds.toNumber(), + maxTimestamp: blockTimestamp + maxTimeVariation[3].toNumber(), } } diff --git a/test/contract/sequencerInboxForceInclude.spec.ts b/test/contract/sequencerInboxForceInclude.spec.ts index 3aefb3fcb..9dda81056 100644 --- a/test/contract/sequencerInboxForceInclude.spec.ts +++ b/test/contract/sequencerInboxForceInclude.spec.ts @@ -225,22 +225,12 @@ describe('SequencerInboxForceInclude', async () => { const dummyRollup = accounts[2] const rollupOwner = accounts[3] const batchPoster = accounts[4] + const batchPosterManager = accounts[5] const rollupMockFac = (await ethers.getContractFactory( 'RollupMock' )) as RollupMock__factory const rollup = await rollupMockFac.deploy(await rollupOwner.getAddress()) - - const dataHashReader = await Toolkit4844.deployDataHashReader(admin) - const blobBasefeeReader = await Toolkit4844.deployBlobBasefeeReader(admin) - const sequencerInboxFac = (await ethers.getContractFactory( - 'SequencerInbox' - )) as SequencerInbox__factory - const seqInboxTemplate = await sequencerInboxFac.deploy( - 117964, - dataHashReader.address, - blobBasefeeReader.address - ) const inboxFac = (await ethers.getContractFactory( 'Inbox' )) as Inbox__factory @@ -258,11 +248,7 @@ describe('SequencerInboxForceInclude', async () => { adminAddr, '0x' ) - const sequencerInboxProxy = await transparentUpgradeableProxyFac.deploy( - seqInboxTemplate.address, - adminAddr, - '0x' - ) + const inboxProxy = await transparentUpgradeableProxyFac.deploy( inboxTemplate.address, adminAddr, @@ -272,17 +258,25 @@ describe('SequencerInboxForceInclude', async () => { const bridgeAdmin = await bridgeFac .attach(bridgeProxy.address) .connect(rollupOwner) - const sequencerInbox = await sequencerInboxFac - .attach(sequencerInboxProxy.address) - .connect(user) await bridge.initialize(rollup.address) - await sequencerInbox.initialize(bridgeProxy.address, { - delayBlocks: maxDelayBlocks, - delaySeconds: maxDelayTime, - futureBlocks: 10, - futureSeconds: 3000, - }) + const reader4844 = await Toolkit4844.deployReader4844(admin) + + const sequencerInboxFac = (await ethers.getContractFactory( + 'SequencerInbox' + )) as SequencerInbox__factory + const sequencerInbox = await sequencerInboxFac.deploy( + bridgeProxy.address, + { + delayBlocks: maxDelayBlocks, + delaySeconds: maxDelayTime, + futureBlocks: 10, + futureSeconds: 3000, + }, + 117964, + reader4844.address, + false + ) await ( await sequencerInbox @@ -345,9 +339,7 @@ describe('SequencerInboxForceInclude', async () => { await ( await sequencerInbox .connect(batchPoster) - .functions[ - 'addSequencerL2BatchFromOrigin(uint256,bytes,uint256,address,uint256,uint256)' - ]( + .addSequencerL2BatchFromOrigin( 0, data, messagesRead, @@ -375,9 +367,9 @@ describe('SequencerInboxForceInclude', async () => { BigNumber.from(10), '0x1010' ) + const maxTimeVariation = await sequencerInbox.maxTimeVariation() - const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() - await mineBlocks(delayBlocks.toNumber()) + await mineBlocks(maxTimeVariation[0].toNumber()) await forceIncludeMessages( sequencerInbox, @@ -420,8 +412,8 @@ describe('SequencerInboxForceInclude', async () => { '0xdeadface' ) - const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() - await mineBlocks(delayBlocks.toNumber()) + const maxTimeVariation = await sequencerInbox.maxTimeVariation() + await mineBlocks(maxTimeVariation[0].toNumber()) await forceIncludeMessages( sequencerInbox, @@ -485,8 +477,8 @@ describe('SequencerInboxForceInclude', async () => { '0x10101010' ) - const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() - await mineBlocks(delayBlocks.toNumber()) + const maxTimeVariation = await sequencerInbox.maxTimeVariation() + await mineBlocks(maxTimeVariation[0].toNumber()) await forceIncludeMessages( sequencerInbox, @@ -516,8 +508,8 @@ describe('SequencerInboxForceInclude', async () => { '0x1010' ) - const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() - await mineBlocks(delayBlocks.toNumber() - 1, 5) + const maxTimeVariation = await sequencerInbox.maxTimeVariation() + await mineBlocks(maxTimeVariation[0].toNumber() - 1, 5) await forceIncludeMessages( sequencerInbox, @@ -548,10 +540,10 @@ describe('SequencerInboxForceInclude', async () => { '0x1010' ) - const [delayBlocks, , ,] = await sequencerInbox.maxTimeVariation() + const maxTimeVariation = await sequencerInbox.maxTimeVariation() // mine a lot of blocks - but use a short time per block // this should mean enough blocks have passed, but not enough time - await mineBlocks(delayBlocks.toNumber() + 1, 5) + await mineBlocks(maxTimeVariation[0].toNumber() + 1, 5) await forceIncludeMessages( sequencerInbox, diff --git a/test/contract/toolkit4844.ts b/test/contract/toolkit4844.ts index bca1ea144..b0271641b 100644 --- a/test/contract/toolkit4844.ts +++ b/test/contract/toolkit4844.ts @@ -1,15 +1,9 @@ import { execSync } from 'child_process' import { ContractFactory, Signer, Wallet, ethers } from 'ethers' import * as http from 'http' -import { - IBlobBasefeeReader, - IBlobBasefeeReader__factory, - IDataHashReader, - IDataHashReader__factory, -} from '../../build/types' +import { IReader4844, IReader4844__factory } from '../../build/types' import { JsonRpcProvider } from '@ethersproject/providers' -import { bytecode as blobBasefeeReaderBytecode } from '../../out/yul/BlobBasefeeReader.yul/BlobBasefeeReader.json' -import { bytecode as dataHeashesReaderBytecode } from '../../out/yul/DataHashesReader.yul/DataHashesReader.json' +import { bytecode as Reader4844Bytecode } from '../../out/yul/Reader4844.yul/Reader4844.json' const wait = async (ms: number) => new Promise((res, rej) => { @@ -17,7 +11,7 @@ const wait = async (ms: number) => }) export class Toolkit4844 { - public static DATA_BLOB_HEADER_FLAG = '0x40' + public static DATA_BLOB_HEADER_FLAG = '0x50' // 0x40 | 0x10 public static postDataToGeth(body: any): Promise { return new Promise((resolve, reject) => { @@ -128,34 +122,15 @@ export class Toolkit4844 { } } - public static async deployDataHashReader( - wallet: Signer - ): Promise { + public static async deployReader4844(wallet: Signer): Promise { const contractFactory = new ContractFactory( - IDataHashReader__factory.abi, - dataHeashesReaderBytecode, + IReader4844__factory.abi, + Reader4844Bytecode, wallet ) - const dataHashReader = await contractFactory.deploy() - await dataHashReader.deployed() + const reader4844 = await contractFactory.deploy() + await reader4844.deployed() - return IDataHashReader__factory.connect(dataHashReader.address, wallet) - } - - public static async deployBlobBasefeeReader( - wallet: Signer - ): Promise { - const contractFactory = new ContractFactory( - IBlobBasefeeReader__factory.abi, - blobBasefeeReaderBytecode, - wallet - ) - const blobBasefeeReader = await contractFactory.deploy() - await blobBasefeeReader.deployed() - - return IBlobBasefeeReader__factory.connect( - blobBasefeeReader.address, - wallet - ) + return IReader4844__factory.connect(reader4844.address, wallet) } } diff --git a/test/foundry/AbsBridge.t.sol b/test/foundry/AbsBridge.t.sol index 1ce9737bf..7f30beb5f 100644 --- a/test/foundry/AbsBridge.t.sol +++ b/test/foundry/AbsBridge.t.sol @@ -22,6 +22,14 @@ abstract contract AbsBridgeTest is Test { address public outbox = address(1002); address public seqInbox = address(1003); + IBridge.TimeBounds timeBounds = + IBridge.TimeBounds({ + minTimestamp: uint64(block.timestamp + 1), + maxTimestamp: uint64(block.timestamp + 10), + minBlockNumber: uint64(block.number + 1), + maxBlockNumber: uint64(block.number + 10) + }); + /* solhint-disable func-name-mixedcase */ function test_enqueueSequencerMessage_NoDelayedMsgs() public { vm.prank(rollup); @@ -38,7 +46,9 @@ abstract contract AbsBridgeTest is Test { dataHash, afterDelayedMessagesRead, prevMessageCount, - newMessageCount + newMessageCount, + timeBounds, + IBridge.BatchDataLocation.TxInput ); // checks @@ -78,7 +88,9 @@ abstract contract AbsBridgeTest is Test { dataHash, afterDelayedMessagesRead, prevMessageCount, - newMessageCount + newMessageCount, + timeBounds, + IBridge.BatchDataLocation.TxInput ); // checks @@ -107,7 +119,14 @@ abstract contract AbsBridgeTest is Test { bridge.submitBatchSpendingReport(address(1), keccak256("1")); bridge.submitBatchSpendingReport(address(2), keccak256("2")); bridge.submitBatchSpendingReport(address(3), keccak256("3")); - bridge.enqueueSequencerMessage(keccak256("seq"), 2, 0, 10); + bridge.enqueueSequencerMessage( + keccak256("seq"), + 2, + 0, + 10, + timeBounds, + IBridge.BatchDataLocation.SeparateBatchEvent + ); vm.stopPrank(); // enqueue 2nd sequencer msg with additional delayed msgs @@ -121,7 +140,9 @@ abstract contract AbsBridgeTest is Test { dataHash, afterDelayedMessagesRead, prevMessageCount, - newMessageCount + newMessageCount, + timeBounds, + IBridge.BatchDataLocation.TxInput ); // checks @@ -150,7 +171,14 @@ abstract contract AbsBridgeTest is Test { bridge.submitBatchSpendingReport(address(1), keccak256("1")); bridge.submitBatchSpendingReport(address(2), keccak256("2")); bridge.submitBatchSpendingReport(address(3), keccak256("3")); - bridge.enqueueSequencerMessage(keccak256("seq"), 2, 0, 10); + bridge.enqueueSequencerMessage( + keccak256("seq"), + 2, + 0, + 10, + timeBounds, + IBridge.BatchDataLocation.SeparateBatchEvent + ); vm.stopPrank(); // setting wrong msg counter shall revert @@ -159,13 +187,27 @@ abstract contract AbsBridgeTest is Test { vm.expectRevert( abi.encodeWithSelector(BadSequencerMessageNumber.selector, 10, incorrectPrevMsgCount) ); - bridge.enqueueSequencerMessage(keccak256("seq"), 2, incorrectPrevMsgCount, 10); + bridge.enqueueSequencerMessage( + keccak256("seq"), + 2, + incorrectPrevMsgCount, + 10, + timeBounds, + IBridge.BatchDataLocation.SeparateBatchEvent + ); } function test_enqueueSequencerMessage_revert_NonSeqInboxCall() public { // enqueueSequencerMessage shall revert vm.expectRevert(abi.encodeWithSelector(NotSequencerInbox.selector, address(this))); - bridge.enqueueSequencerMessage(keccak256("msg"), 0, 0, 10); + bridge.enqueueSequencerMessage( + keccak256("msg"), + 0, + 0, + 10, + timeBounds, + IBridge.BatchDataLocation.SeparateBatchEvent + ); } function test_submitBatchSpendingReport() public { diff --git a/test/foundry/AbsInbox.t.sol b/test/foundry/AbsInbox.t.sol index 34eb35f7b..c97bb3ef1 100644 --- a/test/foundry/AbsInbox.t.sol +++ b/test/foundry/AbsInbox.t.sol @@ -63,13 +63,18 @@ abstract contract AbsInboxTest is Test { // mock the owner() call on rollup address mockRollupOwner = address(10_000); vm.mockCall( - rollup, abi.encodeWithSelector(IOwnable.owner.selector), abi.encode(mockRollupOwner) + rollup, + abi.encodeWithSelector(IOwnable.owner.selector), + abi.encode(mockRollupOwner) ); // setAllowList shall revert vm.expectRevert( abi.encodeWithSelector( - NotRollupOrOwner.selector, address(this), rollup, mockRollupOwner + NotRollupOrOwner.selector, + address(this), + rollup, + mockRollupOwner ) ); @@ -126,13 +131,18 @@ abstract contract AbsInboxTest is Test { // mock the owner() call on rollup address mockRollupOwner = address(10_000); vm.mockCall( - rollup, abi.encodeWithSelector(IOwnable.owner.selector), abi.encode(mockRollupOwner) + rollup, + abi.encodeWithSelector(IOwnable.owner.selector), + abi.encode(mockRollupOwner) ); // setAllowListEnabled shall revert vm.expectRevert( abi.encodeWithSelector( - NotRollupOrOwner.selector, address(this), rollup, mockRollupOwner + NotRollupOrOwner.selector, + address(this), + rollup, + mockRollupOwner ) ); @@ -141,7 +151,9 @@ abstract contract AbsInboxTest is Test { function test_pause() public { assertEq( - (PausableUpgradeable(address(inbox))).paused(), false, "Invalid initial paused state" + (PausableUpgradeable(address(inbox))).paused(), + false, + "Invalid initial paused state" ); vm.prank(rollup); @@ -154,7 +166,9 @@ abstract contract AbsInboxTest is Test { vm.prank(rollup); inbox.pause(); assertEq( - (PausableUpgradeable(address(inbox))).paused(), true, "Invalid initial paused state" + (PausableUpgradeable(address(inbox))).paused(), + true, + "Invalid initial paused state" ); vm.prank(rollup); inbox.unpause(); @@ -287,8 +301,14 @@ abstract contract AbsInboxTest is Test { // send TX vm.prank(user, user); - uint256 msgNum = - inbox.sendUnsignedTransaction(gasLimit, maxFeePerGas, nonce, user, value, data); + uint256 msgNum = inbox.sendUnsignedTransaction( + gasLimit, + maxFeePerGas, + nonce, + user, + value, + data + ); //// checks assertEq(msgNum, 0, "Invalid msgNum"); diff --git a/test/foundry/BridgeCreator.t.sol b/test/foundry/BridgeCreator.t.sol index 25bdddc83..f05f1b60a 100644 --- a/test/foundry/BridgeCreator.t.sol +++ b/test/foundry/BridgeCreator.t.sol @@ -13,25 +13,18 @@ contract BridgeCreatorTest is Test { BridgeCreator public creator; address public owner = address(100); uint256 public constant MAX_DATA_SIZE = 117_964; - IDataHashReader dummyDataHashReader = IDataHashReader(address(137)); - IBlobBasefeeReader dummyBlobBasefeeReader = IBlobBasefeeReader(address(138)); + IReader4844 dummyReader4844 = IReader4844(address(137)); - BridgeCreator.BridgeContracts ethBasedTemplates = - BridgeCreator.BridgeContracts({ + BridgeCreator.BridgeTemplates ethBasedTemplates = + BridgeCreator.BridgeTemplates({ bridge: new Bridge(), - sequencerInbox: new SequencerInbox( - MAX_DATA_SIZE, - dummyDataHashReader, - dummyBlobBasefeeReader - ), inbox: new Inbox(MAX_DATA_SIZE), rollupEventInbox: new RollupEventInbox(), outbox: new Outbox() }); - BridgeCreator.BridgeContracts erc20BasedTemplates = - BridgeCreator.BridgeContracts({ + BridgeCreator.BridgeTemplates erc20BasedTemplates = + BridgeCreator.BridgeTemplates({ bridge: new ERC20Bridge(), - sequencerInbox: ethBasedTemplates.sequencerInbox, inbox: new ERC20Inbox(MAX_DATA_SIZE), rollupEventInbox: new ERC20RollupEventInbox(), outbox: new ERC20Outbox() @@ -42,30 +35,34 @@ contract BridgeCreatorTest is Test { creator = new BridgeCreator(ethBasedTemplates, erc20BasedTemplates); } - function getEthBasedTemplates() internal returns (BridgeCreator.BridgeContracts memory) { - BridgeCreator.BridgeContracts memory templates; - ( - templates.bridge, - templates.sequencerInbox, - templates.inbox, - templates.rollupEventInbox, - templates.outbox - ) = creator.ethBasedTemplates(); + function getEthBasedTemplates() internal returns (BridgeCreator.BridgeTemplates memory) { + BridgeCreator.BridgeTemplates memory templates; + (templates.bridge, templates.inbox, templates.rollupEventInbox, templates.outbox) = creator + .ethBasedTemplates(); return templates; } - function getErc20BasedTemplates() internal returns (BridgeCreator.BridgeContracts memory) { - BridgeCreator.BridgeContracts memory templates; - ( - templates.bridge, - templates.sequencerInbox, - templates.inbox, - templates.rollupEventInbox, - templates.outbox - ) = creator.erc20BasedTemplates(); + function getErc20BasedTemplates() internal returns (BridgeCreator.BridgeTemplates memory) { + BridgeCreator.BridgeTemplates memory templates; + (templates.bridge, templates.inbox, templates.rollupEventInbox, templates.outbox) = creator + .erc20BasedTemplates(); return templates; } + function assertEq( + BridgeCreator.BridgeTemplates memory a, + BridgeCreator.BridgeTemplates memory b + ) internal { + assertEq(address(a.bridge), address(b.bridge), "Invalid bridge"); + assertEq(address(a.inbox), address(b.inbox), "Invalid inbox"); + assertEq( + address(a.rollupEventInbox), + address(b.rollupEventInbox), + "Invalid rollup event inbox" + ); + assertEq(address(a.outbox), address(b.outbox), "Invalid outbox"); + } + function assertEq( BridgeCreator.BridgeContracts memory a, BridgeCreator.BridgeContracts memory b @@ -88,9 +85,8 @@ contract BridgeCreatorTest is Test { } function test_updateTemplates() public { - BridgeCreator.BridgeContracts memory templs = BridgeCreator.BridgeContracts({ + BridgeCreator.BridgeTemplates memory templs = BridgeCreator.BridgeTemplates({ bridge: Bridge(address(200)), - sequencerInbox: SequencerInbox(address(201)), inbox: Inbox(address(202)), rollupEventInbox: RollupEventInbox(address(203)), outbox: Outbox(address(204)) @@ -103,9 +99,8 @@ contract BridgeCreatorTest is Test { } function test_updateERC20Templates() public { - BridgeCreator.BridgeContracts memory templs = BridgeCreator.BridgeContracts({ + BridgeCreator.BridgeTemplates memory templs = BridgeCreator.BridgeTemplates({ bridge: ERC20Bridge(address(400)), - sequencerInbox: SequencerInbox(address(401)), inbox: ERC20Inbox(address(402)), rollupEventInbox: ERC20RollupEventInbox(address(403)), outbox: ERC20Outbox(address(404)) @@ -127,13 +122,13 @@ contract BridgeCreatorTest is Test { 30, 40 ); - timeVars.delayBlocks; - BridgeCreator.BridgeContracts memory contracts = creator.createBridge( proxyAdmin, rollup, nativeToken, - timeVars + timeVars, + MAX_DATA_SIZE, + dummyReader4844 ); ( IBridge bridge, @@ -150,22 +145,17 @@ contract BridgeCreatorTest is Test { ); // bridge - assertEq(address(bridge.rollup()), rollup, "Invalid rollup ref"); + assertEq(address(bridge.rollup()), rollup, "Invalid bridge rollup ref"); assertEq(bridge.activeOutbox(), address(0), "Invalid activeOutbox ref"); // seqInbox assertEq(address(seqInbox.bridge()), address(bridge), "Invalid bridge ref"); - assertEq(address(seqInbox.rollup()), rollup, "Invalid rollup ref"); - ( - uint256 _delayBlocks, - uint256 _futureBlocks, - uint256 _delaySeconds, - uint256 _futureSeconds - ) = seqInbox.maxTimeVariation(); - assertEq(_delayBlocks, timeVars.delayBlocks, "Invalid delayBlocks"); - assertEq(_futureBlocks, timeVars.futureBlocks, "Invalid futureBlocks"); - assertEq(_delaySeconds, timeVars.delaySeconds, "Invalid delaySeconds"); - assertEq(_futureSeconds, timeVars.futureSeconds, "Invalid futureSeconds"); + assertEq(address(seqInbox.rollup()), rollup, "Invalid seq rollup ref"); + (uint256 delayBlocks, uint256 futureBlocks, uint256 delaySeconds, uint256 futureSeconds) = seqInbox.maxTimeVariation(); + assertEq(delayBlocks, timeVars.delayBlocks, "Invalid delayBlocks"); + assertEq(futureBlocks, timeVars.futureBlocks, "Invalid futureBlocks"); + assertEq(delaySeconds, timeVars.delaySeconds, "Invalid delaySeconds"); + assertEq(futureSeconds, timeVars.futureSeconds, "Invalid futureSeconds"); // inbox assertEq(address(inbox.bridge()), address(bridge), "Invalid bridge ref"); @@ -175,11 +165,11 @@ contract BridgeCreatorTest is Test { // rollup event inbox assertEq(address(eventInbox.bridge()), address(bridge), "Invalid bridge ref"); - assertEq(address(eventInbox.rollup()), rollup, "Invalid rollup ref"); + assertEq(address(eventInbox.rollup()), rollup, "Invalid event inbox rollup ref"); // outbox assertEq(address(outbox.bridge()), address(bridge), "Invalid bridge ref"); - assertEq(address(outbox.rollup()), rollup, "Invalid rollup ref"); + assertEq(address(outbox.rollup()), rollup, "Invalid outbox rollup ref"); // revert fetching native token vm.expectRevert(); @@ -198,13 +188,13 @@ contract BridgeCreatorTest is Test { 30, 40 ); - timeVars.delayBlocks; // TODO: what is this? - BridgeCreator.BridgeContracts memory contracts = creator.createBridge( proxyAdmin, rollup, nativeToken, - timeVars + timeVars, + MAX_DATA_SIZE, + dummyReader4844 ); ( IBridge bridge, @@ -221,7 +211,7 @@ contract BridgeCreatorTest is Test { ); // bridge - assertEq(address(bridge.rollup()), rollup, "Invalid rollup ref"); + assertEq(address(bridge.rollup()), rollup, "Invalid bridge rollup ref"); assertEq( address(IERC20Bridge(address(bridge)).nativeToken()), nativeToken, @@ -231,17 +221,12 @@ contract BridgeCreatorTest is Test { // seqInbox assertEq(address(seqInbox.bridge()), address(bridge), "Invalid bridge ref"); - assertEq(address(seqInbox.rollup()), rollup, "Invalid rollup ref"); - ( - uint256 _delayBlocks, - uint256 _futureBlocks, - uint256 _delaySeconds, - uint256 _futureSeconds - ) = seqInbox.maxTimeVariation(); - assertEq(_delayBlocks, timeVars.delayBlocks, "Invalid delayBlocks"); - assertEq(_futureBlocks, timeVars.futureBlocks, "Invalid futureBlocks"); - assertEq(_delaySeconds, timeVars.delaySeconds, "Invalid delaySeconds"); - assertEq(_futureSeconds, timeVars.futureSeconds, "Invalid futureSeconds"); + assertEq(address(seqInbox.rollup()), rollup, "Invalid seq inbox rollup ref"); + (uint256 delayBlocks, uint256 futureBlocks, uint256 delaySeconds, uint256 futureSeconds) = seqInbox.maxTimeVariation(); + assertEq(delayBlocks, timeVars.delayBlocks, "Invalid delayBlocks"); + assertEq(futureBlocks, timeVars.futureBlocks, "Invalid futureBlocks"); + assertEq(delaySeconds, timeVars.delaySeconds, "Invalid delaySeconds"); + assertEq(futureSeconds, timeVars.futureSeconds, "Invalid futureSeconds"); // inbox assertEq(address(inbox.bridge()), address(bridge), "Invalid bridge ref"); @@ -251,10 +236,10 @@ contract BridgeCreatorTest is Test { // rollup event inbox assertEq(address(eventInbox.bridge()), address(bridge), "Invalid bridge ref"); - assertEq(address(eventInbox.rollup()), rollup, "Invalid rollup ref"); + assertEq(address(eventInbox.rollup()), rollup, "Invalid event inbox rollup ref"); // outbox assertEq(address(outbox.bridge()), address(bridge), "Invalid bridge ref"); - assertEq(address(outbox.rollup()), rollup, "Invalid rollup ref"); + assertEq(address(outbox.rollup()), rollup, "Invalid outbox rollup ref"); } } diff --git a/test/foundry/ChallengeManager.t.sol b/test/foundry/ChallengeManager.t.sol new file mode 100644 index 000000000..5e25e51c0 --- /dev/null +++ b/test/foundry/ChallengeManager.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.4; + +import "forge-std/Test.sol"; +import "./util/TestUtil.sol"; +import "../../src/challenge/ChallengeManager.sol"; + + +contract ChallengeManagerTest is Test { + IChallengeResultReceiver resultReceiver = IChallengeResultReceiver(address(137)); + ISequencerInbox sequencerInbox = ISequencerInbox(address(138)); + IBridge bridge = IBridge(address(139)); + IOneStepProofEntry osp = IOneStepProofEntry(address(140)); + IOneStepProofEntry newOsp = IOneStepProofEntry(address(141)); + address proxyAdmin = address(141); + ChallengeManager chalmanImpl = new ChallengeManager(); + + + function deploy() public returns(ChallengeManager) { + ChallengeManager chalman = ChallengeManager(address(new TransparentUpgradeableProxy(address(chalmanImpl), proxyAdmin, ""))); + chalman.initialize( + resultReceiver, + sequencerInbox, + bridge, + osp + ); + assertEq(address(chalman.resultReceiver()), address(resultReceiver), "Result receiver not set"); + assertEq(address(chalman.sequencerInbox()), address(sequencerInbox), "Sequencer inbox not set"); + assertEq(address(chalman.bridge()), address(bridge), "Bridge not set"); + assertEq(address(chalman.osp()), address(osp), "OSP not set"); + return chalman; + } + + function testPostUpgradeInit() public { + ChallengeManager chalman = deploy(); + + vm.prank(proxyAdmin); + TransparentUpgradeableProxy(payable(address(chalman))).upgradeToAndCall(address(chalmanImpl), abi.encodeWithSelector(ChallengeManager.postUpgradeInit.selector, newOsp)); + + assertEq(address(chalman.osp()), address(newOsp), "New osp not set"); + } + + function testPostUpgradeInitFailsNotAdmin() public { + ChallengeManager chalman = deploy(); + + vm.expectRevert(abi.encodeWithSelector(NotOwner.selector, address(151), proxyAdmin)); + vm.prank(address(151)); + chalman.postUpgradeInit(osp); + } + + function testPostUpgradeInitFailsNotDelCall() public { + vm.expectRevert(bytes("Function must be called through delegatecall")); + vm.prank(proxyAdmin); + chalmanImpl.postUpgradeInit(osp); + } +} \ No newline at end of file diff --git a/test/foundry/ERC20Bridge.t.sol b/test/foundry/ERC20Bridge.t.sol index 71b815ab7..fc1d2acbf 100644 --- a/test/foundry/ERC20Bridge.t.sol +++ b/test/foundry/ERC20Bridge.t.sol @@ -41,7 +41,9 @@ contract ERC20BridgeTest is AbsBridgeTest { /* solhint-disable func-name-mixedcase */ function test_initialize() public { assertEq( - address(erc20Bridge.nativeToken()), address(nativeToken), "Invalid nativeToken ref" + address(erc20Bridge.nativeToken()), + address(nativeToken), + "Invalid nativeToken ref" ); assertEq(address(bridge.rollup()), rollup, "Invalid rollup ref"); assertEq(bridge.activeOutbox(), address(0), "Invalid activeOutbox ref"); @@ -105,7 +107,9 @@ contract ERC20BridgeTest is AbsBridgeTest { //// checks uint256 userNativeTokenBalanceAfter = nativeToken.balanceOf(address(user)); assertEq( - userNativeTokenBalanceAfter, userNativeTokenBalanceBefore, "Invalid user token balance" + userNativeTokenBalanceAfter, + userNativeTokenBalanceBefore, + "Invalid user token balance" ); uint256 bridgeNativeTokenBalanceAfter = nativeToken.balanceOf(address(bridge)); @@ -135,7 +139,9 @@ contract ERC20BridgeTest is AbsBridgeTest { hoax(inbox); vm.expectRevert(); IEthBridge(address(bridge)).enqueueDelayedMessage{value: 0.1 ether}( - kind, user, messageDataHash + kind, + user, + messageDataHash ); } @@ -166,7 +172,7 @@ contract ERC20BridgeTest is AbsBridgeTest { //// execute call vm.prank(outbox); - (bool success,) = bridge.executeCall(user, withdrawalAmount, data); + (bool success, ) = bridge.executeCall(user, withdrawalAmount, data); //// checks assertTrue(success, "Execute call failed"); @@ -212,8 +218,11 @@ contract ERC20BridgeTest is AbsBridgeTest { //// execute call vm.prank(outbox); - (bool success,) = - bridge.executeCall({to: address(vault), value: withdrawalAmount, data: data}); + (bool success, ) = bridge.executeCall({ + to: address(vault), + value: withdrawalAmount, + data: data + }); //// checks assertTrue(success, "Execute call failed"); @@ -259,8 +268,11 @@ contract ERC20BridgeTest is AbsBridgeTest { //// execute call - do call which reverts vm.prank(outbox); - (bool success, bytes memory returnData) = - bridge.executeCall({to: address(vault), value: withdrawalAmount, data: data}); + (bool success, bytes memory returnData) = bridge.executeCall({ + to: address(vault), + value: withdrawalAmount, + data: data + }); //// checks assertEq(success, false, "Execute shall be unsuccessful"); @@ -361,7 +373,9 @@ contract ERC20BridgeTest is AbsBridgeTest { address to = _gateway; uint256 withdrawAmount = 25 ether; bytes memory data = abi.encodeWithSelector( - MockGateway.withdraw.selector, MockBridgedToken(_nativeToken), withdrawAmount + MockGateway.withdraw.selector, + MockBridgedToken(_nativeToken), + withdrawAmount ); vm.expectRevert(abi.encodeWithSelector(CallNotAllowed.selector)); vm.prank(_outbox); @@ -376,6 +390,7 @@ contract MockBridgedToken is ERC20 { gateway = _gateway; _mint(msg.sender, 1_000_000 ether); } + function bridgeBurn(address account, uint256 amount) external { require(msg.sender == gateway, "ONLY_GATEWAY"); _burn(account, amount); diff --git a/test/foundry/ERC20Inbox.t.sol b/test/foundry/ERC20Inbox.t.sol index 44948af2e..a4680eda9 100644 --- a/test/foundry/ERC20Inbox.t.sol +++ b/test/foundry/ERC20Inbox.t.sol @@ -128,7 +128,8 @@ contract ERC20InboxTest is AbsInboxTest { // expect event vm.expectEmit(true, true, true, true); emit InboxMessageDelivered( - 0, abi.encodePacked(AddressAliasHelper.applyL1ToL2Alias(user), depositAmount) + 0, + abi.encodePacked(AddressAliasHelper.applyL1ToL2Alias(user), depositAmount) ); // deposit tokens -> tx.origin != msg.sender diff --git a/test/foundry/RollupCreator.t.sol b/test/foundry/RollupCreator.t.sol index 6efaf803c..af4e8f1a4 100644 --- a/test/foundry/RollupCreator.t.sol +++ b/test/foundry/RollupCreator.t.sol @@ -27,31 +27,26 @@ contract RollupCreatorTest is Test { IRollupAdmin public rollupAdmin; IRollupUser public rollupUser; DeployHelper public deployHelper; - IDataHashReader dummyDataHashReader = IDataHashReader(address(137)); - IBlobBasefeeReader dummyBlobBasefeeReader = IBlobBasefeeReader(address(138)); + IReader4844 dummyReader4844 = IReader4844(address(137)); // 1 gwei uint256 public constant MAX_FEE_PER_GAS = 1_000_000_000; uint256 public constant MAX_DATA_SIZE = 117_964; - BridgeCreator.BridgeContracts public ethBasedTemplates = BridgeCreator.BridgeContracts({ - bridge: new Bridge(), - sequencerInbox: new SequencerInbox( - MAX_DATA_SIZE, - dummyDataHashReader, - dummyBlobBasefeeReader - ), - inbox: new Inbox(MAX_DATA_SIZE), - rollupEventInbox: new RollupEventInbox(), - outbox: new Outbox() - }); - BridgeCreator.BridgeContracts public erc20BasedTemplates = BridgeCreator.BridgeContracts({ - bridge: new ERC20Bridge(), - sequencerInbox: ethBasedTemplates.sequencerInbox, - inbox: new ERC20Inbox(MAX_DATA_SIZE), - rollupEventInbox: new ERC20RollupEventInbox(), - outbox: new ERC20Outbox() - }); + BridgeCreator.BridgeTemplates public ethBasedTemplates = + BridgeCreator.BridgeTemplates({ + bridge: new Bridge(), + inbox: new Inbox(MAX_DATA_SIZE), + rollupEventInbox: new RollupEventInbox(), + outbox: new Outbox() + }); + BridgeCreator.BridgeTemplates public erc20BasedTemplates = + BridgeCreator.BridgeTemplates({ + bridge: new ERC20Bridge(), + inbox: new ERC20Inbox(MAX_DATA_SIZE), + rollupEventInbox: new ERC20RollupEventInbox(), + outbox: new ERC20Outbox() + }); /* solhint-disable func-name-mixedcase */ function setUp() public { @@ -95,8 +90,12 @@ contract RollupCreatorTest is Test { vm.startPrank(deployer); // deployment params - ISequencerInbox.MaxTimeVariation memory timeVars = - ISequencerInbox.MaxTimeVariation(((60 * 60 * 24) / 15), 12, 60 * 60 * 24, 60 * 60); + ISequencerInbox.MaxTimeVariation memory timeVars = ISequencerInbox.MaxTimeVariation( + ((60 * 60 * 24) / 15), + 12, + 60 * 60 * 24, + 60 * 60 + ); Config memory config = Config({ confirmPeriodBlocks: 20, extraChallengeTimeBlocks: 200, @@ -117,7 +116,9 @@ contract RollupCreatorTest is Test { uint256 balanceBefore = deployer.balance; /// deploy rollup - address batchPoster = makeAddr("batch poster"); + address[] memory batchPosters = new address[](1); + batchPosters[0] = makeAddr("batch poster 1"); + address batchPosterManager = makeAddr("batch poster manager"); address[] memory validators = new address[](2); validators[0] = makeAddr("validator1"); validators[1] = makeAddr("validator2"); @@ -125,12 +126,14 @@ contract RollupCreatorTest is Test { RollupCreator.RollupDeploymentParams memory deployParams = RollupCreator .RollupDeploymentParams({ config: config, - batchPoster: batchPoster, + batchPosters: batchPosters, validators: validators, maxDataSize: MAX_DATA_SIZE, nativeToken: address(0), deployFactoriesToL2: true, - maxFeePerGasForRetryables: MAX_FEE_PER_GAS + maxFeePerGasForRetryables: MAX_FEE_PER_GAS, + batchPosterManager: batchPosterManager, + reader4844: dummyReader4844 }); address rollupAddress = rollupCreator.createRollup{value: factoryDeploymentFunds}( deployParams @@ -157,17 +160,20 @@ contract RollupCreatorTest is Test { assertTrue(address(rollup.challengeManager()) != address(0), "Invalid challengeManager"); assertTrue(rollup.isValidator(validators[0]), "Invalid validator set"); assertTrue(rollup.isValidator(validators[1]), "Invalid validator set"); - assertTrue( - ISequencerInbox(address(rollup.sequencerInbox())).isBatchPoster(batchPoster), - "Invalid batch poster" + assertTrue(rollup.sequencerInbox().isBatchPoster(batchPosters[0]), "Invalid batch poster"); + assertEq( + rollup.sequencerInbox().batchPosterManager(), + batchPosterManager, + "Invalid batch poster manager" ); // check proxy admin for non-rollup contracts address proxyAdminExpectedAddress = computeCreateAddress(address(rollupCreator), 1); + // seq inbox has no proxy admin assertEq( _getProxyAdmin(address(rollup.sequencerInbox())), - proxyAdminExpectedAddress, + address(0), "Invalid seqInbox' proxyAdmin owner" ); assertEq( @@ -199,14 +205,16 @@ contract RollupCreatorTest is Test { // check upgrade executor owns proxyAdmin address upgradeExecutorExpectedAddress = computeCreateAddress(address(rollupCreator), 4); assertEq( - ProxyAdmin(_getProxyAdmin(address(rollup.sequencerInbox()))).owner(), + ProxyAdmin(_getProxyAdmin(address(rollup.inbox()))).owner(), upgradeExecutorExpectedAddress, "Invalid proxyAdmin's owner" ); // upgrade executor owns rollup assertEq( - IOwnable(rollupAddress).owner(), upgradeExecutorExpectedAddress, "Invalid rollup owner" + IOwnable(rollupAddress).owner(), + upgradeExecutorExpectedAddress, + "Invalid rollup owner" ); assertEq( _getProxyAdmin(rollupAddress), @@ -215,26 +223,36 @@ contract RollupCreatorTest is Test { ); // check rollupOwner has executor role - AccessControlUpgradeable executor = AccessControlUpgradeable(upgradeExecutorExpectedAddress); + AccessControlUpgradeable executor = AccessControlUpgradeable( + upgradeExecutorExpectedAddress + ); assertTrue( - executor.hasRole(keccak256("EXECUTOR_ROLE"), rollupOwner), "Invalid executor role" + executor.hasRole(keccak256("EXECUTOR_ROLE"), rollupOwner), + "Invalid executor role" ); // check funds are refunded uint256 balanceAfter = deployer.balance; - uint256 factoryDeploymentCost = - deployHelper.getDeploymentTotalCost(rollup.inbox(), MAX_FEE_PER_GAS); + uint256 factoryDeploymentCost = deployHelper.getDeploymentTotalCost( + rollup.inbox(), + MAX_FEE_PER_GAS + ); assertEq(balanceBefore - balanceAfter, factoryDeploymentCost, "Invalid balance"); } function test_createErc20Rollup() public { vm.startPrank(deployer); - address nativeToken = - address(new ERC20PresetFixedSupply("Appchain Token", "App", 1_000_000 ether, deployer)); + address nativeToken = address( + new ERC20PresetFixedSupply("Appchain Token", "App", 1_000_000 ether, deployer) + ); // deployment params - ISequencerInbox.MaxTimeVariation memory timeVars = - ISequencerInbox.MaxTimeVariation(((60 * 60 * 24) / 15), 12, 60 * 60 * 24, 60 * 60); + ISequencerInbox.MaxTimeVariation memory timeVars = ISequencerInbox.MaxTimeVariation( + ((60 * 60 * 24) / 15), + 12, + 60 * 60 * 24, + 60 * 60 + ); Config memory config = Config({ confirmPeriodBlocks: 20, extraChallengeTimeBlocks: 200, @@ -250,11 +268,15 @@ contract RollupCreatorTest is Test { }); // approve fee token to pay for deployment of L2 factories - uint256 expectedCost = 0.1247 ether + 4 * (1400 * 100_000_000_000 + 100_000 * 1_000_000_000); + uint256 expectedCost = 0.1247 ether + + 4 * + (1400 * 100_000_000_000 + 100_000 * 1_000_000_000); IERC20(nativeToken).approve(address(rollupCreator), expectedCost); /// deploy rollup - address batchPoster = makeAddr("batch poster"); + address[] memory batchPosters = new address[](1); + batchPosters[0] = makeAddr("batch poster 1"); + address batchPosterManager = makeAddr("batch poster manager"); address[] memory validators = new address[](2); validators[0] = makeAddr("validator1"); validators[1] = makeAddr("validator2"); @@ -262,12 +284,14 @@ contract RollupCreatorTest is Test { RollupCreator.RollupDeploymentParams memory deployParams = RollupCreator .RollupDeploymentParams({ config: config, - batchPoster: batchPoster, + batchPosters: batchPosters, validators: validators, maxDataSize: MAX_DATA_SIZE, nativeToken: nativeToken, deployFactoriesToL2: true, - maxFeePerGasForRetryables: MAX_FEE_PER_GAS + maxFeePerGasForRetryables: MAX_FEE_PER_GAS, + batchPosterManager: batchPosterManager, + reader4844: dummyReader4844 }); address rollupAddress = rollupCreator.createRollup(deployParams); @@ -282,7 +306,6 @@ contract RollupCreatorTest is Test { /// rollup proxy assertEq(_getPrimary(rollupAddress), address(rollupAdmin), "Invalid proxy primary impl"); assertEq(_getSecondary(rollupAddress), address(rollupUser), "Invalid proxy secondary impl"); - /// rollup check RollupCore rollup = RollupCore(rollupAddress); assertTrue(address(rollup.sequencerInbox()) != address(0), "Invalid seqInbox"); @@ -294,21 +317,30 @@ contract RollupCreatorTest is Test { assertTrue(rollup.isValidator(validators[0]), "Invalid validator set"); assertTrue(rollup.isValidator(validators[1]), "Invalid validator set"); assertTrue( - ISequencerInbox(address(rollup.sequencerInbox())).isBatchPoster(batchPoster), + ISequencerInbox(address(rollup.sequencerInbox())).isBatchPoster(batchPosters[0]), "Invalid batch poster" ); + assertEq( + ISequencerInbox(address(rollup.sequencerInbox())).batchPosterManager(), + batchPosterManager, + "Invalid batch poster manager" + ); + // native token check IBridge bridge = RollupCore(address(rollupAddress)).bridge(); assertEq( - IERC20Bridge(address(bridge)).nativeToken(), nativeToken, "Invalid native token ref" + IERC20Bridge(address(bridge)).nativeToken(), + nativeToken, + "Invalid native token ref" ); // check proxy admin for non-rollup contracts address proxyAdminExpectedAddress = computeCreateAddress(address(rollupCreator), 1); + // seq inbox has no proxy admin assertEq( _getProxyAdmin(address(rollup.sequencerInbox())), - proxyAdminExpectedAddress, + address(0), "Invalid seqInbox' proxyAdmin owner" ); assertEq( @@ -340,25 +372,32 @@ contract RollupCreatorTest is Test { // check upgrade executor owns proxyAdmin address upgradeExecutorExpectedAddress = computeCreateAddress(address(rollupCreator), 4); assertEq( - ProxyAdmin(_getProxyAdmin(address(rollup.sequencerInbox()))).owner(), + ProxyAdmin(_getProxyAdmin(address(rollup.inbox()))).owner(), upgradeExecutorExpectedAddress, "Invalid proxyAdmin's owner" ); + console.log("c2"); // upgrade executor owns rollup assertEq( - IOwnable(rollupAddress).owner(), upgradeExecutorExpectedAddress, "Invalid rollup owner" + IOwnable(rollupAddress).owner(), + upgradeExecutorExpectedAddress, + "Invalid rollup owner" ); assertEq( _getProxyAdmin(rollupAddress), upgradeExecutorExpectedAddress, "Invalid rollup's proxyAdmin owner" ); + console.log("d"); // check rollupOwner has executor role - AccessControlUpgradeable executor = AccessControlUpgradeable(upgradeExecutorExpectedAddress); + AccessControlUpgradeable executor = AccessControlUpgradeable( + upgradeExecutorExpectedAddress + ); assertTrue( - executor.hasRole(keccak256("EXECUTOR_ROLE"), rollupOwner), "Invalid executor role" + executor.hasRole(keccak256("EXECUTOR_ROLE"), rollupOwner), + "Invalid executor role" ); } @@ -366,8 +405,12 @@ contract RollupCreatorTest is Test { vm.startPrank(deployer); // deployment params - ISequencerInbox.MaxTimeVariation memory timeVars = - ISequencerInbox.MaxTimeVariation(((60 * 60 * 24) / 15), 12, 60 * 60 * 24, 60 * 60); + ISequencerInbox.MaxTimeVariation memory timeVars = ISequencerInbox.MaxTimeVariation( + ((60 * 60 * 24) / 15), + 12, + 60 * 60 * 24, + 60 * 60 + ); Config memory config = Config({ confirmPeriodBlocks: 20, extraChallengeTimeBlocks: 200, @@ -387,7 +430,9 @@ contract RollupCreatorTest is Test { vm.deal(deployer, factoryDeploymentFunds); /// deploy rollup - address batchPoster = makeAddr("batch poster"); + address[] memory batchPosters = new address[](1); + batchPosters[0] = makeAddr("batch poster 1"); + address batchPosterManager = makeAddr("batch poster manager"); address[] memory validators = new address[](2); validators[0] = makeAddr("validator1"); validators[1] = makeAddr("validator2"); @@ -395,12 +440,14 @@ contract RollupCreatorTest is Test { RollupCreator.RollupDeploymentParams memory deployParams = RollupCreator .RollupDeploymentParams({ config: config, - batchPoster: batchPoster, + batchPosters: batchPosters, validators: validators, maxDataSize: MAX_DATA_SIZE, nativeToken: address(0), deployFactoriesToL2: true, - maxFeePerGasForRetryables: MAX_FEE_PER_GAS + maxFeePerGasForRetryables: MAX_FEE_PER_GAS, + batchPosterManager: batchPosterManager, + reader4844: dummyReader4844 }); address rollupAddress = rollupCreator.createRollup{value: factoryDeploymentFunds}( deployParams @@ -412,12 +459,16 @@ contract RollupCreatorTest is Test { RollupCore rollup = RollupCore(rollupAddress); address inbox = address(rollup.inbox()); address proxyAdmin = computeCreateAddress(address(rollupCreator), 1); - IUpgradeExecutor upgradeExecutor = - IUpgradeExecutor(computeCreateAddress(address(rollupCreator), 4)); + IUpgradeExecutor upgradeExecutor = IUpgradeExecutor( + computeCreateAddress(address(rollupCreator), 4) + ); Dummy newLogicImpl = new Dummy(); bytes memory data = abi.encodeWithSelector( - ProxyUpgradeAction.perform.selector, address(proxyAdmin), inbox, address(newLogicImpl) + ProxyUpgradeAction.perform.selector, + address(proxyAdmin), + inbox, + address(newLogicImpl) ); address upgradeAction = address(new ProxyUpgradeAction()); @@ -469,14 +520,19 @@ contract RollupCreatorTest is Test { } function _getSecondary(address proxy) internal view returns (address) { - bytes32 secondarySlot = - bytes32(uint256(keccak256("eip1967.proxy.implementation.secondary")) - 1); + bytes32 secondarySlot = bytes32( + uint256(keccak256("eip1967.proxy.implementation.secondary")) - 1 + ); return address(uint160(uint256(vm.load(proxy, secondarySlot)))); } } contract ProxyUpgradeAction { - function perform(address admin, address payable target, address newLogic) public payable { + function perform( + address admin, + address payable target, + address newLogic + ) public payable { ProxyAdmin(admin).upgrade(TransparentUpgradeableProxy(target), newLogic); } } diff --git a/test/foundry/SequencerInbox.t.sol b/test/foundry/SequencerInbox.t.sol index f947e8ac5..1d4e1247f 100644 --- a/test/foundry/SequencerInbox.t.sol +++ b/test/foundry/SequencerInbox.t.sol @@ -5,6 +5,8 @@ import "forge-std/Test.sol"; import "./util/TestUtil.sol"; import "../../src/bridge/Bridge.sol"; import "../../src/bridge/SequencerInbox.sol"; +import {ERC20Bridge} from "../../src/bridge/ERC20Bridge.sol"; +import "@openzeppelin/contracts/token/ERC20/presets/ERC20PresetMinterPauser.sol"; contract RollupMock { address immutable public owner; @@ -34,7 +36,8 @@ contract SequencerInboxTest is Test { bytes32 delayedAcc, uint256 afterDelayedMessagesRead, IBridge.TimeBounds timeBounds, - IBridge.BatchDataLocation dataLocation + IBridge.BatchDataLocation dataLocation, + address sequencerInbox ); @@ -49,10 +52,11 @@ contract SequencerInboxTest is Test { }); address dummyInbox = address(139); address proxyAdmin = address(140); - IDataHashReader dummyDataHashReader = IDataHashReader(address(141)); - IBlobBasefeeReader dummyBlobBasefeeReader = IBlobBasefeeReader(address(142)); + IReader4844 dummyReader4844 = IReader4844(address(137)); - function deploy() public returns(SequencerInbox, Bridge) { + uint256 constant public MAX_DATA_SIZE = 117964; + + function deployRollup(bool isArbHosted) internal returns(SequencerInbox, Bridge) { RollupMock rollupMock = new RollupMock(rollupOwner); Bridge bridgeImpl = new Bridge(); Bridge bridge = Bridge(address(new TransparentUpgradeableProxy(address(bridgeImpl), proxyAdmin, ""))); @@ -61,15 +65,45 @@ contract SequencerInboxTest is Test { vm.prank(rollupOwner); bridge.setDelayedInbox(dummyInbox, true); - SequencerInbox seqInboxImpl = new SequencerInbox( + SequencerInbox seqInbox = new SequencerInbox( + bridge, + maxTimeVariation, maxDataSize, - dummyDataHashReader, - dummyBlobBasefeeReader + isArbHosted ? IReader4844(address(0)) : dummyReader4844, + false + ); + + vm.prank(rollupOwner); + seqInbox.setIsBatchPoster(tx.origin, true); + + vm.prank(rollupOwner); + bridge.setSequencerInbox(address(seqInbox)); + + return (seqInbox, bridge); + } + + function deployFeeTokenBasedRollup(bool isArbHosted) internal returns(SequencerInbox, ERC20Bridge) { + RollupMock rollupMock = new RollupMock(rollupOwner); + ERC20Bridge bridgeImpl = new ERC20Bridge(); + ERC20Bridge bridge = ERC20Bridge(address(new TransparentUpgradeableProxy(address(bridgeImpl), proxyAdmin, ""))); + address nativeToken = address(new ERC20PresetMinterPauser("Appchain Token", "App")); + + bridge.initialize(IOwnable(address(rollupMock)), nativeToken); + vm.prank(rollupOwner); + bridge.setDelayedInbox(dummyInbox, true); + + /// this will result in 'hostChainIsArbitrum = true' + vm.mockCall( + address(100), + abi.encodeWithSelector(ArbSys.arbOSVersion.selector), + abi.encode(uint256(11)) ); - SequencerInbox seqInbox = SequencerInbox(address(new TransparentUpgradeableProxy(address(seqInboxImpl), proxyAdmin, ""))); - seqInbox.initialize( + SequencerInbox seqInbox = new SequencerInbox( bridge, - maxTimeVariation + maxTimeVariation, + maxDataSize, + isArbHosted ? IReader4844(address(0)) : dummyReader4844, + true ); vm.prank(rollupOwner); @@ -81,7 +115,7 @@ contract SequencerInboxTest is Test { return (seqInbox, bridge); } - function expectEvents(Bridge bridge, SequencerInbox seqInbox, bytes memory data) internal { + function expectEvents(IBridge bridge, SequencerInbox seqInbox, bytes memory data, bool hostChainIsArbitrum, bool isUsingFeeToken) internal { uint256 delayedMessagesRead = bridge.delayedMessageCount(); uint256 sequenceNumber = bridge.sequencerMessageCount(); IBridge.TimeBounds memory timeBounds; @@ -101,58 +135,118 @@ contract SequencerInboxTest is Test { uint64(delayedMessagesRead) ), data)); + if(!isUsingFeeToken) { + uint256 expectedReportedExtraGas = 0; + if(hostChainIsArbitrum) { + // set 0.1 gwei basefee + uint256 basefee = 100000000; + vm.fee(basefee); + // 30 gwei TX L1 fees + uint256 l1Fees = 30000000000; + vm.mockCall( + address(0x6c), + abi.encodeWithSignature("getCurrentTxL1GasFees()"), + abi.encode(l1Fees) + ); + expectedReportedExtraGas = l1Fees / basefee; + } + bytes memory spendingReportMsg = abi.encodePacked( block.timestamp, msg.sender, dataHash, sequenceNumber, block.basefee, - uint64(0), - uint256(0) + uint64(expectedReportedExtraGas) ); bytes32 beforeAcc = bytes32(0); bytes32 delayedAcc = bridge.delayedInboxAccs(delayedMessagesRead - 1); bytes32 afterAcc = keccak256(abi.encodePacked(beforeAcc, dataHash, delayedAcc)); - // spending report - vm.expectEmit(); - emit MessageDelivered( - delayedMessagesRead, - delayedAcc, - address(seqInbox), - L1MessageType_batchPostingReport, - tx.origin, - keccak256(spendingReportMsg), - block.basefee, - uint64(block.timestamp) - ); + // spending report + vm.expectEmit(); + emit MessageDelivered( + delayedMessagesRead, + delayedAcc, + address(seqInbox), + L1MessageType_batchPostingReport, + tx.origin, + keccak256(spendingReportMsg), + block.basefee, + uint64(block.timestamp) + ); + + // spending report event in seq inbox + vm.expectEmit(); + emit InboxMessageDelivered( + delayedMessagesRead, + spendingReportMsg + ); + } + // TODO: fix stack too deep + // sequencer batch delivered + // vm.expectEmit(); + // emit SequencerBatchDelivered( + // sequenceNumber, + // beforeAcc, + // afterAcc, + // delayedAcc, + // delayedMessagesRead, + // timeBounds, + // IBridge.BatchDataLocation.TxInput, + // address(seqInbox) + // ); + } - // spending report event in seq inbox - vm.expectEmit(); - emit InboxMessageDelivered( - delayedMessagesRead, - spendingReportMsg + bytes biggerData = hex"00a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890a4567890"; + + function testAddSequencerL2BatchFromOrigin() public { + (SequencerInbox seqInbox, Bridge bridge) = deployRollup(false); + address delayedInboxSender = address(140); + uint8 delayedInboxKind = 3; + bytes32 messageDataHash = RAND.Bytes32(); + bytes memory data = biggerData; // 00 is BROTLI_MESSAGE_HEADER_FLAG + + vm.prank(dummyInbox); + bridge.enqueueDelayedMessage( + delayedInboxKind, + delayedInboxSender, + messageDataHash ); - // sequencer batch delivered - vm.expectEmit(); - emit SequencerBatchDelivered( - sequenceNumber, - beforeAcc, - afterAcc, - delayedAcc, + uint256 subMessageCount = bridge.sequencerReportedSubMessageCount(); + uint256 sequenceNumber = bridge.sequencerMessageCount(); + uint256 delayedMessagesRead = bridge.delayedMessageCount(); + + // set 60 gwei basefee + uint256 basefee = 60000000000; + vm.fee(basefee); + expectEvents(bridge, seqInbox, data, false, false); + + vm.prank(tx.origin); + seqInbox.addSequencerL2BatchFromOrigin( + sequenceNumber, + data, delayedMessagesRead, - timeBounds, - IBridge.BatchDataLocation.TxInput + IGasRefunder(address(0)), + subMessageCount, + subMessageCount + 1 ); } - function testAddSequencerL2BatchFromOrigin() public { - (SequencerInbox seqInbox, Bridge bridge) = deploy(); + function testAddSequencerL2BatchFromOrigin_ArbitrumHosted() public { + // this will result in 'hostChainIsArbitrum = true' + vm.mockCall( + address(100), + abi.encodeWithSelector(ArbSys.arbOSVersion.selector), + abi.encode(uint256(11)) + ); + (SequencerInbox seqInbox, Bridge bridge) = deployRollup(true); + address delayedInboxSender = address(140); uint8 delayedInboxKind = 3; bytes32 messageDataHash = RAND.Bytes32(); - bytes memory data = hex"00a4567890"; // CHRIS: TODO: bigger data; // 00 is BROTLI_MESSAGE_HEADER_FLAG + bytes memory data = hex"00567890"; vm.prank(dummyInbox); bridge.enqueueDelayedMessage( @@ -165,7 +259,7 @@ contract SequencerInboxTest is Test { uint256 sequenceNumber = bridge.sequencerMessageCount(); uint256 delayedMessagesRead = bridge.delayedMessageCount(); - expectEvents(bridge, seqInbox, data); + expectEvents(bridge, seqInbox, data, true, false); vm.prank(tx.origin); seqInbox.addSequencerL2BatchFromOrigin( @@ -177,13 +271,49 @@ contract SequencerInboxTest is Test { subMessageCount + 1 ); } + + function testAddSequencerL2BatchFromOrigin_ArbitrumHostedFeeTokenBased() public { + (SequencerInbox seqInbox, ERC20Bridge bridge) = deployFeeTokenBasedRollup(true); + address delayedInboxSender = address(140); + uint8 delayedInboxKind = 3; + bytes32 messageDataHash = RAND.Bytes32(); + bytes memory data = hex"80567890"; + + vm.prank(dummyInbox); + bridge.enqueueDelayedMessage( + delayedInboxKind, + delayedInboxSender, + messageDataHash, + 0 + ); + + uint256 subMessageCount = bridge.sequencerReportedSubMessageCount(); + uint256 sequenceNumber = bridge.sequencerMessageCount(); + uint256 delayedMessagesRead = bridge.delayedMessageCount(); + + // set 40 gwei basefee + uint256 basefee = 40000000000; + vm.fee(basefee); - function testAddSequencerL2BatchFromOriginRevers() public { - (SequencerInbox seqInbox, Bridge bridge) = deploy(); + expectEvents(IBridge(address(bridge)), seqInbox, data, true, true); + + vm.prank(tx.origin); + seqInbox.addSequencerL2BatchFromOrigin( + sequenceNumber, + data, + delayedMessagesRead, + IGasRefunder(address(0)), + subMessageCount, + subMessageCount + 1 + ); + } + + function testAddSequencerL2BatchFromOriginReverts() public { + (SequencerInbox seqInbox, Bridge bridge) = deployRollup(false); address delayedInboxSender = address(140); uint8 delayedInboxKind = 3; bytes32 messageDataHash = RAND.Bytes32(); - bytes memory data = hex"00567890"; // CHRIS: TODO: bigger data; // 00 is BROTLI_MESSAGE_HEADER_FLAG + bytes memory data = biggerData; // 00 is BROTLI_MESSAGE_HEADER_FLAG vm.prank(dummyInbox); bridge.enqueueDelayedMessage( @@ -236,7 +366,7 @@ contract SequencerInboxTest is Test { subMessageCount + 1 ); - bytes memory authenticatedData = bytes.concat(seqInbox.DATA_BLOB_HEADER_FLAG(), data); // TODO: unsure + bytes memory authenticatedData = bytes.concat(seqInbox.DATA_BLOB_HEADER_FLAG(), data); vm.expectRevert(abi.encodeWithSelector(InvalidHeaderFlag.selector, authenticatedData[0])); vm.prank(tx.origin); seqInbox.addSequencerL2BatchFromOrigin( @@ -259,62 +389,4 @@ contract SequencerInboxTest is Test { subMessageCount + 1 ); } - - function testPostUpgradeInitAlreadyInit() public returns (SequencerInbox, SequencerInbox){ - (SequencerInbox seqInbox, ) = deploy(); - SequencerInbox seqInboxImpl = new SequencerInbox( - maxDataSize, - dummyDataHashReader, - dummyBlobBasefeeReader - ); - - vm.expectRevert(abi.encodeWithSelector(AlreadyInit.selector)); - vm.prank(proxyAdmin); - TransparentUpgradeableProxy(payable(address(seqInbox))).upgradeToAndCall(address(seqInboxImpl), abi.encodeWithSelector(SequencerInbox.postUpgradeInit.selector)); - return (seqInbox, seqInboxImpl); - } - - function testPostUpgradeInit(uint64 delayBlocks, uint64 futureBlocks, uint64 delaySeconds, uint64 futureSeconds) public { - vm.assume(delayBlocks != 0 || futureBlocks != 0 || delaySeconds != 0 || futureSeconds != 0); - - (SequencerInbox seqInbox, SequencerInbox seqInboxImpl) = testPostUpgradeInitAlreadyInit(); - - vm.expectRevert(abi.encodeWithSelector(AlreadyInit.selector)); - vm.prank(proxyAdmin); - TransparentUpgradeableProxy(payable(address(seqInbox))).upgradeToAndCall(address(seqInboxImpl), abi.encodeWithSelector(SequencerInbox.postUpgradeInit.selector)); - - vm.store(address(seqInbox), bytes32(uint256(4)), bytes32(uint256(delayBlocks))); // slot 4: delayBlocks - vm.store(address(seqInbox), bytes32(uint256(5)), bytes32(uint256(futureBlocks))); // slot 5: futureBlocks - vm.store(address(seqInbox), bytes32(uint256(6)), bytes32(uint256(delaySeconds))); // slot 6: delaySeconds - vm.store(address(seqInbox), bytes32(uint256(7)), bytes32(uint256(futureSeconds))); // slot 7: futureSeconds - vm.prank(proxyAdmin); - TransparentUpgradeableProxy(payable(address(seqInbox))).upgradeToAndCall(address(seqInboxImpl), abi.encodeWithSelector(SequencerInbox.postUpgradeInit.selector)); - - (uint256 delayBlocks_, uint256 futureBlocks_, uint256 delaySeconds_, uint256 futureSeconds_) = seqInbox.maxTimeVariation(); - assertEq(delayBlocks_, delayBlocks); - assertEq(futureBlocks_, futureBlocks); - assertEq(delaySeconds_, delaySeconds); - assertEq(futureSeconds_, futureSeconds); - - vm.expectRevert(abi.encodeWithSelector(AlreadyInit.selector)); - vm.prank(proxyAdmin); - TransparentUpgradeableProxy(payable(address(seqInbox))).upgradeToAndCall(address(seqInboxImpl), abi.encodeWithSelector(SequencerInbox.postUpgradeInit.selector)); - } - - function testPostUpgradeInitBadInit(uint256 delayBlocks, uint256 futureBlocks, uint256 delaySeconds, uint256 futureSeconds) public { - vm.assume(delayBlocks > uint256(type(uint64).max)); - vm.assume(futureBlocks > uint256(type(uint64).max)); - vm.assume(delaySeconds > uint256(type(uint64).max)); - vm.assume(futureSeconds > uint256(type(uint64).max)); - - (SequencerInbox seqInbox, SequencerInbox seqInboxImpl) = testPostUpgradeInitAlreadyInit(); - - vm.store(address(seqInbox), bytes32(uint256(4)), bytes32(delayBlocks)); // slot 4: delayBlocks - vm.store(address(seqInbox), bytes32(uint256(5)), bytes32(futureBlocks)); // slot 5: futureBlocks - vm.store(address(seqInbox), bytes32(uint256(6)), bytes32(delaySeconds)); // slot 6: delaySeconds - vm.store(address(seqInbox), bytes32(uint256(7)), bytes32(futureSeconds)); // slot 7: futureSeconds - vm.expectRevert(abi.encodeWithSelector(BadPostUpgradeInit.selector)); - vm.prank(proxyAdmin); - TransparentUpgradeableProxy(payable(address(seqInbox))).upgradeToAndCall(address(seqInboxImpl), abi.encodeWithSelector(SequencerInbox.postUpgradeInit.selector)); - } } \ No newline at end of file diff --git a/test/storage/Bridge b/test/storage/Bridge index 9ed7f48a2..078c31e7a 100644 --- a/test/storage/Bridge +++ b/test/storage/Bridge @@ -12,4 +12,5 @@ | rollup | contract IOwnable | 8 | 0 | 20 | src/bridge/Bridge.sol:Bridge | | sequencerInbox | address | 9 | 0 | 20 | src/bridge/Bridge.sol:Bridge | | sequencerReportedSubMessageCount | uint256 | 10 | 0 | 32 | src/bridge/Bridge.sol:Bridge | -| __gap | uint256[40] | 11 | 0 | 1280 | src/bridge/Bridge.sol:Bridge | +| totalDelayedMessagesRead | uint256 | 11 | 0 | 32 | src/bridge/Bridge.sol:Bridge | +| __gap | uint256[39] | 12 | 0 | 1248 | src/bridge/Bridge.sol:Bridge | diff --git a/test/storage/ERC20Bridge b/test/storage/ERC20Bridge index 1b0d838b4..952d876ff 100644 --- a/test/storage/ERC20Bridge +++ b/test/storage/ERC20Bridge @@ -12,5 +12,6 @@ | rollup | contract IOwnable | 8 | 0 | 20 | src/bridge/ERC20Bridge.sol:ERC20Bridge | | sequencerInbox | address | 9 | 0 | 20 | src/bridge/ERC20Bridge.sol:ERC20Bridge | | sequencerReportedSubMessageCount | uint256 | 10 | 0 | 32 | src/bridge/ERC20Bridge.sol:ERC20Bridge | -| __gap | uint256[40] | 11 | 0 | 1280 | src/bridge/ERC20Bridge.sol:ERC20Bridge | +| totalDelayedMessagesRead | uint256 | 11 | 0 | 32 | src/bridge/ERC20Bridge.sol:ERC20Bridge | +| __gap | uint256[39] | 12 | 0 | 1248 | src/bridge/ERC20Bridge.sol:ERC20Bridge | | nativeToken | address | 51 | 0 | 20 | src/bridge/ERC20Bridge.sol:ERC20Bridge | diff --git a/test/storage/SequencerInbox b/test/storage/SequencerInbox index 81cd1415a..142437c5a 100644 --- a/test/storage/SequencerInbox +++ b/test/storage/SequencerInbox @@ -1,13 +1,7 @@ -| Name | Type | Slot | Offset | Bytes | Contract | -|-----------------------------|----------------------------------------------------------|------|--------|-------|----------------------------------------------| -| totalDelayedMessagesRead | uint256 | 0 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | -| bridge | contract IBridge | 1 | 0 | 20 | src/bridge/SequencerInbox.sol:SequencerInbox | -| rollup | contract IOwnable | 2 | 0 | 20 | src/bridge/SequencerInbox.sol:SequencerInbox | -| isBatchPoster | mapping(address => bool) | 3 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | -| __LEGACY_MAX_TIME_VARIATION | uint256[4] | 4 | 0 | 128 | src/bridge/SequencerInbox.sol:SequencerInbox | -| dasKeySetInfo | mapping(bytes32 => struct ISequencerInbox.DasKeySetInfo) | 8 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | -| isSequencer | mapping(address => bool) | 9 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | -| delayBlocks | uint64 | 10 | 0 | 8 | src/bridge/SequencerInbox.sol:SequencerInbox | -| futureBlocks | uint64 | 10 | 8 | 8 | src/bridge/SequencerInbox.sol:SequencerInbox | -| delaySeconds | uint64 | 10 | 16 | 8 | src/bridge/SequencerInbox.sol:SequencerInbox | -| futureSeconds | uint64 | 10 | 24 | 8 | src/bridge/SequencerInbox.sol:SequencerInbox | +| Name | Type | Slot | Offset | Bytes | Contract | +|--------------------|----------------------------------------------------------|------|--------|-------|----------------------------------------------| +| rollup | contract IOwnable | 0 | 0 | 20 | src/bridge/SequencerInbox.sol:SequencerInbox | +| isBatchPoster | mapping(address => bool) | 1 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | +| dasKeySetInfo | mapping(bytes32 => struct ISequencerInbox.DasKeySetInfo) | 2 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | +| isSequencer | mapping(address => bool) | 3 | 0 | 32 | src/bridge/SequencerInbox.sol:SequencerInbox | +| batchPosterManager | address | 4 | 0 | 20 | src/bridge/SequencerInbox.sol:SequencerInbox | diff --git a/yul/BlobBasefeeReader.yul b/yul/BlobBasefeeReader.yul deleted file mode 100644 index de12c8e27..000000000 --- a/yul/BlobBasefeeReader.yul +++ /dev/null @@ -1,20 +0,0 @@ - -object "BlobBasefeeReader" { - code { - datacopy(0, dataoffset("runtime"), datasize("runtime")) - return(0, datasize("runtime")) - } - object "runtime" { - code { - // Match against the keccak of the ABI function signature needed. - switch shr(0xe0,calldataload(0)) - // bytes4(keccak("getBlobBaseFee()")) - case 0x1f6d6ef7 { - // BLOBBASEFEE opcode has hex value 0x4a - let blobBasefee := verbatim_0i_1o(hex"4a") - mstore(0, blobBasefee) - return(0, 32) - } - } - } -} \ No newline at end of file diff --git a/yul/DataHashesReader.yul b/yul/DataHashesReader.yul deleted file mode 100644 index 90e736f90..000000000 --- a/yul/DataHashesReader.yul +++ /dev/null @@ -1,29 +0,0 @@ -// taken from https://github.com/rauljordan/eip4844-interop/blob/b23c1afa79ad18b0318dca41c000c7c0edfe29d9/upload/contracts/DataHashesReader.yul -object "DataHashesReader" { - code { - datacopy(0, dataoffset("runtime"), datasize("runtime")) - return(0, datasize("runtime")) - } - object "runtime" { - code { - // Match against the keccak of the ABI function signature needed. - switch shr(0xe0,calldataload(0)) - // bytes4(keccak("getDataHashes()")) - case 0xe83a2d82 { - // DATAHASH opcode has hex value 0x49 - let i := 0 - for {} true {} { - let hash := verbatim_1i_1o(hex"49", i) - if iszero(hash) { - break - } - mstore(add(mul(i, 32), 64), hash) - i := add(i, 1) - } - mstore(0, 32) - mstore(32, i) - return(0, add(mul(i, 32), 64)) - } - } - } -} \ No newline at end of file diff --git a/yul/Reader4844.yul b/yul/Reader4844.yul new file mode 100644 index 000000000..ef183d793 --- /dev/null +++ b/yul/Reader4844.yul @@ -0,0 +1,39 @@ +object "Reader4844" { + code { + datacopy(0, dataoffset("runtime"), datasize("runtime")) + return(0, datasize("runtime")) + } + object "runtime" { + code { + // This contract does not accept callvalue + if callvalue() { revert(0, 0) } + + // Match against the keccak of the ABI function signature needed. + switch shr(0xe0, calldataload(0)) + // bytes4(keccak("getDataHashes()")) + case 0xe83a2d82 { + let i := 0 + for { } true { } + { + // DATAHASH opcode has hex value 0x49 + let hash := verbatim_1i_1o(hex"49", i) + if iszero(hash) { break } + mstore(add(mul(i, 32), 64), hash) + i := add(i, 1) + } + mstore(0, 32) + mstore(32, i) + return(0, add(mul(i, 32), 64)) + } + // bytes4(keccak("getBlobBaseFee()")) + case 0x1f6d6ef7 { + // BLOBBASEFEE opcode has hex value 0x4a + let blobBasefee := verbatim_0i_1o(hex"4a") + mstore(0, blobBasefee) + return(0, 32) + } + // Unknown selector (revert) + default { revert(0, 0) } + } + } +}