diff --git a/packages/protocol/contracts/L1/TaikoErrors.sol b/packages/protocol/contracts/L1/TaikoErrors.sol index dcd64681a3..ae54b5a415 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 8c4ef52b32..3f434306c1 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/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 2fb39f0623..193d598138 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -17,7 +17,7 @@ pragma solidity 0.8.24; 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"; diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol deleted file mode 100644 index 19af30ea1e..0000000000 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ /dev/null @@ -1,431 +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.24; - -import "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import "../../common/AddressResolver.sol"; -import "../../libs/LibMath.sol"; -import "../tiers/ITierProvider.sol"; -import "../verifiers/IVerifier.sol"; -import "../TaikoData.sol"; -import "./LibUtils.sol"; - -/// @title LibProving -/// @notice A library for handling block contestation and proving in the Taiko -/// protocol. -library LibProving { - using LibMath for uint256; - - 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 toPause) external { - if (state.slotB.provingPaused == toPause) revert L1_INVALID_PAUSE_STATUS(); - - state.slotB.provingPaused = toPause; - - if (!toPause) { - state.slotB.lastUnpausedAt = uint64(block.timestamp); - } - emit ProvingPaused(toPause); - } - - /// @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 - 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 = 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(); - } - } - - // 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) { - 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); - - maxBlocksToVerify = tier.maxBlocksToVerify; - - // 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); - } - } - - 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; - - // 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 - ) { - 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; - } - - ts.timestamp = uint64(block.timestamp); - ts.tier = proof.tier; - - emit TransitionProved({ - blockId: blk.blockId, - tran: tran, - prover: msg.sender, - validityBond: 0, - 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(); - } - - // 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; - - 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. - - // 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. - // - // 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(); - } - - if ( - tid == 1 && ts.tier == 0 - && block.timestamp - <= uint256(ts.timestamp).max(state.slotB.lastUnpausedAt) + 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(); - } - - 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); - } - - // Burn the validity bond from the prover. - tko.transferFrom(msg.sender, address(this), tier.validityBond); - - // 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; - - emit TransitionProved({ - blockId: blk.blockId, - tran: tran, - prover: msg.sender, - validityBond: tier.validityBond, - tier: proof.tier - }); - } - } -} diff --git a/packages/protocol/contracts/L1/libs/LibProvingAlt.sol b/packages/protocol/contracts/L1/libs/LibProvingAlt.sol index 84532d4a30..4162f1c3e5 100644 --- a/packages/protocol/contracts/L1/libs/LibProvingAlt.sol +++ b/packages/protocol/contracts/L1/libs/LibProvingAlt.sol @@ -22,15 +22,16 @@ 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 +/// @title LibProving +/// @notice A library for handling block contestation and proving in the Taiko /// protocol. library LibProvingAlt { using LibMath for uint256; 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, @@ -58,6 +59,7 @@ library LibProvingAlt { error L1_INVALID_PAUSE_STATUS(); error L1_INVALID_TIER(); error L1_INVALID_TRANSITION(); + error L1_MISSING_VERIFIER(); error L1_NOT_ASSIGNED_PROVER(); error L1_UNEXPECTED_TRANSITION_TIER(); @@ -108,7 +110,7 @@ library LibProvingAlt { // 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); + _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. @@ -142,6 +144,10 @@ library LibProvingAlt { // 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) { + revert L1_MISSING_VERIFIER(); + } + if (verifier != address(0)) { bool isContesting = proof.tier == ts.tier && tier.contestBond != 0; @@ -237,14 +243,13 @@ library LibProvingAlt { } ts.timestamp = uint64(block.timestamp); - return tier.maxBlocksToVerify; + 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 ) @@ -317,9 +322,6 @@ library LibProvingAlt { } 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(); - } } } diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index b1ef7aa99e..03e9e12f91 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -93,11 +93,14 @@ 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 || 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; @@ -212,8 +215,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 72a4ae75aa..beb553aea4 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 dc0412302e..3950ed7550 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 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 00e72ab5e9..ed48fab3b7 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: 500 ether, // TKO cooldownWindow: 24 hours, provingWindow: 2 hours, - maxBlocksToVerify: 10 + maxBlocksToVerifyPerProof: 10 }); } @@ -52,7 +52,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 1000 ether, // TKO cooldownWindow: 24 hours, provingWindow: 4 hours, - maxBlocksToVerify: 8 + maxBlocksToVerifyPerProof: 8 }); } @@ -63,7 +63,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 2000 ether, // TKO cooldownWindow: 24 hours, provingWindow: 6 hours, - maxBlocksToVerify: 6 + maxBlocksToVerifyPerProof: 6 }); } @@ -74,7 +74,7 @@ contract TaikoA6TierProvider is EssentialContract, ITierProvider { contestBond: 0, // must be 0 for top tier cooldownWindow: 24 hours, provingWindow: 8 hours, - maxBlocksToVerify: 4 + maxBlocksToVerifyPerProof: 4 }); } diff --git a/packages/protocol/contracts/L1/verifiers/PseZkVerifier.sol b/packages/protocol/contracts/L1/verifiers/PseZkVerifier.sol index 6d82105508..de23171bc5 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/L2/TaikoL2.sol b/packages/protocol/contracts/L2/TaikoL2.sol index b0e3c01a6c..1b5f517944 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 efd8539ae2..d3feffbb47 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); diff --git a/packages/protocol/contracts/thirdparty/LibBytesUtils.sol b/packages/protocol/contracts/thirdparty/LibBytesUtils.sol index 66608c3f8d..12081842ab 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 c2cfbe718e..eb0c9b0d11 100644 --- a/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol +++ b/packages/protocol/contracts/thirdparty/LibMerkleTrie.sol @@ -293,13 +293,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 68bdbf2fe9..965034d597 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/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/contracts/L2/TaikoL2Signer.sol b/packages/protocol/test/L2/LibL2Signer.sol similarity index 98% rename from packages/protocol/contracts/L2/TaikoL2Signer.sol rename to packages/protocol/test/L2/LibL2Signer.sol index 21a323673d..9fce6048b8 100644 --- a/packages/protocol/contracts/L2/TaikoL2Signer.sol +++ b/packages/protocol/test/L2/LibL2Signer.sol @@ -16,10 +16,10 @@ pragma solidity 0.8.24; 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. -abstract 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 3f91015452..08445f64c8 100644 --- a/packages/protocol/test/L2/TaikoL2.t.sol +++ b/packages/protocol/test/L2/TaikoL2.t.sol @@ -227,19 +227,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) = LibL2Signer.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) = LibL2Signer.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)); + LibL2Signer.signAnchor(digest, uint8(0)); vm.expectRevert(); - L2.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 919718f1a8..df5a3cb81f 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/LibL2Signer.sol"; abstract contract TaikoTest is Test, DeployCapability { uint256 private _seed = 0x12345678; diff --git a/packages/protocol/contracts/thirdparty/LibUint512Math.sol b/packages/protocol/test/thirdparty/LibUint512Math.sol similarity index 95% rename from packages/protocol/contracts/thirdparty/LibUint512Math.sol rename to packages/protocol/test/thirdparty/LibUint512Math.sol index b7f5178d99..a5077d8006 100644 --- a/packages/protocol/contracts/thirdparty/LibUint512Math.sol +++ b/packages/protocol/test/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)