diff --git a/packages/protocol/contracts/L1/ITaikoL1.sol b/packages/protocol/contracts/L1/ITaikoL1.sol index 385b16b93d..b01e83131e 100644 --- a/packages/protocol/contracts/L1/ITaikoL1.sol +++ b/packages/protocol/contracts/L1/ITaikoL1.sol @@ -19,6 +19,17 @@ interface ITaikoL1 { payable returns (TaikoData.BlockMetadata memory meta_, TaikoData.EthDeposit[] memory deposits_); + /// @notice Proposes a Taiko L2 block (version 2) + /// @param _params Block parameters, currently an encoded BlockParams object. + /// @param _txList txList data if calldata is used for DA. + /// @return meta_ The metadata of the proposed L2 block. + function proposeBlockV2( + bytes calldata _params, + bytes calldata _txList + ) + external + returns (TaikoData.BlockMetadataV2 memory meta_); + /// @notice Proves or contests a block transition. /// @param _blockId The index of the block to prove. This is also used to /// select the right implementation version. diff --git a/packages/protocol/contracts/L1/TaikoData.sol b/packages/protocol/contracts/L1/TaikoData.sol index 9f7f12e518..86c5093e43 100644 --- a/packages/protocol/contracts/L1/TaikoData.sol +++ b/packages/protocol/contracts/L1/TaikoData.sol @@ -35,7 +35,17 @@ library TaikoData { // --------------------------------------------------------------------- // The number of L2 blocks between each L2-to-L1 state root sync. uint8 stateRootSyncInternal; - bool checkEOAForCalldataDA; + uint64 maxAnchorHeightOffset; + // --------------------------------------------------------------------- + // Group 5: Previous configs in TaikoL2 + // --------------------------------------------------------------------- + uint8 basefeeAdjustmentQuotient; + uint8 basefeeSharingPctg; + uint32 blockGasIssuance; + // --------------------------------------------------------------------- + // Group 6: Others + // --------------------------------------------------------------------- + uint64 ontakeForkHeight; } /// @dev A proof and the tier of proof it belongs to @@ -57,7 +67,18 @@ library TaikoData { bytes32 extraData; bytes32 parentMetaHash; HookCall[] hookCalls; // DEPRECATED, value ignored. - bytes signature; + bytes signature; // DEPRECATED, value ignored. + } + + struct BlockParamsV2 { + address coinbase; + bytes32 extraData; + bytes32 parentMetaHash; + uint64 anchorBlockId; // NEW + uint64 timestamp; // NEW + uint32 blobTxListOffset; // NEW + uint32 blobTxListLength; // NEW + uint8 blobIndex; // NEW } /// @dev Struct containing data only required for proving a block @@ -81,6 +102,33 @@ library TaikoData { address sender; // a.k.a proposer } + struct BlockMetadataV2 { + bytes32 anchorBlockHash; // `_l1BlockHash` in TaikoL2's anchor tx. + bytes32 difficulty; + bytes32 blobHash; + bytes32 extraData; + address coinbase; + uint64 id; + uint32 gasLimit; + uint64 timestamp; + uint64 anchorBlockId; // `_l1BlockId` in TaikoL2's anchor tx. + uint16 minTier; + bool blobUsed; + bytes32 parentMetaHash; + address proposer; + uint96 livenessBond; + // Time this block is proposed at, used to check proving window and cooldown window. + uint64 proposedAt; + // L1 block number, required/used by node/client. + uint64 proposedIn; + uint32 blobTxListOffset; + uint32 blobTxListLength; + uint8 blobIndex; + uint8 basefeeAdjustmentQuotient; + uint8 basefeeSharingPctg; + uint32 blockGasIssuance; + } + /// @dev Struct representing transition to be proven. struct Transition { bytes32 parentHash; @@ -111,13 +159,20 @@ library TaikoData { address assignedProver; // slot 2 uint96 livenessBond; uint64 blockId; // slot 3 - uint64 proposedAt; // timestamp - uint64 proposedIn; // L1 block number, required/used by node/client. - uint32 nextTransitionId; + // Before the fork, this field is the L1 timestamp when this block is proposed. + // After the fork, this is the timestamp of the L2 block. + // In a later fork, we an rename this field to `timestamp`. + uint64 proposedAt; + // Before the fork, this field is the L1 block number where this block is proposed. + // After the fork, this is the L1 block number input for the anchor transaction. + // In a later fork, we an rename this field to `anchorBlockId`. + uint64 proposedIn; + uint24 nextTransitionId; + bool livenessBondReturned; // The ID of the transaction that is used to verify this block. However, if // this block is not verified as the last block in a batch, verifiedTransitionId // will remain zero. - uint32 verifiedTransitionId; + uint24 verifiedTransitionId; } /// @dev Struct representing an Ethereum deposit. @@ -156,7 +211,7 @@ library TaikoData { // Ring buffer for proposed blocks and a some recent verified blocks. mapping(uint64 blockId_mod_blockRingBufferSize => Block blk) blocks; // Indexing to transition ids (ring buffer not possible) - mapping(uint64 blockId => mapping(bytes32 parentHash => uint32 transitionId)) transitionIds; + mapping(uint64 blockId => mapping(bytes32 parentHash => uint24 transitionId)) transitionIds; // Ring buffer for transitions mapping( uint64 blockId_mod_blockRingBufferSize diff --git a/packages/protocol/contracts/L1/TaikoEvents.sol b/packages/protocol/contracts/L1/TaikoEvents.sol index c3ebf43efc..07c8cd0965 100644 --- a/packages/protocol/contracts/L1/TaikoEvents.sol +++ b/packages/protocol/contracts/L1/TaikoEvents.sol @@ -32,6 +32,11 @@ abstract contract TaikoEvents { TaikoData.EthDeposit[] depositsProcessed ); + /// @notice Emitted when a block is proposed. + /// @param blockId The ID of the proposed block. + /// @param meta The metadata of the proposed block. + event BlockProposedV2(uint256 indexed blockId, TaikoData.BlockMetadataV2 meta); + /// @notice Emitted when a block's txList is in the calldata. /// @param blockId The ID of the proposed block. /// @param txList The txList. @@ -51,6 +56,22 @@ abstract contract TaikoEvents { uint16 tier ); + /// @notice Emitted when a transition is proved. + /// @param blockId The block ID. + /// @param tran The transition data. + /// @param prover The prover's address. + /// @param validityBond The validity bond amount. + /// @param tier The tier of the proof. + /// @param proposedIn The L1 block in which a transition is proved. + event TransitionProvedV2( + uint256 indexed blockId, + TaikoData.Transition tran, + address prover, + uint96 validityBond, + uint16 tier, + uint64 proposedIn + ); + /// @notice Emitted when a transition is contested. /// @param blockId The block ID. /// @param tran The transition data. @@ -65,6 +86,22 @@ abstract contract TaikoEvents { uint16 tier ); + /// @notice Emitted when a transition is contested. + /// @param blockId The block ID. + /// @param tran The transition data. + /// @param contester The contester's address. + /// @param contestBond The contest bond amount. + /// @param tier The tier of the proof. + /// @param proposedIn The L1 block in which this L2 block is proposed. + event TransitionContestedV2( + uint256 indexed blockId, + TaikoData.Transition tran, + address contester, + uint96 contestBond, + uint16 tier, + uint64 proposedIn + ); + /// @notice Emitted when proving is paused or unpaused. /// @param paused The pause status. event ProvingPaused(bool paused); @@ -84,6 +121,16 @@ abstract contract TaikoEvents { uint16 tier ); + /// @dev Emitted when a block is verified. + /// @param blockId The ID of the verified block. + /// @param prover The prover whose transition is used for verifying the + /// block. + /// @param blockHash The hash of the verified block. + /// @param tier The tier ID of the proof. + event BlockVerifiedV2( + uint256 indexed blockId, address indexed prover, bytes32 blockHash, uint16 tier + ); + /// @notice Emitted when some state variable values changed. /// @dev This event is currently used by Taiko node/client for block proposal/proving. /// @param slotB The SlotB data structure. diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 5c411158fd..b9896f2d81 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.24; import "../common/EssentialContract.sol"; +import "./libs/LibData.sol"; import "./libs/LibProposing.sol"; import "./libs/LibProving.sol"; import "./libs/LibVerifying.sol"; @@ -23,6 +24,7 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents { uint256[50] private __gap; + error L1_FORK_ERROR(); error L1_RECEIVE_DISABLED(); modifier whenProvingNotPaused() { @@ -55,7 +57,7 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents { initializer { __Essential_init(_owner, _rollupAddressManager); - LibUtils.init(state, _genesisBlockHash); + LibUtils.init(state, getConfig(), _genesisBlockHash); if (_toPause) _pause(); } @@ -81,7 +83,31 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents { { TaikoData.Config memory config = getConfig(); - (meta_, deposits_) = LibProposing.proposeBlock(state, config, this, _params, _txList); + TaikoData.BlockMetadataV2 memory meta2; + (meta2, deposits_) = LibProposing.proposeBlock(state, config, this, _params, _txList); + + if (meta2.id >= config.ontakeForkHeight) revert L1_FORK_ERROR(); + + if (LibUtils.shouldVerifyBlocks(config, meta_.id, true) && !state.slotB.provingPaused) { + LibVerifying.verifyBlocks(state, config, this, config.maxBlocksToVerify); + } + meta_ = LibData.blockMetadataV2toV1(meta2); + } + + function proposeBlockV2( + bytes calldata _params, + bytes calldata _txList + ) + external + whenNotPaused + nonReentrant + emitEventForClient + returns (TaikoData.BlockMetadataV2 memory meta_) + { + TaikoData.Config memory config = getConfig(); + + (meta_,) = LibProposing.proposeBlock(state, config, this, _params, _txList); + if (meta_.id < config.ontakeForkHeight) revert L1_FORK_ERROR(); if (LibUtils.shouldVerifyBlocks(config, meta_.id, true) && !state.slotB.provingPaused) { LibVerifying.verifyBlocks(state, config, this, config.maxBlocksToVerify); @@ -242,16 +268,15 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents { // their data. blockRingBufferSize: 360_000, // = 7200 * 50 maxBlocksToVerify: 16, - // This value is set based on `gasTargetPerL1Block = 15_000_000 * 4` in TaikoL2. - // We use 8x rather than 4x here to handle the scenario where the average number of - // Taiko blocks proposed per Ethereum block is smaller than 1. - // There is 250_000 additional gas for the anchor tx. Therefore, on explorers, you'll - // read Taiko's gas limit to be 240_250_000. blockMaxGasLimit: 240_000_000, livenessBond: 125e18, // 125 Taiko token stateRootSyncInternal: 16, - checkEOAForCalldataDA: true - }); + maxAnchorHeightOffset: 64, + basefeeAdjustmentQuotient: 8, + basefeeSharingPctg: 75, + blockGasIssuance: 20_000_000, + ontakeForkHeight: 374_400 // = 7200 * 52 + }); } /// @dev chain_pauser is supposed to be a cold wallet. diff --git a/packages/protocol/contracts/L1/access/IProposerAccess.sol b/packages/protocol/contracts/L1/access/IProposerAccess.sol new file mode 100644 index 0000000000..dc23e5f8b3 --- /dev/null +++ b/packages/protocol/contracts/L1/access/IProposerAccess.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title IProposerAccess +/// @dev An interface to check if a proposer is eligible to propose blocks in a specific Ethereum +/// block. +/// @custom:security-contact security@taiko.xyz +interface IProposerAccess { + /// @notice Checks if a proposer can propose block in the current Ethereum block. + /// @param _proposer The proposer. + /// @return true if the proposer can propose blocks, false otherwise. + function isProposerEligible(address _proposer) external view returns (bool); +} diff --git a/packages/protocol/contracts/L1/libs/LibData.sol b/packages/protocol/contracts/L1/libs/LibData.sol new file mode 100644 index 0000000000..250dfe53cc --- /dev/null +++ b/packages/protocol/contracts/L1/libs/LibData.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "../TaikoData.sol"; + +/// @title LibData +/// @notice A library that offers helper functions. +/// @custom:security-contact security@taiko.xyz +library LibData { + // = keccak256(abi.encode(new TaikoData.EthDeposit[](0))) + bytes32 internal constant EMPTY_ETH_DEPOSIT_HASH = + 0x569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd; + + function blockParamsV1ToV2(TaikoData.BlockParams memory _v1) + internal + pure + returns (TaikoData.BlockParamsV2 memory) + { + return TaikoData.BlockParamsV2({ + coinbase: _v1.coinbase, + extraData: _v1.extraData, + parentMetaHash: _v1.parentMetaHash, + anchorBlockId: 0, + timestamp: 0, + blobTxListOffset: 0, + blobTxListLength: 0, + blobIndex: 0 + }); + } + + function blockMetadataV2toV1(TaikoData.BlockMetadataV2 memory _v2) + internal + pure + returns (TaikoData.BlockMetadata memory) + { + return TaikoData.BlockMetadata({ + l1Hash: _v2.anchorBlockHash, + difficulty: _v2.difficulty, + blobHash: _v2.blobHash, + extraData: _v2.extraData, + depositsHash: EMPTY_ETH_DEPOSIT_HASH, + coinbase: _v2.coinbase, + id: _v2.id, + gasLimit: _v2.gasLimit, + timestamp: _v2.timestamp, + l1Height: _v2.anchorBlockId, + minTier: _v2.minTier, + blobUsed: _v2.blobUsed, + parentMetaHash: _v2.parentMetaHash, + sender: _v2.proposer + }); + } + + function metadataV1toV2( + TaikoData.BlockMetadata memory _v1, + uint96 _livenessBond + ) + internal + pure + returns (TaikoData.BlockMetadataV2 memory) + { + return TaikoData.BlockMetadataV2({ + anchorBlockHash: _v1.l1Hash, + difficulty: _v1.difficulty, + blobHash: _v1.blobHash, + extraData: _v1.extraData, + coinbase: _v1.coinbase, + id: _v1.id, + gasLimit: _v1.gasLimit, + timestamp: _v1.timestamp, + anchorBlockId: _v1.l1Height, + minTier: _v1.minTier, + blobUsed: _v1.blobUsed, + parentMetaHash: _v1.parentMetaHash, + proposer: _v1.sender, + livenessBond: _livenessBond, + proposedAt: 0, + proposedIn: 0, + blobTxListOffset: 0, + blobTxListLength: 0, + blobIndex: 0, + basefeeAdjustmentQuotient: 0, + basefeeSharingPctg: 0, + blockGasIssuance: 0 + }); + } + + function hashMetadata( + bool postFork, + TaikoData.BlockMetadataV2 memory _meta + ) + internal + pure + returns (bytes32) + { + return postFork + ? keccak256(abi.encode(_meta)) // + : keccak256(abi.encode(blockMetadataV2toV1(_meta))); + } +} diff --git a/packages/protocol/contracts/L1/libs/LibProposing.sol b/packages/protocol/contracts/L1/libs/LibProposing.sol index d5d9a28141..5a1f04564b 100644 --- a/packages/protocol/contracts/L1/libs/LibProposing.sol +++ b/packages/protocol/contracts/L1/libs/LibProposing.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; -import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "../../libs/LibAddress.sol"; import "../../libs/LibNetwork.sol"; +import "../access/IProposerAccess.sol"; import "./LibBonds.sol"; +import "./LibData.sol"; import "./LibUtils.sol"; /// @title LibProposing @@ -13,14 +14,10 @@ import "./LibUtils.sol"; library LibProposing { using LibAddress for address; - // = keccak256(abi.encode(new TaikoData.EthDeposit[](0))) - bytes32 private constant _EMPTY_ETH_DEPOSIT_HASH = - 0x569e75fc77c1a856f6daaf9e69d8a9566ca34aa47f9133711ce065a571af0cfd; - struct Local { TaikoData.SlotB b; - TaikoData.BlockParams params; bytes32 parentMetaHash; + bool postFork; } /// @notice Emitted when a block is proposed. @@ -38,6 +35,11 @@ library LibProposing { TaikoData.EthDeposit[] depositsProcessed ); + /// @notice Emitted when a block is proposed. + /// @param blockId The ID of the proposed block. + /// @param meta The metadata of the proposed block. + event BlockProposedV2(uint256 indexed blockId, TaikoData.BlockMetadataV2 meta); + /// @notice Emitted when a block's txList is in the calldata. /// @param blockId The ID of the proposed block. /// @param txList The txList. @@ -45,7 +47,9 @@ library LibProposing { error L1_BLOB_NOT_AVAILABLE(); error L1_BLOB_NOT_FOUND(); - error L1_INVALID_SIG(); + error L1_INVALID_ANCHOR_BLOCK(); + error L1_INVALID_PROPOSER(); + error L1_INVALID_TIMESTAMP(); error L1_LIVENESS_BOND_NOT_RECEIVED(); error L1_TOO_MANY_BLOCKS(); error L1_UNEXPECTED_PARENT(); @@ -64,18 +68,21 @@ library LibProposing { bytes calldata _data, bytes calldata _txList ) - internal - returns (TaikoData.BlockMetadata memory meta_, TaikoData.EthDeposit[] memory deposits_) + public + returns (TaikoData.BlockMetadataV2 memory meta_, TaikoData.EthDeposit[] memory deposits_) { - Local memory local; - local.params = abi.decode(_data, (TaikoData.BlockParams)); - - if (local.params.coinbase == address(0)) { - local.params.coinbase = msg.sender; + // Checks proposer access. + { + address access = _resolver.resolve(LibStrings.B_PROPOSER_ACCESS, true); + if (access != address(0) && !IProposerAccess(access).isProposerEligible(msg.sender)) { + revert L1_INVALID_PROPOSER(); + } } // Taiko, as a Based Rollup, enables permissionless block proposals. + Local memory local; local.b = _state.slotB; + local.postFork = local.b.numBlocks >= _config.ontakeForkHeight; // It's essential to ensure that the ring buffer for proposed blocks // still has space for at least one more block. @@ -83,15 +90,66 @@ library LibProposing { revert L1_TOO_MANY_BLOCKS(); } - local.parentMetaHash = - _state.blocks[(local.b.numBlocks - 1) % _config.blockRingBufferSize].metaHash; - // assert(parentMetaHash != 0); + TaikoData.BlockParamsV2 memory params; + + if (local.postFork) { + if (_data.length != 0) { + params = abi.decode(_data, (TaikoData.BlockParamsV2)); + // otherwise use a default BlockParamsV2 with 0 values + } + } else { + params = LibData.blockParamsV1ToV2(abi.decode(_data, (TaikoData.BlockParams))); + } + + if (params.coinbase == address(0)) { + params.coinbase = msg.sender; + } + + if (!local.postFork || params.anchorBlockId == 0) { + params.anchorBlockId = uint64(block.number - 1); + } + + if (!local.postFork || params.timestamp == 0) { + params.timestamp = uint64(block.timestamp); + } - // Check if parent block has the right meta hash. This is to allow the proposer to make sure - // the block builds on the expected latest chain state. - if (local.params.parentMetaHash != 0 && local.parentMetaHash != local.params.parentMetaHash) + // Verify params against the parent block. { - revert L1_UNEXPECTED_PARENT(); + TaikoData.Block storage parentBlk = + _state.blocks[(local.b.numBlocks - 1) % _config.blockRingBufferSize]; + + if (local.postFork) { + // Verify the passed in L1 state block number. + // We only allow the L1 block to be 2 epochs old. + // The other constraint is that the L1 block number needs to be larger than or equal + // the one in the previous L2 block. + if ( + params.anchorBlockId + _config.maxAnchorHeightOffset < block.number // + || params.anchorBlockId >= block.number + || params.anchorBlockId < parentBlk.proposedIn + ) { + revert L1_INVALID_ANCHOR_BLOCK(); + } + + // Verify the passed in timestamp. + // We only allow the timestamp to be 2 epochs old. + // The other constraint is that the timestamp needs to be larger than or equal the + // one in the previous L2 block. + if ( + params.timestamp + _config.maxAnchorHeightOffset * 12 < block.timestamp + || params.timestamp > block.timestamp || params.timestamp < parentBlk.proposedAt + ) { + revert L1_INVALID_TIMESTAMP(); + } + } + + // Check if parent block has the right meta hash. This is to allow the proposer to make + // sure the block builds on the expected latest chain state. + if (params.parentMetaHash == 0) { + params.parentMetaHash = parentBlk.metaHash; + } else if (params.parentMetaHash != parentBlk.metaHash) { + revert L1_UNEXPECTED_PARENT(); + } } // Initialize metadata to compute a metaHash, which forms a part of @@ -99,21 +157,29 @@ library LibProposing { // If we choose to persist all data fields in the metadata, it will // require additional storage slots. unchecked { - meta_ = TaikoData.BlockMetadata({ - l1Hash: blockhash(block.number - 1), - difficulty: 0, // to be initialized below + meta_ = TaikoData.BlockMetadataV2({ + anchorBlockHash: blockhash(params.anchorBlockId), + difficulty: keccak256(abi.encode("TAIKO_DIFFICULTY", local.b.numBlocks)), blobHash: 0, // to be initialized below - extraData: local.params.extraData, - depositsHash: _EMPTY_ETH_DEPOSIT_HASH, - coinbase: local.params.coinbase, + extraData: params.extraData, + coinbase: params.coinbase, id: local.b.numBlocks, gasLimit: _config.blockMaxGasLimit, - timestamp: uint64(block.timestamp), - l1Height: uint64(block.number - 1), + timestamp: params.timestamp, + anchorBlockId: params.anchorBlockId, minTier: 0, // to be initialized below blobUsed: _txList.length == 0, - parentMetaHash: local.parentMetaHash, - sender: msg.sender + parentMetaHash: params.parentMetaHash, + proposer: msg.sender, + livenessBond: _config.livenessBond, + proposedAt: uint64(block.timestamp), + proposedIn: uint64(block.number), + blobTxListOffset: params.blobTxListOffset, + blobTxListLength: params.blobTxListLength, + blobIndex: params.blobIndex, + basefeeAdjustmentQuotient: _config.basefeeAdjustmentQuotient, + basefeeSharingPctg: _config.basefeeSharingPctg, + blockGasIssuance: _config.blockGasIssuance }); } @@ -125,33 +191,13 @@ library LibProposing { // proposeBlock functions are called more than once in the same // L1 transaction, these multiple L2 blocks will share the same // blob. - meta_.blobHash = blobhash(0); + meta_.blobHash = blobhash(params.blobIndex); if (meta_.blobHash == 0) revert L1_BLOB_NOT_FOUND(); } else { meta_.blobHash = keccak256(_txList); - - // This function must be called as the outmost transaction (not an internal one) for - // the node to extract the calldata easily. - // We cannot rely on `msg.sender != tx.origin` for EOA check, as it will break after EIP - // 7645: Alias ORIGIN to SENDER - if ( - _config.checkEOAForCalldataDA - && ECDSA.recover(meta_.blobHash, local.params.signature) != msg.sender - ) { - revert L1_INVALID_SIG(); - } - emit CalldataTxList(meta_.id, _txList); } - // Following the Merge, the L1 mixHash incorporates the - // prevrandao value from the beacon chain. Given the possibility - // of multiple Taiko blocks being proposed within a single - // Ethereum block, we choose to introduce a salt to this random - // number as the L2 mixHash. - meta_.difficulty = - keccak256(abi.encodePacked(block.prevrandao, local.b.numBlocks, block.number)); - { ITierRouter tierRouter = ITierRouter(_resolver.resolve(LibStrings.B_TIER_ROUTER, false)); ITierProvider tierProvider = ITierProvider(tierRouter.getProvider(local.b.numBlocks)); @@ -162,17 +208,15 @@ library LibProposing { // Create the block that will be stored onchain TaikoData.Block memory blk = TaikoData.Block({ - metaHash: LibUtils.hashMetadata(meta_), - // Safeguard the liveness bond to ensure its preservation, - // particularly in scenarios where it might be altered after the - // block's proposal but before it has been proven or verified. + metaHash: LibData.hashMetadata(local.postFork, meta_), assignedProver: address(0), - livenessBond: _config.livenessBond, + livenessBond: local.postFork ? 0 : meta_.livenessBond, blockId: local.b.numBlocks, - proposedAt: meta_.timestamp, - proposedIn: uint64(block.number), + proposedAt: local.postFork ? params.timestamp : uint64(block.timestamp), + proposedIn: local.postFork ? params.anchorBlockId : uint64(block.number), // For a new block, the next transition ID is always 1, not 0. nextTransitionId: 1, + livenessBondReturned: false, // For unverified block, its verifiedTransitionId is always 0. verifiedTransitionId: 0 }); @@ -194,12 +238,17 @@ library LibProposing { } deposits_ = new TaikoData.EthDeposit[](0); - emit BlockProposed({ - blockId: meta_.id, - assignedProver: msg.sender, - livenessBond: _config.livenessBond, - meta: meta_, - depositsProcessed: deposits_ - }); + + if (local.postFork) { + emit BlockProposedV2(meta_.id, meta_); + } else { + emit BlockProposed({ + blockId: meta_.id, + assignedProver: msg.sender, + livenessBond: _config.livenessBond, + meta: LibData.blockMetadataV2toV1(meta_), + depositsProcessed: deposits_ + }); + } } } diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index a0afe0ad19..9e1f8eb235 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -3,6 +3,7 @@ pragma solidity 0.8.24; import "../../verifiers/IVerifier.sol"; import "./LibBonds.sol"; +import "./LibData.sol"; import "./LibUtils.sol"; /// @title LibProving @@ -23,11 +24,13 @@ library LibProving { uint96 livenessBond; uint64 slot; uint64 blockId; - uint32 tid; + uint24 tid; bool lastUnpausedAt; bool isTopTier; bool inProvingWindow; bool sameTransition; + bool postFork; + uint64 proposedAt; } /// @notice Emitted when a transition is proved. @@ -44,6 +47,22 @@ library LibProving { uint16 tier ); + /// @notice Emitted when a transition is proved. + /// @param blockId The block ID. + /// @param tran The transition data. + /// @param prover The prover's address. + /// @param validityBond The validity bond amount. + /// @param tier The tier of the proof. + /// @param proposedIn The L1 block in which a transition is proved. + event TransitionProvedV2( + uint256 indexed blockId, + TaikoData.Transition tran, + address prover, + uint96 validityBond, + uint16 tier, + uint64 proposedIn + ); + /// @notice Emitted when a transition is contested. /// @param blockId The block ID. /// @param tran The transition data. @@ -58,12 +77,29 @@ library LibProving { uint16 tier ); + /// @notice Emitted when a transition is contested. + /// @param blockId The block ID. + /// @param tran The transition data. + /// @param contester The contester's address. + /// @param contestBond The contest bond amount. + /// @param tier The tier of the proof. + /// @param proposedIn The L1 block in which this L2 block is proposed. + event TransitionContestedV2( + uint256 indexed blockId, + TaikoData.Transition tran, + address contester, + uint96 contestBond, + uint16 tier, + uint64 proposedIn + ); + /// @notice Emitted when proving is paused or unpaused. /// @param paused The pause status. event ProvingPaused(bool paused); error L1_ALREADY_CONTESTED(); error L1_ALREADY_PROVED(); + error L1_BLOCK_MISMATCH(); error L1_CANNOT_CONTEST(); error L1_INVALID_PAUSE_STATUS(); error L1_INVALID_TIER(); @@ -101,11 +137,29 @@ library LibProving { ) internal { - ( - TaikoData.BlockMetadata memory meta, - TaikoData.Transition memory tran, - TaikoData.TierProof memory proof - ) = abi.decode(_input, (TaikoData.BlockMetadata, TaikoData.Transition, TaikoData.TierProof)); + Local memory local; + + local.b = _state.slotB; + local.blockId = _blockId; + local.postFork = _blockId >= _config.ontakeForkHeight; + + TaikoData.BlockMetadataV2 memory meta; + TaikoData.Transition memory tran; + TaikoData.TierProof memory proof; + + if (local.postFork) { + (meta, tran, proof) = abi.decode( + _input, (TaikoData.BlockMetadataV2, TaikoData.Transition, TaikoData.TierProof) + ); + } else { + TaikoData.BlockMetadata memory meta1; + + (meta1, tran, proof) = abi.decode( + _input, (TaikoData.BlockMetadata, TaikoData.Transition, TaikoData.TierProof) + ); + // Below, the liveness bond parameter must be 0 to force reading from block storage. + meta = LibData.metadataV1toV2(meta1, 0); + } if (_blockId != meta.id) revert LibUtils.L1_INVALID_BLOCK_ID(); @@ -116,9 +170,6 @@ library LibProving { revert L1_INVALID_TRANSITION(); } - Local memory local; - local.b = _state.slotB; - // Check that the block has been proposed but has not yet been verified. if (meta.id <= local.b.lastVerifiedBlockId || meta.id >= local.b.numBlocks) { revert LibUtils.L1_INVALID_BLOCK_ID(); @@ -127,7 +178,7 @@ library LibProving { local.slot = meta.id % _config.blockRingBufferSize; TaikoData.Block storage blk = _state.blocks[local.slot]; - local.blockId = blk.blockId; + local.proposedAt = local.postFork ? meta.proposedAt : blk.proposedAt; if (LibUtils.shouldSyncStateRoot(_config.stateRootSyncInternal, local.blockId)) { local.stateRoot = tran.stateRoot; @@ -135,17 +186,19 @@ library LibProving { local.assignedProver = blk.assignedProver; if (local.assignedProver == address(0)) { - local.assignedProver = meta.sender; + local.assignedProver = meta.proposer; } - local.livenessBond = blk.livenessBond; + if (!blk.livenessBondReturned) { + local.livenessBond = meta.livenessBond == 0 ? blk.livenessBond : meta.livenessBond; + } local.metaHash = blk.metaHash; // Check the integrity of the block data. It's worth noting that in // theory, this check may be skipped, but it's included for added // caution. - if (local.blockId != meta.id || local.metaHash != LibUtils.hashMetadata(meta)) { - revert LibUtils.L1_BLOCK_MISMATCH(); + if (local.metaHash != LibData.hashMetadata(local.postFork, meta)) { + revert L1_BLOCK_MISMATCH(); } // Each transition is uniquely identified by the parentHash, with the @@ -228,13 +281,24 @@ library LibProving { // (L1_ALREADY_PROVED). _overrideWithHigherProof(_state, _resolver, blk, ts, tran, proof, local); - emit TransitionProved({ - blockId: local.blockId, - tran: tran, - prover: msg.sender, - validityBond: local.tier.validityBond, - tier: proof.tier - }); + if (local.postFork) { + emit TransitionProvedV2({ + blockId: local.blockId, + tran: tran, + prover: msg.sender, + validityBond: local.tier.validityBond, + tier: proof.tier, + proposedIn: meta.proposedIn + }); + } else { + emit TransitionProved({ + blockId: local.blockId, + tran: tran, + prover: msg.sender, + validityBond: local.tier.validityBond, + tier: proof.tier + }); + } } else { // New transition and old transition on the same tier - and if this transaction tries to // prove the same, it reverts @@ -249,13 +313,24 @@ library LibProving { ts.blockHash = tran.blockHash; ts.stateRoot = local.stateRoot; - emit TransitionProved({ - blockId: local.blockId, - tran: tran, - prover: msg.sender, - validityBond: 0, - tier: proof.tier - }); + if (local.postFork) { + emit TransitionProvedV2({ + blockId: local.blockId, + tran: tran, + prover: msg.sender, + validityBond: 0, + tier: proof.tier, + proposedIn: meta.proposedIn + }); + } else { + emit TransitionProved({ + blockId: local.blockId, + tran: tran, + prover: msg.sender, + validityBond: 0, + tier: proof.tier + }); + } } else { // Contesting but not on the highest tier if (ts.contester != address(0)) revert L1_ALREADY_CONTESTED(); @@ -283,13 +358,24 @@ library LibProving { ts.contestBond = local.tier.contestBond; ts.contester = msg.sender; - emit TransitionContested({ - blockId: local.blockId, - tran: tran, - contester: msg.sender, - contestBond: local.tier.contestBond, - tier: proof.tier - }); + if (local.postFork) { + emit TransitionContestedV2({ + blockId: local.blockId, + tran: tran, + contester: msg.sender, + contestBond: local.tier.contestBond, + tier: proof.tier, + proposedIn: meta.proposedIn + }); + } else { + emit TransitionContested({ + blockId: local.blockId, + tran: tran, + contester: msg.sender, + contestBond: local.tier.contestBond, + tier: proof.tier + }); + } } } @@ -305,7 +391,7 @@ library LibProving { Local memory _local ) private - returns (uint32 tid_, TaikoData.TransitionState memory ts_) + returns (uint24 tid_, TaikoData.TransitionState memory ts_) { tid_ = LibUtils.getTransitionId(_state, _blk, _local.slot, _tran.parentHash); @@ -328,7 +414,7 @@ library LibProving { // Keep in mind that state.transitions are also reusable storage // slots, so it's necessary to reinitialize all transition fields // below. - ts_.timestamp = _blk.proposedAt; + ts_.timestamp = _local.proposedAt; if (tid_ == 1) { // This approach serves as a cost-saving technique for the @@ -416,6 +502,7 @@ library LibProving { // After the first proof, the block's liveness bond will always be reset to 0. // This means liveness bond will be handled only once for any given block. _blk.livenessBond = 0; + _blk.livenessBondReturned = true; if (_returnLivenessBond(_local, _proof.data)) { if (_local.assignedProver == msg.sender) { diff --git a/packages/protocol/contracts/L1/libs/LibUtils.sol b/packages/protocol/contracts/L1/libs/LibUtils.sol index b564bbda9d..0eadc38434 100644 --- a/packages/protocol/contracts/L1/libs/LibUtils.sol +++ b/packages/protocol/contracts/L1/libs/LibUtils.sol @@ -31,6 +31,16 @@ library LibUtils { uint16 tier ); + /// @dev Emitted when a block is verified. + /// @param blockId The ID of the verified block. + /// @param prover The prover whose transition is used for verifying the + /// block. + /// @param blockHash The hash of the verified block. + /// @param tier The tier ID of the proof. + event BlockVerifiedV2( + uint256 indexed blockId, address indexed prover, bytes32 blockHash, uint16 tier + ); + error L1_BLOCK_MISMATCH(); error L1_INVALID_BLOCK_ID(); error L1_INVALID_GENESIS_HASH(); @@ -40,7 +50,13 @@ library LibUtils { /// @notice Initializes the Taiko protocol state. /// @param _state The state to initialize. /// @param _genesisBlockHash The block hash of the genesis block. - function init(TaikoData.State storage _state, bytes32 _genesisBlockHash) internal { + function init( + TaikoData.State storage _state, + TaikoData.Config memory _config, + bytes32 _genesisBlockHash + ) + internal + { if (_genesisBlockHash == 0) revert L1_INVALID_GENESIS_HASH(); // Init state _state.slotA.genesisHeight = uint64(block.number); @@ -60,13 +76,22 @@ library LibUtils { ts.prover = address(0); ts.timestamp = uint64(block.timestamp); - emit BlockVerified({ - blockId: 0, - prover: address(0), - blockHash: _genesisBlockHash, - stateRoot: 0, - tier: 0 - }); + if (_config.ontakeForkHeight == 0) { + emit BlockVerifiedV2({ + blockId: 0, + prover: address(0), + blockHash: _genesisBlockHash, + tier: 0 + }); + } else { + emit BlockVerified({ + blockId: 0, + prover: address(0), + blockHash: _genesisBlockHash, + stateRoot: 0, + tier: 0 + }); + } } /// @dev Retrieves a block based on its ID. @@ -157,7 +182,7 @@ library LibUtils { { (TaikoData.Block storage blk, uint64 slot) = getBlock(_state, _config, _blockId); - uint32 tid = getTransitionId(_state, blk, slot, _parentHash); + uint24 tid = getTransitionId(_state, blk, slot, _parentHash); if (tid == 0) revert L1_TRANSITION_NOT_FOUND(); return _state.transitions[slot][tid]; @@ -173,7 +198,7 @@ library LibUtils { ) internal view - returns (uint32 tid_) + returns (uint24 tid_) { if (_state.transitions[_slot][1].key == _parentHash) { tid_ = 1; @@ -244,8 +269,4 @@ library LibUtils { return _blockId % _stateRootSyncInternal == _stateRootSyncInternal - 1; } } - - function hashMetadata(TaikoData.BlockMetadata memory _meta) internal pure returns (bytes32) { - return keccak256(abi.encode(_meta)); - } } diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index 6cb7af423f..2f25cf311f 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -16,14 +16,15 @@ library LibVerifying { uint64 blockId; uint64 slot; uint64 numBlocksVerified; - uint32 tid; - uint32 lastVerifiedTransitionId; + uint24 tid; + uint24 lastVerifiedTransitionId; uint16 tier; bytes32 blockHash; bytes32 syncStateRoot; uint64 syncBlockId; - uint32 syncTransitionId; + uint24 syncTransitionId; address prover; + bool postFork; ITierRouter tierRouter; } @@ -76,6 +77,7 @@ library LibVerifying { local.blockId < local.b.numBlocks && local.numBlocksVerified < _maxBlocksToVerify ) { local.slot = local.blockId % _config.blockRingBufferSize; + local.postFork = local.blockId >= _config.ontakeForkHeight; blk = _state.blocks[local.slot]; if (blk.blockId != local.blockId) revert L1_BLOCK_MISMATCH(); @@ -126,13 +128,22 @@ library LibVerifying { // either when the transitions are generated or proven. In such cases, both the // provers and contesters of those transitions forfeit their bonds. - emit LibUtils.BlockVerified({ - blockId: local.blockId, - prover: local.prover, - blockHash: local.blockHash, - stateRoot: 0, // DEPRECATED and is always zero. - tier: local.tier - }); + if (local.postFork) { + emit LibUtils.BlockVerifiedV2({ + blockId: local.blockId, + prover: local.prover, + blockHash: local.blockHash, + tier: local.tier + }); + } else { + emit LibUtils.BlockVerified({ + blockId: local.blockId, + prover: local.prover, + blockHash: local.blockHash, + stateRoot: 0, // DEPRECATED and is always zero. + tier: local.tier + }); + } if (LibUtils.shouldSyncStateRoot(_config.stateRootSyncInternal, local.blockId)) { bytes32 stateRoot = ts.stateRoot; @@ -188,7 +199,7 @@ library LibVerifying { { (TaikoData.Block storage blk,) = LibUtils.getBlock(_state, _config, _blockId); - uint32 tid = blk.verifiedTransitionId; + uint24 tid = blk.verifiedTransitionId; if (tid == 0) return address(0); return LibUtils.getTransition(_state, _config, _blockId, tid).prover; diff --git a/packages/protocol/contracts/L1/provers/GuardianProver.sol b/packages/protocol/contracts/L1/provers/GuardianProver.sol index 80dce3bed2..d77abcde01 100644 --- a/packages/protocol/contracts/L1/provers/GuardianProver.sol +++ b/packages/protocol/contracts/L1/provers/GuardianProver.sol @@ -185,41 +185,34 @@ contract GuardianProver is IVerifier, EssentialContract { external whenNotPaused nonReentrant - returns (bool approved_) + returns (bool) { - bytes32 proofHash = keccak256(abi.encode(_meta, _tran, _proof.data)); - uint256 _version = version; - bytes32 currProofHash = latestProofHash[_version][_meta.id]; - - if (currProofHash == 0) { - latestProofHash[_version][_meta.id] = proofHash; - currProofHash = proofHash; - } - - bool conflicting = currProofHash != proofHash; - bool pauseProving = conflicting && provingAutoPauseEnabled - && address(this) == resolve(LibStrings.B_CHAIN_WATCHDOG, true); - - if (conflicting) { - latestProofHash[_version][_meta.id] = proofHash; - emit ConflictingProofs(_meta.id, msg.sender, currProofHash, proofHash, pauseProving); - } - - if (pauseProving) { - ITaikoL1(resolve(LibStrings.B_TAIKO, false)).pauseProving(true); - } else { - approved_ = _approve(_meta.id, proofHash); - emit GuardianApproval(msg.sender, _meta.id, _tran.blockHash, approved_, _proof.data); - - if (approved_) { - delete approvals[_version][proofHash]; - delete latestProofHash[_version][_meta.id]; + return _approve({ + _blockId: _meta.id, + _proofHash: keccak256(abi.encode(_meta, _tran, _proof.data)), + _blockHash: _tran.blockHash, + _data: abi.encode(_meta, _tran, _proof), + _proofData: _proof.data + }); + } - ITaikoL1(resolve(LibStrings.B_TAIKO, false)).proveBlock( - _meta.id, abi.encode(_meta, _tran, _proof) - ); - } - } + function approveV2( + TaikoData.BlockMetadataV2 calldata _meta2, + TaikoData.Transition calldata _tran, + TaikoData.TierProof calldata _proof + ) + external + whenNotPaused + nonReentrant + returns (bool) + { + return _approve({ + _blockId: _meta2.id, + _proofHash: keccak256(abi.encode(_meta2, _tran, _proof.data)), + _blockHash: _tran.blockHash, + _data: abi.encode(_meta2, _tran, _proof), + _proofData: _proof.data + }); } /// @notice Pauses chain proving and verification. @@ -251,7 +244,55 @@ contract GuardianProver is IVerifier, EssentialContract { return guardians.length; } - function _approve(uint256 _blockId, bytes32 _proofHash) internal returns (bool approved_) { + function _approve( + uint64 _blockId, + bytes32 _proofHash, + bytes32 _blockHash, + bytes memory _data, + bytes memory _proofData + ) + internal + returns (bool approved_) + { + uint256 _version = version; + bytes32 currProofHash = latestProofHash[_version][_blockId]; + + if (currProofHash == 0) { + latestProofHash[_version][_blockId] = _proofHash; + currProofHash = _proofHash; + } + + bool conflicting = currProofHash != _proofHash; + bool pauseProving = conflicting && provingAutoPauseEnabled + && address(this) == resolve(LibStrings.B_CHAIN_WATCHDOG, true); + + if (conflicting) { + latestProofHash[_version][_blockId] = _proofHash; + emit ConflictingProofs(_blockId, msg.sender, currProofHash, _proofHash, pauseProving); + } + + if (pauseProving) { + ITaikoL1(resolve(LibStrings.B_TAIKO, false)).pauseProving(true); + } else { + approved_ = _saveApproval(_blockId, _proofHash); + emit GuardianApproval(msg.sender, _blockId, _blockHash, approved_, _proofData); + + if (approved_) { + delete approvals[_version][_proofHash]; + delete latestProofHash[_version][_blockId]; + + ITaikoL1(resolve(LibStrings.B_TAIKO, false)).proveBlock(_blockId, _data); + } + } + } + + function _saveApproval( + uint256 _blockId, + bytes32 _proofHash + ) + internal + returns (bool approved_) + { uint256 id = guardianIds[msg.sender]; if (id == 0) revert GP_INVALID_GUARDIAN(); diff --git a/packages/protocol/contracts/L1/tiers/ITierProvider.sol b/packages/protocol/contracts/L1/tiers/ITierProvider.sol index af262802f0..7faf63571f 100644 --- a/packages/protocol/contracts/L1/tiers/ITierProvider.sol +++ b/packages/protocol/contracts/L1/tiers/ITierProvider.sol @@ -40,6 +40,7 @@ library LibTiers { /// @notice SGX tier ID. uint16 public constant TIER_SGX = 200; + uint16 public constant TIER_SGX2 = 200; /// @notice SGX + ZKVM tier ID. uint16 public constant TIER_SGX_ZKVM = 300; diff --git a/packages/protocol/contracts/L1/tiers/TierProviderBase.sol b/packages/protocol/contracts/L1/tiers/TierProviderBase.sol index ca45e5fe17..90a08e4386 100644 --- a/packages/protocol/contracts/L1/tiers/TierProviderBase.sol +++ b/packages/protocol/contracts/L1/tiers/TierProviderBase.sol @@ -37,6 +37,17 @@ abstract contract TierProviderBase is ITierProvider { }); } + if (_tierId == LibTiers.TIER_SGX2) { + return ITierProvider.Tier({ + verifierName: LibStrings.B_TIER_SGX2, + validityBond: 125 ether, // TKO + contestBond: 820 ether, // =250TKO * 6.5625 + cooldownWindow: 1440, //24 hours + provingWindow: 60, // 1 hours + maxBlocksToVerifyPerProof: 0 + }); + } + if (_tierId == LibTiers.TIER_SGX_ZKVM) { return ITierProvider.Tier({ verifierName: LibStrings.B_TIER_SGX_ZKVM, diff --git a/packages/protocol/contracts/L1/tiers/TierProviderV3.sol b/packages/protocol/contracts/L1/tiers/TierProviderV3.sol index d8e6555a65..c8cdd5d7de 100644 --- a/packages/protocol/contracts/L1/tiers/TierProviderV3.sol +++ b/packages/protocol/contracts/L1/tiers/TierProviderV3.sol @@ -8,17 +8,14 @@ import "./TierProviderBase.sol"; contract TierProviderV3 is TierProviderBase { /// @inheritdoc ITierProvider function getTierIds() public pure override returns (uint16[] memory tiers_) { - tiers_ = new uint16[](4); - tiers_[0] = LibTiers.TIER_SGX; - tiers_[1] = LibTiers.TIER_SGX_ZKVM; - tiers_[2] = LibTiers.TIER_GUARDIAN_MINORITY; - tiers_[3] = LibTiers.TIER_GUARDIAN; + tiers_ = new uint16[](3); + tiers_[0] = LibTiers.TIER_SGX2; + tiers_[1] = LibTiers.TIER_GUARDIAN_MINORITY; + tiers_[2] = LibTiers.TIER_GUARDIAN; } /// @inheritdoc ITierProvider - function getMinTier(uint256 _rand) public pure override returns (uint16) { - // 0.1% require SGX + ZKVM; all others require SGX - if (_rand % 1000 == 0) return LibTiers.TIER_SGX_ZKVM; - else return LibTiers.TIER_SGX; + function getMinTier(uint256) public pure override returns (uint16) { + return LibTiers.TIER_SGX2; } } diff --git a/packages/protocol/contracts/L2/Lib1559Math.sol b/packages/protocol/contracts/L2/Lib1559Math.sol index 4b5316c28f..1ee53ef118 100644 --- a/packages/protocol/contracts/L2/Lib1559Math.sol +++ b/packages/protocol/contracts/L2/Lib1559Math.sol @@ -15,7 +15,7 @@ library Lib1559Math { error EIP1559_INVALID_PARAMS(); function calc1559BaseFee( - uint32 _gasTargetPerL1Block, + uint32 _gasTarget, uint8 _adjustmentQuotient, uint64 _gasExcess, uint64 _gasIssuance, @@ -35,7 +35,7 @@ library Lib1559Math { // bonding curve, regardless the actual amount of gas used by this // block, however, this block's gas used will affect the next // block's base fee. - basefee_ = basefee(gasExcess_, uint256(_adjustmentQuotient) * _gasTargetPerL1Block); + basefee_ = basefee(gasExcess_, uint256(_adjustmentQuotient) * _gasTarget); // Always make sure basefee is nonzero, this is required by the node. if (basefee_ == 0) basefee_ = 1; diff --git a/packages/protocol/contracts/L2/TaikoL2.sol b/packages/protocol/contracts/L2/TaikoL2.sol index 56f79f936e..417a62149f 100644 --- a/packages/protocol/contracts/L2/TaikoL2.sol +++ b/packages/protocol/contracts/L2/TaikoL2.sol @@ -25,6 +25,8 @@ contract TaikoL2 is EssentialContract { /// @notice Golden touch address is the only address that can do the anchor transaction. address public constant GOLDEN_TOUCH_ADDRESS = 0x0000777735367b36bC9B61C50022d9D0700dB4Ec; + uint256 public constant ONTAKE_FORK_HEIGHT = 374_400; // = 7200 * 52 + /// @notice Mapping from L2 block numbers to their block hashes. All L2 block hashes will /// be saved in this mapping. mapping(uint256 blockId => bytes32 blockHash) public l2Hashes; @@ -54,6 +56,7 @@ contract TaikoL2 is EssentialContract { event Anchored(bytes32 parentHash, uint64 gasExcess); error L2_BASEFEE_MISMATCH(); + error L2_FORK_ERROR(); error L2_INVALID_L1_CHAIN_ID(); error L2_INVALID_L2_CHAIN_ID(); error L2_INVALID_PARAM(); @@ -104,10 +107,9 @@ contract TaikoL2 is EssentialContract { /// @dev This function can be called freely as the golden touch private key is publicly known, /// but the Taiko node guarantees the first transaction of each block is always this anchor /// transaction, and any subsequent calls will revert with L2_PUBLIC_INPUT_HASH_MISMATCH. - /// @param _l1BlockHash The latest L1 block hash when this block was - /// proposed. - /// @param _l1StateRoot The latest L1 block's state root. - /// @param _l1BlockId The latest L1 block height when this block was proposed. + /// @param _l1BlockHash The `anchorBlockHash` value in this block's metadata. + /// @param _l1StateRoot The state root for the L1 block with id equals `_anchorBlockId` + /// @param _l1BlockId The `anchorBlockId` value in this block's metadata. /// @param _parentGasUsed The gas used in the parent block. function anchor( bytes32 _l1BlockHash, @@ -118,50 +120,34 @@ contract TaikoL2 is EssentialContract { external nonReentrant { - if ( - _l1BlockHash == 0 || _l1StateRoot == 0 || _l1BlockId == 0 - || (block.number != 1 && _parentGasUsed == 0) - ) { - revert L2_INVALID_PARAM(); - } - - if (msg.sender != GOLDEN_TOUCH_ADDRESS) revert L2_INVALID_SENDER(); - - uint256 parentId; - unchecked { - parentId = block.number - 1; - } - - // Verify ancestor hashes - (bytes32 publicInputHashOld, bytes32 publicInputHashNew) = _calcPublicInputHash(parentId); - if (publicInputHash != publicInputHashOld) { - revert L2_PUBLIC_INPUT_HASH_MISMATCH(); - } - - // Verify the base fee per gas is correct - (uint256 _basefee, uint64 _gasExcess) = getBasefee(_l1BlockId, _parentGasUsed); - - if (!skipFeeCheck() && block.basefee != _basefee) { - revert L2_BASEFEE_MISMATCH(); - } - - if (_l1BlockId > lastSyncedBlock) { - // Store the L1's state root as a signal to the local signal service to - // allow for multi-hop bridging. - ISignalService(resolve(LibStrings.B_SIGNAL_SERVICE, false)).syncChainData( - l1ChainId, LibStrings.H_STATE_ROOT, _l1BlockId, _l1StateRoot - ); - - lastSyncedBlock = _l1BlockId; - } - - // Update state variables - bytes32 _parentHash = blockhash(parentId); - l2Hashes[parentId] = _parentHash; - publicInputHash = publicInputHashNew; - gasExcess = _gasExcess; + if (block.number >= ONTAKE_FORK_HEIGHT) revert L2_FORK_ERROR(); + _anchor( + _l1BlockId, + _l1StateRoot, + _parentGasUsed, + 0, // not used + 0 // not used + ); + } - emit Anchored(_parentHash, _gasExcess); + function anchorV2( + uint64 _anchorBlockId, + bytes32 _anchorStateRoot, + uint32 _parentGasUsed, + uint32 _blockGasIssuance, + uint8 _basefeeAdjustmentQuotient + ) + external + nonReentrant + { + if (block.number < ONTAKE_FORK_HEIGHT) revert L2_FORK_ERROR(); + _anchor( + _anchorBlockId, + _anchorStateRoot, + _parentGasUsed, + _blockGasIssuance, + _basefeeAdjustmentQuotient + ); } /// @notice Withdraw token or Ether from this address @@ -186,12 +172,14 @@ contract TaikoL2 is EssentialContract { /// @notice Gets the basefee and gas excess using EIP-1559 configuration for /// the given parameters. - /// @param _l1BlockId The synced L1 height in the next Taiko block + /// @dev This function will deprecate after Ontake fork, node/client shall use calculateBaseFee + /// instead for base fee prediction. + /// @param _anchorBlockId The synced L1 height in the next Taiko block /// @param _parentGasUsed Gas used in the parent block. /// @return basefee_ The calculated EIP-1559 base fee per gas. /// @return gasExcess_ The new gasExcess value. function getBasefee( - uint64 _l1BlockId, + uint64 _anchorBlockId, uint32 _parentGasUsed ) public @@ -199,7 +187,7 @@ contract TaikoL2 is EssentialContract { returns (uint256 basefee_, uint64 gasExcess_) { LibL2Config.Config memory config = getConfig(); - uint64 gasIssuance = uint64(_l1BlockId - lastSyncedBlock) * config.gasTargetPerL1Block; + uint64 gasIssuance = uint64(_anchorBlockId - lastSyncedBlock) * config.gasTargetPerL1Block; (basefee_, gasExcess_) = Lib1559Math.calc1559BaseFee( config.gasTargetPerL1Block, @@ -232,6 +220,86 @@ contract TaikoL2 is EssentialContract { return false; } + /// @notice Calculates the basefee and the new gas excess value based on parent gas used and gas + /// excess. + /// @param _blockGasIssuance The L2 block's gas issuance. + /// @param _adjustmentQuotient The gas adjustment quotient. + /// @param _gasExcess The current gas excess value. + /// @param _parentGasUsed Total gas used by the parent block. + /// @return basefee_ Next block's base fee. + /// @return gasExcess_ The new gas excess value. + function calculateBaseFee( + uint32 _blockGasIssuance, + uint8 _adjustmentQuotient, + uint64 _gasExcess, + uint32 _parentGasUsed + ) + public + pure + returns (uint256 basefee_, uint64 gasExcess_) + { + return Lib1559Math.calc1559BaseFee( + _blockGasIssuance, _adjustmentQuotient, _gasExcess, _blockGasIssuance, _parentGasUsed + ); + } + + function _anchor( + uint64 _anchorBlockId, + bytes32 _anchorStateRoot, + uint32 _parentGasUsed, + uint32 _blockGasIssuance, // only used by ontake + uint8 _basefeeAdjustmentQuotient // only used by ontake + ) + private + { + if ( + _anchorStateRoot == 0 || _anchorBlockId == 0 + || (block.number != 1 && _parentGasUsed == 0) + ) { + revert L2_INVALID_PARAM(); + } + + if (msg.sender != GOLDEN_TOUCH_ADDRESS) revert L2_INVALID_SENDER(); + + uint256 parentId; + unchecked { + parentId = block.number - 1; + } + + // Verify ancestor hashes + (bytes32 publicInputHashOld, bytes32 publicInputHashNew) = _calcPublicInputHash(parentId); + if (publicInputHash != publicInputHashOld) { + revert L2_PUBLIC_INPUT_HASH_MISMATCH(); + } + + // Verify the base fee per gas is correct + (uint256 _basefee, uint64 _gasExcess) = block.number < ONTAKE_FORK_HEIGHT + ? getBasefee(_anchorBlockId, _parentGasUsed) + : calculateBaseFee(_blockGasIssuance, _basefeeAdjustmentQuotient, gasExcess, _parentGasUsed); + + if (!skipFeeCheck() && block.basefee != _basefee) { + revert L2_BASEFEE_MISMATCH(); + } + + if (_anchorBlockId > lastSyncedBlock) { + // Store the L1's state root as a signal to the local signal service to + // allow for multi-hop bridging. + ISignalService(resolve(LibStrings.B_SIGNAL_SERVICE, false)).syncChainData( + l1ChainId, LibStrings.H_STATE_ROOT, _anchorBlockId, _anchorStateRoot + ); + + lastSyncedBlock = _anchorBlockId; + } + + // Update state variables + bytes32 _parentHash = blockhash(parentId); + l2Hashes[parentId] = _parentHash; + publicInputHash = publicInputHashNew; + gasExcess = _gasExcess; + + emit Anchored(_parentHash, _gasExcess); + } + function _calcPublicInputHash(uint256 _blockId) private view diff --git a/packages/protocol/contracts/common/LibStrings.sol b/packages/protocol/contracts/common/LibStrings.sol index 3403c683a1..e064c92125 100644 --- a/packages/protocol/contracts/common/LibStrings.sol +++ b/packages/protocol/contracts/common/LibStrings.sol @@ -14,6 +14,7 @@ library LibStrings { bytes32 internal constant B_ERC1155_VAULT = bytes32("erc1155_vault"); bytes32 internal constant B_ERC20_VAULT = bytes32("erc20_vault"); bytes32 internal constant B_ERC721_VAULT = bytes32("erc721_vault"); + bytes32 internal constant B_PROPOSER_ACCESS = bytes32("proposer_access"); bytes32 internal constant B_PROVER_ASSIGNMENT = bytes32("PROVER_ASSIGNMENT"); bytes32 internal constant B_PROVER_SET = bytes32("prover_set"); bytes32 internal constant B_QUOTA_MANAGER = bytes32("quota_manager"); @@ -25,6 +26,7 @@ library LibStrings { bytes32 internal constant B_TIER_GUARDIAN_MINORITY = bytes32("tier_guardian_minority"); bytes32 internal constant B_TIER_ROUTER = bytes32("tier_router"); bytes32 internal constant B_TIER_SGX = bytes32("tier_sgx"); + bytes32 internal constant B_TIER_SGX2 = bytes32("tier_sgx2"); bytes32 internal constant B_TIER_SGX_ZKVM = bytes32("tier_sgx_zkvm"); bytes32 internal constant B_WITHDRAWER = bytes32("withdrawer"); bytes32 internal constant H_RETURN_LIVENESS_BOND = keccak256("RETURN_LIVENESS_BOND"); diff --git a/packages/protocol/contracts/hekla/HeklaTaikoL1.sol b/packages/protocol/contracts/hekla/HeklaTaikoL1.sol index 4a809824d6..79d7c75010 100644 --- a/packages/protocol/contracts/hekla/HeklaTaikoL1.sol +++ b/packages/protocol/contracts/hekla/HeklaTaikoL1.sol @@ -10,15 +10,20 @@ contract HeklaTaikoL1 is TaikoL1 { /// @inheritdoc ITaikoL1 function getConfig() public pure override returns (TaikoData.Config memory) { return TaikoData.Config({ - chainId: LibNetwork.TAIKO_HEKLA, - blockMaxProposals: 324_000, // Never change this value as ring buffer is being reused!!! - blockRingBufferSize: 324_512, // Never change this value as ring buffer is being - // reused!!! + chainId: LibNetwork.TAIKO_MAINNET, + // Never change this value as ring buffer is being reused!!! + blockMaxProposals: 324_000, + // Never change this value as ring buffer is being reused!!! + blockRingBufferSize: 324_512, maxBlocksToVerify: 16, blockMaxGasLimit: 240_000_000, livenessBond: 125e18, // 125 Taiko token stateRootSyncInternal: 16, - checkEOAForCalldataDA: true - }); + maxAnchorHeightOffset: 64, + basefeeAdjustmentQuotient: 8, + basefeeSharingPctg: 75, + blockGasIssuance: 20_000_000, + ontakeForkHeight: 720_000 // = 7200 * 100 + }); } } diff --git a/packages/protocol/contracts/mainnet/LibRollupAddressCache.sol b/packages/protocol/contracts/mainnet/LibRollupAddressCache.sol index 7591512c06..e15b21692c 100644 --- a/packages/protocol/contracts/mainnet/LibRollupAddressCache.sol +++ b/packages/protocol/contracts/mainnet/LibRollupAddressCache.sol @@ -43,6 +43,9 @@ library LibRollupAddressCache { if (_name == LibStrings.B_AUTOMATA_DCAP_ATTESTATION) { return (true, 0x8d7C954960a36a7596d7eA4945dDf891967ca8A3); } + if (_name == LibStrings.B_PROPOSER_ACCESS) { + return (true, address(0)); + } if (_name == LibStrings.B_CHAIN_WATCHDOG) { return (true, address(0)); } diff --git a/packages/protocol/contracts/mainnet/MainnetTierRouter.sol b/packages/protocol/contracts/mainnet/MainnetTierRouter.sol index 795bd7bd35..4047ab77de 100644 --- a/packages/protocol/contracts/mainnet/MainnetTierRouter.sol +++ b/packages/protocol/contracts/mainnet/MainnetTierRouter.sol @@ -8,7 +8,7 @@ import "../L1/tiers/ITierRouter.sol"; /// @custom:security-contact security@taiko.xyz contract MainnetTierRouter is ITierRouter { /// @inheritdoc ITierRouter - function getProvider(uint256 /*_blockId*/ ) external pure returns (address) { + function getProvider(uint256 _blockId) external pure returns (address) { return 0x4cffe56C947E26D07C14020499776DB3e9AE3a23; // TierProviderV2 } } diff --git a/packages/protocol/contracts/team/proving/ProverSet.sol b/packages/protocol/contracts/team/proving/ProverSet.sol index 532b76f892..9b0b49b0cb 100644 --- a/packages/protocol/contracts/team/proving/ProverSet.sol +++ b/packages/protocol/contracts/team/proving/ProverSet.sol @@ -96,6 +96,19 @@ contract ProverSet is EssentialContract, IERC1271 { ITaikoL1(taikoL1()).proposeBlock(_params, _txList); } + /// @notice Propose a Taiko block. + function proposeBlockV2( + bytes calldata _params, + bytes calldata _txList + ) + external + payable + onlyProver + nonReentrant + { + ITaikoL1(taikoL1()).proposeBlockV2(_params, _txList); + } + /// @notice Proves or contests a Taiko block. function proveBlock(uint64 _blockId, bytes calldata _input) external onlyProver nonReentrant { ITaikoL1(taikoL1()).proveBlock(_blockId, _input); diff --git a/packages/protocol/contracts/tko/BridgedTaikoToken.sol b/packages/protocol/contracts/tko/BridgedTaikoToken.sol index a2259a94f3..092d89c2a9 100644 --- a/packages/protocol/contracts/tko/BridgedTaikoToken.sol +++ b/packages/protocol/contracts/tko/BridgedTaikoToken.sol @@ -14,7 +14,7 @@ contract BridgedTaikoToken is TaikoTokenBase, IBridgedERC20 { /// @param _sharedAddressManager The address manager address. function init(address _owner, address _sharedAddressManager) external initializer { __Essential_init(_owner, _sharedAddressManager); - __ERC20_init("Taiko Token", "TKO"); + __ERC20_init("Taiko Token", "TAIKO"); __ERC20Votes_init(); __ERC20Permit_init("Taiko Token"); } diff --git a/packages/protocol/contracts/tko/TaikoToken.sol b/packages/protocol/contracts/tko/TaikoToken.sol index dd275d79f5..4d10bfd1bc 100644 --- a/packages/protocol/contracts/tko/TaikoToken.sol +++ b/packages/protocol/contracts/tko/TaikoToken.sol @@ -21,7 +21,7 @@ contract TaikoToken is TaikoTokenBase { /// @param _recipient The address to receive initial token minting. function init(address _owner, address _recipient) public initializer { __Essential_init(_owner); - __ERC20_init("Taiko Token", "TKO"); + __ERC20_init("Taiko Token", "TAIKO"); __ERC20Votes_init(); __ERC20Permit_init("Taiko Token"); // Mint 1 billion tokens diff --git a/packages/protocol/contracts/tokenvault/BridgedERC20V2.sol b/packages/protocol/contracts/tokenvault/BridgedERC20V2.sol index ded2c64168..7121f135ff 100644 --- a/packages/protocol/contracts/tokenvault/BridgedERC20V2.sol +++ b/packages/protocol/contracts/tokenvault/BridgedERC20V2.sol @@ -22,7 +22,7 @@ contract BridgedERC20V2 is BridgedERC20, IERC20PermitUpgradeable, EIP712Upgradea "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ); - mapping(address => CountersUpgradeable.Counter) private _nonces; + mapping(address account => CountersUpgradeable.Counter counter) private _nonces; uint256[49] private __gap; error BTOKEN_DEADLINE_EXPIRED(); diff --git a/packages/protocol/deployments/mainnet-contract-logs-L1.md b/packages/protocol/deployments/mainnet-contract-logs-L1.md index c575f80624..4b554f33cc 100644 --- a/packages/protocol/deployments/mainnet-contract-logs-L1.md +++ b/packages/protocol/deployments/mainnet-contract-logs-L1.md @@ -38,6 +38,7 @@ - Upgraded from `0x9496502d7D121B3D5eF25cA6c58d4f7593398a17` to `0x2f7126f78365AD54EAB26fD7faEc60435008E2fD` @commit`bb2abc5` @tx`0x7d584f0a645cad61e634f64ffaf7e1bbfb92749878eb25b39ce0e5cf698897c7` - todos: - deploy and register BridgedERC20V2 + - deploy a new version (override function change) #### taiko_token @@ -204,6 +205,8 @@ - register `prover_set` to `0x74828E5fe803072AF9Df512B3911B4223572D652` @commit`bb2abc5` @tx`0x7d584f0a645cad61e634f64ffaf7e1bbfb92749878eb25b39ce0e5cf698897c7` - upgraded from `0x29a88d60246C76E4F28806b9C8a42d2183154900` to `0x4f6D5D3109C07E77035B410602996e445b18E8E9` @commit`bb2abc5` @tx`0x7d584f0a645cad61e634f64ffaf7e1bbfb92749878eb25b39ce0e5cf698897c7` - register `prover_set` to `0xCE5a119479337a153CA3bd1b2bF9755c78F2B15A` @commit`be34059` @tx`0x170617251f2345eda4bcbd29e316caa0b014602a44244c60b963382ac7da7748` +- todo: + - deploy a new version (override function change) #### taikoL1 diff --git a/packages/protocol/test/L1/GuardianProver1.t.sol b/packages/protocol/test/L1/GuardianProver1.t.sol index 9ae2bd02e4..3c717f2dd1 100644 --- a/packages/protocol/test/L1/GuardianProver1.t.sol +++ b/packages/protocol/test/L1/GuardianProver1.t.sol @@ -11,7 +11,7 @@ contract DummyGuardianProver is GuardianProver { } function approve(bytes32 hash) public returns (bool) { - return _approve(operationId++, hash); + return _saveApproval(operationId++, hash); } } diff --git a/packages/protocol/test/L1/TaikoL1.t.sol b/packages/protocol/test/L1/TaikoL1.t.sol index b91b26886b..30942d3222 100644 --- a/packages/protocol/test/L1/TaikoL1.t.sol +++ b/packages/protocol/test/L1/TaikoL1.t.sol @@ -11,7 +11,6 @@ contract TaikoL1_NoCooldown is TaikoL1 { config.blockMaxProposals = 10; config.blockRingBufferSize = 12; config.livenessBond = 1e18; // 1 Taiko token - config.checkEOAForCalldataDA = false; } } @@ -46,7 +45,7 @@ contract TaikoL1Test is TaikoL1TestBase { for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) { //printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -75,7 +74,7 @@ contract TaikoL1Test is TaikoL1TestBase { for (uint256 blockId = 1; blockId <= 20; ++blockId) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); printVariables("after propose"); bytes32 blockHash = bytes32(1e10 + blockId); @@ -109,7 +108,7 @@ contract TaikoL1Test is TaikoL1TestBase { for (uint256 blockId = 1; blockId <= conf.blockMaxProposals; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); printVariables("after propose"); bytes32 blockHash = bytes32(1e10 + blockId); @@ -135,7 +134,7 @@ contract TaikoL1Test is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId <= conf.blockMaxProposals; blockId++) { - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); bytes32 blockHash; bytes32 stateRoot; if (blockId % 2 == 0) { @@ -196,7 +195,7 @@ contract TaikoL1Test is TaikoL1TestBase { giveEthAndTko(Bob, 1e8 ether, 100 ether); // Proposing is still possible - (meta,) = proposeBlock(Alice, 1_000_000, 1024); + (meta,) = proposeBlock(Alice, 1024); // Proving is not, so supply the revert reason to proveBlock proveBlock( Bob, @@ -222,7 +221,7 @@ contract TaikoL1Test is TaikoL1TestBase { L1.unpause(); // Proposing is possible again - proposeBlock(Alice, 1_000_000, 1024); + proposeBlock(Alice, 1024); } function test_getTierIds() external { diff --git a/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol b/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol index 6bdc973936..77351fa6c8 100644 --- a/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol +++ b/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol @@ -11,7 +11,6 @@ contract TaikoL1Tiers is TaikoL1 { config.blockMaxProposals = 10; config.blockRingBufferSize = 12; config.livenessBond = 1e18; // 1 Taiko token - config.checkEOAForCalldataDA = false; } } @@ -54,7 +53,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -95,7 +94,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -140,7 +139,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -184,7 +183,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -233,7 +232,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId < 10; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -292,7 +291,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { for (uint256 blockId = 1; blockId < 10; blockId++) { //printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -326,7 +325,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -380,7 +379,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -428,7 +427,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { console2.log("storeStateRoot:", storeStateRoot); printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); mine(1); bytes32 blockHash = bytes32(1_000_000 + blockId); @@ -480,7 +479,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId < 10; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -516,7 +515,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId < 10; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -548,7 +547,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) { printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); @@ -591,7 +590,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 parentHash = GENESIS_BLOCK_HASH; printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1_000_000, 1024); + (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, 1024); //printVariables("after propose"); mine(1); diff --git a/packages/protocol/test/L1/TaikoL1TestBase.sol b/packages/protocol/test/L1/TaikoL1TestBase.sol index edc69ceb5c..37eb8dc32b 100644 --- a/packages/protocol/test/L1/TaikoL1TestBase.sol +++ b/packages/protocol/test/L1/TaikoL1TestBase.sol @@ -113,7 +113,6 @@ abstract contract TaikoL1TestBase is TaikoTest { function proposeBlock( address proposer, - uint32 gasLimit, uint24 txListSize ) internal @@ -124,20 +123,6 @@ abstract contract TaikoL1TestBase is TaikoTest { // anyways uint256 msgValue = 2 ether; - (, TaikoData.SlotB memory b) = L1.getStateVariables(); - - uint256 _difficulty; - unchecked { - _difficulty = block.prevrandao * b.numBlocks; - } - - // TODO: why init meta here? - meta.timestamp = uint64(block.timestamp); - meta.l1Height = uint64(block.number - 1); - meta.l1Hash = blockhash(block.number - 1); - meta.difficulty = bytes32(_difficulty); - meta.gasLimit = gasLimit; - TaikoData.HookCall[] memory hookcalls = new TaikoData.HookCall[](0); vm.prank(proposer, proposer); (meta, ethDeposits) = L1.proposeBlock{ value: msgValue }( diff --git a/packages/protocol/test/L1/TaikoL1TestGroupBase.sol b/packages/protocol/test/L1/TaikoL1TestGroupBase.sol index 23483d8c62..aec4326e44 100644 --- a/packages/protocol/test/L1/TaikoL1TestGroupBase.sol +++ b/packages/protocol/test/L1/TaikoL1TestGroupBase.sol @@ -7,15 +7,14 @@ contract TaikoL1New is TaikoL1 { function getConfig() public pure override returns (TaikoData.Config memory config) { config = TaikoL1.getConfig(); config.maxBlocksToVerify = 0; - config.blockMaxProposals = 10; - config.blockRingBufferSize = 20; - config.checkEOAForCalldataDA = true; + config.blockMaxProposals = 20; + config.blockRingBufferSize = 25; config.stateRootSyncInternal = 2; } } abstract contract TaikoL1TestGroupBase is TaikoL1TestBase { - function deployTaikoL1() internal override returns (TaikoL1) { + function deployTaikoL1() internal virtual override returns (TaikoL1) { return TaikoL1( payable(deployProxy({ name: "taiko", impl: address(new TaikoL1New()), data: "" })) ); @@ -30,33 +29,29 @@ abstract contract TaikoL1TestGroupBase is TaikoL1TestBase { { TaikoData.HookCall[] memory hookcalls = new TaikoData.HookCall[](0); bytes memory txList = new bytes(10); - bytes memory eoaSig; - { - uint256 privateKey; - if (proposer == Alice) { - privateKey = 0x1; - } else if (proposer == Bob) { - privateKey = 0x2; - } else if (proposer == Carol) { - privateKey = 0x3; - } else if (proposer == David) { - privateKey = 0x4; - } else { - revert("test setup: you need to change proposeBlock() in TaikoL1TestGroupBase"); - } - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, keccak256(txList)); - eoaSig = abi.encodePacked(r, s, v); - } vm.prank(proposer); if (revertReason != "") vm.expectRevert(revertReason); (meta,) = L1.proposeBlock{ value: 3 ether }( - abi.encode(TaikoData.BlockParams(address(0), address(0), 0, 0, hookcalls, eoaSig)), - txList + abi.encode(TaikoData.BlockParams(address(0), address(0), 0, 0, hookcalls, "")), txList ); } + function proposeBlockV2( + address proposer, + TaikoData.BlockParamsV2 memory params, + bytes4 revertReason + ) + internal + returns (TaikoData.BlockMetadataV2 memory) + { + bytes memory txList = new bytes(10); + + vm.prank(proposer); + if (revertReason != "") vm.expectRevert(revertReason); + return L1.proposeBlockV2(abi.encode(params), txList); + } + function proveBlock( address prover, TaikoData.BlockMetadata memory meta, @@ -116,6 +111,64 @@ abstract contract TaikoL1TestGroupBase is TaikoL1TestBase { } } + function proveBlock2( + address prover, + TaikoData.BlockMetadataV2 memory meta, + bytes32 parentHash, + bytes32 blockHash, + bytes32 stateRoot, + uint16 tier, + bytes4 revertReason + ) + internal + { + TaikoData.Transition memory tran = TaikoData.Transition({ + parentHash: parentHash, + blockHash: blockHash, + stateRoot: stateRoot, + graffiti: 0x0 + }); + + TaikoData.TierProof memory proof; + proof.tier = tier; + address newInstance; + + // Keep changing the pub key associated with an instance to avoid + // attacks, + // obviously just a mock due to 2 addresses changing all the time. + (newInstance,) = sv.instances(0); + if (newInstance == SGX_X_0) { + newInstance = SGX_X_1; + } else { + newInstance = SGX_X_0; + } + + if (tier == LibTiers.TIER_SGX) { + bytes memory signature = + createSgxSignatureProof(tran, newInstance, prover, keccak256(abi.encode(meta))); + + proof.data = bytes.concat(bytes4(0), bytes20(newInstance), signature); + } + + if (tier == LibTiers.TIER_GUARDIAN) { + proof.data = ""; + + // Grant 2 signatures, 3rd might be a revert + vm.prank(David, David); + gp.approveV2(meta, tran, proof); + vm.prank(Emma, Emma); + gp.approveV2(meta, tran, proof); + + if (revertReason != "") vm.expectRevert(revertReason); + vm.prank(Frank); + gp.approveV2(meta, tran, proof); + } else { + if (revertReason != "") vm.expectRevert(revertReason); + vm.prank(prover); + L1.proveBlock(meta.id, abi.encode(meta, tran, proof)); + } + } + function printBlockAndTrans(uint64 blockId) internal view { TaikoData.Block memory blk = L1.getBlock(blockId); printBlock(blk); @@ -139,6 +192,7 @@ abstract contract TaikoL1TestGroupBase is TaikoL1TestBase { function printBlock(TaikoData.Block memory blk) internal view { (, TaikoData.SlotB memory b) = L1.getStateVariables(); + console2.log("\n=================="); console2.log("---CHAIN:"); console2.log(" | lastVerifiedBlockId:", b.lastVerifiedBlockId); console2.log(" | numBlocks:", b.numBlocks); @@ -147,6 +201,7 @@ abstract contract TaikoL1TestGroupBase is TaikoL1TestBase { console2.log(" | assignedProver:", blk.assignedProver); console2.log(" | livenessBond:", blk.livenessBond); console2.log(" | proposedAt:", blk.proposedAt); + console2.log(" | proposedIn:", blk.proposedIn); console2.log(" | metaHash:", vm.toString(blk.metaHash)); console2.log(" | nextTransitionId:", blk.nextTransitionId); console2.log(" | verifiedTransitionId:", blk.verifiedTransitionId); @@ -166,6 +221,7 @@ abstract contract TaikoL1TestGroupBase is TaikoL1TestBase { } function mineAndWrap(uint256 value) internal { + vm.roll(block.number + 1); vm.warp(block.timestamp + value); } } diff --git a/packages/protocol/test/L1/TaikoL1testGroupA1.t.sol b/packages/protocol/test/L1/TaikoL1testGroupA1.t.sol new file mode 100644 index 0000000000..6ff58ad9e0 --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1testGroupA1.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestGroupBase.sol"; + +contract TaikoL1ForkA1 is TaikoL1 { + function getConfig() public pure override returns (TaikoData.Config memory config) { + config = TaikoL1.getConfig(); + config.maxBlocksToVerify = 0; + config.blockMaxProposals = 20; + config.blockRingBufferSize = 25; + config.stateRootSyncInternal = 2; + config.ontakeForkHeight = 10; + } +} + +contract TaikoL1TestGroupA1 is TaikoL1TestGroupBase { + function deployTaikoL1() internal override returns (TaikoL1) { + return TaikoL1( + payable(deployProxy({ name: "taiko", impl: address(new TaikoL1ForkA1()), data: "" })) + ); + } + + // Test summary: + // - Use the v2 on block 10 - ontakeForkHeight = 10 + // - propose and prove block 1 to 9 using v1 + // - propose and prove block 10 to 15 using v2 + // - try to verify more than 15 blocks to verify all 15 blocks are verified. + function test_taikoL1_group_a1_case_1() external { + vm.warp(1_000_000); + mine(1); + printBlockAndTrans(0); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + + console2.log("====== Alice propose 5 block"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + + uint64 ontakeForkHeight = L1.getConfig().ontakeForkHeight; + + uint64 i = 1; + for (; i < ontakeForkHeight; ++i) { + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, ""); + printBlockAndTrans(meta.id); + TaikoData.Block memory blk = L1.getBlock(i); + assertTrue(blk.livenessBond > 0); + assertEq(blk.assignedProver, address(0)); + assertEq(blk.proposedAt, block.timestamp); + assertEq(blk.proposedIn, block.number); + + // Prove the block + bytes32 blockHash = bytes32(uint256(10_000 + i)); + bytes32 stateRoot = bytes32(uint256(20_000 + i)); + + mineAndWrap(10 seconds); + + proveBlock(Alice, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + parentHash = blockHash; + + printBlockAndTrans(meta.id); + blk = L1.getBlock(i); + assertEq(blk.livenessBond, 0); + assertEq(blk.assignedProver, address(0)); + } + + TaikoData.BlockParamsV2 memory params; + for (; i <= ontakeForkHeight + 5; ++i) { + TaikoData.BlockMetadataV2 memory meta2 = proposeBlockV2(Alice, params, ""); + printBlockAndTrans(meta2.id); + TaikoData.Block memory blk = L1.getBlock(i); + assertEq(blk.livenessBond, 0); + assertEq(blk.assignedProver, address(0)); + assertEq(blk.proposedAt, block.timestamp); + assertEq(blk.proposedIn, block.number - 1); + + // Prove the block + bytes32 blockHash = bytes32(uint256(10_000 + i)); + bytes32 stateRoot = bytes32(uint256(20_000 + i)); + + mineAndWrap(10 seconds); + + proveBlock2(Alice, meta2, parentHash, blockHash, stateRoot, meta2.minTier, ""); + parentHash = blockHash; + + printBlockAndTrans(meta2.id); + blk = L1.getBlock(i); + assertEq(blk.livenessBond, 0); + assertEq(blk.assignedProver, address(0)); + } + + console2.log("====== Verify many blocks"); + mineAndWrap(7 days); + verifyBlock(ontakeForkHeight + 10); + { + (, TaikoData.SlotB memory b) = L1.getStateVariables(); + assertEq(b.lastVerifiedBlockId, ontakeForkHeight + 5); + + assertEq(totalTkoBalance(tko, L1, Alice), 10_000 ether); + } + } +} diff --git a/packages/protocol/test/L1/TaikoL1testGroupA2.t.sol b/packages/protocol/test/L1/TaikoL1testGroupA2.t.sol new file mode 100644 index 0000000000..a872e25185 --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1testGroupA2.t.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestGroupBase.sol"; + +contract TaikoL1ForkA2 is TaikoL1 { + function getConfig() public pure override returns (TaikoData.Config memory config) { + config = TaikoL1.getConfig(); + config.maxBlocksToVerify = 0; + config.blockMaxProposals = 10; + config.blockRingBufferSize = 15; + config.stateRootSyncInternal = 2; + config.ontakeForkHeight = 0; // or 1, works the same. + } +} + +contract TaikoL1TestGroupA2 is TaikoL1TestGroupBase { + function deployTaikoL1() internal override returns (TaikoL1) { + return TaikoL1( + payable(deployProxy({ name: "taiko", impl: address(new TaikoL1ForkA2()), data: "" })) + ); + } + + // Test summary: + // - Use the v2 immediately - ontakeForkHeight = 0 or 1 + // - propose and prove 5 blocks + // - try to verify more than 5 blocks to verify all 5 blocks are verified. + function test_taikoL1_group_a2_case_1() external { + vm.warp(1_000_000); + mine(1); + printBlockAndTrans(0); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + TaikoData.Config memory config = L1.getConfig(); + + bytes32 parentHash = GENESIS_BLOCK_HASH; + + proposeBlock(Alice, TaikoL1.L1_FORK_ERROR.selector); + + TaikoData.BlockParamsV2 memory params; + for (uint64 i = 1; i <= 5; ++i) { + TaikoData.BlockMetadataV2 memory meta = proposeBlockV2(Alice, params, ""); + printBlockAndTrans(i); + + assertTrue(meta.difficulty != 0); + assertEq(meta.proposedAt, block.timestamp); + assertEq(meta.proposedIn, block.number); + assertEq(meta.timestamp, block.timestamp); + assertEq(meta.anchorBlockId, block.number - 1); + assertEq(meta.anchorBlockHash, blockhash(block.number - 1)); + assertEq(meta.livenessBond, config.livenessBond); + assertEq(meta.coinbase, Alice); + assertEq(meta.extraData, params.extraData); + + TaikoData.Block memory blk = L1.getBlock(i); + assertEq(blk.blockId, i); + assertEq(blk.proposedAt, meta.timestamp); + assertEq(blk.proposedIn, meta.anchorBlockId); + assertEq(blk.assignedProver, address(0)); + assertEq(blk.livenessBond, 0); + assertEq(blk.nextTransitionId, 1); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.metaHash, keccak256(abi.encode(meta))); + + // Prove the block + bytes32 blockHash = bytes32(uint256(10_000 + i)); + bytes32 stateRoot = bytes32(uint256(20_000 + i)); + + mineAndWrap(10 seconds); + + proveBlock2(Alice, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + parentHash = blockHash; + + printBlockAndTrans(i); + blk = L1.getBlock(i); + assertEq(blk.livenessBond, 0); + assertEq(blk.assignedProver, address(0)); + } + + console2.log("====== Verify many blocks"); + mineAndWrap(7 days); + verifyBlock(10); + { + (, TaikoData.SlotB memory b) = L1.getStateVariables(); + assertEq(b.lastVerifiedBlockId, 5); + + assertEq(totalTkoBalance(tko, L1, Alice), 10_000 ether); + } + } + + // Test summary: + // - Use the v2 immediately - ontakeForkHeight = 0 or 1 + // - propose and prove 5 blocks + // - try to verify more than 5 blocks to verify all 5 blocks are verified. + function test_taikoL1_group_a2_case_2() external { + vm.warp(1_000_000); + mine(1); + printBlockAndTrans(0); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + + TaikoData.Config memory config = L1.getConfig(); + + // Propose the first block with default parameters + TaikoData.BlockParamsV2 memory params = TaikoData.BlockParamsV2({ + coinbase: address(0), + extraData: 0, + parentMetaHash: 0, + anchorBlockId: 0, + timestamp: 0, + blobTxListOffset: 0, + blobTxListLength: 0, + blobIndex: 0 + }); + TaikoData.BlockMetadataV2 memory meta = proposeBlockV2(Alice, params, ""); + + assertEq(meta.id, 1); + + assertTrue(meta.difficulty != 0); + assertEq(meta.proposedAt, block.timestamp); + assertEq(meta.proposedIn, block.number); + assertEq(meta.timestamp, block.timestamp); + assertEq(meta.anchorBlockId, block.number - 1); + assertEq(meta.anchorBlockHash, blockhash(block.number - 1)); + assertEq(meta.livenessBond, config.livenessBond); + assertEq(meta.coinbase, Alice); + assertEq(meta.parentMetaHash, bytes32(uint256(1))); + assertEq(meta.extraData, params.extraData); + + TaikoData.Block memory blk = L1.getBlock(1); + assertEq(blk.blockId, 1); + assertEq(blk.proposedAt, meta.timestamp); + assertEq(blk.proposedIn, meta.anchorBlockId); + assertEq(blk.assignedProver, address(0)); + assertEq(blk.livenessBond, 0); + assertEq(blk.nextTransitionId, 1); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.metaHash, keccak256(abi.encode(meta))); + + // mine 100 blocks + vm.roll(100); + vm.warp(100 days); + + // Propose the second block with custom parameters + + params = TaikoData.BlockParamsV2({ + coinbase: Bob, + extraData: bytes32(uint256(123)), + parentMetaHash: 0, + anchorBlockId: 90, + timestamp: uint64(block.timestamp - 100), + blobTxListOffset: 0, + blobTxListLength: 0, + blobIndex: 0 + }); + meta = proposeBlockV2(Alice, params, ""); + + assertEq(meta.id, 2); + assertTrue(meta.difficulty != 0); + assertEq(meta.proposedAt, block.timestamp); + assertEq(meta.proposedIn, block.number); + assertEq(meta.timestamp, params.timestamp); + assertEq(meta.anchorBlockId, 90); + assertEq(meta.anchorBlockHash, blockhash(90)); + assertEq(meta.livenessBond, config.livenessBond); + assertEq(meta.coinbase, Bob); + assertEq(meta.parentMetaHash, blk.metaHash); + assertEq(meta.extraData, params.extraData); + + blk = L1.getBlock(2); + assertEq(blk.blockId, 2); + assertEq(blk.proposedAt, meta.timestamp); + assertEq(blk.proposedIn, meta.anchorBlockId); + assertEq(blk.assignedProver, address(0)); + assertEq(blk.livenessBond, 0); + assertEq(blk.nextTransitionId, 1); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.metaHash, keccak256(abi.encode(meta))); + + for (uint256 i = 0; i < 3; ++i) { + TaikoData.BlockParamsV2 memory params2; + proposeBlockV2(Alice, params2, ""); + } + } +} diff --git a/packages/protocol/test/thirdparty/muticall3/Multicall3.sol b/packages/protocol/test/thirdparty/muticall3/Multicall3.sol index 205cc0b7e6..e96d0d2d2b 100644 --- a/packages/protocol/test/thirdparty/muticall3/Multicall3.sol +++ b/packages/protocol/test/thirdparty/muticall3/Multicall3.sol @@ -222,7 +222,7 @@ contract Multicall3 { /// @notice Returns the block difficulty function getCurrentBlockDifficulty() public view returns (uint256 difficulty) { - difficulty = block.difficulty; + difficulty = block.prevrandao; } /// @notice Returns the block gas limit