diff --git a/packages/protocol/contracts/L1/TaikoData.sol b/packages/protocol/contracts/L1/TaikoData.sol index 48df4caf873..cca1a568a98 100644 --- a/packages/protocol/contracts/L1/TaikoData.sol +++ b/packages/protocol/contracts/L1/TaikoData.sol @@ -106,7 +106,7 @@ library TaikoData { uint96 contestBond; uint64 timestamp; // slot 6 (90 bits) uint16 tier; - uint8 contestations; + uint8 __reserved1; } /// @dev Struct containing data required for verifying a block. diff --git a/packages/protocol/contracts/L1/TaikoErrors.sol b/packages/protocol/contracts/L1/TaikoErrors.sol index f4825dc5156..12146c56519 100644 --- a/packages/protocol/contracts/L1/TaikoErrors.sol +++ b/packages/protocol/contracts/L1/TaikoErrors.sol @@ -11,10 +11,10 @@ pragma solidity 0.8.24; abstract contract TaikoErrors { error L1_ALREADY_CONTESTED(); error L1_ALREADY_PROVED(); - error L1_ASSIGNED_PROVER_NOT_ALLOWED(); error L1_BLOB_NOT_AVAILABLE(); error L1_BLOB_NOT_FOUND(); error L1_BLOCK_MISMATCH(); + error L1_CANNOT_CONTEST(); error L1_INVALID_BLOCK_ID(); error L1_INVALID_CONFIG(); error L1_INVALID_GENESIS_HASH(); diff --git a/packages/protocol/contracts/L1/TaikoEvents.sol b/packages/protocol/contracts/L1/TaikoEvents.sol index 2a196bad5b9..60b877443e8 100644 --- a/packages/protocol/contracts/L1/TaikoEvents.sol +++ b/packages/protocol/contracts/L1/TaikoEvents.sol @@ -27,21 +27,17 @@ abstract contract TaikoEvents { ); /// @dev Emitted when a block is verified. /// @param blockId The ID of the verified block. - /// @param assignedProver The block's assigned prover. /// @param prover The prover whose transition is used for verifying the /// block. /// @param blockHash The hash of the verified block. /// @param stateRoot The block's state root. /// @param tier The tier ID of the proof. - /// @param contestations Number of total contestations. event BlockVerified( uint256 indexed blockId, - address indexed assignedProver, address indexed prover, bytes32 blockHash, bytes32 stateRoot, - uint16 tier, - uint8 contestations + uint16 tier ); /// @notice Emitted when some state variable values changed. diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 6f76c668e00..32817ffb460 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -138,18 +138,8 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors { /// @notice Gets the details of a block. /// @param _blockId Index of the block. /// @return blk_ The block. - /// @return ts_ The transition used to verify this block. - function getBlock(uint64 _blockId) - public - view - returns (TaikoData.Block memory blk_, TaikoData.TransitionState memory ts_) - { - uint64 slot; - (blk_, slot) = LibUtils.getBlock(state, getConfig(), _blockId); - - if (blk_.verifiedTransitionId != 0) { - ts_ = state.transitions[slot][blk_.verifiedTransitionId]; - } + function getBlock(uint64 _blockId) public view returns (TaikoData.Block memory blk_) { + (blk_,) = LibUtils.getBlock(state, getConfig(), _blockId); } /// @notice Gets the state transition for a specific block. @@ -167,10 +157,25 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors { return LibUtils.getTransition(state, getConfig(), _blockId, _parentHash); } + /// @notice Gets the state transition for a specific block. + /// @param _blockId Index of the block. + /// @param _tid The transition id. + /// @return The state transition data of the block. + function getTransition( + uint64 _blockId, + uint32 _tid + ) + public + view + returns (TaikoData.TransitionState memory) + { + return LibUtils.getTransition(state, getConfig(), _blockId, _tid); + } /// @notice Gets the state variables of the TaikoL1 contract. /// @dev This method can be deleted once node/client stops using it. /// @return State variables stored at SlotA. /// @return State variables stored at SlotB. + function getStateVariables() public view diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index f500a01b567..e29d24f7358 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -16,6 +16,22 @@ library LibProving { using LibMath for uint256; using SafeERC20 for IERC20; + // A struct to get around stack too deep issue and to cache state variables for multiple reads. + struct Local { + TaikoData.SlotB b; + ITierProvider.Tier tier; + bytes32 metaHash; + address assignedProver; + uint96 livenessBond; + uint64 slot; + uint64 blockId; + uint32 tid; + bool lastUnpausedAt; + bool isTopTier; + bool inProvingWindow; + bool sameTransition; + } + /// @notice Keccak hash of the string "RETURN_LIVENESS_BOND". bytes32 public constant RETURN_LIVENESS_BOND = keccak256("RETURN_LIVENESS_BOND"); @@ -60,7 +76,6 @@ library LibProving { // 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(); @@ -109,28 +124,34 @@ library LibProving { revert L1_INVALID_TRANSITION(); } + Local memory local; + local.b = _state.slotB; + // Check that the block has been proposed but has not yet been verified. - TaikoData.SlotB memory b = _state.slotB; - if (_meta.id <= b.lastVerifiedBlockId || _meta.id >= b.numBlocks) { + if (_meta.id <= local.b.lastVerifiedBlockId || _meta.id >= local.b.numBlocks) { revert L1_INVALID_BLOCK_ID(); } - uint64 slot = _meta.id % _config.blockRingBufferSize; - TaikoData.Block storage blk = _state.blocks[slot]; + local.slot = _meta.id % _config.blockRingBufferSize; + TaikoData.Block storage blk = _state.blocks[local.slot]; + + local.blockId = blk.blockId; + local.assignedProver = blk.assignedProver; + local.metaHash = blk.metaHash; // Check the integrity of the block data. It's worth noting that in // theory, this check may be skipped, but it's included for added // caution. - if (blk.blockId != _meta.id || blk.metaHash != keccak256(abi.encode(_meta))) { + if (local.blockId != _meta.id || local.metaHash != keccak256(abi.encode(_meta))) { revert L1_BLOCK_MISMATCH(); } - // Each transition is uniquely identified by the parentHash, with the blockHash and - // stateRoot 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) is 0, - // but after receiving a unique transition it is set to 1. - (uint32 tid, TaikoData.TransitionState storage ts) = - _fetchOrCreateTransition(_state, blk, _tran, slot); + // Each transition is uniquely identified by the parentHash, with the + // blockHash and stateRoot 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. + TaikoData.TransitionState storage ts; + (local.tid, ts) = _fetchOrCreateTransition(_state, blk, _tran, local); // 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. @@ -140,12 +161,20 @@ library LibProving { // 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); + local.tier = ITierProvider(_resolver.resolve("tier_provider", false)).getTier(_proof.tier); - // Check if this prover is allowed to submit a proof for this block - _checkProverPermission(blk, ts, tid, tier, b.lastUnpausedAt); + local.inProvingWindow = + !LibUtils.isPostDeadline(ts.timestamp, local.b.lastUnpausedAt, local.tier.provingWindow); + // Checks if only the assigned prover is permissioned to prove the block. + // The guardian prover is granted exclusive permission to prove only the first + // transition. + if ( + local.tier.contestBond != 0 && ts.contester == address(0) && local.tid == 1 + && ts.tier == 0 && local.inProvingWindow + ) { + if (msg.sender != local.assignedProver) revert L1_NOT_ASSIGNED_PROVER(); + } // We must verify the proof, and any failure in proof verification will // result in a revert. // @@ -161,24 +190,24 @@ library LibProving { // It's obvious that proof verification is entirely decoupled from // Taiko's core protocol. { - address verifier = _resolver.resolve(tier.verifierName, true); + address verifier = _resolver.resolve(local.tier.verifierName, true); if (verifier != address(0)) { - bool isContesting = _proof.tier == ts.tier && tier.contestBond != 0; + bool isContesting = _proof.tier == ts.tier && local.tier.contestBond != 0; IVerifier.Context memory ctx = IVerifier.Context({ - metaHash: blk.metaHash, + metaHash: local.metaHash, blobHash: _meta.blobHash, // Separate msgSender to allow the prover to be any address in the future. prover: msg.sender, msgSender: msg.sender, - blockId: blk.blockId, + blockId: local.blockId, isContesting: isContesting, blobUsed: _meta.blobUsed }); IVerifier(verifier).verifyProof(ctx, _tran, _proof); - } else if (tier.verifierName != TIER_OP) { + } else if (local.tier.verifierName != TIER_OP) { // The verifier can be address-zero, signifying that there are no // proof checks for the tier. In practice, this only applies to // optimistic proofs. @@ -186,44 +215,46 @@ library LibProving { } } - bool isTopTier = tier.contestBond == 0; + local.isTopTier = local.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.safeTransfer(blk.assignedProver, blk.livenessBond); + local.livenessBond = blk.livenessBond; + if (local.isTopTier) { + if (local.livenessBond != 0) { + if ( + local.inProvingWindow + || (_proof.data.length == 32 && bytes32(_proof.data) == RETURN_LIVENESS_BOND) + ) { + tko.safeTransfer(local.assignedProver, local.livenessBond); + } blk.livenessBond = 0; + local.livenessBond = 0; } } - bool sameTransition = _tran.blockHash == ts.blockHash && _tran.stateRoot == ts.stateRoot; + local.sameTransition = _tran.blockHash == ts.blockHash && _tran.stateRoot == ts.stateRoot; 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); + _overrideWithHigherProof(blk, ts, _tran, _proof, local, tko); emit TransitionProved({ - blockId: blk.blockId, + blockId: local.blockId, tran: _tran, prover: msg.sender, - validityBond: tier.validityBond, + validityBond: local.tier.validityBond, tier: _proof.tier }); } else { // New transition and old transition on the same tier - and if this transaction tries to // prove the same, it reverts - if (sameTransition) revert L1_ALREADY_PROVED(); + if (local.sameTransition) revert L1_ALREADY_PROVED(); - if (isTopTier) { + if (local.isTopTier) { // The top tier prover re-proves. - assert(tier.validityBond == 0); + assert(local.tier.validityBond == 0); assert(ts.validityBond == 0 && ts.contester == address(0)); ts.prover = msg.sender; @@ -231,7 +262,7 @@ library LibProving { ts.stateRoot = _tran.stateRoot; emit TransitionProved({ - blockId: blk.blockId, + blockId: local.blockId, tran: _tran, prover: msg.sender, validityBond: 0, @@ -243,13 +274,17 @@ library LibProving { // Making it a non-sliding window, relative when ts.timestamp was registered (or to // lastUnpaused if that one is bigger) - if (LibUtils.isPostDeadline(ts.timestamp, b.lastUnpausedAt, tier.cooldownWindow)) { + if ( + LibUtils.isPostDeadline( + ts.timestamp, local.b.lastUnpausedAt, local.tier.cooldownWindow + ) + ) { revert L1_CANNOT_CONTEST(); } // _checkIfContestable(/*_state,*/ tier.cooldownWindow, ts.timestamp); // Burn the contest bond from the prover. - tko.safeTransferFrom(msg.sender, address(this), tier.contestBond); + tko.safeTransferFrom(msg.sender, address(this), local.tier.contestBond); // We retain the contest bond within the transition, just in // case this configuration is altered to a different value @@ -257,22 +292,21 @@ library LibProving { // // It's worth noting that the previous value of ts.contestBond // doesn't have any significance. - ts.contestBond = tier.contestBond; + ts.contestBond = local.tier.contestBond; ts.contester = msg.sender; - ts.contestations += 1; emit TransitionContested({ - blockId: blk.blockId, + blockId: local.blockId, tran: _tran, contester: msg.sender, - contestBond: tier.contestBond, + contestBond: local.tier.contestBond, tier: _proof.tier }); } } ts.timestamp = uint64(block.timestamp); - return tier.maxBlocksToVerifyPerProof; + return local.tier.maxBlocksToVerifyPerProof; } /// @dev Handle the transition initialization logic @@ -280,12 +314,12 @@ library LibProving { TaikoData.State storage _state, TaikoData.Block storage _blk, TaikoData.Transition memory _tran, - uint64 slot + Local memory _local ) private returns (uint32 tid_, TaikoData.TransitionState storage ts_) { - tid_ = LibUtils.getTransitionId(_state, _blk, slot, _tran.parentHash); + tid_ = LibUtils.getTransitionId(_state, _blk, _local.slot, _tran.parentHash); if (tid_ == 0) { // In cases where a transition with the provided parentHash is not @@ -306,7 +340,7 @@ library LibProving { // Keep in mind that state.transitions are also reusable storage // slots, so it's necessary to reinitialize all transition fields // below. - ts_ = _state.transitions[slot][tid_]; + ts_ = _state.transitions[_local.slot][tid_]; ts_.blockHash = 0; ts_.stateRoot = 0; ts_.validityBond = 0; @@ -314,7 +348,7 @@ library LibProving { ts_.contestBond = 1; // to save gas ts_.timestamp = _blk.proposedAt; ts_.tier = 0; - ts_.contestations = 0; + ts_.__reserved1 = 0; if (tid_ == 1) { // This approach serves as a cost-saving technique for the @@ -334,7 +368,7 @@ library LibProving { // // While alternative implementations are possible, introducing // such changes would require additional if-else logic. - ts_.prover = _blk.assignedProver; + ts_.prover = _local.assignedProver; } else { // In scenarios where this transition is not the first one, we // straightforwardly reset the transition prover to address @@ -346,13 +380,13 @@ library LibProving { // 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_; + _state.transitionIds[_local.blockId][_tran.parentHash] = tid_; // There is no need to initialize ts.key here because it's only used when tid == 1 } } else { // A transition with the provided parentHash has been located. - ts_ = _state.transitions[slot][tid_]; + ts_ = _state.transitions[_local.slot][tid_]; } } @@ -368,12 +402,12 @@ library LibProving { // validity bond `V` ratio is `C/V = 21/(32*r)`, and if `r` set at 10%, the C/V ratio will be // 6.5625. function _overrideWithHigherProof( + TaikoData.Block storage _blk, TaikoData.TransitionState storage _ts, TaikoData.Transition memory _tran, TaikoData.TierProof memory _proof, - ITierProvider.Tier memory _tier, - IERC20 _tko, - bool _sameTransition + Local memory _local, + IERC20 _tko ) private { @@ -381,7 +415,7 @@ library LibProving { uint256 reward; // reward to the new (current) prover if (_ts.contester != address(0)) { - if (_sameTransition) { + if (_local.sameTransition) { // The contested transition is proven to be valid, contester loses the game reward = _rewardAfterFriction(_ts.contestBond); @@ -395,64 +429,46 @@ library LibProving { _tko.safeTransfer(_ts.contester, _ts.contestBond + reward * 3); } } else { - if (_sameTransition) revert L1_ALREADY_PROVED(); - // Contest the existing transition and prove it to be invalid. The new prover get all - // rewards. + if (_local.sameTransition) revert L1_ALREADY_PROVED(); + + // The code below will be executed if + // - 1) the transition is proved for the fist time, or + // - 2) the transition is contested. reward = _rewardAfterFriction(_ts.validityBond); - _ts.contestations += 1; + + if (_local.livenessBond != 0) { + if (_local.assignedProver == msg.sender && _local.inProvingWindow) { + unchecked { + reward += _local.livenessBond; + } + } + _blk.livenessBond = 0; + _local.livenessBond = 0; + } } unchecked { - if (reward > _tier.validityBond) { - _tko.safeTransfer(msg.sender, reward - _tier.validityBond); - } else { - _tko.safeTransferFrom(msg.sender, address(this), _tier.validityBond - reward); + if (reward > _local.tier.validityBond) { + _tko.safeTransfer(msg.sender, reward - _local.tier.validityBond); + } else if (reward < _local.tier.validityBond) { + _tko.safeTransferFrom(msg.sender, address(this), _local.tier.validityBond - reward); } } - _ts.validityBond = _tier.validityBond; + _ts.validityBond = _local.tier.validityBond; _ts.contestBond = 1; // to save gas _ts.contester = address(0); _ts.prover = msg.sender; _ts.tier = _proof.tier; - if (!_sameTransition) { + if (!_local.sameTransition) { _ts.blockHash = _tran.blockHash; _ts.stateRoot = _tran.stateRoot; } } - /// @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, - uint64 _lastUnpausedAt - ) - private - view - { - // The highest tier proof can always submit new proofs - if (_tier.contestBond == 0) return; - - bool isAssignedProver = msg.sender == _blk.assignedProver; - - // The assigned prover can only submit the very first transition. - if ( - _tid == 1 && _ts.tier == 0 - && !LibUtils.isPostDeadline(_ts.timestamp, _lastUnpausedAt, _tier.provingWindow) - ) { - if (!isAssignedProver) revert L1_NOT_ASSIGNED_PROVER(); - } else { - // Disallow the same address to prove the block so that we can detect that the - // assigned prover should not receive his liveness bond back - if (isAssignedProver) revert L1_ASSIGNED_PROVER_NOT_ALLOWED(); - } - } - /// @dev Returns the reward after applying 12.5% friction. function _rewardAfterFriction(uint256 _amount) private pure returns (uint256) { - return (_amount * 7) >> 3; + return _amount == 0 ? 0 : (_amount * 7) >> 3; } } diff --git a/packages/protocol/contracts/L1/libs/LibUtils.sol b/packages/protocol/contracts/L1/libs/LibUtils.sol index 65882939a60..bc59d2f70ff 100644 --- a/packages/protocol/contracts/L1/libs/LibUtils.sol +++ b/packages/protocol/contracts/L1/libs/LibUtils.sol @@ -33,14 +33,8 @@ library LibUtils { view returns (TaikoData.TransitionState storage) { - TaikoData.SlotB memory b = _state.slotB; - if (_blockId < b.lastVerifiedBlockId || _blockId >= b.numBlocks) { - revert L1_INVALID_BLOCK_ID(); - } - - uint64 slot = _blockId % _config.blockRingBufferSize; - TaikoData.Block storage blk = _state.blocks[slot]; - if (blk.blockId != _blockId) revert L1_BLOCK_MISMATCH(); + _checkBlockId(_state, _blockId); + (TaikoData.Block storage blk, uint64 slot) = getBlock(_state, _config, _blockId); uint32 tid = getTransitionId(_state, blk, slot, _parentHash); if (tid == 0) revert L1_TRANSITION_NOT_FOUND(); @@ -48,6 +42,30 @@ library LibUtils { return _state.transitions[slot][tid]; } + /// @notice This function will revert if the transition is not found. + /// @dev Retrieves the transition with a given parentHash. + /// @param _state Current TaikoData.State. + /// @param _config Actual TaikoData.Config. + /// @param _blockId Id of the block. + /// @param _tid The transition id. + /// @return The state transition data of the block. + function getTransition( + TaikoData.State storage _state, + TaikoData.Config memory _config, + uint64 _blockId, + uint32 _tid + ) + internal + view + returns (TaikoData.TransitionState storage) + { + _checkBlockId(_state, _blockId); + (TaikoData.Block storage blk, uint64 slot) = getBlock(_state, _config, _blockId); + + if (_tid == 0 || _tid >= blk.nextTransitionId) revert L1_TRANSITION_NOT_FOUND(); + return _state.transitions[slot][_tid]; + } + /// @dev Retrieves a block based on its ID. /// @param _state Current TaikoData.State. /// @param _config Actual TaikoData.Config. @@ -103,4 +121,11 @@ library LibUtils { return block.timestamp >= deadline; } } + + function _checkBlockId(TaikoData.State storage _state, uint64 _blockId) private view { + TaikoData.SlotB memory b = _state.slotB; + if (_blockId < b.lastVerifiedBlockId || _blockId >= b.numBlocks) { + revert L1_INVALID_BLOCK_ID(); + } + } } diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index 6df70f232c0..52f654a4644 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -19,20 +19,16 @@ library LibVerifying { // Warning: Any events defined here must also be defined in TaikoEvents.sol. /// @notice Emitted when a block is verified. /// @param blockId The block ID. - /// @param assignedProver The assigned prover of the block. /// @param prover The actual prover of the block. /// @param blockHash The block hash. /// @param stateRoot The state root. /// @param tier The tier of the transition used for verification. - /// @param contestations The number of contestations. event BlockVerified( uint256 indexed blockId, - address indexed assignedProver, address indexed prover, bytes32 blockHash, bytes32 stateRoot, - uint16 tier, - uint8 contestations + uint16 tier ); /// @notice Emitted when some state variable values changed. @@ -80,12 +76,10 @@ library LibVerifying { emit BlockVerified({ blockId: 0, - assignedProver: address(0), prover: address(0), blockHash: _genesisBlockHash, stateRoot: 0, - tier: 0, - contestations: 0 + tier: 0 }); } @@ -177,32 +171,8 @@ library LibVerifying { blockHash = ts.blockHash; stateRoot = ts.stateRoot; - // We consistently return the liveness bond and the validity - // bond to the actual prover of the transition utilized for - // block verification. If the actual prover happens to be the - // block's assigned prover, he will receive both deposits, - // ultimately earning the proving fee paid during block - // proposal. In contrast, if the actual prover is different from - // the block's assigned prover, the liveness bond serves as a - // reward to the actual prover, while the assigned prover - // forfeits his liveness bond due to failure to fulfill their - // commitment. - uint256 bondToReturn = ts.validityBond; - - // Nevertheless, it's possible for the actual prover to be the - // same individual or entity as the block's assigned prover. - // Consequently, we have chosen to grant the actual prover only - // half of the liveness bond as a reward. - if (blk.livenessBond != 0) { - // livenessBond could have been returned in proving by guardian - bondToReturn += ts.prover != blk.assignedProver - ? blk.livenessBond >> 1 // half is burnt - more precisely, held in custody - // in TaikoL1 contract - : blk.livenessBond; - } - IERC20 tko = IERC20(_resolver.resolve("taiko_token", false)); - tko.safeTransfer(ts.prover, bondToReturn); + tko.safeTransfer(ts.prover, ts.validityBond); // Note: We exclusively address the bonds linked to the // transition used for verification. While there may exist @@ -213,12 +183,10 @@ library LibVerifying { emit BlockVerified({ blockId: blockId, - assignedProver: blk.assignedProver, prover: ts.prover, blockHash: blockHash, stateRoot: stateRoot, - tier: ts.tier, - contestations: ts.contestations + tier: ts.tier }); ++blockId; diff --git a/packages/protocol/test/L1/TaikoL1.t.sol b/packages/protocol/test/L1/TaikoL1.t.sol index b46b73b1a9b..1c4c2927da2 100644 --- a/packages/protocol/test/L1/TaikoL1.t.sol +++ b/packages/protocol/test/L1/TaikoL1.t.sol @@ -57,13 +57,13 @@ contract TaikoL1Test is TaikoL1TestBase { bytes32 blockHash = bytes32(1e10 + blockId); bytes32 stateRoot = bytes32(1e9 + blockId); - proveBlock(Bob, Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); vm.roll(block.number + 15 * 12); uint16 minTier = meta.minTier; vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } printVariables(""); @@ -90,17 +90,17 @@ contract TaikoL1Test is TaikoL1TestBase { bytes32 blockHash = bytes32(1e10 + blockId); bytes32 stateRoot = bytes32(1e9 + blockId); - proveBlock(Bob, Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); vm.roll(block.number + 15 * 12); uint16 minTier = meta.minTier; vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - verifyBlock(Alice, 2); + verifyBlock(2); - (TaikoData.Block memory blk, TaikoData.TransitionState memory ts) = L1.getBlock(meta.id); + TaikoData.Block memory blk = L1.getBlock(meta.id); assertEq(meta.id, blk.blockId); - ts = L1.getTransition(meta.id, parentHash); + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, parentHash); assertEq(ts.prover, Bob); parentHash = blockHash; @@ -128,14 +128,14 @@ contract TaikoL1Test is TaikoL1TestBase { bytes32 blockHash = bytes32(1e10 + blockId); bytes32 stateRoot = bytes32(1e9 + blockId); - proveBlock(Bob, Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); parentHash = blockHash; } vm.roll(block.number + 15 * 12); - verifyBlock(Alice, conf.blockMaxProposals - 1); + verifyBlock(conf.blockMaxProposals - 1); printVariables("after verify"); - verifyBlock(Alice, conf.blockMaxProposals); + verifyBlock(conf.blockMaxProposals); printVariables("after verify"); } @@ -151,7 +151,6 @@ contract TaikoL1Test is TaikoL1TestBase { (meta,) = proposeBlock(Alice, Bob, 1_000_000, 1024); // Proving is not, so supply the revert reason to proveBlock proveBlock( - Bob, Bob, meta, GENESIS_BLOCK_HASH, diff --git a/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol b/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol index 9d01fe70c55..9b01703567b 100644 --- a/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol +++ b/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol @@ -46,7 +46,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { } else if (minTier == LibTiers.TIER_SGX) { tierToProveWith = LibTiers.TIER_GUARDIAN; } - proveBlock(Carol, Carol, meta, parentHash, blockHash, stateRoot, tierToProveWith, ""); + proveBlock(Carol, meta, parentHash, blockHash, stateRoot, tierToProveWith, ""); } function test_L1_ContestingWithSameProof() external { @@ -73,11 +73,10 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 stateRoot = bytes32(1e9 + blockId); // This proof cannot be verified obviously because of // blockhash:blockId - proveBlock(Bob, Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); // Try to contest - but should revert with L1_ALREADY_PROVED proveBlock( - Carol, Carol, meta, parentHash, @@ -92,7 +91,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { uint16 minTier = meta.minTier; vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } @@ -125,17 +124,17 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { // stateRoot instead of blockHash uint16 minTier = meta.minTier; - proveBlock(Bob, Bob, meta, parentHash, stateRoot, stateRoot, minTier, ""); + proveBlock(Bob, meta, parentHash, stateRoot, stateRoot, minTier, ""); // Try to contest - proveBlock(Carol, Carol, meta, parentHash, blockHash, stateRoot, minTier, ""); + proveBlock(Carol, meta, parentHash, blockHash, stateRoot, minTier, ""); vm.roll(block.number + 15 * 12); vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); // Cannot verify block because it is contested.. - verifyBlock(Carol, 1); + verifyBlock(1); proveHigherTierProof(meta, parentHash, stateRoot, blockHash, minTier); @@ -145,7 +144,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { ); // Now can verify console2.log("Probalom verify-olni"); - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } @@ -177,17 +176,17 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { // This proof cannot be verified obviously because of // stateRoot instead of blockHash uint16 minTier = meta.minTier; - proveBlock(Bob, Bob, meta, parentHash, stateRoot, stateRoot, minTier, ""); + proveBlock(Bob, meta, parentHash, stateRoot, stateRoot, minTier, ""); // Try to contest - proveBlock(Carol, Carol, meta, parentHash, blockHash, stateRoot, minTier, ""); + proveBlock(Carol, meta, parentHash, blockHash, stateRoot, minTier, ""); vm.roll(block.number + 15 * 12); vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); // Cannot verify block because it is contested.. - verifyBlock(Carol, 1); + verifyBlock(1); proveHigherTierProof(meta, parentHash, stateRoot, blockHash, minTier); @@ -197,7 +196,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { + 1 ); // Now can verify - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } @@ -230,21 +229,21 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { // stateRoot instead of blockHash uint16 minTier = meta.minTier; - proveBlock(Bob, Bob, meta, parentHash, blockHash, stateRoot, minTier, ""); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, minTier, ""); if (minTier == LibTiers.TIER_OPTIMISTIC) { // Try to contest - proveBlock(Carol, Carol, meta, parentHash, stateRoot, stateRoot, minTier, ""); + proveBlock(Carol, meta, parentHash, stateRoot, stateRoot, minTier, ""); vm.roll(block.number + 15 * 12); vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); // Cannot verify block because it is contested.. - verifyBlock(Carol, 1); + verifyBlock(1); proveBlock( - Carol, Carol, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, "" + Carol, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, "" ); } @@ -254,7 +253,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { + 1 ); // Now can verify - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } @@ -286,11 +285,11 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { // This proof cannot be verified obviously because of // stateRoot instead of blockHash uint16 minTier = meta.minTier; - proveBlock(Bob, Bob, meta, parentHash, stateRoot, stateRoot, minTier, ""); + proveBlock(Bob, meta, parentHash, stateRoot, stateRoot, minTier, ""); if (minTier == LibTiers.TIER_OPTIMISTIC) { // Try to contest - proveBlock(Carol, Carol, meta, parentHash, blockHash, stateRoot, minTier, ""); + proveBlock(Carol, meta, parentHash, blockHash, stateRoot, minTier, ""); vm.roll(block.number + 15 * 12); @@ -300,10 +299,9 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { ); // Cannot verify block because it is contested.. - verifyBlock(Carol, 1); + verifyBlock(1); proveBlock( - Carol, Carol, meta, parentHash, @@ -320,7 +318,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { + 1 ); // Now can verify - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } @@ -350,7 +348,6 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 blockHash = bytes32(1e10 + blockId); bytes32 stateRoot = bytes32(1e9 + blockId); proveBlock( - Carol, Carol, meta, parentHash, @@ -364,52 +361,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { uint16 minTier = meta.minTier; vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - verifyBlock(Carol, 1); - parentHash = blockHash; - } - printVariables(""); - } - - function test_L1_assignedProverCannotProveAfterHisWindowElapsed() external { - giveEthAndTko(Alice, 1e8 ether, 100 ether); - // This is a very weird test (code?) issue here. - // If this line (or Bob's query balance) is uncommented, - // Alice/Bob has no balance.. (Causing reverts !!!) - console2.log("Alice balance:", tko.balanceOf(Alice)); - giveEthAndTko(Bob, 1e8 ether, 100 ether); - console2.log("Bob balance:", tko.balanceOf(Bob)); - giveEthAndTko(Carol, 1e8 ether, 100 ether); - // Bob - vm.prank(Bob, Bob); - - bytes32 parentHash = GENESIS_BLOCK_HASH; - - for (uint256 blockId = 1; blockId < 10; blockId++) { - //printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, Bob, 1_000_000, 1024); - //printVariables("after propose"); - mine(1); - - bytes32 blockHash = bytes32(1e10 + blockId); - bytes32 stateRoot = bytes32(1e9 + blockId); - - vm.roll(block.number + 15 * 12); - - uint16 minTier = meta.minTier; - vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - - proveBlock( - Bob, - Bob, - meta, - parentHash, - blockHash, - stateRoot, - meta.minTier, - TaikoErrors.L1_ASSIGNED_PROVER_NOT_ALLOWED.selector - ); - - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } printVariables(""); @@ -442,25 +394,18 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { (, TaikoData.SlotB memory b) = L1.getStateVariables(); uint64 lastVerifiedBlockBefore = b.lastVerifiedBlockId; - proveBlock(Bob, Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); console2.log("mintTier is:", meta.minTier); // Try to contest proveBlock( - Carol, - Carol, - meta, - parentHash, - bytes32(uint256(1)), - bytes32(uint256(1)), - meta.minTier, - "" + Carol, meta, parentHash, bytes32(uint256(1)), bytes32(uint256(1)), meta.minTier, "" ); vm.roll(block.number + 15 * 12); uint16 minTier = meta.minTier; vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - verifyBlock(Carol, 1); + verifyBlock(1); (, b) = L1.getStateVariables(); uint64 lastVerifiedBlockAfter = b.lastVerifiedBlockId; @@ -470,9 +415,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { assertEq(lastVerifiedBlockAfter, lastVerifiedBlockBefore); // Guardian can prove with the original (good) hashes. - proveBlock( - Carol, Carol, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, "" - ); + proveBlock(Carol, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, ""); vm.roll(block.number + 15 * 12); vm.warp( @@ -480,7 +423,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { + 1 ); - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } printVariables(""); @@ -510,11 +453,10 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 stateRoot = bytes32(1e9 + blockId); // This proof cannot be verified obviously because of // blockhash:blockId - proveBlock(Bob, Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); // Try to contest - but should revert with L1_ALREADY_PROVED proveBlock( - Carol, Carol, meta, parentHash, @@ -529,7 +471,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { uint16 minTier = meta.minTier; vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } @@ -560,31 +502,22 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 stateRoot = bytes32(1e9 + blockId); // This proof cannot be verified obviously because of // blockhash:blockId - proveBlock(Bob, Bob, meta, parentHash, stateRoot, stateRoot, meta.minTier, ""); + proveBlock(Bob, meta, parentHash, stateRoot, stateRoot, meta.minTier, ""); // Prove as guardian proveBlock( - Carol, - Carol, - meta, - parentHash, - blockHash, - bytes32(uint256(1)), - LibTiers.TIER_GUARDIAN, - "" + Carol, meta, parentHash, blockHash, bytes32(uint256(1)), LibTiers.TIER_GUARDIAN, "" ); // Prove as guardian again - proveBlock( - Carol, Carol, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, "" - ); + proveBlock(Carol, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, ""); vm.roll(block.number + 15 * 12); uint16 minTier = meta.minTier; vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } @@ -617,11 +550,10 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 stateRoot = bytes32(1e9 + blockId); // This proof cannot be verified obviously because of // blockhash:blockId - proveBlock(Bob, Bob, meta, parentHash, stateRoot, stateRoot, meta.minTier, ""); + proveBlock(Bob, meta, parentHash, stateRoot, stateRoot, meta.minTier, ""); // Prove as guardian but in reality not a guardian proveBlock( - Carol, Carol, meta, parentHash, @@ -636,7 +568,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { uint16 minTier = meta.minTier; vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } @@ -670,7 +602,6 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { meta.id = 100; proveBlock( - Carol, Carol, meta, parentHash, @@ -713,7 +644,6 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { // Mess up metahash meta.l1Height = 200; proveBlock( - Bob, Bob, meta, parentHash, @@ -752,16 +682,13 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 stateRoot = bytes32(1e9 + blockId); // This proof cannot be verified obviously because of blockhash is // exchanged with stateRoot - proveBlock(Bob, Bob, meta, parentHash, stateRoot, stateRoot, meta.minTier, ""); + proveBlock(Bob, meta, parentHash, stateRoot, stateRoot, meta.minTier, ""); // Prove as guardian - proveBlock( - Carol, Carol, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, "" - ); + proveBlock(Carol, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, ""); // Try to re-prove but reverts proveBlock( - Bob, Bob, meta, parentHash, @@ -776,77 +703,7 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { uint16 minTier = meta.minTier; vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - verifyBlock(Carol, 1); - - parentHash = blockHash; - } - printVariables(""); - } - - function test_L1_GuardianCanReturnBondIfBlockUnprovable() external { - giveEthAndTko(Alice, 1e7 ether, 1000 ether); - giveEthAndTko(Carol, 1e7 ether, 1000 ether); - console2.log("Alice balance:", tko.balanceOf(Alice)); - // This is a very weird test (code?) issue here. - // If this line is uncommented, - // Alice/Bob has no balance.. (Causing reverts !!!) - // Current investigations are ongoing with foundry team - giveEthAndTko(Bob, 1e7 ether, 100 ether); - console2.log("Bob balance:", tko.balanceOf(Bob)); - // Bob - vm.prank(Bob, Bob); - - bytes32 parentHash = GENESIS_BLOCK_HASH; - for (uint256 blockId = 1; blockId < conf.blockMaxProposals * 3; blockId++) { - printVariables("before propose"); - (TaikoData.BlockMetadata memory meta,) = proposeBlock(Alice, Bob, 1_000_000, 1024); - //printVariables("after propose"); - mine(1); - - bytes32 blockHash = bytes32(1e10 + blockId); - bytes32 stateRoot = bytes32(1e9 + blockId); - // This proof cannot be verified obviously because of blockhash is - // exchanged with stateRoot - proveBlock(Bob, Bob, meta, parentHash, stateRoot, stateRoot, meta.minTier, ""); - - // Let's say the 10th block is unprovable so prove accordingly - if (blockId == 10) { - TaikoData.Transition memory tran = TaikoData.Transition({ - parentHash: parentHash, - blockHash: blockHash, - stateRoot: stateRoot, - graffiti: 0x0 - }); - - TaikoData.TierProof memory proof; - proof.tier = LibTiers.TIER_GUARDIAN; - proof.data = bytes.concat(keccak256("RETURN_LIVENESS_BOND")); - - uint256 balanceBeforeReimbursement = tko.balanceOf(Bob); - - vm.prank(David, David); - gp.approve(meta, tran, proof); - vm.prank(Emma, Emma); - gp.approve(meta, tran, proof); - vm.prank(Frank, Frank); - gp.approve(meta, tran, proof); - - // // Credited back the bond (not transferred to the user - // wallet, - // // but in-contract account credited only.) - assertEq(tko.balanceOf(Bob) - balanceBeforeReimbursement, 1 ether); - } else { - // Prove as guardian - proveBlock( - Carol, Carol, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, "" - ); - } - vm.roll(block.number + 15 * 12); - - uint16 minTier = meta.minTier; - vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); - - verifyBlock(Carol, 1); + verifyBlock(1); parentHash = blockHash; } @@ -874,11 +731,10 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { bytes32 blockHash = bytes32(uint256(1)); bytes32 stateRoot = bytes32(uint256(1)); - proveBlock(Bob, Bob, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, ""); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, ""); // Try to contest with a lower tier proof- but should revert with L1_INVALID_TIER proveBlock( - Carol, Carol, meta, parentHash, diff --git a/packages/protocol/test/L1/TaikoL1TestBase.sol b/packages/protocol/test/L1/TaikoL1TestBase.sol index 3ad05ace406..ab17da50aad 100644 --- a/packages/protocol/test/L1/TaikoL1TestBase.sol +++ b/packages/protocol/test/L1/TaikoL1TestBase.sol @@ -181,6 +181,7 @@ abstract contract TaikoL1TestBase is TaikoTest { _difficulty = block.prevrandao * b.numBlocks; } + // TODO: why init meta here? meta.timestamp = uint64(block.timestamp); meta.l1Height = uint64(block.number - 1); meta.l1Hash = blockhash(block.number - 1); @@ -199,7 +200,6 @@ abstract contract TaikoL1TestBase is TaikoTest { } function proveBlock( - address msgSender, address prover, TaikoData.BlockMetadata memory meta, bytes32 parentHash, @@ -209,6 +209,7 @@ abstract contract TaikoL1TestBase is TaikoTest { bytes4 revertReason ) internal + virtual { TaikoData.Transition memory tran = TaikoData.Transition({ parentHash: parentHash, @@ -258,17 +259,17 @@ abstract contract TaikoL1TestBase is TaikoTest { } } else { if (revertReason != "") { - vm.prank(msgSender, msgSender); + vm.prank(prover); vm.expectRevert(revertReason); L1.proveBlock(meta.id, abi.encode(meta, tran, proof)); } else { - vm.prank(msgSender, msgSender); + vm.prank(prover); L1.proveBlock(meta.id, abi.encode(meta, tran, proof)); } } } - function verifyBlock(address, uint64 count) internal { + function verifyBlock(uint64 count) internal { L1.verifyBlocks(count); } @@ -313,6 +314,8 @@ abstract contract TaikoL1TestBase is TaikoTest { signerPrivateKey = 0x2; } else if (prover == Carol) { signerPrivateKey = 0x3; + } else { + revert("unexpected"); } bytes32 assignmentHash = @@ -362,15 +365,14 @@ abstract contract TaikoL1TestBase is TaikoTest { console2.log("ETH balance:", to, to.balance); } - function printVariables(string memory comment) internal { + function printVariables(string memory comment) internal view { (, TaikoData.SlotB memory b) = L1.getStateVariables(); string memory str = string.concat( - Strings.toString(logCount++), - ":[", - Strings.toString(b.lastVerifiedBlockId), + "---chain [", + vm.toString(b.lastVerifiedBlockId), unicode"→", - Strings.toString(b.numBlocks), + vm.toString(b.numBlocks), "] // ", comment ); diff --git a/packages/protocol/test/L1/TaikoL1TestGroup1.t.sol b/packages/protocol/test/L1/TaikoL1TestGroup1.t.sol new file mode 100644 index 00000000000..d5adbcb343a --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1TestGroup1.t.sol @@ -0,0 +1,474 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestGroupBase.sol"; + +contract TaikoL1TestGroup1 is TaikoL1TestGroupBase { + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. Bob proves the block within the proving window, using the correct parent hash. + // 3. Bob's proof is used to verify the block. + function test_taikoL1_group_1_case_1() external { + vm.warp(1_000_000); + printBlockAndTrans(0); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + uint96 livenessBond = L1.getConfig().livenessBond; + uint256 proposedAt; + { + printBlockAndTrans(meta.id); + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(meta.minTier, LibTiers.TIER_OPTIMISTIC); + + assertEq(blk.nextTransitionId, 1); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.proposedAt, block.timestamp); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, livenessBond); + + proposedAt = blk.proposedAt; + + assertEq(tko.balanceOf(Alice), 10_000 ether); + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + } + + // Prove the block + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + console2.log("====== Taylor cannot prove the block in the proving window"); + mineAndWrap(10 seconds); + proveBlock( + Taylor, + meta, + parentHash, + blockHash, + stateRoot, + meta.minTier, + TaikoErrors.L1_NOT_ASSIGNED_PROVER.selector + ); + + console2.log("====== Bob proves the block"); + mineAndWrap(10 seconds); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + uint256 provenAt; + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.proposedAt, proposedAt); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_OPTIMISTIC); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.prover, Bob); + assertEq(ts.validityBond, tierOp.validityBond); + assertEq(ts.timestamp, block.timestamp); + + provenAt = ts.timestamp; + + assertEq(tko.balanceOf(Bob), 10_000 ether - tierOp.validityBond); + } + + console2.log("====== Verify block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.proposedAt, proposedAt); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_OPTIMISTIC); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.prover, Bob); + assertEq(ts.validityBond, tierOp.validityBond); + assertEq(ts.timestamp, provenAt); + + assertEq(tko.balanceOf(Bob), 10_000 ether); + } + } + + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. Taylor proposes the block outside the proving window. + // 3. Taylor's proof is used to verify the block. + function test_taikoL1_group_1_case_2() external { + vm.warp(1_000_000); + printBlockAndTrans(0); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + uint96 livenessBond = L1.getConfig().livenessBond; + uint256 proposedAt; + { + printBlockAndTrans(meta.id); + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(meta.minTier, LibTiers.TIER_OPTIMISTIC); + + assertEq(blk.nextTransitionId, 1); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.proposedAt, block.timestamp); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, livenessBond); + + proposedAt = blk.proposedAt; + + assertEq(tko.balanceOf(Alice), 10_000 ether); + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + } + + // Prove the block + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + console2.log("====== Taylor proves the block"); + mineAndWrap(7 days); + proveBlock(Taylor, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + uint256 provenAt; + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.proposedAt, proposedAt); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_OPTIMISTIC); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.prover, Taylor); + assertEq(ts.validityBond, tierOp.validityBond); + assertEq(ts.timestamp, block.timestamp); + + provenAt = ts.timestamp; + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + assertEq(tko.balanceOf(Taylor), 10_000 ether - tierOp.validityBond); + } + + console2.log("====== Verify block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.proposedAt, proposedAt); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_OPTIMISTIC); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.prover, Taylor); + assertEq(ts.validityBond, tierOp.validityBond); + assertEq(ts.timestamp, provenAt); + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + assertEq(tko.balanceOf(Taylor), 10_000 ether); + } + } + + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. Bob proves the block within the proving window. + // 3. Taylor proves the block outside the proving window. + // 4. Taylor's proof is used to verify the block. + function test_taikoL1_group_1_case_3() external { + vm.warp(1_000_000); + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + // Prove the block + bytes32 parentHash1 = bytes32(uint256(9)); + bytes32 parentHash2 = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(10 seconds); + + console2.log("====== Bob proves the block first"); + proveBlock(Bob, meta, parentHash1, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Taylor proves the block later"); + mineAndWrap(10 seconds); + proveBlock(Taylor, meta, parentHash2, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Verify block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 3); + assertEq(blk.verifiedTransitionId, 2); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 2); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.prover, Taylor); + assertEq(ts.validityBond, tierOp.validityBond); + + assertEq(tko.balanceOf(Bob), 10_000 ether - tierOp.validityBond); + assertEq(tko.balanceOf(Taylor), 10_000 ether); + } + } + + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. Bob proves the block within the proving window. + // 3. Taylor proves the block outside the proving window. + // 4. Bob's proof is used to verify the block. + function test_taikoL1_group_1_case_4() external { + vm.warp(1_000_000); + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + // Prove the block + bytes32 parentHash1 = GENESIS_BLOCK_HASH; + bytes32 parentHash2 = bytes32(uint256(9)); + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(10 seconds); + + console2.log("====== Bob proves the block first"); + proveBlock(Bob, meta, parentHash1, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Taylor proves the block later"); + mineAndWrap(10 seconds); + proveBlock(Taylor, meta, parentHash2, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Verify block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 3); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.prover, Bob); + assertEq(ts.validityBond, tierOp.validityBond); + + assertEq(tko.balanceOf(Bob), 10_000 ether); + assertEq(tko.balanceOf(Taylor), 10_000 ether - tierOp.validityBond); + } + } + + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. William proves the block outside the proving window. + // 3. Taylor also proves the block outside the proving window. + // 4. Taylor's proof is used to verify the block. + function test_taikoL1_group_1_case_5() external { + vm.warp(1_000_000); + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + giveEthAndTko(William, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + // Prove the block + bytes32 parentHash1 = bytes32(uint256(9)); + bytes32 parentHash2 = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(7 days); + + console2.log("====== William proves the block first"); + proveBlock(William, meta, parentHash1, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Taylor proves the block later"); + mineAndWrap(10 seconds); + proveBlock(Taylor, meta, parentHash2, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Verify block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 3); + assertEq(blk.verifiedTransitionId, 2); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 2); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.prover, Taylor); + assertEq(ts.validityBond, tierOp.validityBond); + + assertEq(tko.balanceOf(Bob), 10_000 ether - L1.getConfig().livenessBond); + assertEq(tko.balanceOf(Taylor), 10_000 ether); + } + } + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. Bob proves the block outside the proving window, using the correct parent hash. + // 3. Bob's proof is used to verify the block. + + function test_taikoL1_group_1_case_6() external { + vm.warp(1_000_000); + printBlockAndTrans(0); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + uint96 livenessBond = L1.getConfig().livenessBond; + uint256 proposedAt; + { + printBlockAndTrans(meta.id); + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(meta.minTier, LibTiers.TIER_OPTIMISTIC); + + assertEq(blk.nextTransitionId, 1); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.proposedAt, block.timestamp); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, livenessBond); + + proposedAt = blk.proposedAt; + + assertEq(tko.balanceOf(Alice), 10_000 ether); + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + } + + // Prove the block + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + console2.log("====== Bob proves the block outside the proving window"); + mineAndWrap(7 days); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + uint256 provenAt; + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.proposedAt, proposedAt); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_OPTIMISTIC); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.prover, Bob); + assertEq(ts.validityBond, tierOp.validityBond); + assertEq(ts.timestamp, block.timestamp); + + provenAt = ts.timestamp; + + assertEq(tko.balanceOf(Bob), 10_000 ether - tierOp.validityBond - livenessBond); + } + + console2.log("====== Verify block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.proposedAt, proposedAt); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_OPTIMISTIC); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.prover, Bob); + assertEq(ts.validityBond, tierOp.validityBond); + assertEq(ts.timestamp, provenAt); + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + } + } +} diff --git a/packages/protocol/test/L1/TaikoL1TestGroup2.t.sol b/packages/protocol/test/L1/TaikoL1TestGroup2.t.sol new file mode 100644 index 00000000000..7265f4139d8 --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1TestGroup2.t.sol @@ -0,0 +1,232 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestGroupBase.sol"; + +contract TaikoL1TestGroup2 is TaikoL1TestGroupBase { + // Test summary: + // 1. Alice proposes a block, Bob as the prover. + // 2. Bob proves the block within the proving window, with correct parent hash. + // 3. Taylor contests Bob's proof. + // 4. William proves Bob is correct and Taylor is wrong. + // 5. William's proof is used to verify the block. + function test_taikoL1_group_2_case_1() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + giveEthAndTko(William, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + ITierProvider.Tier memory tierSgx = TierProviderV1(cp).getTier(LibTiers.TIER_SGX); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Bob proves the block as the assigned prover"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(10 seconds); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Taylor contests Bob"); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + mineAndWrap(10 seconds); + proveBlock(Taylor, meta, parentHash, blockHash2, stateRoot2, meta.minTier, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_OPTIMISTIC); + assertEq(ts.contester, Taylor); + assertEq(ts.contestBond, tierOp.contestBond); + assertEq(ts.validityBond, tierOp.validityBond); + assertEq(ts.prover, Bob); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - tierOp.validityBond); + assertEq(tko.balanceOf(Taylor), 10_000 ether - tierOp.contestBond); + } + + console2.log("====== William proves Bob is right"); + mineAndWrap(10 seconds); + proveBlock(William, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_SGX, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.validityBond, tierSgx.validityBond); + assertEq(ts.prover, William); + assertEq(ts.timestamp, block.timestamp); // not zero + + assertEq(tko.balanceOf(Bob), 10_000 ether); + assertEq(tko.balanceOf(Taylor), 10_000 ether - tierOp.contestBond); + assertEq( + tko.balanceOf(William), + 10_000 ether - tierSgx.validityBond + tierOp.contestBond * 7 / 8 + ); + } + + console2.log("====== Verify the block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + // assertEq(blk.livenessBond, livenessBond); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contestBond, 1); + assertEq(ts.prover, William); + + assertEq(tko.balanceOf(William), 10_000 ether + tierOp.contestBond * 7 / 8); + } + } + + // Test summary: + // 1. Alice proposes a block, Bob as the prover. + // 2. Bob proves the block within the proving window, with correct parent hash. + // 3. Taylor contests Bob's proof. + // 4. William proves Taylor is correct and Bob is wrong. + // 5. William's proof is used to verify the block. + function test_taikoL1_group_2_case_2() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + giveEthAndTko(William, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + ITierProvider.Tier memory tierSgx = TierProviderV1(cp).getTier(LibTiers.TIER_SGX); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Bob proves the block as the assigned prover"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(10 seconds); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Taylor contests Bob"); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + mineAndWrap(10 seconds); + proveBlock(Taylor, meta, parentHash, blockHash2, stateRoot2, meta.minTier, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_OPTIMISTIC); + assertEq(ts.contester, Taylor); + assertEq(ts.contestBond, tierOp.contestBond); + assertEq(ts.validityBond, tierOp.validityBond); + assertEq(ts.prover, Bob); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - tierOp.validityBond); + assertEq(tko.balanceOf(Taylor), 10_000 ether - tierOp.contestBond); + } + + console2.log("====== William proves Tayler is right"); + mineAndWrap(10 seconds); + proveBlock(William, meta, parentHash, blockHash2, stateRoot2, LibTiers.TIER_SGX, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.validityBond, tierSgx.validityBond); + assertEq(ts.prover, William); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - tierOp.validityBond); + + uint256 quarterReward = tierOp.validityBond * 7 / 8 / 4; + assertEq(tko.balanceOf(Taylor), 10_000 ether + quarterReward * 3); + assertEq(tko.balanceOf(William), 10_000 ether - tierSgx.validityBond + quarterReward); + } + + console2.log("====== Verify the block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.validityBond, tierSgx.validityBond); + assertEq(ts.prover, William); + + assertEq(tko.balanceOf(Bob), 10_000 ether - tierOp.validityBond); + + uint256 quarterReward = tierOp.validityBond * 7 / 8 / 4; + assertEq(tko.balanceOf(Taylor), 10_000 ether + quarterReward * 3); + assertEq(tko.balanceOf(William), 10_000 ether + quarterReward); + } + } +} diff --git a/packages/protocol/test/L1/TaikoL1TestGroup3.t.sol b/packages/protocol/test/L1/TaikoL1TestGroup3.t.sol new file mode 100644 index 00000000000..e5eda09feb1 --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1TestGroup3.t.sol @@ -0,0 +1,242 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestGroupBase.sol"; + +contract TaikoL1TestGroup3 is TaikoL1TestGroupBase { + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. James proves the block outside the proving window, using the correct parent hash. + // 3. Taylor contests James' proof. + // 4. William proves James is correct and Taylor is wrong. + // 5. William's proof is used to verify the block. + function test_taikoL1_group_3_case_1() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(James, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + giveEthAndTko(William, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + ITierProvider.Tier memory tierSgx = TierProviderV1(cp).getTier(LibTiers.TIER_SGX); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + uint96 livenessBond = L1.getConfig().livenessBond; + + console2.log("====== James proves the block"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(7 days); + proveBlock(James, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Taylor contests James"); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + mineAndWrap(10 seconds); + proveBlock(Taylor, meta, parentHash, blockHash2, stateRoot2, meta.minTier, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_OPTIMISTIC); + assertEq(ts.contester, Taylor); + assertEq(ts.contestBond, tierOp.contestBond); + assertEq(ts.validityBond, tierOp.validityBond); + assertEq(ts.prover, James); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + assertEq(tko.balanceOf(James), 10_000 ether - tierOp.validityBond); + assertEq(tko.balanceOf(Taylor), 10_000 ether - tierOp.contestBond); + } + + console2.log("====== William proves James is right"); + mineAndWrap(10 seconds); + proveBlock(William, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_SGX, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.validityBond, tierSgx.validityBond); + assertEq(ts.prover, William); + assertEq(ts.timestamp, block.timestamp); // not zero + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + assertEq(tko.balanceOf(Taylor), 10_000 ether - tierOp.contestBond); + assertEq( + tko.balanceOf(William), + 10_000 ether - tierSgx.validityBond + tierOp.contestBond * 7 / 8 + ); + } + + console2.log("====== Verify the block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + // assertEq(blk.livenessBond, livenessBond); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contestBond, 1); + assertEq(ts.prover, William); + + assertEq(tko.balanceOf(William), 10_000 ether + tierOp.contestBond * 7 / 8); + } + } + + // Test summary: + // 1. Alice proposes a block, Bob as the prover. + // 2. James proves the block outside the proving window, with correct parent hash. + // 3. Taylor contests James' proof. + // 4. William proves Taylor is correct and James is wrong. + // 5. William's proof is used to verify the block. + function test_taikoL1_group_3_case_2() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(James, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + giveEthAndTko(William, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + ITierProvider.Tier memory tierSgx = TierProviderV1(cp).getTier(LibTiers.TIER_SGX); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + uint96 livenessBond = L1.getConfig().livenessBond; + + console2.log("====== James proves the block"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(7 days); + proveBlock(James, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Taylor contests James"); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + mineAndWrap(10 seconds); + proveBlock(Taylor, meta, parentHash, blockHash2, stateRoot2, meta.minTier, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_OPTIMISTIC); + assertEq(ts.contester, Taylor); + assertEq(ts.contestBond, tierOp.contestBond); + assertEq(ts.validityBond, tierOp.validityBond); + assertEq(ts.prover, James); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + assertEq(tko.balanceOf(James), 10_000 ether - tierOp.validityBond); + assertEq(tko.balanceOf(Taylor), 10_000 ether - tierOp.contestBond); + } + + console2.log("====== William proves Tayler is right"); + mineAndWrap(10 seconds); + proveBlock(William, meta, parentHash, blockHash2, stateRoot2, LibTiers.TIER_SGX, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.validityBond, tierSgx.validityBond); + assertEq(ts.prover, William); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + assertEq(tko.balanceOf(James), 10_000 ether - tierOp.validityBond); + + uint256 quarterReward = tierOp.validityBond * 7 / 8 / 4; + assertEq(tko.balanceOf(Taylor), 10_000 ether + quarterReward * 3); + assertEq(tko.balanceOf(William), 10_000 ether - tierSgx.validityBond + quarterReward); + } + + console2.log("====== Verify the block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.validityBond, tierSgx.validityBond); + assertEq(ts.prover, William); + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + + uint256 quarterReward = tierOp.validityBond * 7 / 8 / 4; + assertEq(tko.balanceOf(James), 10_000 ether - tierOp.validityBond); + assertEq(tko.balanceOf(Taylor), 10_000 ether + quarterReward * 3); + assertEq(tko.balanceOf(William), 10_000 ether + quarterReward); + } + } +} diff --git a/packages/protocol/test/L1/TaikoL1TestGroup4.t.sol b/packages/protocol/test/L1/TaikoL1TestGroup4.t.sol new file mode 100644 index 00000000000..ee497768e21 --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1TestGroup4.t.sol @@ -0,0 +1,174 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestGroupBase.sol"; + +contract TaikoL1TestGroup4 is TaikoL1TestGroupBase { + // Test summary: + // 1. Alice proposes a block, Bob is the prover. + // 2. Bob proves the block within the proving window, using the correct parent hash. + // 3. Taylor contests then proves Bob is wrong in the same transaction with a higher-tier + // proof. + // 4. Taylor's proof is used to verify the block. + function test_taikoL1_group_4_case_1() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + ITierProvider.Tier memory tierSgx = TierProviderV1(cp).getTier(LibTiers.TIER_SGX); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Bob proves the block as the assigned prover"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(10 seconds); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Taylor contests Bob with a higher tier proof"); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + mineAndWrap(10 seconds); + proveBlock(Taylor, meta, parentHash, blockHash2, stateRoot2, LibTiers.TIER_SGX, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); + assertEq(ts.validityBond, tierSgx.validityBond); + assertEq(ts.prover, Taylor); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - tierOp.validityBond); + assertEq( + tko.balanceOf(Taylor), + 10_000 ether - tierSgx.validityBond + tierOp.validityBond * 7 / 8 + ); + } + + console2.log("====== Verify the block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + // assertEq(blk.livenessBond, livenessBond); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contestBond, 1); + assertEq(ts.prover, Taylor); + + assertEq(tko.balanceOf(Taylor), 10_000 ether + tierOp.validityBond * 7 / 8); + } + } + + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. David proves the block outside the proving window, using the correct parent hash. + // 3. Taylor contests then proves David is wrong in the same transaction with a higher-tier + // proof. + // 4. Taylor's proof is used to verify the block. + function test_taikoL1_group_4_case_2() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(David, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + ITierProvider.Tier memory tierSgx = TierProviderV1(cp).getTier(LibTiers.TIER_SGX); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + uint96 livenessBond = L1.getConfig().livenessBond; + + console2.log("====== Bob proves the block as the assigned prover"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(7 days); + proveBlock(David, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Taylor contests David with a higher tier proof"); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + mineAndWrap(10 seconds); + proveBlock(Taylor, meta, parentHash, blockHash2, stateRoot2, LibTiers.TIER_SGX, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); + assertEq(ts.validityBond, tierSgx.validityBond); + assertEq(ts.prover, Taylor); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + assertEq(tko.balanceOf(David), 10_000 ether - tierOp.validityBond); + assertEq( + tko.balanceOf(Taylor), + 10_000 ether - tierSgx.validityBond + tierOp.validityBond * 7 / 8 + ); + } + + console2.log("====== Verify the block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contestBond, 1); + assertEq(ts.prover, Taylor); + + assertEq(tko.balanceOf(Taylor), 10_000 ether + tierOp.validityBond * 7 / 8); + } + } +} diff --git a/packages/protocol/test/L1/TaikoL1TestGroup5.t.sol b/packages/protocol/test/L1/TaikoL1TestGroup5.t.sol new file mode 100644 index 00000000000..5e067039840 --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1TestGroup5.t.sol @@ -0,0 +1,363 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestGroupBase.sol"; + +contract TaikoL1TestGroup5 is TaikoL1TestGroupBase { + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. Guardian prover directly proves the block. + // 3. Guardian prover re-proves the same transition and fails. + // 4. Guardian prover proves the block again with a different transition. + // 5. William contests the guardian prover using a lower-tier proof and fails. + function test_taikoL1_group_5_case_1() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(William, 10_000 ether, 1000 ether); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Guardian prover proves"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(10 seconds); + proveBlock(William, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_GUARDIAN); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); + assertEq(ts.validityBond, 0); + assertEq(ts.prover, address(gp)); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether); + assertEq(tko.balanceOf(William), 10_000 ether); + } + + console2.log("====== Guardian re-approve with the same transition"); + mineAndWrap(10 seconds); + proveBlock( + William, + meta, + parentHash, + blockHash, + stateRoot, + LibTiers.TIER_GUARDIAN, + TaikoErrors.L1_ALREADY_PROVED.selector + ); + + console2.log("====== Guardian re-approve with a different transition"); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + mineAndWrap(10 seconds); + proveBlock(William, meta, parentHash, blockHash2, stateRoot2, LibTiers.TIER_GUARDIAN, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_GUARDIAN); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); + assertEq(ts.validityBond, 0); + assertEq(ts.prover, address(gp)); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether); + assertEq(tko.balanceOf(William), 10_000 ether); + } + + console2.log("====== William contests with a lower tier proof"); + mineAndWrap(10 seconds); + proveBlock( + William, + meta, + parentHash, + blockHash, + stateRoot, + LibTiers.TIER_SGX, + TaikoErrors.L1_INVALID_TIER.selector + ); + + console2.log("====== Verify the block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_GUARDIAN); + assertEq(ts.contestBond, 1); + assertEq(ts.prover, address(gp)); + + assertEq(tko.balanceOf(Bob), 10_000 ether); + assertEq(tko.balanceOf(William), 10_000 ether); + } + } + + // Test summary: + // 1. Alice proposes a block, Bob is the prover. + // 2. Bob proves the block. + // 3. Guardian prover re-proves the same transition and fails. + // 4. Guardian prover proves the block with a different transition. + // 5. William contests the guardian prover using a lower-tier proof and fails. + function test_taikoL1_group_5_case_2() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(William, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Bob proves the block"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(10 seconds); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Guardian re-approve with the same transition"); + mineAndWrap(10 seconds); + proveBlock( + William, + meta, + parentHash, + blockHash, + stateRoot, + LibTiers.TIER_GUARDIAN, + TaikoErrors.L1_ALREADY_PROVED.selector + ); + + console2.log("====== Guardian re-approve with a different transition"); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + mineAndWrap(10 seconds); + proveBlock(William, meta, parentHash, blockHash2, stateRoot2, LibTiers.TIER_GUARDIAN, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_GUARDIAN); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); + assertEq(ts.validityBond, 0); + assertEq(ts.prover, address(gp)); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - tierOp.validityBond); + assertEq(tko.balanceOf(William), 10_000 ether); + } + + console2.log("====== Verify the block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_GUARDIAN); + assertEq(ts.contestBond, 1); + assertEq(ts.prover, address(gp)); + + assertEq(tko.balanceOf(Bob), 10_000 ether - tierOp.validityBond); + assertEq(tko.balanceOf(William), 10_000 ether); + } + } + + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. David proves the block outside the proving window. + // 3. Guardian prover re-proves the same transition and fails. + // 4. Guardian prover proves the block with a different transition. + // 5. William contests the guardian prover using a lower-tier proof and fails. + function test_taikoL1_group_5_case_3() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(David, 10_000 ether, 1000 ether); + giveEthAndTko(William, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + uint96 livenessBond = L1.getConfig().livenessBond; + + console2.log("====== David proves the block"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(7 days); + proveBlock(David, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Guardian re-approve with the same transition"); + mineAndWrap(10 seconds); + proveBlock( + William, + meta, + parentHash, + blockHash, + stateRoot, + LibTiers.TIER_GUARDIAN, + TaikoErrors.L1_ALREADY_PROVED.selector + ); + + console2.log("====== Guardian re-approve with a different transition"); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + mineAndWrap(10 seconds); + proveBlock(William, meta, parentHash, blockHash2, stateRoot2, LibTiers.TIER_GUARDIAN, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_GUARDIAN); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); + assertEq(ts.validityBond, 0); + assertEq(ts.prover, address(gp)); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + assertEq(tko.balanceOf(David), 10_000 ether - tierOp.validityBond); + assertEq(tko.balanceOf(William), 10_000 ether); + } + + console2.log("====== Verify the block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash2); + assertEq(ts.stateRoot, stateRoot2); + assertEq(ts.tier, LibTiers.TIER_GUARDIAN); + assertEq(ts.contestBond, 1); + assertEq(ts.prover, address(gp)); + + assertEq(tko.balanceOf(Bob), 10_000 ether - livenessBond); + assertEq(tko.balanceOf(David), 10_000 ether - tierOp.validityBond); + assertEq(tko.balanceOf(William), 10_000 ether); + } + } + + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. Guardian prover directly proves the block out of proving window + function test_taikoL1_group_5_case_4() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(William, 10_000 ether, 1000 ether); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Guardian prover proves"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(7 days); + proveBlock(William, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_GUARDIAN); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); + assertEq(ts.validityBond, 0); + assertEq(ts.prover, address(gp)); + assertEq(ts.timestamp, block.timestamp); + + assertEq(tko.balanceOf(Bob), 10_000 ether - L1.getConfig().livenessBond); + assertEq(tko.balanceOf(William), 10_000 ether); + } + } +} diff --git a/packages/protocol/test/L1/TaikoL1TestGroup6.t.sol b/packages/protocol/test/L1/TaikoL1TestGroup6.t.sol new file mode 100644 index 00000000000..9b8ca7a11ea --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1TestGroup6.t.sol @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestGroupBase.sol"; + +contract TaikoL1TestGroup6 is TaikoL1TestGroupBase { + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. Bob proves the block within the proving window, using the correct parent hash. + // 3. Taylor contests Bob's proof. + // 4. Bob re-proves his proof, showing Taylor is incorrect. + // 5. Bob's proof is validated and used to verify the block. + function test_taikoL1_group_6_case_1() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + ITierProvider.Tier memory tierSgx = TierProviderV1(cp).getTier(LibTiers.TIER_SGX); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Bob proves the block as the assigned prover"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(10 seconds); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + console2.log("====== Taylor contests Bob"); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + mineAndWrap(10 seconds); + proveBlock(Taylor, meta, parentHash, blockHash2, stateRoot2, meta.minTier, ""); + + console2.log("====== Bob cannot proves himself is right"); + mineAndWrap(10 seconds); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_SGX, ""); + + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 0); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contester, address(0)); + assertEq(ts.contestBond, 1); // not zero + assertEq(ts.validityBond, tierSgx.validityBond); + assertEq(ts.prover, Bob); + assertEq(ts.timestamp, block.timestamp); // not zero + + assertEq(tko.balanceOf(Taylor), 10_000 ether - tierOp.contestBond); + assertEq( + tko.balanceOf(Bob), 10_000 ether - tierSgx.validityBond + tierOp.contestBond * 7 / 8 + ); + } + + console2.log("====== Verify the block"); + mineAndWrap(7 days); + verifyBlock(1); + { + printBlockAndTrans(meta.id); + + TaikoData.Block memory blk = L1.getBlock(meta.id); + + assertEq(blk.nextTransitionId, 2); + assertEq(blk.verifiedTransitionId, 1); + assertEq(blk.assignedProver, Bob); + assertEq(blk.livenessBond, 0); + + TaikoData.TransitionState memory ts = L1.getTransition(meta.id, 1); + assertEq(ts.blockHash, blockHash); + assertEq(ts.stateRoot, stateRoot); + assertEq(ts.tier, LibTiers.TIER_SGX); + assertEq(ts.contestBond, 1); + assertEq(ts.prover, Bob); + + assertEq(tko.balanceOf(Taylor), 10_000 ether - tierOp.contestBond); + assertEq(tko.balanceOf(Bob), 10_000 ether + tierOp.contestBond * 7 / 8); + } + } +} diff --git a/packages/protocol/test/L1/TaikoL1TestGroup7.t.sol b/packages/protocol/test/L1/TaikoL1TestGroup7.t.sol new file mode 100644 index 00000000000..6a23fff7ea6 --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1TestGroup7.t.sol @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestGroupBase.sol"; + +contract TaikoL1TestGroup7 is TaikoL1TestGroupBase { + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. Bob proves the block within the proving window, using the correct parent hash. + // 3. After the cooldown window, Taylor contests Bob's proof, and fails. + function test_taikoL1_group_7_case_1() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Bob proves the block as the assigned prover"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(10 seconds); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + mineAndWrap(tierOp.cooldownWindow * 60); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + proveBlock( + Taylor, + meta, + parentHash, + blockHash2, + stateRoot2, + meta.minTier, + TaikoErrors.L1_CANNOT_CONTEST.selector + ); + printBlockAndTrans(meta.id); + } + + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. Bob proves the block within the proving window, using the correct parent hash. + // 3. Taylor contests Bob's proof. + // 4. William attempts but fails to contest Bob again. + function test_taikoL1_group_7_case_2() external { + vm.warp(1_000_000); + + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + giveEthAndTko(Taylor, 10_000 ether, 1000 ether); + giveEthAndTko(William, 10_000 ether, 1000 ether); + ITierProvider.Tier memory tierOp = TierProviderV1(cp).getTier(LibTiers.TIER_OPTIMISTIC); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Bob proves the block as the assigned prover"); + bytes32 parentHash = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + + mineAndWrap(10 seconds); + proveBlock(Bob, meta, parentHash, blockHash, stateRoot, meta.minTier, ""); + + mineAndWrap(tierOp.cooldownWindow * 60 - 1); + bytes32 blockHash2 = bytes32(uint256(20)); + bytes32 stateRoot2 = bytes32(uint256(21)); + proveBlock(Taylor, meta, parentHash, blockHash2, stateRoot2, meta.minTier, ""); + + bytes32 blockHash3 = bytes32(uint256(30)); + bytes32 stateRoot3 = bytes32(uint256(31)); + proveBlock( + William, + meta, + parentHash, + blockHash3, + stateRoot3, + meta.minTier, + TaikoErrors.L1_ALREADY_CONTESTED.selector + ); + + printBlockAndTrans(meta.id); + } +} diff --git a/packages/protocol/test/L1/TaikoL1TestGroup8.t.sol b/packages/protocol/test/L1/TaikoL1TestGroup8.t.sol new file mode 100644 index 00000000000..913ad55eef1 --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1TestGroup8.t.sol @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestGroupBase.sol"; + +contract TaikoL1TestGroup8 is TaikoL1TestGroupBase { + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. TaikoL1 is paused. + // 3. Bob attempts to prove the block within the proving window. + // 4. Alice tries to propose another block. + // 5. TaikoL1 is unpaused. + // 6. Bob attempts again to prove the first block within the proving window. + // 7. Alice tries to propose another block. + function test_taikoL1_group_8_case_1() external { + vm.warp(1_000_000); + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Pause TaikoL1"); + mineAndWrap(10 seconds); + vm.prank(L1.owner()); + L1.pause(); + + console2.log("====== Bob proves the block first after L1 paused"); + + bytes32 parentHash1 = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + proveBlock( + Bob, + meta, + parentHash1, + blockHash, + stateRoot, + meta.minTier, + EssentialContract.INVALID_PAUSE_STATUS.selector + ); + + console2.log("====== Alice tries to propose another block after L1 paused"); + proposeBlock(Alice, Bob, EssentialContract.INVALID_PAUSE_STATUS.selector); + + console2.log("====== Unpause TaikoL1"); + mineAndWrap(10 seconds); + vm.prank(L1.owner()); + L1.unpause(); + + console2.log("====== Bob proves the block first after L1 unpaused"); + proveBlock(Bob, meta, parentHash1, blockHash, stateRoot, meta.minTier, ""); + console2.log("====== Alice tries to propose another block after L1 unpaused"); + proposeBlock(Alice, Bob, ""); + } + + // Test summary: + // 1. Alice proposes a block, assigning Bob as the prover. + // 2. TaikoL1 proving is paused. + // 3. Bob attempts to prove the block within the proving window. + // 4. Alice tries to propose another block. + // 5. TaikoL1 proving is unpaused. + // 6. Bob attempts again to prove the first block within the proving window. + // 7. Alice tries to propose another block. + function test_taikoL1_group_8_case_2() external { + vm.warp(1_000_000); + giveEthAndTko(Alice, 10_000 ether, 1000 ether); + giveEthAndTko(Bob, 10_000 ether, 1000 ether); + + console2.log("====== Alice propose a block with bob as the assigned prover"); + + TaikoData.BlockMetadata memory meta = proposeBlock(Alice, Bob, ""); + + console2.log("====== Pause TaikoL1 proving"); + mineAndWrap(10 seconds); + vm.prank(L1.owner()); + L1.pauseProving(true); + + console2.log("====== Bob proves the block first after L1 proving paused"); + + bytes32 parentHash1 = GENESIS_BLOCK_HASH; + bytes32 blockHash = bytes32(uint256(10)); + bytes32 stateRoot = bytes32(uint256(11)); + proveBlock( + Bob, + meta, + parentHash1, + blockHash, + stateRoot, + meta.minTier, + TaikoErrors.L1_PROVING_PAUSED.selector + ); + + console2.log("====== Alice tries to propose another block after L1 proving paused"); + proposeBlock(Alice, Bob, ""); + + console2.log("====== Unpause TaikoL1 proving"); + mineAndWrap(10 seconds); + vm.prank(L1.owner()); + L1.pauseProving(false); + + console2.log("====== Bob proves the block first after L1 proving unpaused"); + proveBlock(Bob, meta, parentHash1, blockHash, stateRoot, meta.minTier, ""); + } + + // Test summary: + // 1. Gets a block that doesn't exist + // 2. Gets a transition by ID & hash that doesn't exist. + function test_taikoL1_group_8_case_3() external { + vm.expectRevert(TaikoErrors.L1_INVALID_BLOCK_ID.selector); + L1.getBlock(2); + + vm.expectRevert(TaikoErrors.L1_TRANSITION_NOT_FOUND.selector); + L1.getTransition(0, 2); + + vm.expectRevert(TaikoErrors.L1_TRANSITION_NOT_FOUND.selector); + L1.getTransition(0, randBytes32()); + + vm.expectRevert(TaikoErrors.L1_INVALID_BLOCK_ID.selector); + L1.getTransition(3, randBytes32()); + } +} diff --git a/packages/protocol/test/L1/TaikoL1TestGroupBase.sol b/packages/protocol/test/L1/TaikoL1TestGroupBase.sol new file mode 100644 index 00000000000..9110130c8e3 --- /dev/null +++ b/packages/protocol/test/L1/TaikoL1TestGroupBase.sol @@ -0,0 +1,179 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "./TaikoL1TestBase.sol"; + +contract TaikoL1New is TaikoL1 { + function getConfig() public view override returns (TaikoData.Config memory config) { + config = TaikoL1.getConfig(); + config.maxBlocksToVerifyPerProposal = 0; + config.blockMaxProposals = 10; + config.blockRingBufferSize = 20; + } + + function _checkEOAForCalldataDA() internal pure override returns (bool) { + return true; + } +} + +abstract contract TaikoL1TestGroupBase is TaikoL1TestBase { + function deployTaikoL1() internal override returns (TaikoL1) { + return TaikoL1( + payable(deployProxy({ name: "taiko", impl: address(new TaikoL1New()), data: "" })) + ); + } + + function proposeBlock( + address proposer, + address assignedProver, + bytes4 revertReason + ) + internal + returns (TaikoData.BlockMetadata memory meta) + { + TaikoData.TierFee[] memory tierFees = new TaikoData.TierFee[](2); + tierFees[0] = TaikoData.TierFee(LibTiers.TIER_OPTIMISTIC, 1 ether); + tierFees[1] = TaikoData.TierFee(LibTiers.TIER_SGX, 2 ether); + + AssignmentHook.ProverAssignment memory assignment = AssignmentHook.ProverAssignment({ + feeToken: address(0), + tierFees: tierFees, + expiry: uint64(block.timestamp + 60 minutes), + maxBlockId: 0, + maxProposedIn: 0, + metaHash: 0, + parentMetaHash: 0, + signature: new bytes(0) + }); + + bytes memory txList = new bytes(10); + assignment.signature = + _signAssignment(assignedProver, assignment, address(L1), proposer, keccak256(txList)); + + TaikoData.HookCall[] memory hookcalls = new TaikoData.HookCall[](1); + hookcalls[0] = TaikoData.HookCall(address(assignmentHook), abi.encode(assignment)); + + bytes memory eoaSig; + { + uint256 privateKey; + if (proposer == Alice) { + privateKey = 0x1; + } else if (proposer == Bob) { + privateKey = 0x2; + } else if (proposer == Carol) { + privateKey = 0x3; + } else { + revert("unexpected"); + } + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, keccak256(txList)); + eoaSig = abi.encodePacked(r, s, v); + } + + vm.prank(proposer); + if (revertReason != "") vm.expectRevert(revertReason); + (meta,) = L1.proposeBlock{ value: 3 ether }( + abi.encode(TaikoData.BlockParams(assignedProver, address(0), 0, 0, hookcalls, eoaSig)), + txList + ); + } + + function proveBlock( + address prover, + TaikoData.BlockMetadata memory meta, + bytes32 parentHash, + bytes32 blockHash, + bytes32 stateRoot, + uint16 tier, + bytes4 revertReason + ) + internal + override + { + TaikoData.Transition memory tran = TaikoData.Transition({ + parentHash: parentHash, + blockHash: blockHash, + stateRoot: stateRoot, + graffiti: 0x0 + }); + + TaikoData.TierProof memory proof; + proof.tier = tier; + address newInstance; + + // Keep changing the pub key associated with an instance to avoid + // attacks, + // obviously just a mock due to 2 addresses changing all the time. + (newInstance,) = sv.instances(0); + if (newInstance == SGX_X_0) { + newInstance = SGX_X_1; + } else { + newInstance = SGX_X_0; + } + + if (tier == LibTiers.TIER_SGX) { + bytes memory signature = + createSgxSignatureProof(tran, newInstance, prover, keccak256(abi.encode(meta))); + + proof.data = bytes.concat(bytes4(0), bytes20(newInstance), signature); + } + + if (tier == LibTiers.TIER_GUARDIAN) { + proof.data = ""; + + // Grant 2 signatures, 3rd might be a revert + vm.prank(David, David); + gp.approve(meta, tran, proof); + vm.prank(Emma, Emma); + gp.approve(meta, tran, proof); + + if (revertReason != "") vm.expectRevert(revertReason); + vm.prank(Frank); + gp.approve(meta, tran, proof); + } else { + if (revertReason != "") vm.expectRevert(revertReason); + vm.prank(prover); + L1.proveBlock(meta.id, abi.encode(meta, tran, proof)); + } + } + + function printBlockAndTrans(uint64 blockId) internal view { + TaikoData.Block memory blk = L1.getBlock(blockId); + printBlock(blk); + + for (uint32 i = 1; i < blk.nextTransitionId; ++i) { + printTran(i, L1.getTransition(blockId, i)); + } + } + + function printBlock(TaikoData.Block memory blk) internal view { + TaikoData.SlotB memory b = L1.slotB(); + console2.log("---CHAIN:"); + console2.log(" | lastVerifiedBlockId:", b.lastVerifiedBlockId); + console2.log(" | numBlocks:", b.numBlocks); + console2.log(" | timestamp:", block.timestamp); + console2.log("---BLOCK#", blk.blockId); + console2.log(" | assignedProver:", blk.assignedProver); + console2.log(" | livenessBond:", blk.livenessBond); + console2.log(" | proposedAt:", blk.proposedAt); + console2.log(" | metaHash:", vm.toString(blk.metaHash)); + console2.log(" | nextTransitionId:", blk.nextTransitionId); + console2.log(" | verifiedTransitionId:", blk.verifiedTransitionId); + } + + function printTran(uint64 tid, TaikoData.TransitionState memory ts) internal pure { + console2.log(" |---TRANSITION#", tid); + console2.log(" | tier:", ts.tier); + console2.log(" | prover:", ts.prover); + console2.log(" | validityBond:", ts.validityBond); + console2.log(" | contester:", ts.contester); + console2.log(" | contestBond:", ts.contestBond); + console2.log(" | timestamp:", ts.timestamp); + console2.log(" | blockHash:", vm.toString(ts.blockHash)); + console2.log(" | stateRoot:", vm.toString(ts.stateRoot)); + } + + function mineAndWrap(uint256 value) internal { + vm.warp(block.timestamp + value); + } +}