From 9d30769b0244c5b42c72c76709a0132af7281f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Mon, 29 Jan 2024 17:20:59 +0530 Subject: [PATCH 1/9] address_tko26 --- .../protocol/contracts/L1/TaikoErrors.sol | 8 +++----- .../protocol/contracts/L1/TaikoEvents.sol | 14 ------------- .../contracts/L1/gov/TaikoGovernor.sol | 4 +--- .../L1/gov/TaikoTimelockController.sol | 2 -- .../protocol/contracts/L1/libs/LibProving.sol | 16 ++++++++++----- .../contracts/L1/libs/LibProvingAlt.sol | 4 ++-- .../contracts/L1/libs/LibVerifying.sol | 10 +++++++--- .../contracts/L1/provers/Guardians.sol | 20 +++++++++---------- .../contracts/L1/tiers/ITierProvider.sol | 2 +- .../L1/tiers/TaikoA6TierProvider.sol | 8 ++++---- .../contracts/L1/verifiers/PseZkVerifier.sol | 3 ++- packages/protocol/contracts/bridge/Bridge.sol | 6 +++--- .../contracts/thirdparty/LibBytesUtils.sol | 2 +- .../contracts/thirdparty/LibMerkleTrie.sol | 7 ------- .../contracts/thirdparty/LibRLPReader.sol | 2 ++ .../contracts/thirdparty/LibUint512Math.sol | 2 ++ .../contracts/tokenvault/ERC1155Vault.sol | 8 ++++++-- .../contracts/tokenvault/ERC20Vault.sol | 8 ++++++-- .../contracts/tokenvault/ERC721Vault.sol | 8 ++++++-- 19 files changed, 67 insertions(+), 67 deletions(-) diff --git a/packages/protocol/contracts/L1/TaikoErrors.sol b/packages/protocol/contracts/L1/TaikoErrors.sol index 6c515fa252..4fcda67c2a 100644 --- a/packages/protocol/contracts/L1/TaikoErrors.sol +++ b/packages/protocol/contracts/L1/TaikoErrors.sol @@ -27,10 +27,8 @@ abstract contract TaikoErrors { error L1_BLOB_FOR_DA_DISABLED(); error L1_BLOB_NOT_FOUND(); error L1_BLOB_NOT_REUSEABLE(); + error L1_BLOB_NOT_USED(); error L1_BLOCK_MISMATCH(); - error L1_INSUFFICIENT_TOKEN(); - error L1_INVALID_ADDRESS(); - error L1_INVALID_AMOUNT(); error L1_INVALID_BLOCK_ID(); error L1_INVALID_CONFIG(); error L1_INVALID_ETH_DEPOSIT(); @@ -46,12 +44,12 @@ abstract contract TaikoErrors { error L1_PROPOSER_NOT_EOA(); error L1_PROVING_PAUSED(); error L1_RECEIVE_DISABLED(); + error L1_MISSING_VERIFIER(); error L1_TOO_MANY_BLOCKS(); error L1_TOO_MANY_TIERS(); error L1_TRANSITION_ID_ZERO(); error L1_TRANSITION_NOT_FOUND(); - error L1_TXLIST_OFFSET_SIZE(); - error L1_TXLIST_TOO_LARGE(); + error L1_TXLIST_SIZE(); error L1_UNAUTHORIZED(); error L1_UNEXPECTED_PARENT(); error L1_UNEXPECTED_TRANSITION_ID(); diff --git a/packages/protocol/contracts/L1/TaikoEvents.sol b/packages/protocol/contracts/L1/TaikoEvents.sol index 4185e56a04..457527507a 100644 --- a/packages/protocol/contracts/L1/TaikoEvents.sol +++ b/packages/protocol/contracts/L1/TaikoEvents.sol @@ -84,18 +84,4 @@ abstract contract TaikoEvents { /// @param deposit The Ethereum deposit information including recipient, /// amount, and ID. event EthDeposited(TaikoData.EthDeposit deposit); - - /// @dev Emitted when a user deposited Taiko token into this contract. - event TokenDeposited(uint256 amount); - - /// @dev Emitted when a user withdrawed Taiko token from this contract. - event TokenWithdrawn(uint256 amount); - - /// @dev Emitted when Taiko token are credited to the user's in-protocol - /// balance. - event TokenCredited(address to, uint256 amount); - - /// @dev Emitted when Taiko token are debited from the user's in-protocol - /// balance. - event TokenDebited(address from, uint256 amount); } diff --git a/packages/protocol/contracts/L1/gov/TaikoGovernor.sol b/packages/protocol/contracts/L1/gov/TaikoGovernor.sol index 8688d42dfe..a553f7b2a7 100644 --- a/packages/protocol/contracts/L1/gov/TaikoGovernor.sol +++ b/packages/protocol/contracts/L1/gov/TaikoGovernor.sol @@ -33,8 +33,6 @@ contract TaikoGovernor is GovernorVotesQuorumFractionUpgradeable, GovernorTimelockControlUpgradeable { - uint256[50] private __gap; - function init( IVotesUpgradeable _token, TimelockControllerUpgradeable _timelock @@ -91,7 +89,7 @@ contract TaikoGovernor is return 50_400; // 1 week } - // The number of votes required in order for a voter to become a proposer + // The number of votes required in order for a voter to become a vote proposer function proposalThreshold() public pure override returns (uint256) { return 1_000_000_000 ether / 10_000; // 0.01% of Taiko Token } diff --git a/packages/protocol/contracts/L1/gov/TaikoTimelockController.sol b/packages/protocol/contracts/L1/gov/TaikoTimelockController.sol index e1457b55ea..87f0ac75f5 100644 --- a/packages/protocol/contracts/L1/gov/TaikoTimelockController.sol +++ b/packages/protocol/contracts/L1/gov/TaikoTimelockController.sol @@ -19,8 +19,6 @@ import import "../../common/OwnerUUPSUpgradable.sol"; contract TaikoTimelockController is OwnerUUPSUpgradable, TimelockControllerUpgradeable { - uint256[50] private __gap; - function init(uint256 minDelay) external initializer { __OwnerUUPSUpgradable_init(); address[] memory nil = new address[](0); diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index bdb1a8a6aa..e7e9942af2 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -56,6 +56,7 @@ library LibProving { error L1_INVALID_TIER(); error L1_INVALID_TRANSITION(); error L1_NOT_ASSIGNED_PROVER(); + error L1_MISSING_VERIFIER(); error L1_UNEXPECTED_TRANSITION_TIER(); function pauseProving(TaikoData.State storage state, bool pause) external { @@ -91,9 +92,7 @@ library LibProving { uint64 slot = meta.id % config.blockRingBufferSize; TaikoData.Block storage blk = state.blocks[slot]; - // 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. + // Check the integrity of the block data. if (blk.blockId != meta.id || blk.metaHash != keccak256(abi.encode(meta))) { revert L1_BLOCK_MISMATCH(); } @@ -176,7 +175,7 @@ library LibProving { // The new proof must meet or exceed the minimum tier required by the // block or the previous proof; it cannot be on a lower tier. - if (proof.tier == 0 || proof.tier < meta.minTier || proof.tier < ts.tier) { + if (proof.tier == 0 || proof.tier < ts.tier) { revert L1_INVALID_TIER(); } @@ -185,7 +184,7 @@ library LibProving { ITierProvider.Tier memory tier = ITierProvider(resolver.resolve("tier_provider", false)).getTier(proof.tier); - maxBlocksToVerify = tier.maxBlocksToVerify; + maxBlocksToVerify = tier.maxBlocksToVerifyWithTier; // We must verify the proof, and any failure in proof verification will // result in a revert. @@ -206,6 +205,13 @@ library LibProving { // The verifier can be address-zero, signifying that there are no // proof checks for the tier. In practice, this only applies to // optimistic proofs. + if ( + verifier == address(0) + && keccak256(abi.encodePacked(tier.verifierName)) + != keccak256(abi.encodePacked("tier_optimistic")) + ) { + revert L1_MISSING_VERIFIER(); + } if (verifier != address(0)) { bool isContesting = proof.tier == ts.tier && tier.contestBond != 0; diff --git a/packages/protocol/contracts/L1/libs/LibProvingAlt.sol b/packages/protocol/contracts/L1/libs/LibProvingAlt.sol index 7d9bd2623a..c624f1c10e 100644 --- a/packages/protocol/contracts/L1/libs/LibProvingAlt.sol +++ b/packages/protocol/contracts/L1/libs/LibProvingAlt.sol @@ -109,7 +109,7 @@ library LibProvingAlt { // The new proof must meet or exceed the minimum tier required by the // block or the previous proof; it cannot be on a lower tier. - if (proof.tier == 0 || proof.tier < meta.minTier || proof.tier < ts.tier) { + if (proof.tier == 0 || proof.tier < ts.tier) { revert L1_INVALID_TIER(); } @@ -234,7 +234,7 @@ library LibProvingAlt { } ts.timestamp = uint64(block.timestamp); - return tier.maxBlocksToVerify; + return tier.maxBlocksToVerifyWithTier; } /// @dev Handle the transition initialization logic diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index 1e41e8ec74..4314dd6870 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -90,11 +90,16 @@ library LibVerifying { || config.blockMaxTxListBytes > 128 * 1024 // calldata up to 128K || config.livenessBond == 0 || config.ethDepositRingBufferSize <= 1 || config.ethDepositMinCountPerBlock == 0 + // Audit recommendation, and gas tested. Processing 32 deposits (as initially set in + // TaikoL1.sol) costs 72.502 gas. So a safe bet to set max cap around 100K (cca 45) + // as average propose block is somewhere 170-200K depending on the tier). It is + // anyway set by Taiko Labs. + || config.ethDepositMaxCountPerBlock > 45 || config.ethDepositMaxCountPerBlock < config.ethDepositMinCountPerBlock || config.ethDepositMinAmount == 0 || config.ethDepositMaxAmount <= config.ethDepositMinAmount || config.ethDepositMaxAmount >= type(uint96).max || config.ethDepositGas == 0 - || config.ethDepositMaxFee == 0 || config.ethDepositMaxFee >= type(uint96).max + || config.ethDepositMaxFee == 0 || config.ethDepositMaxFee >= type(uint96).max / config.ethDepositMaxCountPerBlock ) return false; @@ -209,8 +214,7 @@ library LibVerifying { // other transitions for this block, we disregard them entirely. // The bonds for these other transitions are burned either when // the transitions are generated or proven. In such cases, both - // the provers and contesters of of those transitions forfeit - // their bonds. + // the provers and contesters of those transitions forfeit their bonds. emit BlockVerified({ blockId: blockId, diff --git a/packages/protocol/contracts/L1/provers/Guardians.sol b/packages/protocol/contracts/L1/provers/Guardians.sol index 832c3ec318..34f8fde2e5 100644 --- a/packages/protocol/contracts/L1/provers/Guardians.sol +++ b/packages/protocol/contracts/L1/provers/Guardians.sol @@ -38,22 +38,22 @@ abstract contract Guardians is EssentialContract { error INVALID_PROOF(); /// @notice Set the set of guardians - /// @param _guardians The new set of guardians + /// @param _newGuardians The new set of guardians + /// @param _minGuardians The minimum required to sign function setGuardians( - address[] memory _guardians, + address[] memory _newGuardians, uint8 _minGuardians ) external onlyOwner nonReentrant { - if (_guardians.length < MIN_NUM_GUARDIANS || _guardians.length > type(uint8).max) { + if (_newGuardians.length < MIN_NUM_GUARDIANS || _newGuardians.length > type(uint8).max) { revert INVALID_GUARDIAN_SET(); } - if ( - _minGuardians == 0 || _minGuardians < _guardians.length >> 1 - || _minGuardians > _guardians.length - ) revert INVALID_MIN_GUARDIANS(); + if (_minGuardians < _newGuardians.length >> 1 || _minGuardians > _newGuardians.length) { + revert INVALID_MIN_GUARDIANS(); + } // Delete current guardians data uint256 guardiansLength = guardians.length; @@ -64,8 +64,8 @@ abstract contract Guardians is EssentialContract { sstore(guardians.slot, 0) } - for (uint256 i = 0; i < _guardians.length;) { - address guardian = _guardians[i]; + for (uint256 i = 0; i < _newGuardians.length;) { + address guardian = _newGuardians[i]; if (guardian == address(0)) revert INVALID_GUARDIAN(); if (guardianIds[guardian] != 0) revert INVALID_GUARDIAN_SET(); @@ -75,7 +75,7 @@ abstract contract Guardians is EssentialContract { } minGuardians = _minGuardians; - emit GuardiansUpdated(++version, _guardians); + emit GuardiansUpdated(++version, _newGuardians); } function isApproved(bytes32 hash) public view returns (bool) { diff --git a/packages/protocol/contracts/L1/tiers/ITierProvider.sol b/packages/protocol/contracts/L1/tiers/ITierProvider.sol index baaf312dad..1475e497b1 100644 --- a/packages/protocol/contracts/L1/tiers/ITierProvider.sol +++ b/packages/protocol/contracts/L1/tiers/ITierProvider.sol @@ -23,7 +23,7 @@ interface ITierProvider { uint96 contestBond; uint24 cooldownWindow; uint16 provingWindow; - uint8 maxBlocksToVerify; + uint8 maxBlocksToVerifyWithTier; } /// @dev Retrieves the configuration for a specified tier. diff --git a/packages/protocol/contracts/L1/tiers/TaikoA6TierProvider.sol b/packages/protocol/contracts/L1/tiers/TaikoA6TierProvider.sol index e2dc81de04..4ed96a8599 100644 --- a/packages/protocol/contracts/L1/tiers/TaikoA6TierProvider.sol +++ b/packages/protocol/contracts/L1/tiers/TaikoA6TierProvider.sol @@ -41,7 +41,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 1000 ether, // TKO cooldownWindow: 24 hours, provingWindow: 2 hours, - maxBlocksToVerify: 10 + maxBlocksToVerifyWithTier: 10 }); } @@ -52,7 +52,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 500 ether, // TKO cooldownWindow: 24 hours, provingWindow: 4 hours, - maxBlocksToVerify: 8 + maxBlocksToVerifyWithTier: 8 }); } @@ -63,7 +63,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 250 ether, // TKO cooldownWindow: 24 hours, provingWindow: 6 hours, - maxBlocksToVerify: 6 + maxBlocksToVerifyWithTier: 6 }); } @@ -74,7 +74,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 0, // not contestable cooldownWindow: 24 hours, provingWindow: 8 hours, - maxBlocksToVerify: 4 + maxBlocksToVerifyWithTier: 4 }); } diff --git a/packages/protocol/contracts/L1/verifiers/PseZkVerifier.sol b/packages/protocol/contracts/L1/verifiers/PseZkVerifier.sol index a289cfd76f..2bc43fc3da 100644 --- a/packages/protocol/contracts/L1/verifiers/PseZkVerifier.sol +++ b/packages/protocol/contracts/L1/verifiers/PseZkVerifier.sol @@ -38,6 +38,7 @@ contract PseZkVerifier is EssentialContract, IVerifier { uint256[50] private __gap; + error L1_BLOB_NOT_USED(); error L1_INVALID_PROOF(); /// @notice Initializes the contract with the provided address manager. @@ -80,7 +81,7 @@ contract PseZkVerifier is EssentialContract, IVerifier { pointProof: pf.pointProof }); } else { - assert(zkProof.pointProof.length == 0); + if (zkProof.pointProof.length != 0) revert L1_BLOB_NOT_USED(); instance = calcInstance({ tran: tran, prover: ctx.prover, diff --git a/packages/protocol/contracts/bridge/Bridge.sol b/packages/protocol/contracts/bridge/Bridge.sol index 3918904844..94928a42da 100644 --- a/packages/protocol/contracts/bridge/Bridge.sol +++ b/packages/protocol/contracts/bridge/Bridge.sol @@ -43,10 +43,8 @@ contract Bridge is EssentialContract, IBridge { mapping(address => bool) public addressBanned; uint256[43] private __gap; - event SignalSent(address indexed sender, bytes32 msgHash); event MessageSent(bytes32 indexed msgHash, Message message); event MessageRecalled(bytes32 indexed msgHash); - event DestChainEnabled(uint64 indexed chainId, bool enabled); event MessageStatusChanged(bytes32 indexed msgHash, Status status); event AddressBanned(address indexed addr, bool banned); @@ -246,7 +244,9 @@ contract Bridge is EssentialContract, IBridge { } else { // If sender is another address, reward it and refund the rest msg.sender.sendEther(message.fee); - refundTo.sendEther(refundAmount); + if (refundAmount > 0) { + refundTo.sendEther(refundAmount); + } } } diff --git a/packages/protocol/contracts/thirdparty/LibBytesUtils.sol b/packages/protocol/contracts/thirdparty/LibBytesUtils.sol index cad328022c..076db3550f 100644 --- a/packages/protocol/contracts/thirdparty/LibBytesUtils.sol +++ b/packages/protocol/contracts/thirdparty/LibBytesUtils.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT // Taken from -// https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts/contracts/libraries/utils/LibBytesUtils.sol +// https://github.com/ethereum-optimism/optimism-legacy/blob/develop/packages/contracts/contracts/libraries/utils/Lib_BytesUtils.sol // (The MIT License) // // Copyright 2020-2021 Optimism diff --git a/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol b/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol index 89f11f4de4..ee6627e2c4 100644 --- a/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol +++ b/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol @@ -296,13 +296,6 @@ library LibMerkleTrie { return LibRLPReader.readBytes(_node.decoded[_node.decoded.length - 1]); } - /** - * @notice Utility; determines the number of nibbles shared between two - * nibble arrays. - * @param _a First nibble array. - * @param _b Second nibble array. - * @return _shared Number of shared nibbles. - */ /** * @notice Utility; determines the number of nibbles shared between two * nibble arrays. diff --git a/packages/protocol/contracts/thirdparty/LibRLPReader.sol b/packages/protocol/contracts/thirdparty/LibRLPReader.sol index 89b2eb0bc2..241828b52f 100644 --- a/packages/protocol/contracts/thirdparty/LibRLPReader.sol +++ b/packages/protocol/contracts/thirdparty/LibRLPReader.sol @@ -167,6 +167,7 @@ library LibRLPReader { out := mload(ptr) // Shift the bytes over to match the item size. + // Note: Will align to the right for an input smaller than 32 bytes. if lt(itemLength, 32) { out := div(out, exp(256, sub(32, itemLength))) } } @@ -235,6 +236,7 @@ library LibRLPReader { */ function readAddress(RLPItem memory _in) internal pure returns (address) { if (_in.length == 1) { + // Note: regardless of value of _in, it returns address(0) if length is 1. return address(0); } diff --git a/packages/protocol/contracts/thirdparty/LibUint512Math.sol b/packages/protocol/contracts/thirdparty/LibUint512Math.sol index d22f06cddd..cfc915a42f 100644 --- a/packages/protocol/contracts/thirdparty/LibUint512Math.sol +++ b/packages/protocol/contracts/thirdparty/LibUint512Math.sol @@ -64,6 +64,8 @@ library LibUint512Math { pure returns (uint256 r0, uint256 r1) { + // Library code itself dies not revert on overflow. + // (Taiko's static signAnchor() usage will not cause any overrun!) assembly { // Standard 256-bit addition for lower bits. r0 := add(a0, b0) diff --git a/packages/protocol/contracts/tokenvault/ERC1155Vault.sol b/packages/protocol/contracts/tokenvault/ERC1155Vault.sol index d7328a26d4..1af9c3de8d 100644 --- a/packages/protocol/contracts/tokenvault/ERC1155Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC1155Vault.sol @@ -144,7 +144,9 @@ contract ERC1155Vault is BaseNFTVault, ERC1155ReceiverUpgradeable { } } - _to.sendEther(msg.value); + if (msg.value > 0) { + _to.sendEther(msg.value); + } emit TokenReceived({ msgHash: ctx.msgHash, @@ -193,7 +195,9 @@ contract ERC1155Vault is BaseNFTVault, ERC1155ReceiverUpgradeable { } } // Send back Ether - message.owner.sendEther(message.value); + if (message.value > 0) { + message.owner.sendEther(message.value); + } // Emit TokenReleased event emit TokenReleased({ diff --git a/packages/protocol/contracts/tokenvault/ERC20Vault.sol b/packages/protocol/contracts/tokenvault/ERC20Vault.sol index 2c140806ab..c88e423673 100644 --- a/packages/protocol/contracts/tokenvault/ERC20Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC20Vault.sol @@ -232,7 +232,9 @@ contract ERC20Vault is BaseVault { IBridgedERC20(token).mint(_to, amount); } - _to.sendEther(msg.value); + if (msg.value > 0) { + _to.sendEther(msg.value); + } emit TokenReceived({ msgHash: ctx.msgHash, @@ -269,7 +271,9 @@ contract ERC20Vault is BaseVault { } } - message.owner.sendEther(message.value); + if (message.value > 0) { + message.owner.sendEther(message.value); + } emit TokenReleased({ msgHash: msgHash, diff --git a/packages/protocol/contracts/tokenvault/ERC721Vault.sol b/packages/protocol/contracts/tokenvault/ERC721Vault.sol index dd93eea0a8..61e6064dcd 100644 --- a/packages/protocol/contracts/tokenvault/ERC721Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC721Vault.sol @@ -122,7 +122,9 @@ contract ERC721Vault is BaseNFTVault, IERC721ReceiverUpgradeable { } } - _to.sendEther(msg.value); + if (msg.value > 0) { + _to.sendEther(msg.value); + } emit TokenReceived({ msgHash: ctx.msgHash, @@ -174,7 +176,9 @@ contract ERC721Vault is BaseNFTVault, IERC721ReceiverUpgradeable { } // send back Ether - message.owner.sendEther(message.value); + if (message.value > 0) { + message.owner.sendEther(message.value); + } emit TokenReleased({ msgHash: msgHash, From be82cd93b440d3222eb30738ff8a85d19552c129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Tue, 30 Jan 2024 10:22:05 +0530 Subject: [PATCH 2/9] pr review changes --- packages/protocol/contracts/L1/gov/TaikoGovernor.sol | 2 ++ .../contracts/L1/gov/TaikoTimelockController.sol | 2 ++ packages/protocol/contracts/L1/libs/LibProving.sol | 2 +- packages/protocol/contracts/L1/libs/LibProvingAlt.sol | 2 +- packages/protocol/contracts/L1/libs/LibVerifying.sol | 8 +++----- .../protocol/contracts/L1/tiers/ITierProvider.sol | 2 +- .../contracts/L1/tiers/TaikoA6TierProvider.sol | 8 ++++---- packages/protocol/contracts/L2/TaikoL2.sol | 6 ++++-- packages/protocol/contracts/bridge/Bridge.sol | 4 +--- .../protocol/contracts/tokenvault/ERC1155Vault.sol | 4 +--- packages/protocol/contracts/tokenvault/ERC20Vault.sol | 8 ++------ .../protocol/contracts/tokenvault/ERC721Vault.sol | 8 ++------ .../thirdparty => test/L2}/LibUint512Math.sol | 0 packages/protocol/test/L2/TaikoL2.t.sol | 11 +++++++---- .../protocol/{contracts => test}/L2/TaikoL2Signer.sol | 4 ++-- packages/protocol/test/TaikoTest.sol | 1 + 16 files changed, 34 insertions(+), 38 deletions(-) rename packages/protocol/{contracts/thirdparty => test/L2}/LibUint512Math.sol (100%) rename packages/protocol/{contracts => test}/L2/TaikoL2Signer.sol (98%) diff --git a/packages/protocol/contracts/L1/gov/TaikoGovernor.sol b/packages/protocol/contracts/L1/gov/TaikoGovernor.sol index a553f7b2a7..a1eba21c3e 100644 --- a/packages/protocol/contracts/L1/gov/TaikoGovernor.sol +++ b/packages/protocol/contracts/L1/gov/TaikoGovernor.sol @@ -33,6 +33,8 @@ contract TaikoGovernor is GovernorVotesQuorumFractionUpgradeable, GovernorTimelockControlUpgradeable { + uint256[50] private __gap; + function init( IVotesUpgradeable _token, TimelockControllerUpgradeable _timelock diff --git a/packages/protocol/contracts/L1/gov/TaikoTimelockController.sol b/packages/protocol/contracts/L1/gov/TaikoTimelockController.sol index 87f0ac75f5..e1457b55ea 100644 --- a/packages/protocol/contracts/L1/gov/TaikoTimelockController.sol +++ b/packages/protocol/contracts/L1/gov/TaikoTimelockController.sol @@ -19,6 +19,8 @@ import import "../../common/OwnerUUPSUpgradable.sol"; contract TaikoTimelockController is OwnerUUPSUpgradable, TimelockControllerUpgradeable { + uint256[50] private __gap; + function init(uint256 minDelay) external initializer { __OwnerUUPSUpgradable_init(); address[] memory nil = new address[](0); diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index e7e9942af2..3c76297e6b 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -184,7 +184,7 @@ library LibProving { ITierProvider.Tier memory tier = ITierProvider(resolver.resolve("tier_provider", false)).getTier(proof.tier); - maxBlocksToVerify = tier.maxBlocksToVerifyWithTier; + maxBlocksToVerify = tier.maxBlocksToVerifyPerProof; // We must verify the proof, and any failure in proof verification will // result in a revert. diff --git a/packages/protocol/contracts/L1/libs/LibProvingAlt.sol b/packages/protocol/contracts/L1/libs/LibProvingAlt.sol index c624f1c10e..013d1611cd 100644 --- a/packages/protocol/contracts/L1/libs/LibProvingAlt.sol +++ b/packages/protocol/contracts/L1/libs/LibProvingAlt.sol @@ -234,7 +234,7 @@ library LibProvingAlt { } ts.timestamp = uint64(block.timestamp); - return tier.maxBlocksToVerifyWithTier; + return tier.maxBlocksToVerifyPerProof; } /// @dev Handle the transition initialization logic diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index 4314dd6870..b8b2b8a8d0 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -90,11 +90,9 @@ library LibVerifying { || config.blockMaxTxListBytes > 128 * 1024 // calldata up to 128K || config.livenessBond == 0 || config.ethDepositRingBufferSize <= 1 || config.ethDepositMinCountPerBlock == 0 - // Audit recommendation, and gas tested. Processing 32 deposits (as initially set in - // TaikoL1.sol) costs 72.502 gas. So a safe bet to set max cap around 100K (cca 45) - // as average propose block is somewhere 170-200K depending on the tier). It is - // anyway set by Taiko Labs. - || config.ethDepositMaxCountPerBlock > 45 + // Audit recommendation, and gas tested. Processing 32 deposits (as initially set in + // TaikoL1.sol) costs 72_502 gas. + || config.ethDepositMaxCountPerBlock > 32 || config.ethDepositMaxCountPerBlock < config.ethDepositMinCountPerBlock || config.ethDepositMinAmount == 0 || config.ethDepositMaxAmount <= config.ethDepositMinAmount diff --git a/packages/protocol/contracts/L1/tiers/ITierProvider.sol b/packages/protocol/contracts/L1/tiers/ITierProvider.sol index 1475e497b1..6b1bcb16aa 100644 --- a/packages/protocol/contracts/L1/tiers/ITierProvider.sol +++ b/packages/protocol/contracts/L1/tiers/ITierProvider.sol @@ -23,7 +23,7 @@ interface ITierProvider { uint96 contestBond; uint24 cooldownWindow; uint16 provingWindow; - uint8 maxBlocksToVerifyWithTier; + uint8 maxBlocksToVerifyPerProof; } /// @dev Retrieves the configuration for a specified tier. diff --git a/packages/protocol/contracts/L1/tiers/TaikoA6TierProvider.sol b/packages/protocol/contracts/L1/tiers/TaikoA6TierProvider.sol index 4ed96a8599..e4f6bafb25 100644 --- a/packages/protocol/contracts/L1/tiers/TaikoA6TierProvider.sol +++ b/packages/protocol/contracts/L1/tiers/TaikoA6TierProvider.sol @@ -41,7 +41,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 1000 ether, // TKO cooldownWindow: 24 hours, provingWindow: 2 hours, - maxBlocksToVerifyWithTier: 10 + maxBlocksToVerifyPerProof: 10 }); } @@ -52,7 +52,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 500 ether, // TKO cooldownWindow: 24 hours, provingWindow: 4 hours, - maxBlocksToVerifyWithTier: 8 + maxBlocksToVerifyPerProof: 8 }); } @@ -63,7 +63,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 250 ether, // TKO cooldownWindow: 24 hours, provingWindow: 6 hours, - maxBlocksToVerifyWithTier: 6 + maxBlocksToVerifyPerProof: 6 }); } @@ -74,7 +74,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 0, // not contestable cooldownWindow: 24 hours, provingWindow: 8 hours, - maxBlocksToVerifyWithTier: 4 + maxBlocksToVerifyPerProof: 4 }); } diff --git a/packages/protocol/contracts/L2/TaikoL2.sol b/packages/protocol/contracts/L2/TaikoL2.sol index c024fd435c..4fd253e651 100644 --- a/packages/protocol/contracts/L2/TaikoL2.sol +++ b/packages/protocol/contracts/L2/TaikoL2.sol @@ -23,7 +23,6 @@ import "../libs/LibAddress.sol"; import "../libs/LibMath.sol"; import "./Lib1559Math.sol"; import "./CrossChainOwned.sol"; -import "./TaikoL2Signer.sol"; /// @title TaikoL2 /// @notice Taiko L2 is a smart contract that handles cross-layer message @@ -31,11 +30,14 @@ import "./TaikoL2Signer.sol"; /// It is used to anchor the latest L1 block details to L2 for cross-layer /// communication, manage EIP-1559 parameters for gas pricing, and store /// verified L1 block information. -contract TaikoL2 is CrossChainOwned, TaikoL2Signer, ICrossChainSync { +contract TaikoL2 is CrossChainOwned, ICrossChainSync { using LibAddress for address; using LibMath for uint256; using SafeERC20 for IERC20; + // Golden touch address + address public constant GOLDEN_TOUCH_ADDRESS = 0x0000777735367b36bC9B61C50022d9D0700dB4Ec; + struct Config { uint32 gasTargetPerL1Block; uint8 basefeeAdjustmentQuotient; diff --git a/packages/protocol/contracts/bridge/Bridge.sol b/packages/protocol/contracts/bridge/Bridge.sol index 94928a42da..140e27af44 100644 --- a/packages/protocol/contracts/bridge/Bridge.sol +++ b/packages/protocol/contracts/bridge/Bridge.sol @@ -244,9 +244,7 @@ contract Bridge is EssentialContract, IBridge { } else { // If sender is another address, reward it and refund the rest msg.sender.sendEther(message.fee); - if (refundAmount > 0) { - refundTo.sendEther(refundAmount); - } + refundTo.sendEther(refundAmount); } } diff --git a/packages/protocol/contracts/tokenvault/ERC1155Vault.sol b/packages/protocol/contracts/tokenvault/ERC1155Vault.sol index 1af9c3de8d..5b0de9fab9 100644 --- a/packages/protocol/contracts/tokenvault/ERC1155Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC1155Vault.sol @@ -144,9 +144,7 @@ contract ERC1155Vault is BaseNFTVault, ERC1155ReceiverUpgradeable { } } - if (msg.value > 0) { - _to.sendEther(msg.value); - } + _to.sendEther(msg.value); emit TokenReceived({ msgHash: ctx.msgHash, diff --git a/packages/protocol/contracts/tokenvault/ERC20Vault.sol b/packages/protocol/contracts/tokenvault/ERC20Vault.sol index c88e423673..2c140806ab 100644 --- a/packages/protocol/contracts/tokenvault/ERC20Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC20Vault.sol @@ -232,9 +232,7 @@ contract ERC20Vault is BaseVault { IBridgedERC20(token).mint(_to, amount); } - if (msg.value > 0) { - _to.sendEther(msg.value); - } + _to.sendEther(msg.value); emit TokenReceived({ msgHash: ctx.msgHash, @@ -271,9 +269,7 @@ contract ERC20Vault is BaseVault { } } - if (message.value > 0) { - message.owner.sendEther(message.value); - } + message.owner.sendEther(message.value); emit TokenReleased({ msgHash: msgHash, diff --git a/packages/protocol/contracts/tokenvault/ERC721Vault.sol b/packages/protocol/contracts/tokenvault/ERC721Vault.sol index 61e6064dcd..dd93eea0a8 100644 --- a/packages/protocol/contracts/tokenvault/ERC721Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC721Vault.sol @@ -122,9 +122,7 @@ contract ERC721Vault is BaseNFTVault, IERC721ReceiverUpgradeable { } } - if (msg.value > 0) { - _to.sendEther(msg.value); - } + _to.sendEther(msg.value); emit TokenReceived({ msgHash: ctx.msgHash, @@ -176,9 +174,7 @@ contract ERC721Vault is BaseNFTVault, IERC721ReceiverUpgradeable { } // send back Ether - if (message.value > 0) { - message.owner.sendEther(message.value); - } + message.owner.sendEther(message.value); emit TokenReleased({ msgHash: msgHash, diff --git a/packages/protocol/contracts/thirdparty/LibUint512Math.sol b/packages/protocol/test/L2/LibUint512Math.sol similarity index 100% rename from packages/protocol/contracts/thirdparty/LibUint512Math.sol rename to packages/protocol/test/L2/LibUint512Math.sol diff --git a/packages/protocol/test/L2/TaikoL2.t.sol b/packages/protocol/test/L2/TaikoL2.t.sol index aa351aaa9e..2a9ca29347 100644 --- a/packages/protocol/test/L2/TaikoL2.t.sol +++ b/packages/protocol/test/L2/TaikoL2.t.sol @@ -20,6 +20,7 @@ contract TestTaikoL2 is TaikoTest { address public addressManager; TaikoL2EIP1559Configurable public L2; SkipBasefeeCheckL2 public L2skip; + TaikoL2Signer public l2Signer; function setUp() public { addressManager = deployProxy({ @@ -68,6 +69,8 @@ contract TestTaikoL2 is TaikoTest { vm.roll(block.number + 1); vm.warp(block.timestamp + 30); + + l2Signer = new TaikoL2Signer(); } function test_L2_AnchorTx_with_constant_block_time() external { @@ -227,19 +230,19 @@ contract TestTaikoL2 is TaikoTest { } function test_L2_AnchorTx_signing(bytes32 digest) external { - (uint8 v, uint256 r, uint256 s) = L2.signAnchor(digest, uint8(1)); + (uint8 v, uint256 r, uint256 s) = l2Signer.signAnchor(digest, uint8(1)); address signer = ecrecover(digest, v + 27, bytes32(r), bytes32(s)); assertEq(signer, L2.GOLDEN_TOUCH_ADDRESS()); - (v, r, s) = L2.signAnchor(digest, uint8(2)); + (v, r, s) = l2Signer.signAnchor(digest, uint8(2)); signer = ecrecover(digest, v + 27, bytes32(r), bytes32(s)); assertEq(signer, L2.GOLDEN_TOUCH_ADDRESS()); vm.expectRevert(); - L2.signAnchor(digest, uint8(0)); + l2Signer.signAnchor(digest, uint8(0)); vm.expectRevert(); - L2.signAnchor(digest, uint8(3)); + l2Signer.signAnchor(digest, uint8(3)); } function _anchor(uint32 parentGasLimit) private { diff --git a/packages/protocol/contracts/L2/TaikoL2Signer.sol b/packages/protocol/test/L2/TaikoL2Signer.sol similarity index 98% rename from packages/protocol/contracts/L2/TaikoL2Signer.sol rename to packages/protocol/test/L2/TaikoL2Signer.sol index 7a960a441a..ba07e842f8 100644 --- a/packages/protocol/contracts/L2/TaikoL2Signer.sol +++ b/packages/protocol/test/L2/TaikoL2Signer.sol @@ -14,12 +14,12 @@ pragma solidity 0.8.20; -import "../thirdparty/LibUint512Math.sol"; +import "./LibUint512Math.sol"; /// @title TaikoL2Signer /// @notice This contract allows for signing operations required on Taiko L2. /// @dev It uses precomputed values for optimized signature creation. -abstract contract TaikoL2Signer { +contract TaikoL2Signer { // Constants related to the golden touch signature. address public constant GOLDEN_TOUCH_ADDRESS = 0x0000777735367b36bC9B61C50022d9D0700dB4Ec; uint256 public constant GOLDEN_TOUCH_PRIVATEKEY = diff --git a/packages/protocol/test/TaikoTest.sol b/packages/protocol/test/TaikoTest.sol index 4d5d591c47..de1c217144 100644 --- a/packages/protocol/test/TaikoTest.sol +++ b/packages/protocol/test/TaikoTest.sol @@ -41,6 +41,7 @@ import "../contracts/test/erc20/FreeMintERC20.sol"; import "./DeployCapability.sol"; import "./HelperContracts.sol"; +import "./L2/TaikoL2Signer.sol"; abstract contract TaikoTest is Test, DeployCapability { uint256 private _seed = 0x12345678; From 14f2b54e9d7143981d11ca9559468b8f1ac1e6ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Tue, 30 Jan 2024 14:01:35 +0530 Subject: [PATCH 3/9] LibL2Signer --- packages/protocol/docs/how_taiko_proves_blocks.md | 2 +- .../test/L2/{TaikoL2Signer.sol => LibL2Signer.sol} | 6 +++--- packages/protocol/test/L2/TaikoL2.t.sol | 11 ++++------- packages/protocol/test/TaikoTest.sol | 2 +- .../test/{L2 => thirdparty}/LibUint512Math.sol | 0 5 files changed, 9 insertions(+), 12 deletions(-) rename packages/protocol/test/L2/{TaikoL2Signer.sol => LibL2Signer.sol} (98%) rename packages/protocol/test/{L2 => thirdparty}/LibUint512Math.sol (100%) diff --git a/packages/protocol/docs/how_taiko_proves_blocks.md b/packages/protocol/docs/how_taiko_proves_blocks.md index 12c6e468fa..24ccb65e61 100644 --- a/packages/protocol/docs/how_taiko_proves_blocks.md +++ b/packages/protocol/docs/how_taiko_proves_blocks.md @@ -68,7 +68,7 @@ Note that the anchor transaction emits an `Anchored` event that may help ZKP to ZKP shall also check the signature of the anchor transaction: - The signer must be _`TaikoL2.GOLDEN_TOUCH_ADDRESS`_. -- The signature must use `1` as the `k` value if the calculated `r` is not `0`, otherwise, `k` must be `2`. See [TaikoL2Signer.sol](https://github.com/taikoxyz/taiko-mono/blob/main/packages/protocol/contracts/L2/TaikoL2Signer.sol). +- The signature must use `1` as the `k` value if the calculated `r` is not `0`, otherwise, `k` must be `2`. See [LibL2Signer.sol](https://github.com/taikoxyz/taiko-mono/blob/main/packages/protocol/contracts/L2/LibL2Signer.sol). ### Block Metadata diff --git a/packages/protocol/test/L2/TaikoL2Signer.sol b/packages/protocol/test/L2/LibL2Signer.sol similarity index 98% rename from packages/protocol/test/L2/TaikoL2Signer.sol rename to packages/protocol/test/L2/LibL2Signer.sol index ba07e842f8..170a880e0a 100644 --- a/packages/protocol/test/L2/TaikoL2Signer.sol +++ b/packages/protocol/test/L2/LibL2Signer.sol @@ -14,12 +14,12 @@ pragma solidity 0.8.20; -import "./LibUint512Math.sol"; +import "../thirdparty/LibUint512Math.sol"; -/// @title TaikoL2Signer +/// @title LibL2Signer /// @notice This contract allows for signing operations required on Taiko L2. /// @dev It uses precomputed values for optimized signature creation. -contract TaikoL2Signer { +library LibL2Signer { // Constants related to the golden touch signature. address public constant GOLDEN_TOUCH_ADDRESS = 0x0000777735367b36bC9B61C50022d9D0700dB4Ec; uint256 public constant GOLDEN_TOUCH_PRIVATEKEY = diff --git a/packages/protocol/test/L2/TaikoL2.t.sol b/packages/protocol/test/L2/TaikoL2.t.sol index 2a9ca29347..91b092b510 100644 --- a/packages/protocol/test/L2/TaikoL2.t.sol +++ b/packages/protocol/test/L2/TaikoL2.t.sol @@ -20,7 +20,6 @@ contract TestTaikoL2 is TaikoTest { address public addressManager; TaikoL2EIP1559Configurable public L2; SkipBasefeeCheckL2 public L2skip; - TaikoL2Signer public l2Signer; function setUp() public { addressManager = deployProxy({ @@ -69,8 +68,6 @@ contract TestTaikoL2 is TaikoTest { vm.roll(block.number + 1); vm.warp(block.timestamp + 30); - - l2Signer = new TaikoL2Signer(); } function test_L2_AnchorTx_with_constant_block_time() external { @@ -230,19 +227,19 @@ contract TestTaikoL2 is TaikoTest { } function test_L2_AnchorTx_signing(bytes32 digest) external { - (uint8 v, uint256 r, uint256 s) = l2Signer.signAnchor(digest, uint8(1)); + (uint8 v, uint256 r, uint256 s) = LibL2Signer.signAnchor(digest, uint8(1)); address signer = ecrecover(digest, v + 27, bytes32(r), bytes32(s)); assertEq(signer, L2.GOLDEN_TOUCH_ADDRESS()); - (v, r, s) = l2Signer.signAnchor(digest, uint8(2)); + (v, r, s) = LibL2Signer.signAnchor(digest, uint8(2)); signer = ecrecover(digest, v + 27, bytes32(r), bytes32(s)); assertEq(signer, L2.GOLDEN_TOUCH_ADDRESS()); vm.expectRevert(); - l2Signer.signAnchor(digest, uint8(0)); + LibL2Signer.signAnchor(digest, uint8(0)); vm.expectRevert(); - l2Signer.signAnchor(digest, uint8(3)); + LibL2Signer.signAnchor(digest, uint8(3)); } function _anchor(uint32 parentGasLimit) private { diff --git a/packages/protocol/test/TaikoTest.sol b/packages/protocol/test/TaikoTest.sol index de1c217144..49508c7b28 100644 --- a/packages/protocol/test/TaikoTest.sol +++ b/packages/protocol/test/TaikoTest.sol @@ -41,7 +41,7 @@ import "../contracts/test/erc20/FreeMintERC20.sol"; import "./DeployCapability.sol"; import "./HelperContracts.sol"; -import "./L2/TaikoL2Signer.sol"; +import "./L2/LibL2Signer.sol"; abstract contract TaikoTest is Test, DeployCapability { uint256 private _seed = 0x12345678; diff --git a/packages/protocol/test/L2/LibUint512Math.sol b/packages/protocol/test/thirdparty/LibUint512Math.sol similarity index 100% rename from packages/protocol/test/L2/LibUint512Math.sol rename to packages/protocol/test/thirdparty/LibUint512Math.sol From 47da1a8608058efa2e787a9aad4142cc636dcff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Tue, 30 Jan 2024 14:04:00 +0530 Subject: [PATCH 4/9] enable 0eth send again --- packages/protocol/contracts/tokenvault/ERC1155Vault.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/protocol/contracts/tokenvault/ERC1155Vault.sol b/packages/protocol/contracts/tokenvault/ERC1155Vault.sol index 5b0de9fab9..d7328a26d4 100644 --- a/packages/protocol/contracts/tokenvault/ERC1155Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC1155Vault.sol @@ -193,9 +193,7 @@ contract ERC1155Vault is BaseNFTVault, ERC1155ReceiverUpgradeable { } } // Send back Ether - if (message.value > 0) { - message.owner.sendEther(message.value); - } + message.owner.sendEther(message.value); // Emit TokenReleased event emit TokenReleased({ From d2c473e8055485227d0b4c7922eb7772f70d6e60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Tue, 30 Jan 2024 14:45:51 +0530 Subject: [PATCH 5/9] Use LibProvingAlt as LibProving from now --- packages/protocol/contracts/L1/TaikoL1.sol | 3 +- .../protocol/contracts/L1/libs/LibProving.sol | 461 +++++++++--------- .../contracts/L1/libs/LibProvingAlt.sol | 397 --------------- 3 files changed, 220 insertions(+), 641 deletions(-) delete mode 100644 packages/protocol/contracts/L1/libs/LibProvingAlt.sol diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 405f908744..1de38a8de8 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -17,8 +17,7 @@ pragma solidity 0.8.20; import "../common/EssentialContract.sol"; import "./libs/LibDepositing.sol"; import "./libs/LibProposing.sol"; -// import "./libs/LibProving.sol"; -import { LibProvingAlt as LibProving } from "./libs/LibProvingAlt.sol"; +import "./libs/LibProving.sol"; import "./libs/LibVerifying.sol"; import "./ITaikoL1.sol"; import "./TaikoErrors.sol"; diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index 3c76297e6b..a42fb98201 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -26,8 +26,9 @@ import "./LibUtils.sol"; /// protocol. library LibProving { bytes32 public constant RETURN_LIVENESS_BOND = keccak256("RETURN_LIVENESS_BOND"); - // Warning: Any events defined here must also be defined in TaikoEvents.sol. + bytes32 public constant TIER_OP = bytes32("tier_optimistic"); + // Warning: Any events defined here must also be defined in TaikoEvents.sol. event TransitionProved( uint256 indexed blockId, TaikoData.Transition tran, @@ -55,8 +56,8 @@ library LibProving { error L1_INVALID_PAUSE_STATUS(); error L1_INVALID_TIER(); error L1_INVALID_TRANSITION(); - error L1_NOT_ASSIGNED_PROVER(); error L1_MISSING_VERIFIER(); + error L1_NOT_ASSIGNED_PROVER(); error L1_UNEXPECTED_TRANSITION_TIER(); function pauseProving(TaikoData.State storage state, bool pause) external { @@ -79,6 +80,8 @@ library LibProving { returns (uint8 maxBlocksToVerify) { // Make sure parentHash is not zero + // To contest an existing transition, simply use any non-zero value as + // the blockHash and signalRoot. if (tran.parentHash == 0 || tran.blockHash == 0 || tran.signalRoot == 0) { revert L1_INVALID_TRANSITION(); } @@ -92,7 +95,9 @@ library LibProving { uint64 slot = meta.id % config.blockRingBufferSize; TaikoData.Block storage blk = state.blocks[slot]; - // Check the integrity of the block data. + // 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 (blk.blockId != meta.id || blk.metaHash != keccak256(abi.encode(meta))) { revert L1_BLOCK_MISMATCH(); } @@ -101,81 +106,12 @@ library LibProving { // blockHash and signalRoot open for later updates as higher-tier proofs // become available. In cases where a transition with the specified // parentHash does not exist, the transition ID (tid) will be set to 0. - uint32 tid = LibUtils.getTransitionId(state, blk, slot, tran.parentHash); - TaikoData.TransitionState storage ts; - - if (tid == 0) { - // In cases where a transition with the provided parentHash is not - // found, we must essentially "create" one and set it to its initial - // state. This initial state can be viewed as a special transition - // on tier-0. - // - // Subsequently, we transform this tier-0 transition into a - // non-zero-tier transition with a proof. This approach ensures that - // the same logic is applicable for both 0-to-non-zero transition - // updates and non-zero-to-non-zero transition updates. - unchecked { - // Unchecked is safe: Not realistic 2**32 different fork choice - // per block will be proven and none of them is valid - tid = blk.nextTransitionId++; - } - - // Keep in mind that state.transitions are also reusable storage - // slots, so it's necessary to reinitialize all transition fields - // below. - ts = state.transitions[slot][tid]; - ts.blockHash = 0; - ts.signalRoot = 0; - ts.validityBond = 0; - ts.contester = address(0); - ts.contestBond = 1; // see below (the value does't matter) - ts.timestamp = blk.proposedAt; - ts.tier = 0; - ts.contestations = 0; - - if (tid == 1) { - // This approach serves as a cost-saving technique for the - // majority of blocks, where the first transition is expected to - // be the correct one. Writing to `tran` is more economical - // since it resides in the ring buffer, whereas writing to - // `transitionIds` is not as cost-effective. - ts.key = tran.parentHash; - - // In the case of this first transition, the block's assigned - // prover has the privilege to re-prove it, but only when the - // assigned prover matches the previous prover. To ensure this, - // we establish the transition's prover as the block's assigned - // prover. Consequently, when we carry out a 0-to-non-zero - // transition update, the previous prover will consistently be - // the block's assigned prover. - // - // While alternative implementations are possible, introducing - // such changes would require additional if-else logic. - ts.prover = blk.assignedProver; - } else { - // In scenarios where this transition is not the first one, we - // straightforwardly reset the transition prover to address - // zero. - ts.prover = address(0); - - // Furthermore, we index the transition for future retrieval. - // It's worth emphasizing that this mapping for indexing is not - // reusable. However, given that the majority of blocks will - // only possess one transition — the correct one — we don't need - // to be concerned about the cost in this case. - state.transitionIds[blk.blockId][tran.parentHash] = tid; - } - } else { - // A transition with the provided parentHash has been located. - ts = state.transitions[slot][tid]; - if (ts.tier < meta.minTier) { - revert L1_UNEXPECTED_TRANSITION_TIER(); - } - } + (uint32 tid, TaikoData.TransitionState storage ts) = + _createTransition(state, blk, tran, slot); // The new proof must meet or exceed the minimum tier required by the // block or the previous proof; it cannot be on a lower tier. - if (proof.tier == 0 || proof.tier < ts.tier) { + if (proof.tier == 0 || proof.tier < meta.minTier || proof.tier < ts.tier) { revert L1_INVALID_TIER(); } @@ -184,7 +120,7 @@ library LibProving { ITierProvider.Tier memory tier = ITierProvider(resolver.resolve("tier_provider", false)).getTier(proof.tier); - maxBlocksToVerify = tier.maxBlocksToVerifyPerProof; + _checkProverPermission(blk, ts, tid, tier); // We must verify the proof, and any failure in proof verification will // result in a revert. @@ -207,11 +143,12 @@ library LibProving { // optimistic proofs. if ( verifier == address(0) - && keccak256(abi.encodePacked(tier.verifierName)) - != keccak256(abi.encodePacked("tier_optimistic")) + && tier.verifierName + != TIER_OP ) { revert L1_MISSING_VERIFIER(); } + if (verifier != address(0)) { bool isContesting = proof.tier == ts.tier && tier.contestBond != 0; @@ -228,199 +165,239 @@ library LibProving { } } + bool isTopTier = tier.contestBond == 0; IERC20 tko = IERC20(resolver.resolve("taiko_token", false)); - if (tier.contestBond == 0) { - assert(tier.validityBond == 0); - - // It means prover is right (not the contester) - bool sameTransition = tran.blockHash == ts.blockHash && tran.signalRoot == ts.signalRoot; - + if (isTopTier) { // A special return value from the top tier prover can signal this // contract to return all liveness bond. - if ( - blk.livenessBond > 0 && proof.data.length == 32 - && bytes32(proof.data) == RETURN_LIVENESS_BOND - ) { + bool returnLivenessBond = blk.livenessBond > 0 && proof.data.length == 32 + && bytes32(proof.data) == RETURN_LIVENESS_BOND; + + if (returnLivenessBond) { tko.transfer(blk.assignedProver, blk.livenessBond); blk.livenessBond = 0; } + } - ts.blockHash = tran.blockHash; - ts.signalRoot = tran.signalRoot; - ts.prover = msg.sender; - - if (ts.contester != address(0)) { - if (!sameTransition) { - // At this point we know that the contester was right - tko.transfer(ts.contester, ts.validityBond >> 2 + ts.contestBond); - } - ts.contester = address(0); - ts.validityBond = 0; - } + bool sameTransition = tran.blockHash == ts.blockHash && tran.signalRoot == ts.signalRoot; - ts.timestamp = uint64(block.timestamp); - ts.tier = proof.tier; + if (proof.tier > ts.tier) { + // Handles the case when an incoming tier is higher than the current transition's tier. + // Reverts when the incoming proof tries to prove the same transition + // (L1_ALREADY_PROVED). + _overrideWithHigherProof(ts, tran, proof, tier, tko, sameTransition); emit TransitionProved({ blockId: blk.blockId, tran: tran, prover: msg.sender, - validityBond: 0, + validityBond: tier.validityBond, tier: proof.tier }); - } else if (proof.tier == ts.tier) { - // Contesting an existing transition requires either the blockHash - // or signalRoot to be different. This precaution is necessary - // because this `proveBlock` transaction might aim to prove a - // transition but could potentially be front-run by another prover - // attempting to prove the same transition. - if (tran.blockHash == ts.blockHash && tran.signalRoot == ts.signalRoot) { - revert L1_ALREADY_PROVED(); + } else { + // New transition and old transition on the same tier - and if this transaction tries to + // prove the same, it reverts + if (sameTransition) revert L1_ALREADY_PROVED(); + + if (isTopTier) { + // The top tier prover re-proves. + assert(tier.validityBond == 0); + assert(ts.validityBond == 0 && ts.contestBond == 0 && ts.contester == address(0)); + + ts.prover = msg.sender; + ts.blockHash = tran.blockHash; + ts.signalRoot = tran.signalRoot; + + emit TransitionProved({ + blockId: blk.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(); + + // Burn the contest bond from the prover. + tko.transferFrom(msg.sender, address(this), tier.contestBond); + + // We retain the contest bond within the transition, just in + // case this configuration is altered to a different value + // before the contest is resolved. + // + // It's worth noting that the previous value of ts.contestBond + // doesn't have any significance. + ts.contestBond = tier.contestBond; + ts.contester = msg.sender; + ts.contestations += 1; + + emit TransitionContested({ + blockId: blk.blockId, + tran: tran, + contester: msg.sender, + contestBond: tier.contestBond, + tier: proof.tier + }); } + } - // The new tier is the same as the previous tier, we are in the - // contesting mode. - // - // It's important to note that tran.blockHash and - // tran.signalRoot are not permanently stored, so their - // specific values are inconsequential. They only need to differ - // from the existing values to signify a contest. Therefore, a - // contester can conveniently utilize the value 1 for these two - // parameters. - - // The existing transiton must not have been contested. - if (ts.contester != address(0)) revert L1_ALREADY_CONTESTED(); - - // Burn the contest bond from the prover. - tko.transferFrom(msg.sender, address(this), tier.contestBond); - - // We retain the contest bond within the transition, just in - // case this configuration is altered to a different value - // before the contest is resolved. - // - // It's worth noting that the previous value of ts.contestBond - // doesn't have any significance. - ts.contestBond = tier.contestBond; - ts.contester = msg.sender; - ts.timestamp = uint64(block.timestamp); - ts.contestations += 1; + ts.timestamp = uint64(block.timestamp); + return tier.maxBlocksToVerifyPerProof; + } - emit TransitionContested({ - blockId: blk.blockId, - tran: tran, - contester: msg.sender, - contestBond: tier.contestBond, - tier: proof.tier - }); - } else { - assert(proof.tier > ts.tier); - // The new tier is higher than the previous tier, we are in the - // proving mode. This works even if this transition's contester is - // address zero, see more info below. + /// @dev Handle the transition initialization logic + function _createTransition( + TaikoData.State storage state, + TaikoData.Block storage blk, + TaikoData.Transition memory tran, + uint64 slot + ) + private + returns (uint32 tid, TaikoData.TransitionState storage ts) + { + tid = LibUtils.getTransitionId(state, blk, slot, tran.parentHash); - // The ability to prove a transition is granted under the following - // two circumstances: - // - // 1. When the transition has been contested, indicated by - // ts.contester not being address zero. + if (tid == 0) { + // In cases where a transition with the provided parentHash is not + // found, we must essentially "create" one and set it to its initial + // state. This initial state can be viewed as a special transition + // on tier-0. // - // 2. When the transition's blockHash and/or signalRoot differs. In - // this case, the new prover essentially contests the previous proof - // but immediately validates it, obviating the requirement to set a - // contester, burn the contest bond, and other associated actions. - // This streamlined process is applied to 0-to-non-zero transition - // updates. - if ( - ts.contester == address(0) && ts.blockHash == tran.blockHash - && ts.signalRoot == tran.signalRoot - ) { - // Alternatively, it can be understood that a transition cannot - // be re-approved by higher-tier proofs without undergoing - // contestation. - revert L1_ALREADY_PROVED(); + // Subsequently, we transform this tier-0 transition into a + // non-zero-tier transition with a proof. This approach ensures that + // the same logic is applicable for both 0-to-non-zero transition + // updates and non-zero-to-non-zero transition updates. + unchecked { + // Unchecked is safe: Not realistic 2**32 different fork choice + // per block will be proven and none of them is valid + tid = blk.nextTransitionId++; + } + + // Keep in mind that state.transitions are also reusable storage + // slots, so it's necessary to reinitialize all transition fields + // below. + ts = state.transitions[slot][tid]; + ts.blockHash = 0; + ts.signalRoot = 0; + ts.validityBond = 0; + ts.contester = address(0); + ts.contestBond = 1; // to save gas + ts.timestamp = blk.proposedAt; + ts.tier = 0; + ts.contestations = 0; + + if (tid == 1) { + // This approach serves as a cost-saving technique for the + // majority of blocks, where the first transition is expected to + // be the correct one. Writing to `tran` is more economical + // since it resides in the ring buffer, whereas writing to + // `transitionIds` is not as cost-effective. + ts.key = tran.parentHash; + + // In the case of this first transition, the block's assigned + // prover has the privilege to re-prove it, but only when the + // assigned prover matches the previous prover. To ensure this, + // we establish the transition's prover as the block's assigned + // prover. Consequently, when we carry out a 0-to-non-zero + // transition update, the previous prover will consistently be + // the block's assigned prover. + // + // While alternative implementations are possible, introducing + // such changes would require additional if-else logic. + ts.prover = blk.assignedProver; + } else { + // In scenarios where this transition is not the first one, we + // straightforwardly reset the transition prover to address + // zero. + ts.prover = address(0); + + // Furthermore, we index the transition for future retrieval. + // It's worth emphasizing that this mapping for indexing is not + // reusable. However, given that the majority of blocks will + // only possess one transition — the correct one — we don't need + // to be concerned about the cost in this case. + state.transitionIds[blk.blockId][tran.parentHash] = tid; } + } else { + // A transition with the provided parentHash has been located. + ts = state.transitions[slot][tid]; + } + } - if (tid == 1 && ts.tier == 0 && block.timestamp <= ts.timestamp + tier.provingWindow) { - // For the first transition, (1) if the previous prover is - // still the assigned prover, we exclusively grant permission to - // the assigned approver to re-prove the block, (2) unless the - // proof window has elapsed. - if (msg.sender != blk.assignedProver) revert L1_NOT_ASSIGNED_PROVER(); - } else if (msg.sender == blk.assignedProver) { - // However, if the previous prover of the first transition is - // not the block's assigned prover, or for any other - // transitions, the assigned prover is not permitted to prove - // such transitions. - revert L1_ASSIGNED_PROVER_NOT_ALLOWED(); + /// @dev Handles what happens when there is a higher proof incoming + function _overrideWithHigherProof( + TaikoData.TransitionState storage ts, + TaikoData.Transition memory tran, + TaikoData.TierProof memory proof, + ITierProvider.Tier memory tier, + IERC20 tko, + bool sameTransition + ) + private + { + // Higher tier proof overwriting lower tier proof + uint256 reward; + + if (ts.contester != address(0)) { + if (sameTransition) { + // The contested transition is proven to be valid, contestor loses the game + reward = ts.contestBond >> 2; + tko.transfer(ts.prover, ts.validityBond + reward); + } else { + // The contested transition is proven to be invalid, contestor wins the game + reward = ts.validityBond >> 2; + tko.transfer(ts.contester, ts.contestBond + reward); } + } else { + if (sameTransition) revert L1_ALREADY_PROVED(); + // Contest the existing transition and prove it to be invalid + reward = ts.validityBond >> 1; + ts.contestations += 1; + } - unchecked { - // This is the amount of Taiko tokens to send to the new prover - // and the winner of the contest (same amount to both parties). - uint256 reward; - if (ts.blockHash == tran.blockHash && ts.signalRoot == tran.signalRoot) { - assert(ts.contester != address(0)); - // In the event that the previous prover emerges as the - // winner, half of the contest bond is designated as the - // reward, to be divided equally between the new prover and - // the previous prover -- 1/4 each - reward = ts.contestBond >> 2; - - // Mint the reward and the validity bond and return it to - // the previous prover. - tko.transfer(ts.prover, reward + ts.validityBond); - } else { - // In the event that the contester is the winner, half of - // the validity bond is designated as the reward, to be - // divided equally between the new prover and the contester. - reward = ts.validityBond >> 2; - - // It's important to note that the contester is set to zero - // for the tier-0 transition. Consequently, we only grant a - // reward to the contester if it is not a zero-address. - if (ts.contester != address(0)) { - tko.transfer(ts.contester, reward + ts.contestBond); - } else { - // The prover is also the contester, so the reward is - // sent to him. - tko.transfer(msg.sender, reward); - } - - // Given that the contester emerges as the winner, the - // previous blockHash and signalRoot are considered - // incorrect, and we must replace them with the correct - // values. - ts.blockHash = tran.blockHash; - ts.signalRoot = tran.signalRoot; - } - - // Reward this prover. - // In theory, the reward can also be zero for certain tiers if - // their validity bonds are set to zero. - tko.transfer(msg.sender, reward); + unchecked { + if (reward > tier.validityBond) { + tko.transfer(msg.sender, reward - tier.validityBond); + } else { + tko.transferFrom(msg.sender, address(this), tier.validityBond - reward); } + } - // Burn the validity bond from the prover. - tko.transferFrom(msg.sender, address(this), tier.validityBond); + ts.validityBond = tier.validityBond; + ts.contestBond = 1; // to save gas + ts.contester = address(0); + ts.prover = msg.sender; + ts.tier = proof.tier; - // Regardless of whether the previous prover or the contester - // emerges as the winner, we consistently erase the contest history - // to make this transition appear entirely new. - ts.prover = msg.sender; - ts.validityBond = tier.validityBond; - ts.contester = address(0); - ts.contestBond = 1; // to save gas - ts.timestamp = uint64(block.timestamp); - ts.tier = proof.tier; + if (!sameTransition) { + ts.blockHash = tran.blockHash; + ts.signalRoot = tran.signalRoot; + } + } - emit TransitionProved({ - blockId: blk.blockId, - tran: tran, - prover: msg.sender, - validityBond: tier.validityBond, - tier: proof.tier - }); + /// @dev Check the msg.sender (the new prover) against the block's assigned prover. + function _checkProverPermission( + TaikoData.Block storage blk, + TaikoData.TransitionState storage ts, + uint32 tid, + ITierProvider.Tier memory tier + ) + private + view + { + // The highest tier proof can always submit new proofs + if (tier.contestBond == 0) return; + + bool inProvingWindow = block.timestamp <= ts.timestamp + tier.provingWindow; + bool isAssignedPover = msg.sender == blk.assignedProver; + + // The assigned prover can only submit the very first transition. + if (tid == 1 && ts.tier == 0 && inProvingWindow) { + if (!isAssignedPover) revert L1_NOT_ASSIGNED_PROVER(); + } else { + if (isAssignedPover) revert L1_ASSIGNED_PROVER_NOT_ALLOWED(); } } } diff --git a/packages/protocol/contracts/L1/libs/LibProvingAlt.sol b/packages/protocol/contracts/L1/libs/LibProvingAlt.sol deleted file mode 100644 index 013d1611cd..0000000000 --- a/packages/protocol/contracts/L1/libs/LibProvingAlt.sol +++ /dev/null @@ -1,397 +0,0 @@ -// SPDX-License-Identifier: MIT -// _____ _ _ _ _ -// |_ _|_ _(_) |_____ | | __ _| |__ ___ -// | |/ _` | | / / _ \ | |__/ _` | '_ (_-< -// |_|\__,_|_|_\_\___/ |____\__,_|_.__/__/ -// -// Email: security@taiko.xyz -// Website: https://taiko.xyz -// GitHub: https://github.com/taikoxyz -// Discord: https://discord.gg/taikoxyz -// Twitter: https://twitter.com/taikoxyz -// Blog: https://mirror.xyz/labs.taiko.eth -// Youtube: https://www.youtube.com/@taikoxyz - -pragma solidity 0.8.20; - -import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import "../../common/AddressResolver.sol"; -import "../tiers/ITierProvider.sol"; -import "../verifiers/IVerifier.sol"; -import "../TaikoData.sol"; -import "./LibUtils.sol"; - -/// @title LibProvingAlt -/// @notice An alternative library for handling block contestation and proving in the Taiko -/// protocol. -library LibProvingAlt { - bytes32 public constant RETURN_LIVENESS_BOND = keccak256("RETURN_LIVENESS_BOND"); - // Warning: Any events defined here must also be defined in TaikoEvents.sol. - - event TransitionProved( - uint256 indexed blockId, - TaikoData.Transition tran, - address prover, - uint96 validityBond, - uint16 tier - ); - - event TransitionContested( - uint256 indexed blockId, - TaikoData.Transition tran, - address contester, - uint96 contestBond, - uint16 tier - ); - - event ProvingPaused(bool paused); - - // Warning: Any errors defined here must also be defined in TaikoErrors.sol. - error L1_ALREADY_CONTESTED(); - error L1_ALREADY_PROVED(); - error L1_ASSIGNED_PROVER_NOT_ALLOWED(); - error L1_BLOCK_MISMATCH(); - error L1_INVALID_BLOCK_ID(); - error L1_INVALID_PAUSE_STATUS(); - error L1_INVALID_TIER(); - error L1_INVALID_TRANSITION(); - error L1_NOT_ASSIGNED_PROVER(); - error L1_UNEXPECTED_TRANSITION_TIER(); - - function pauseProving(TaikoData.State storage state, bool pause) external { - if (state.slotB.provingPaused == pause) revert L1_INVALID_PAUSE_STATUS(); - - state.slotB.provingPaused = pause; - emit ProvingPaused(pause); - } - - /// @dev Proves or contests a block transition. - function proveBlock( - TaikoData.State storage state, - TaikoData.Config memory config, - AddressResolver resolver, - TaikoData.BlockMetadata memory meta, - TaikoData.Transition memory tran, - TaikoData.TierProof memory proof - ) - external - returns (uint8 maxBlocksToVerify) - { - // Make sure parentHash is not zero - // To contest an existing transition, simply use any non-zero value as - // the blockHash and signalRoot. - if (tran.parentHash == 0 || tran.blockHash == 0 || tran.signalRoot == 0) { - revert L1_INVALID_TRANSITION(); - } - - // Check that the block has been proposed but has not yet been verified. - TaikoData.SlotB memory b = state.slotB; - if (meta.id <= b.lastVerifiedBlockId || meta.id >= b.numBlocks) { - revert L1_INVALID_BLOCK_ID(); - } - - uint64 slot = meta.id % config.blockRingBufferSize; - TaikoData.Block storage blk = state.blocks[slot]; - - // 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 (blk.blockId != meta.id || blk.metaHash != keccak256(abi.encode(meta))) { - revert L1_BLOCK_MISMATCH(); - } - - // Each transition is uniquely identified by the parentHash, with the - // blockHash and signalRoot open for later updates as higher-tier proofs - // become available. In cases where a transition with the specified - // parentHash does not exist, the transition ID (tid) will be set to 0. - (uint32 tid, TaikoData.TransitionState storage ts) = - _createTransition(state, blk, meta, tran, slot); - - // The new proof must meet or exceed the minimum tier required by the - // block or the previous proof; it cannot be on a lower tier. - if (proof.tier == 0 || proof.tier < ts.tier) { - revert L1_INVALID_TIER(); - } - - // Retrieve the tier configurations. If the tier is not supported, the - // subsequent action will result in a revert. - ITierProvider.Tier memory tier = - ITierProvider(resolver.resolve("tier_provider", false)).getTier(proof.tier); - - _checkProverPermission(blk, ts, tid, tier); - - // We must verify the proof, and any failure in proof verification will - // result in a revert. - // - // It's crucial to emphasize that the proof can be assessed in two - // potential modes: "proving mode" and "contesting mode." However, the - // precise verification logic is defined within each tier'IVerifier - // contract implementation. We simply specify to the verifier contract - // which mode it should utilize - if the new tier is higher than the - // previous tier, we employ the proving mode; otherwise, we employ the - // contesting mode (the new tier cannot be lower than the previous tier, - // this has been checked above). - // - // It's obvious that proof verification is entirely decoupled from - // Taiko's core protocol. - { - address verifier = resolver.resolve(tier.verifierName, true); - // The verifier can be address-zero, signifying that there are no - // proof checks for the tier. In practice, this only applies to - // optimistic proofs. - if (verifier != address(0)) { - bool isContesting = proof.tier == ts.tier && tier.contestBond != 0; - - IVerifier.Context memory ctx = IVerifier.Context({ - metaHash: blk.metaHash, - blobHash: meta.blobHash, - prover: msg.sender, - blockId: blk.blockId, - isContesting: isContesting, - blobUsed: meta.blobUsed - }); - - IVerifier(verifier).verifyProof(ctx, tran, proof); - } - } - - bool isTopTier = tier.contestBond == 0; - IERC20 tko = IERC20(resolver.resolve("taiko_token", false)); - - if (isTopTier) { - // A special return value from the top tier prover can signal this - // contract to return all liveness bond. - bool returnLivenessBond = blk.livenessBond > 0 && proof.data.length == 32 - && bytes32(proof.data) == RETURN_LIVENESS_BOND; - - if (returnLivenessBond) { - tko.transfer(blk.assignedProver, blk.livenessBond); - blk.livenessBond = 0; - } - } - - bool sameTransition = tran.blockHash == ts.blockHash && tran.signalRoot == ts.signalRoot; - - if (proof.tier > ts.tier) { - // Handles the case when an incoming tier is higher than the current transition's tier. - // Reverts when the incoming proof tries to prove the same transition - // (L1_ALREADY_PROVED). - _overrideWithHigherProof(ts, tran, proof, tier, tko, sameTransition); - - emit TransitionProved({ - blockId: blk.blockId, - tran: tran, - prover: msg.sender, - validityBond: 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 - if (sameTransition) revert L1_ALREADY_PROVED(); - - if (isTopTier) { - // The top tier prover re-proves. - assert(tier.validityBond == 0); - assert(ts.validityBond == 0 && ts.contestBond == 0 && ts.contester == address(0)); - - ts.prover = msg.sender; - ts.blockHash = tran.blockHash; - ts.signalRoot = tran.signalRoot; - - emit TransitionProved({ - blockId: blk.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(); - - // Burn the contest bond from the prover. - tko.transferFrom(msg.sender, address(this), tier.contestBond); - - // We retain the contest bond within the transition, just in - // case this configuration is altered to a different value - // before the contest is resolved. - // - // It's worth noting that the previous value of ts.contestBond - // doesn't have any significance. - ts.contestBond = tier.contestBond; - ts.contester = msg.sender; - ts.contestations += 1; - - emit TransitionContested({ - blockId: blk.blockId, - tran: tran, - contester: msg.sender, - contestBond: tier.contestBond, - tier: proof.tier - }); - } - } - - ts.timestamp = uint64(block.timestamp); - return tier.maxBlocksToVerifyPerProof; - } - - /// @dev Handle the transition initialization logic - function _createTransition( - TaikoData.State storage state, - TaikoData.Block storage blk, - TaikoData.BlockMetadata memory meta, - TaikoData.Transition memory tran, - uint64 slot - ) - private - returns (uint32 tid, TaikoData.TransitionState storage ts) - { - tid = LibUtils.getTransitionId(state, blk, slot, tran.parentHash); - - if (tid == 0) { - // In cases where a transition with the provided parentHash is not - // found, we must essentially "create" one and set it to its initial - // state. This initial state can be viewed as a special transition - // on tier-0. - // - // Subsequently, we transform this tier-0 transition into a - // non-zero-tier transition with a proof. This approach ensures that - // the same logic is applicable for both 0-to-non-zero transition - // updates and non-zero-to-non-zero transition updates. - unchecked { - // Unchecked is safe: Not realistic 2**32 different fork choice - // per block will be proven and none of them is valid - tid = blk.nextTransitionId++; - } - - // Keep in mind that state.transitions are also reusable storage - // slots, so it's necessary to reinitialize all transition fields - // below. - ts = state.transitions[slot][tid]; - ts.blockHash = 0; - ts.signalRoot = 0; - ts.validityBond = 0; - ts.contester = address(0); - ts.contestBond = 1; // to save gas - ts.timestamp = blk.proposedAt; - ts.tier = 0; - ts.contestations = 0; - - if (tid == 1) { - // This approach serves as a cost-saving technique for the - // majority of blocks, where the first transition is expected to - // be the correct one. Writing to `tran` is more economical - // since it resides in the ring buffer, whereas writing to - // `transitionIds` is not as cost-effective. - ts.key = tran.parentHash; - - // In the case of this first transition, the block's assigned - // prover has the privilege to re-prove it, but only when the - // assigned prover matches the previous prover. To ensure this, - // we establish the transition's prover as the block's assigned - // prover. Consequently, when we carry out a 0-to-non-zero - // transition update, the previous prover will consistently be - // the block's assigned prover. - // - // While alternative implementations are possible, introducing - // such changes would require additional if-else logic. - ts.prover = blk.assignedProver; - } else { - // In scenarios where this transition is not the first one, we - // straightforwardly reset the transition prover to address - // zero. - ts.prover = address(0); - - // Furthermore, we index the transition for future retrieval. - // It's worth emphasizing that this mapping for indexing is not - // reusable. However, given that the majority of blocks will - // only possess one transition — the correct one — we don't need - // to be concerned about the cost in this case. - state.transitionIds[blk.blockId][tran.parentHash] = tid; - } - } else { - // A transition with the provided parentHash has been located. - ts = state.transitions[slot][tid]; - if (ts.tier < meta.minTier) { - revert L1_UNEXPECTED_TRANSITION_TIER(); - } - } - } - - /// @dev Handles what happens when there is a higher proof incoming - function _overrideWithHigherProof( - TaikoData.TransitionState storage ts, - TaikoData.Transition memory tran, - TaikoData.TierProof memory proof, - ITierProvider.Tier memory tier, - IERC20 tko, - bool sameTransition - ) - private - { - // Higher tier proof overwriting lower tier proof - uint256 reward; - - if (ts.contester != address(0)) { - if (sameTransition) { - // The contested transition is proven to be valid, contestor loses the game - reward = ts.contestBond >> 2; - tko.transfer(ts.prover, ts.validityBond + reward); - } else { - // The contested transition is proven to be invalid, contestor wins the game - reward = ts.validityBond >> 2; - tko.transfer(ts.contester, ts.contestBond + reward); - } - } else { - if (sameTransition) revert L1_ALREADY_PROVED(); - // Contest the existing transition and prove it to be invalid - reward = ts.validityBond >> 1; - ts.contestations += 1; - } - - unchecked { - if (reward > tier.validityBond) { - tko.transfer(msg.sender, reward - tier.validityBond); - } else { - tko.transferFrom(msg.sender, address(this), tier.validityBond - reward); - } - } - - ts.validityBond = tier.validityBond; - ts.contestBond = 1; // to save gas - ts.contester = address(0); - ts.prover = msg.sender; - ts.tier = proof.tier; - - if (!sameTransition) { - ts.blockHash = tran.blockHash; - ts.signalRoot = tran.signalRoot; - } - } - - /// @dev Check the msg.sender (the new prover) against the block's assigned prover. - function _checkProverPermission( - TaikoData.Block storage blk, - TaikoData.TransitionState storage ts, - uint32 tid, - ITierProvider.Tier memory tier - ) - private - view - { - // The highest tier proof can always submit new proofs - if (tier.contestBond == 0) return; - - bool inProvingWindow = block.timestamp <= ts.timestamp + tier.provingWindow; - bool isAssignedPover = msg.sender == blk.assignedProver; - - // The assigned prover can only submit the very first transition. - if (tid == 1 && ts.tier == 0 && inProvingWindow) { - if (!isAssignedPover) revert L1_NOT_ASSIGNED_PROVER(); - } else { - if (isAssignedPover) revert L1_ASSIGNED_PROVER_NOT_ALLOWED(); - } - } -} From f069e822af4977c33b788384d84ae5749e376cb6 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Tue, 30 Jan 2024 17:28:25 +0800 Subject: [PATCH 6/9] Update LibVerifying.sol --- packages/protocol/contracts/L1/libs/LibVerifying.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index b8b2b8a8d0..f3443a95ab 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -90,9 +90,9 @@ library LibVerifying { || config.blockMaxTxListBytes > 128 * 1024 // calldata up to 128K || config.livenessBond == 0 || config.ethDepositRingBufferSize <= 1 || config.ethDepositMinCountPerBlock == 0 - // Audit recommendation, and gas tested. Processing 32 deposits (as initially set in - // TaikoL1.sol) costs 72_502 gas. - || config.ethDepositMaxCountPerBlock > 32 + // Audit recommendation, and gas tested. Processing 32 deposits (as initially set in + // TaikoL1.sol) costs 72_502 gas. + || config.ethDepositMaxCountPerBlock > 32 || config.ethDepositMaxCountPerBlock < config.ethDepositMinCountPerBlock || config.ethDepositMinAmount == 0 || config.ethDepositMaxAmount <= config.ethDepositMinAmount From 9981b229a47179ff9e89e380e8676bf9c962a905 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Tue, 30 Jan 2024 17:28:51 +0800 Subject: [PATCH 7/9] Update LibProving.sol --- packages/protocol/contracts/L1/libs/LibProving.sol | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index a42fb98201..766c378114 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -141,11 +141,7 @@ library LibProving { // The verifier can be address-zero, signifying that there are no // proof checks for the tier. In practice, this only applies to // optimistic proofs. - if ( - verifier == address(0) - && tier.verifierName - != TIER_OP - ) { + if (verifier == address(0) && tier.verifierName != TIER_OP) { revert L1_MISSING_VERIFIER(); } From 27683ab46556e1e6043d36c1a840bfdf7acd1ce5 Mon Sep 17 00:00:00 2001 From: Daniel Wang <99078276+dantaik@users.noreply.github.com> Date: Tue, 30 Jan 2024 17:30:45 +0800 Subject: [PATCH 8/9] Update packages/protocol/contracts/L1/gov/TaikoGovernor.sol --- packages/protocol/contracts/L1/gov/TaikoGovernor.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/contracts/L1/gov/TaikoGovernor.sol b/packages/protocol/contracts/L1/gov/TaikoGovernor.sol index a1eba21c3e..8688d42dfe 100644 --- a/packages/protocol/contracts/L1/gov/TaikoGovernor.sol +++ b/packages/protocol/contracts/L1/gov/TaikoGovernor.sol @@ -91,7 +91,7 @@ contract TaikoGovernor is return 50_400; // 1 week } - // The number of votes required in order for a voter to become a vote proposer + // The number of votes required in order for a voter to become a proposer function proposalThreshold() public pure override returns (uint256) { return 1_000_000_000 ether / 10_000; // 0.01% of Taiko Token } From 7f1478456f4591898058fb9a4b984af2597742b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Keszey=20D=C3=A1niel?= Date: Tue, 30 Jan 2024 15:18:54 +0530 Subject: [PATCH 9/9] add back LibProvingAlt due to merge conflicts --- packages/protocol/contracts/L1/TaikoL1.sol | 3 ++- .../contracts/L1/libs/{LibProving.sol => LibProvingAlt.sol} | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) rename packages/protocol/contracts/L1/libs/{LibProving.sol => LibProvingAlt.sol} (99%) diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 1de38a8de8..9c0ecad6c9 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -17,7 +17,8 @@ pragma solidity 0.8.20; import "../common/EssentialContract.sol"; import "./libs/LibDepositing.sol"; import "./libs/LibProposing.sol"; -import "./libs/LibProving.sol"; +//import "./libs/LibProving.sol"; +import { LibProvingAlt as LibProving } from "./libs/LibProvingAlt.sol"; import "./libs/LibVerifying.sol"; import "./ITaikoL1.sol"; import "./TaikoErrors.sol"; diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProvingAlt.sol similarity index 99% rename from packages/protocol/contracts/L1/libs/LibProving.sol rename to packages/protocol/contracts/L1/libs/LibProvingAlt.sol index 766c378114..b593f82377 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProvingAlt.sol @@ -24,7 +24,7 @@ import "./LibUtils.sol"; /// @title LibProving /// @notice A library for handling block contestation and proving in the Taiko /// protocol. -library LibProving { +library LibProvingAlt { bytes32 public constant RETURN_LIVENESS_BOND = keccak256("RETURN_LIVENESS_BOND"); bytes32 public constant TIER_OP = bytes32("tier_optimistic");