From 96a3859dd31cfa3015e9bd97b08018c0b5029ef5 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:02:26 +0800 Subject: [PATCH 01/14] migrate changes from private repo --- .../protocol/contracts/L1/TaikoEvents.sol | 5 + packages/protocol/contracts/L1/TaikoL1.sol | 21 +- .../contracts/L1/libs/LibProposing.sol | 8 +- .../protocol/contracts/L1/libs/LibProving.sol | 60 ++-- .../protocol/contracts/L1/libs/LibUtils.sol | 16 ++ .../contracts/L1/libs/LibVerifying.sol | 18 +- ...netTierProvider.sol => TierProviderV1.sol} | 8 +- ...netTierProvider.sol => TierProviderV2.sol} | 8 +- .../protocol/contracts/L2/CrossChainOwned.sol | 74 ----- .../protocol/contracts/L2/DelegateOwner.sol | 94 +++++++ .../protocol/contracts/L2/Lib1559Math.sol | 32 ++- packages/protocol/contracts/L2/TaikoL2.sol | 109 +++----- .../lib/QuoteV3Auth/V3Parser.sol | 2 +- packages/protocol/contracts/bridge/Bridge.sol | 44 ++- .../contracts/common/EssentialContract.sol | 5 +- .../protocol/contracts/libs/LibAddress.sol | 4 - .../protocol/contracts/libs/LibNetwork.sol | 46 +++ .../contracts/tokenvault/BridgedERC1155.sol | 2 +- .../contracts/tokenvault/BridgedERC721.sol | 2 +- .../contracts/tokenvault/ERC20Vault.sol | 2 +- .../contracts/tokenvault/LibBridgedToken.sol | 22 +- .../verifiers/libs/LibPublicInput.sol | 2 +- .../protocol/genesis/GenerateGenesis.g.sol | 7 +- packages/protocol/genesis/test_config.js | 2 +- packages/protocol/package.json | 8 +- packages/protocol/test/L1/TaikoL1.t.sol | 103 ------- .../test/L1/TaikoL1LibProvingWithTiers.t.sol | 12 + packages/protocol/test/L1/TaikoL1TestBase.sol | 8 +- packages/protocol/test/L2/TaikoL2.t.sol | 18 -- .../protocol/test/L2/TaikoL2NoFeeCheck.t.sol | 2 +- packages/protocol/test/TaikoTest.sol | 3 +- packages/protocol/test/bridge/Bridge.t.sol | 261 +++++++++++++----- packages/protocol/test/libs/LibAddress.t.sol | 154 ----------- 33 files changed, 584 insertions(+), 578 deletions(-) rename packages/protocol/contracts/L1/tiers/{TestnetTierProvider.sol => TierProviderV1.sol} (92%) rename packages/protocol/contracts/L1/tiers/{MainnetTierProvider.sol => TierProviderV2.sol} (91%) delete mode 100644 packages/protocol/contracts/L2/CrossChainOwned.sol create mode 100644 packages/protocol/contracts/L2/DelegateOwner.sol create mode 100644 packages/protocol/contracts/libs/LibNetwork.sol delete mode 100644 packages/protocol/test/libs/LibAddress.t.sol diff --git a/packages/protocol/contracts/L1/TaikoEvents.sol b/packages/protocol/contracts/L1/TaikoEvents.sol index 83912b0424e..2a196bad5b9 100644 --- a/packages/protocol/contracts/L1/TaikoEvents.sol +++ b/packages/protocol/contracts/L1/TaikoEvents.sol @@ -44,6 +44,11 @@ abstract contract TaikoEvents { uint8 contestations ); + /// @notice Emitted when some state variable values changed. + /// @dev This event is currently used by Taiko node/client for block proposal/proving. + /// @param slotB The SlotB data structure. + event StateVariablesUpdated(TaikoData.SlotB slotB); + /// @dev Emitted when a block transition is proved or re-proved. /// @param blockId The ID of the proven block. /// @param tran The verified transition. diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index d57fcf780e1..14c12095203 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -30,6 +30,11 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors { _; } + modifier emitEventForClient() { + _; + LibVerifying.emitEventForClient(state); + } + /// @dev Fallback function to receive Ether from Hooks receive() external payable { if (!_inNonReentrant()) revert L1_RECEIVE_DISABLED(); @@ -60,6 +65,7 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors { payable nonReentrant whenNotPaused + emitEventForClient returns (TaikoData.BlockMetadata memory meta_, TaikoData.EthDeposit[] memory deposits_) { TaikoData.Config memory config = getConfig(); @@ -80,6 +86,7 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors { nonReentrant whenNotPaused whenProvingNotPaused + emitEventForClient { ( TaikoData.BlockMetadata memory meta, @@ -102,6 +109,7 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors { nonReentrant whenNotPaused whenProvingNotPaused + emitEventForClient { LibVerifying.verifyBlocks(state, getConfig(), this, _maxBlocksToVerify); } @@ -184,14 +192,17 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors { // - anchorGasLimit: 250_000 (based on internal devnet, its ~220_000 // after 256 L2 blocks) return TaikoData.Config({ - chainId: 167_008, - // Assume the block time is 3s, the protocol will allow ~1 month of + chainId: 167_009, + // Assume the block time is 3s, the protocol will allow ~90 days of // new blocks without any verification. - blockMaxProposals: 864_000, - blockRingBufferSize: 864_100, + blockMaxProposals: 3_000_000, + blockRingBufferSize: 3_010_000, // Can be overridden by the tier config. maxBlocksToVerifyPerProposal: 10, - blockMaxGasLimit: 15_000_000, + // This value is set based on `gasTargetPerL1Block = 15_000_000 * 4` in TaikoL2. + // We use 8x rather than 4x here to handle the scenario where the average number of + // Taiko blocks proposed per Ethereum block is smaller than 1. + blockMaxGasLimit: 30_000_000 * 8, livenessBond: 250e18, // 250 Taiko token // ETH deposit related. ethDepositRingBufferSize: 1024, diff --git a/packages/protocol/contracts/L1/libs/LibProposing.sol b/packages/protocol/contracts/L1/libs/LibProposing.sol index d8395d1c2cb..f832756e56c 100644 --- a/packages/protocol/contracts/L1/libs/LibProposing.sol +++ b/packages/protocol/contracts/L1/libs/LibProposing.sol @@ -134,11 +134,9 @@ library LibProposing { meta_.blobHash = blobhash(0); if (meta_.blobHash == 0) revert L1_BLOB_NOT_FOUND(); } else { - // The proposer must be an Externally Owned Account (EOA) for - // calldata usage. This ensures that the transaction is not an - // internal one, making calldata retrieval more straightforward for - // Taiko node software. - if (!LibAddress.isSenderEOA()) revert L1_PROPOSER_NOT_EOA(); + // This function must be called as the outmost transaction (not an internal one) for + // the node to extract the calldata easily. + if (msg.sender != tx.origin) revert L1_PROPOSER_NOT_EOA(); meta_.blobHash = keccak256(_txList); } diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index c0998cec736..4ed79bbace7 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -68,6 +68,7 @@ library LibProving { error L1_INVALID_TRANSITION(); error L1_MISSING_VERIFIER(); error L1_NOT_ASSIGNED_PROVER(); + error L1_CANNOT_CONTEST(); /// @notice Pauses or unpauses the proving process. /// @param _state Current TaikoData.State. @@ -143,7 +144,7 @@ library LibProving { ITierProvider(_resolver.resolve("tier_provider", false)).getTier(_proof.tier); // Check if this prover is allowed to submit a proof for this block - _checkProverPermission(_state, blk, ts, tid, tier); + _checkProverPermission(blk, ts, tid, tier, b.lastUnpausedAt); // We must verify the proof, and any failure in proof verification will // result in a revert. @@ -223,7 +224,7 @@ library LibProving { if (isTopTier) { // The top tier prover re-proves. assert(tier.validityBond == 0); - assert(ts.validityBond == 0 && ts.contestBond == 0 && ts.contester == address(0)); + assert(ts.validityBond == 0 && ts.contester == address(0)); ts.prover = msg.sender; ts.blockHash = _tran.blockHash; @@ -240,6 +241,13 @@ library LibProving { // Contesting but not on the highest tier if (ts.contester != address(0)) revert L1_ALREADY_CONTESTED(); + // 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)) { + 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); @@ -349,6 +357,15 @@ library LibProving { } /// @dev Handles what happens when there is a higher proof incoming + /// + /// Assume Alice is the initial prover, Bob is the contester, and Cindy is the subsequent + /// prover. The validity bond `V` is set at 100, and the contestation bond `C` at 500. If Bob + /// successfully contests, he receives a reward of 65.625, calculated as 3/4 of 7/8 of 100. Cindy + /// receives 21.875, which is 1/4 of 7/8 of 100, while the protocol retains 12.5 as friction. + /// Bob's Return on Investment (ROI) is 13.125%, calculated from 65.625 divided by 500. + // To establish the expected ROI `r` for valid contestations, where the contestation bond `C` to + // 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.TransitionState storage _ts, TaikoData.Transition memory _tran, @@ -360,22 +377,27 @@ library LibProving { private { // Higher tier proof overwriting lower tier proof - uint256 reward; + uint256 reward; // reward to the new (current) prover if (_ts.contester != address(0)) { if (_sameTransition) { - // The contested transition is proven to be valid, contestor loses the game - reward = _ts.contestBond >> 2; - _tko.safeTransfer(_ts.prover, _ts.validityBond + reward); + // The contested transition is proven to be valid, contester loses the game + reward = _rewardAfterFriction(_ts.contestBond); + + // We return the validity bond back, but the origianl prover doesn't get any reward. + _tko.safeTransfer(_ts.prover, _ts.validityBond); } else { - // The contested transition is proven to be invalid, contestor wins the game - reward = _ts.validityBond >> 2; - _tko.safeTransfer(_ts.contester, _ts.contestBond + reward); + // The contested transition is proven to be invalid, contester wins the game. + // Contester gets 3/4 of reward, the new prover gets 1/4. + reward = _rewardAfterFriction(_ts.validityBond) >> 2; + + _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 - reward = _ts.validityBond >> 1; + // Contest the existing transition and prove it to be invalid. The new prover get all + // rewards. + reward = _rewardAfterFriction(_ts.validityBond); _ts.contestations += 1; } @@ -401,11 +423,11 @@ library LibProving { /// @dev Check the msg.sender (the new prover) against the block's assigned prover. function _checkProverPermission( - TaikoData.State storage _state, TaikoData.Block storage _blk, TaikoData.TransitionState storage _ts, uint32 _tid, - ITierProvider.Tier memory _tier + ITierProvider.Tier memory _tier, + uint64 _lastUnpausedAt ) private view @@ -413,12 +435,13 @@ library LibProving { // The highest tier proof can always submit new proofs if (_tier.contestBond == 0) return; - bool inProvingWindow = uint256(_ts.timestamp).max(_state.slotB.lastUnpausedAt) - + _tier.provingWindow * 60 >= block.timestamp; bool isAssignedPover = msg.sender == _blk.assignedProver; // The assigned prover can only submit the very first transition. - if (_tid == 1 && _ts.tier == 0 && inProvingWindow) { + if ( + _tid == 1 && _ts.tier == 0 + && !LibUtils.isPostDeadline(_ts.timestamp, _lastUnpausedAt, _tier.provingWindow) + ) { if (!isAssignedPover) revert L1_NOT_ASSIGNED_PROVER(); } else { // Disallow the same address to prove the block so that we can detect that the @@ -426,4 +449,9 @@ library LibProving { if (isAssignedPover) 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; + } } diff --git a/packages/protocol/contracts/L1/libs/LibUtils.sol b/packages/protocol/contracts/L1/libs/LibUtils.sol index 749a2ebfe9f..b72bfa7e34d 100644 --- a/packages/protocol/contracts/L1/libs/LibUtils.sol +++ b/packages/protocol/contracts/L1/libs/LibUtils.sol @@ -1,12 +1,15 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.24; +import "../../libs/LibMath.sol"; import "../TaikoData.sol"; /// @title LibUtils /// @notice A library that offers helper functions. /// @custom:security-contact security@taiko.xyz library LibUtils { + using LibMath for uint256; + // Warning: Any errors defined here must also be defined in TaikoErrors.sol. error L1_BLOCK_MISMATCH(); error L1_INVALID_BLOCK_ID(); @@ -85,4 +88,17 @@ library LibUtils { if (tid_ >= _blk.nextTransitionId) revert L1_UNEXPECTED_TRANSITION_ID(); } + + function isPostDeadline( + uint256 _tsTimestamp, + uint256 _lastUnpausedAt, + uint256 _windowMinutes + ) + internal + view + returns (bool) + { + uint256 deadline = _tsTimestamp.max(_lastUnpausedAt) + _windowMinutes * 60; + return block.timestamp >= deadline; + } } diff --git a/packages/protocol/contracts/L1/libs/LibVerifying.sol b/packages/protocol/contracts/L1/libs/LibVerifying.sol index 6a45983ee0c..073edf4682a 100644 --- a/packages/protocol/contracts/L1/libs/LibVerifying.sol +++ b/packages/protocol/contracts/L1/libs/LibVerifying.sol @@ -37,6 +37,11 @@ library LibVerifying { uint8 contestations ); + /// @notice Emitted when some state variable values changed. + /// @dev This event is currently used by Taiko node/client for block proposal/proving. + /// @param slotB The SlotB data structure. + event StateVariablesUpdated(TaikoData.SlotB slotB); + // Warning: Any errors defined here must also be defined in TaikoErrors.sol. error L1_BLOCK_MISMATCH(); error L1_INVALID_CONFIG(); @@ -150,9 +155,13 @@ library LibVerifying { if (tierProvider == address(0)) { tierProvider = _resolver.resolve("tier_provider", false); } + if ( - uint256(ITierProvider(tierProvider).getTier(ts.tier).cooldownWindow) * 60 - + uint256(ts.timestamp).max(_state.slotB.lastUnpausedAt) > block.timestamp + !LibUtils.isPostDeadline( + ts.timestamp, + b.lastUnpausedAt, + ITierProvider(tierProvider).getTier(ts.tier).cooldownWindow + ) ) { // If cooldownWindow is 0, the block can theoretically // be proved and verified within the same L1 block. @@ -223,6 +232,11 @@ library LibVerifying { } } + /// @notice Emit events used by client/node. + function emitEventForClient(TaikoData.State storage _state) internal { + emit StateVariablesUpdated({ slotB: _state.slotB }); + } + function _syncChainData( TaikoData.Config memory _config, IAddressResolver _resolver, diff --git a/packages/protocol/contracts/L1/tiers/TestnetTierProvider.sol b/packages/protocol/contracts/L1/tiers/TierProviderV1.sol similarity index 92% rename from packages/protocol/contracts/L1/tiers/TestnetTierProvider.sol rename to packages/protocol/contracts/L1/tiers/TierProviderV1.sol index adfe491e99a..1ad5d344feb 100644 --- a/packages/protocol/contracts/L1/tiers/TestnetTierProvider.sol +++ b/packages/protocol/contracts/L1/tiers/TierProviderV1.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.24; import "../../common/EssentialContract.sol"; import "./ITierProvider.sol"; -/// @title TestnetTierProvider +/// @title TierProviderV1 /// @dev Labeled in AddressResolver as "tier_provider" /// @custom:security-contact security@taiko.xyz -contract TestnetTierProvider is EssentialContract, ITierProvider { +contract TierProviderV1 is EssentialContract, ITierProvider { uint256[50] private __gap; /// @notice Initializes the contract. @@ -32,8 +32,8 @@ contract TestnetTierProvider is EssentialContract, ITierProvider { if (_tierId == LibTiers.TIER_SGX) { return ITierProvider.Tier({ verifierName: "tier_sgx", - validityBond: 500 ether, // TKO - contestBond: 1000 ether, // TKO + validityBond: 250 ether, // TKO + contestBond: 1640 ether, // =250TKO * 6.5625 cooldownWindow: 1440, //24 hours provingWindow: 60, // 1 hours maxBlocksToVerifyPerProof: 8 diff --git a/packages/protocol/contracts/L1/tiers/MainnetTierProvider.sol b/packages/protocol/contracts/L1/tiers/TierProviderV2.sol similarity index 91% rename from packages/protocol/contracts/L1/tiers/MainnetTierProvider.sol rename to packages/protocol/contracts/L1/tiers/TierProviderV2.sol index 062193a6cd2..f66bda989e6 100644 --- a/packages/protocol/contracts/L1/tiers/MainnetTierProvider.sol +++ b/packages/protocol/contracts/L1/tiers/TierProviderV2.sol @@ -4,10 +4,10 @@ pragma solidity 0.8.24; import "../../common/EssentialContract.sol"; import "./ITierProvider.sol"; -/// @title MainnetTierProvider +/// @title TierProviderV2 /// @dev Labeled in AddressResolver as "tier_provider" /// @custom:security-contact security@taiko.xyz -contract MainnetTierProvider is EssentialContract, ITierProvider { +contract TierProviderV2 is EssentialContract, ITierProvider { uint256[50] private __gap; /// @notice Initializes the contract. @@ -22,7 +22,7 @@ contract MainnetTierProvider is EssentialContract, ITierProvider { return ITierProvider.Tier({ verifierName: "tier_sgx", validityBond: 250 ether, // TKO - contestBond: 500 ether, // TKO + contestBond: 1640 ether, // =250TKO * 6.5625 cooldownWindow: 1440, //24 hours provingWindow: 60, // 1 hours maxBlocksToVerifyPerProof: 8 @@ -33,7 +33,7 @@ contract MainnetTierProvider is EssentialContract, ITierProvider { return ITierProvider.Tier({ verifierName: "tier_sgx_zkvm", validityBond: 500 ether, // TKO - contestBond: 1000 ether, // TKO + contestBond: 3280 ether, // =500TKO * 6.5625 cooldownWindow: 1440, //24 hours provingWindow: 240, // 4 hours maxBlocksToVerifyPerProof: 4 diff --git a/packages/protocol/contracts/L2/CrossChainOwned.sol b/packages/protocol/contracts/L2/CrossChainOwned.sol deleted file mode 100644 index 3c41ce621f7..00000000000 --- a/packages/protocol/contracts/L2/CrossChainOwned.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import "../common/EssentialContract.sol"; -import "../bridge/IBridge.sol"; - -/// @title CrossChainOwned -/// @notice This contract's owner can be a local address or one that lives on another chain and uses -/// signals for transaction approval. -/// @dev Notice that when sending the message on the owner chain, the gas limit of the message must -/// not be zero, so on this chain, some EOA can help execute this transaction. -/// @custom:security-contact security@taiko.xyz -abstract contract CrossChainOwned is EssentialContract, IMessageInvocable { - /// @notice The owner chain ID. - uint64 public ownerChainId; - - /// @notice The next transaction ID. - uint64 public nextTxId; - - uint256[49] private __gap; - - /// @notice Emitted when a transaction is executed. - /// @param txId The transaction ID. - /// @param selector The function selector. - event TransactionExecuted(uint64 indexed txId, bytes4 indexed selector); - - error XCO_INVALID_OWNER_CHAINID(); - error XCO_INVALID_TX_ID(); - error XCO_PERMISSION_DENIED(); - error XCO_TX_REVERTED(); - - /// @inheritdoc IMessageInvocable - /// @dev Do not guard with nonReentrant as this function will re-enter the contract as _data - /// represents calls to address(this). - function onMessageInvocation(bytes calldata _data) - external - payable - whenNotPaused - onlyFromNamed("bridge") - { - (uint64 txId, bytes memory txdata) = abi.decode(_data, (uint64, bytes)); - if (txId != nextTxId) revert XCO_INVALID_TX_ID(); - - IBridge.Context memory ctx = IBridge(msg.sender).context(); - if (ctx.srcChainId != ownerChainId || ctx.from != owner()) { - revert XCO_PERMISSION_DENIED(); - } - - (bool success,) = address(this).call(txdata); - if (!success) revert XCO_TX_REVERTED(); - - emit TransactionExecuted(nextTxId++, bytes4(txdata)); - } - - /// @notice Initializes the contract. - /// @param _owner The owner of this contract. msg.sender will be used if this value is zero. - /// @param _addressManager The address of the {AddressManager} contract. - /// @param _ownerChainId The owner's deployment chain ID. - function __CrossChainOwned_init( - address _owner, - address _addressManager, - uint64 _ownerChainId - ) - internal - virtual - onlyInitializing - { - __Essential_init(_owner, _addressManager); - if (_ownerChainId == 0 || _ownerChainId == block.chainid) { - revert XCO_INVALID_OWNER_CHAINID(); - } - ownerChainId = _ownerChainId; - } -} diff --git a/packages/protocol/contracts/L2/DelegateOwner.sol b/packages/protocol/contracts/L2/DelegateOwner.sol new file mode 100644 index 00000000000..ba937aa7dc7 --- /dev/null +++ b/packages/protocol/contracts/L2/DelegateOwner.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "../common/EssentialContract.sol"; +import "../bridge/IBridge.sol"; + +/// @title DelegateOwner +/// @notice This contract will be the owner of all essential contracts deployed on the L2 chain. +/// @dev Notice that when sending the message on the owner chain, the gas limit of the message must +/// not be zero, so on this chain, some EOA can help execute this transaction. +/// @custom:security-contact security@taiko.xyz +contract DelegateOwner is EssentialContract, IMessageInvocable { + /// @notice The owner chain ID. + uint64 public l1ChainId; + + /// @notice The next transaction ID. + uint64 public nextTxId; + + /// @notice The real owner on L1, supposedly the DAO. + address public realOwner; + + uint256[48] private __gap; + + /// @notice Emitted when a transaction is executed. + /// @param txId The transaction ID. + /// @param target The target address. + /// @param selector The function selector. + event TransactionExecuted(uint64 indexed txId, address indexed target, bytes4 indexed selector); + + /// @notice Emitted when this contract accepted the ownership of a target contract. + /// @param target The target address. + event OwnershipAccepted(address indexed target); + + error DO_INVALID_PARAM(); + error DO_INVALID_TX_ID(); + error DO_PERMISSION_DENIED(); + error DO_TX_REVERTED(); + error DO_UNSUPPORTED(); + + /// @notice Initializes the contract. + /// @param _realOwner The real owner on L1 that can send a cross-chain message to invoke + /// `onMessageInvocation`. + /// @param _addressManager The address of the {AddressManager} contract. + /// @param _l1ChainId The L1 chain's ID. + function init( + address _realOwner, + address _addressManager, + uint64 _l1ChainId + ) + external + initializer + { + // This contract's owner will be itself. + __Essential_init(address(this), _addressManager); + + if (_realOwner == address(0) || _l1ChainId == 0 || _l1ChainId == block.chainid) { + revert DO_INVALID_PARAM(); + } + + realOwner = _realOwner; + l1ChainId = _l1ChainId; + } + + /// @inheritdoc IMessageInvocable + /// @dev Do not guard with nonReentrant as this function may re-enter the contract as _data + /// represents calls to address(this). + function onMessageInvocation(bytes calldata _data) external payable onlyFromNamed("bridge") { + (uint64 txId, address target, bytes memory txdata) = + abi.decode(_data, (uint64, address, bytes)); + + if (txId != nextTxId) revert DO_INVALID_TX_ID(); + + IBridge.Context memory ctx = IBridge(msg.sender).context(); + if (ctx.srcChainId != l1ChainId || ctx.from != realOwner) { + revert DO_PERMISSION_DENIED(); + } + + // Sending ether along with the function call. Although this is sending Ether from this + // contract back to itself, txData's function can now be payable. + (bool success,) = target.call{ value: msg.value }(txdata); + if (!success) revert DO_TX_REVERTED(); + + emit TransactionExecuted(nextTxId++, target, bytes4(txdata)); + } + + function acceptOwnership(address target) public { + Ownable2StepUpgradeable(target).acceptOwnership(); + emit OwnershipAccepted(target); + } + + function _authorizePause(address) internal pure override { + revert DO_UNSUPPORTED(); + } +} diff --git a/packages/protocol/contracts/L2/Lib1559Math.sol b/packages/protocol/contracts/L2/Lib1559Math.sol index 8f4641e44dc..2bbd80f519f 100644 --- a/packages/protocol/contracts/L2/Lib1559Math.sol +++ b/packages/protocol/contracts/L2/Lib1559Math.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.24; import "../thirdparty/solmate/LibFixedPointMath.sol"; +import "../libs/LibMath.sol"; /// @title Lib1559Math /// @notice Implements e^(x) based bonding curve for EIP-1559 @@ -9,10 +10,39 @@ import "../thirdparty/solmate/LibFixedPointMath.sol"; /// difference as stated in docs/eip1559_on_l2.md. /// @custom:security-contact security@taiko.xyz library Lib1559Math { + using LibMath for uint256; + error EIP1559_INVALID_PARAMS(); + function calc1559BaseFee( + uint32 _gasTargetPerL1Block, + uint8 _adjustmentQuotient, + uint64 _gasExcess, + uint64 _gasIssuance, + uint32 _parentGasUsed + ) + internal + pure + returns (uint256 basefee_, uint64 gasExcess_) + { + // We always add the gas used by parent block to the gas excess + // value as this has already happened + uint256 excess = uint256(_gasExcess) + _parentGasUsed; + excess = excess > _gasIssuance ? excess - _gasIssuance : 1; + gasExcess_ = uint64(excess.min(type(uint64).max)); + + // The base fee per gas used by this block is the spot price at the + // bonding curve, regardless the actual amount of gas used by this + // block, however, this block's gas used will affect the next + // block's base fee. + basefee_ = basefee(gasExcess_, uint256(_adjustmentQuotient) * _gasTargetPerL1Block); + + // Always make sure basefee is nonzero, this is required by the node. + if (basefee_ == 0) basefee_ = 1; + } + /// @dev eth_qty(excess_gas_issued) / (TARGET * ADJUSTMENT_QUOTIENT) - /// @param _gasExcess TBD + /// @param _gasExcess The gas excess value /// @param _adjustmentFactor The product of gasTarget and adjustmentQuotient function basefee( uint256 _gasExcess, diff --git a/packages/protocol/contracts/L2/TaikoL2.sol b/packages/protocol/contracts/L2/TaikoL2.sol index 4dd7c2085b5..eb0c7e09f4f 100644 --- a/packages/protocol/contracts/L2/TaikoL2.sol +++ b/packages/protocol/contracts/L2/TaikoL2.sol @@ -4,12 +4,11 @@ pragma solidity 0.8.24; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import "../common/EssentialContract.sol"; import "../libs/LibAddress.sol"; -import "../libs/LibMath.sol"; import "../signal/ISignalService.sol"; import "../signal/LibSignals.sol"; import "./Lib1559Math.sol"; -import "./CrossChainOwned.sol"; /// @title TaikoL2 /// @notice Taiko L2 is a smart contract that handles cross-layer message @@ -18,9 +17,8 @@ import "./CrossChainOwned.sol"; /// communication, manage EIP-1559 parameters for gas pricing, and store /// verified L1 block information. /// @custom:security-contact security@taiko.xyz -contract TaikoL2 is CrossChainOwned { +contract TaikoL2 is EssentialContract { using LibAddress for address; - using LibMath for uint256; using SafeERC20 for IERC20; struct Config { @@ -31,9 +29,6 @@ contract TaikoL2 is CrossChainOwned { /// @notice Golden touch address is the only address that can do the anchor transaction. address public constant GOLDEN_TOUCH_ADDRESS = 0x0000777735367b36bC9B61C50022d9D0700dB4Ec; - /// @notice The number of L2 blocks to wait before syncing L1 block details. - uint8 public constant BLOCK_SYNC_THRESHOLD = 5; - /// @notice Mapping from L2 block numbers to their block hashes. All L2 block hashes will /// be saved in this mapping. mapping(uint256 blockId => bytes32 blockHash) public l2Hashes; @@ -55,7 +50,10 @@ contract TaikoL2 is CrossChainOwned { /// @notice The current block's timestamp. uint64 private __currentBlockTimestamp; - uint256[47] private __gap; + /// @notice The L1's chain ID. + uint64 public l1ChainId; + + uint256[46] private __gap; /// @notice Emitted when the latest L1 block details are anchored to L2. /// @param parentHash The hash of the parent block. @@ -63,7 +61,8 @@ contract TaikoL2 is CrossChainOwned { event Anchored(bytes32 parentHash, uint64 gasExcess); error L2_BASEFEE_MISMATCH(); - error L2_INVALID_CHAIN_ID(); + error L2_INVALID_L1_CHAIN_ID(); + error L2_INVALID_L2_CHAIN_ID(); error L2_INVALID_PARAM(); error L2_INVALID_SENDER(); error L2_PUBLIC_INPUT_HASH_MISMATCH(); @@ -83,10 +82,13 @@ contract TaikoL2 is CrossChainOwned { external initializer { - __CrossChainOwned_init(_owner, _addressManager, _l1ChainId); + __Essential_init(_owner, _addressManager); + if (_l1ChainId == 0 || _l1ChainId == block.chainid) { + revert L2_INVALID_L1_CHAIN_ID(); + } if (block.chainid <= 1 || block.chainid > type(uint64).max) { - revert L2_INVALID_CHAIN_ID(); + revert L2_INVALID_L2_CHAIN_ID(); } if (block.number == 0) { @@ -99,6 +101,7 @@ contract TaikoL2 is CrossChainOwned { revert L2_TOO_LATE(); } + l1ChainId = _l1ChainId; gasExcess = _gasExcess; (publicInputHash,) = _calcPublicInputHash(block.number); __currentBlockTimestamp = uint64(block.timestamp); @@ -106,6 +109,9 @@ contract TaikoL2 is CrossChainOwned { /// @notice Anchors the latest L1 block details to L2 for cross-layer /// message verification. + /// @dev This function can be called freely as the golden touch private key is publicly known, + /// but the Taiko node guarantees the first transaction of each block is always this anchor + /// transaction, and any subsequent calls will revert with L2_PUBLIC_INPUT_HASH_MISMATCH. /// @param _l1BlockHash The latest L1 block hash when this block was /// proposed. /// @param _l1StateRoot The latest L1 block's state root. @@ -140,23 +146,24 @@ contract TaikoL2 is CrossChainOwned { revert L2_PUBLIC_INPUT_HASH_MISMATCH(); } - Config memory config = getConfig(); - // Verify the base fee per gas is correct uint256 basefee; - (basefee, gasExcess) = _calc1559BaseFee(config, _l1BlockId, _parentGasUsed); + (basefee, gasExcess) = getBasefee(_l1BlockId, _parentGasUsed); + if (!skipFeeCheck() && block.basefee != basefee) { revert L2_BASEFEE_MISMATCH(); } - if (_l1BlockId > lastSyncedBlock + BLOCK_SYNC_THRESHOLD) { + if (_l1BlockId > lastSyncedBlock) { // Store the L1's state root as a signal to the local signal service to // allow for multi-hop bridging. ISignalService(resolve("signal_service", false)).syncChainData( - ownerChainId, LibSignals.STATE_ROOT, _l1BlockId, _l1StateRoot + l1ChainId, LibSignals.STATE_ROOT, _l1BlockId, _l1StateRoot ); + lastSyncedBlock = _l1BlockId; } + // Update state variables l2Hashes[parentId] = blockhash(parentId); publicInputHash = publicInputHashNew; @@ -192,15 +199,25 @@ contract TaikoL2 is CrossChainOwned { /// @param _l1BlockId The synced L1 height in the next Taiko block /// @param _parentGasUsed Gas used in the parent block. /// @return basefee_ The calculated EIP-1559 base fee per gas. + /// @return gasExcess_ The new gasExcess value. function getBasefee( uint64 _l1BlockId, uint32 _parentGasUsed ) public view - returns (uint256 basefee_) + returns (uint256 basefee_, uint64 gasExcess_) { - (basefee_,) = _calc1559BaseFee(getConfig(), _l1BlockId, _parentGasUsed); + Config memory config = getConfig(); + uint64 gasIssuance = uint64(_l1BlockId - lastSyncedBlock) * config.gasTargetPerL1Block; + + (basefee_, gasExcess_) = Lib1559Math.calc1559BaseFee( + config.gasTargetPerL1Block, + config.basefeeAdjustmentQuotient, + gasExcess, + gasIssuance, + _parentGasUsed + ); } /// @notice Retrieves the block hash for the given L2 block number. @@ -216,11 +233,10 @@ contract TaikoL2 is CrossChainOwned { /// @notice Returns EIP1559 related configurations. /// @return config_ struct containing configuration parameters. function getConfig() public view virtual returns (Config memory config_) { - // 4x Ethereum gas target, if we assume most of the time, L2 block time - // is 3s, and each block is full (gasUsed is 15_000_000), then its - // ~60_000_000, if the network is congester than that, the base fee - // will increase. - config_.gasTargetPerL1Block = 15 * 1e6 * 4; + // Assuming we sell 3x more blockspace than Ethereum: 15_000_000 * 4 + // Note that Brecht's concern is that this value may be too large. + // We need to monitor L2 state growth and lower this value when necessary. + config_.gasTargetPerL1Block = 60_000_000; config_.basefeeAdjustmentQuotient = 8; } @@ -258,51 +274,4 @@ contract TaikoL2 is CrossChainOwned { publicInputHashNew := keccak256(inputs, 8192 /*mul(256, 32)*/ ) } } - - function _calc1559BaseFee( - Config memory _config, - uint64 _l1BlockId, - uint32 _parentGasUsed - ) - private - view - returns (uint256 basefee_, uint64 gasExcess_) - { - // gasExcess being 0 indicate the dynamic 1559 base fee is disabled. - if (gasExcess != 0) { - // We always add the gas used by parent block to the gas excess - // value as this has already happened - uint256 excess = uint256(gasExcess) + _parentGasUsed; - - // Calculate how much more gas to issue to offset gas excess. - // after each L1 block time, config.gasTarget more gas is issued, - // the gas excess will be reduced accordingly. - // Note that when lastSyncedBlock is zero, we skip this step - // because that means this is the first time calculating the basefee - // and the difference between the L1 height would be extremely big, - // reverting the initial gas excess value back to 0. - uint256 numL1Blocks; - if (lastSyncedBlock != 0 && _l1BlockId > lastSyncedBlock) { - numL1Blocks = _l1BlockId - lastSyncedBlock; - } - - if (numL1Blocks != 0) { - uint256 issuance = numL1Blocks * _config.gasTargetPerL1Block; - excess = excess > issuance ? excess - issuance : 1; - } - - gasExcess_ = uint64(excess.min(type(uint64).max)); - - // The base fee per gas used by this block is the spot price at the - // bonding curve, regardless the actual amount of gas used by this - // block, however, this block's gas used will affect the next - // block's base fee. - basefee_ = Lib1559Math.basefee( - gasExcess_, uint256(_config.basefeeAdjustmentQuotient) * _config.gasTargetPerL1Block - ); - } - - // Always make sure basefee is nonzero, this is required by the node. - if (basefee_ == 0) basefee_ = 1; - } } diff --git a/packages/protocol/contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol b/packages/protocol/contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol index 31e0db79c8a..4e574b7eec9 100644 --- a/packages/protocol/contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol +++ b/packages/protocol/contracts/automata-attestation/lib/QuoteV3Auth/V3Parser.sol @@ -128,7 +128,7 @@ library V3Parser { + v3Quote.v3AuthData.qeAuthData.parsedDataSize + 2 // sizeof(v3Quote.v3AuthData.certification.certType) + 4 // sizeof(v3Quote.v3AuthData.certification.certDataSize) + v3Quote.v3AuthData.certification.certDataSize; - if (totalQuoteSize < MINIMUM_QUOTE_LENGTH) { + if (totalQuoteSize <= MINIMUM_QUOTE_LENGTH) { revert V3PARSER_INVALID_QUOTE_LENGTN(); } diff --git a/packages/protocol/contracts/bridge/Bridge.sol b/packages/protocol/contracts/bridge/Bridge.sol index 8bcb7991198..da7b4a69694 100644 --- a/packages/protocol/contracts/bridge/Bridge.sol +++ b/packages/protocol/contracts/bridge/Bridge.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.24; import "@openzeppelin/contracts/utils/Address.sol"; import "../common/EssentialContract.sol"; import "../libs/LibAddress.sol"; +import "../libs/LibNetwork.sol"; import "../signal/ISignalService.sol"; import "./IBridge.sol"; @@ -95,6 +96,7 @@ contract Bridge is EssentialContract, IBridge { /// @notice Ban or unban an address. A banned addresses will not be invoked upon /// with message calls. + /// @dev Do not make this function `nonReentrant`, this breaks {DelegateOwner} support. /// @param _addr The address to ban or unban. /// @param _ban True if ban, false if unban. function banAddress( @@ -103,7 +105,6 @@ contract Bridge is EssentialContract, IBridge { ) external onlyFromOwnerOrNamed("bridge_watchdog") - nonReentrant { if (addressBanned[_addr] == _ban) revert B_INVALID_STATUS(); addressBanned[_addr] = _ban; @@ -274,8 +275,8 @@ contract Bridge is EssentialContract, IBridge { refundAmount = _message.value; _updateMessageStatus(msgHash, Status.DONE); } else { - // Use the specified message gas limit if called by the owner, else - // use remaining gas + // Use the remaining gas if called by a the destOwner, else + // use the specified gas limit. uint256 gasLimit = msg.sender == _message.destOwner ? gasleft() : _message.gasLimit; if (_invokeMessageCall(_message, msgHash, gasLimit)) { @@ -417,24 +418,11 @@ contract Bridge is EssentialContract, IBridge { virtual returns (uint256 invocationDelay_, uint256 invocationExtraDelay_) { - if ( - block.chainid == 1 // Ethereum mainnet - ) { - // For Taiko mainnet + if (LibNetwork.isEthereumMainnetOrTestnet(block.chainid)) { + // For Taiko mainnet and public testnets // 384 seconds = 6.4 minutes = one ethereum epoch return (1 hours, 384 seconds); - } else if ( - block.chainid == 2 // Ropsten - || block.chainid == 4 // Rinkeby - || block.chainid == 5 // Goerli - || block.chainid == 42 // Kovan - || block.chainid == 17_000 // Holesky - || block.chainid == 11_155_111 // Sepolia - ) { - // For all Taiko public testnets - return (30 minutes, 384 seconds); - } else if (block.chainid >= 32_300 && block.chainid <= 32_400) { - // For all Taiko internal devnets + } else if (LibNetwork.isTaikoDevnet(block.chainid)) { return (5 minutes, 384 seconds); } else { // This is a Taiko L2 chain where no deleys are applied. @@ -518,7 +506,7 @@ contract Bridge is EssentialContract, IBridge { /// @notice Resets the call context function _resetContext() private { - if (block.chainid == 1) { + if (LibNetwork.isDencunSupported(block.chainid)) { _storeContext(bytes32(0), address(0), uint64(0)); } else { _storeContext(bytes32(PLACEHOLDER), address(uint160(PLACEHOLDER)), uint64(PLACEHOLDER)); @@ -530,7 +518,7 @@ contract Bridge is EssentialContract, IBridge { /// @param _from The sender's address. /// @param _srcChainId The source chain ID. function _storeContext(bytes32 _msgHash, address _from, uint64 _srcChainId) private { - if (block.chainid == 1) { + if (LibNetwork.isDencunSupported(block.chainid)) { assembly { tstore(_CTX_SLOT, _msgHash) tstore(add(_CTX_SLOT, 1), _from) @@ -544,7 +532,7 @@ contract Bridge is EssentialContract, IBridge { /// @notice Loads and returns the call context. /// @return ctx_ The call context. function _loadContext() private view returns (Context memory) { - if (block.chainid == 1) { + if (LibNetwork.isDencunSupported(block.chainid)) { bytes32 msgHash; address from; uint64 srcChainId; @@ -574,10 +562,12 @@ contract Bridge is EssentialContract, IBridge { private returns (bool) { - bytes memory data = abi.encodeCall( - ISignalService.proveSignalReceived, - (_chainId, resolve(_chainId, "bridge", false), _signal, _proof) - ); - return _signalService.sendEther(0, gasleft(), data); + try ISignalService(_signalService).proveSignalReceived( + _chainId, resolve(_chainId, "bridge", false), _signal, _proof + ) { + return true; + } catch { + return false; + } } } diff --git a/packages/protocol/contracts/common/EssentialContract.sol b/packages/protocol/contracts/common/EssentialContract.sol index a4d6dee54a9..20ba84e0fe4 100644 --- a/packages/protocol/contracts/common/EssentialContract.sol +++ b/packages/protocol/contracts/common/EssentialContract.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.24; import "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; import "./AddressResolver.sol"; +import "../libs/LibNetwork.sol"; /// @title EssentialContract /// @custom:security-contact security@taiko.xyz @@ -117,7 +118,7 @@ abstract contract EssentialContract is UUPSUpgradeable, Ownable2StepUpgradeable, // Stores the reentry lock function _storeReentryLock(uint8 _reentry) internal virtual { - if (block.chainid == 1) { + if (LibNetwork.isDencunSupported(block.chainid)) { assembly { tstore(_REENTRY_SLOT, _reentry) } @@ -128,7 +129,7 @@ abstract contract EssentialContract is UUPSUpgradeable, Ownable2StepUpgradeable, // Loads the reentry lock function _loadReentryLock() internal view virtual returns (uint8 reentry_) { - if (block.chainid == 1) { + if (LibNetwork.isDencunSupported(block.chainid)) { assembly { reentry_ := tload(_REENTRY_SLOT) } diff --git a/packages/protocol/contracts/libs/LibAddress.sol b/packages/protocol/contracts/libs/LibAddress.sol index 6afa642eb3a..095c1d73ca3 100644 --- a/packages/protocol/contracts/libs/LibAddress.sol +++ b/packages/protocol/contracts/libs/LibAddress.sol @@ -99,8 +99,4 @@ library LibAddress { return ECDSA.recover(_hash, _sig) == _addr; } } - - function isSenderEOA() internal view returns (bool) { - return msg.sender == tx.origin; - } } diff --git a/packages/protocol/contracts/libs/LibNetwork.sol b/packages/protocol/contracts/libs/LibNetwork.sol new file mode 100644 index 00000000000..d4d55cafa25 --- /dev/null +++ b/packages/protocol/contracts/libs/LibNetwork.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +/// @title LibNetwork +library LibNetwork { + uint256 internal constant MAINNET = 1; + uint256 internal constant ROPSTEN = 2; + uint256 internal constant RINKEBY = 4; + uint256 internal constant GOERLI = 5; + uint256 internal constant KOVAN = 42; + uint256 internal constant HOLESKY = 17_000; + uint256 internal constant SEPOLIA = 11_155_111; + + /// @dev Checks if the chain ID represents an Ethereum testnet. + /// @param _chainId The chain ID. + /// @return true if the chain ID represents an Ethereum testnet, false otherwise. + function isEthereumTestnet(uint256 _chainId) internal pure returns (bool) { + return _chainId == LibNetwork.ROPSTEN || _chainId == LibNetwork.RINKEBY + || _chainId == LibNetwork.GOERLI || _chainId == LibNetwork.KOVAN + || _chainId == LibNetwork.HOLESKY || _chainId == LibNetwork.SEPOLIA; + } + + /// @dev Checks if the chain ID represents an Ethereum testnet or the Etheruem mainnet. + /// @param _chainId The chain ID. + /// @return true if the chain ID represents an Ethereum testnet or the Etheruem mainnet, false + /// otherwise. + function isEthereumMainnetOrTestnet(uint256 _chainId) internal pure returns (bool) { + return _chainId == LibNetwork.MAINNET || isEthereumTestnet(_chainId); + } + + /// @dev Checks if the chain ID represents an internal Taiko devnet. + /// @param _chainId The chain ID. + /// @return true if the chain ID represents an internal Taiko devnet, false otherwise. + function isTaikoDevnet(uint256 _chainId) internal pure returns (bool) { + return _chainId >= 32_300 && _chainId <= 32_400; + } + + /// @dev Checks if the chain supports Dencun hardfork. Note that this check doesn't need to be + /// exhaustive. + /// @param _chainId The chain ID. + /// @return true if the chain supports Dencun hardfork, false otherwise. + function isDencunSupported(uint256 _chainId) internal pure returns (bool) { + return _chainId == LibNetwork.MAINNET || _chainId == LibNetwork.HOLESKY + || _chainId == LibNetwork.SEPOLIA; + } +} diff --git a/packages/protocol/contracts/tokenvault/BridgedERC1155.sol b/packages/protocol/contracts/tokenvault/BridgedERC1155.sol index 70aa16371fc..00ecffdcd9c 100644 --- a/packages/protocol/contracts/tokenvault/BridgedERC1155.sol +++ b/packages/protocol/contracts/tokenvault/BridgedERC1155.sol @@ -48,7 +48,7 @@ contract BridgedERC1155 is EssentialContract, IERC1155MetadataURIUpgradeable, ER // Check if provided parameters are valid. // The symbol and the name can be empty for ERC1155 tokens so we use some placeholder data // for them instead. - LibBridgedToken.validateInputs(_srcToken, _srcChainId, "foo", "foo"); + LibBridgedToken.validateInputs(_srcToken, _srcChainId); __Essential_init(_owner, _addressManager); __ERC1155_init(LibBridgedToken.buildURI(_srcToken, _srcChainId)); diff --git a/packages/protocol/contracts/tokenvault/BridgedERC721.sol b/packages/protocol/contracts/tokenvault/BridgedERC721.sol index 82f941182be..4425984f800 100644 --- a/packages/protocol/contracts/tokenvault/BridgedERC721.sol +++ b/packages/protocol/contracts/tokenvault/BridgedERC721.sol @@ -40,7 +40,7 @@ contract BridgedERC721 is EssentialContract, ERC721Upgradeable { initializer { // Check if provided parameters are valid - LibBridgedToken.validateInputs(_srcToken, _srcChainId, _symbol, _name); + LibBridgedToken.validateInputs(_srcToken, _srcChainId); __Essential_init(_owner, _addressManager); __ERC721_init(_name, _symbol); diff --git a/packages/protocol/contracts/tokenvault/ERC20Vault.sol b/packages/protocol/contracts/tokenvault/ERC20Vault.sol index f30a41eaafe..5aa6861aef6 100644 --- a/packages/protocol/contracts/tokenvault/ERC20Vault.sol +++ b/packages/protocol/contracts/tokenvault/ERC20Vault.sol @@ -12,7 +12,7 @@ import "./BaseVault.sol"; /// @title ERC20Vault /// @notice This vault holds all ERC20 tokens (excluding Ether) that users have /// deposited. It also manages the mapping between canonical ERC20 tokens and -/// their bridged tokens. +/// their bridged tokens. This vault does not support rebase/elastic tokens. /// @dev Labeled in AddressResolver as "erc20_vault". /// @custom:security-contact security@taiko.xyz contract ERC20Vault is BaseVault { diff --git a/packages/protocol/contracts/tokenvault/LibBridgedToken.sol b/packages/protocol/contracts/tokenvault/LibBridgedToken.sol index ad5944587a9..7578bee24d8 100644 --- a/packages/protocol/contracts/tokenvault/LibBridgedToken.sol +++ b/packages/protocol/contracts/tokenvault/LibBridgedToken.sol @@ -8,6 +8,12 @@ import "@openzeppelin/contracts/utils/Strings.sol"; library LibBridgedToken { error BTOKEN_INVALID_PARAMS(); + function validateInputs(address _srcToken, uint256 _srcChainId) internal view { + if (_srcToken == address(0) || _srcChainId == 0 || _srcChainId == block.chainid) { + revert BTOKEN_INVALID_PARAMS(); + } + } + function validateInputs( address _srcToken, uint256 _srcChainId, @@ -17,10 +23,8 @@ library LibBridgedToken { internal view { - if ( - _srcToken == address(0) || _srcChainId == 0 || _srcChainId == block.chainid - || bytes(_symbol).length == 0 || bytes(_name).length == 0 - ) { + validateInputs(_srcToken, _srcChainId); + if (bytes(_symbol).length == 0 || bytes(_name).length == 0) { revert BTOKEN_INVALID_PARAMS(); } } @@ -33,11 +37,17 @@ library LibBridgedToken { pure returns (string memory) { - return string.concat("Bridged ", _name, unicode" (⭀", Strings.toString(_srcChainId), ")"); + if (bytes(_name).length == 0) { + return ""; + } else { + return + string.concat("Bridged ", _name, unicode" (⭀", Strings.toString(_srcChainId), ")"); + } } function buildSymbol(string memory _symbol) internal pure returns (string memory) { - return string.concat(_symbol, ".t"); + if (bytes(_symbol).length == 0) return ""; + else return string.concat(_symbol, ".t"); } function buildURI( diff --git a/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol b/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol index 722dc187d71..4bf3a6b7d1a 100644 --- a/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol +++ b/packages/protocol/contracts/verifiers/libs/LibPublicInput.sol @@ -25,7 +25,7 @@ library LibPublicInput { bytes32 _metaHash, uint64 _chainId ) - public + internal pure returns (bytes32) { diff --git a/packages/protocol/genesis/GenerateGenesis.g.sol b/packages/protocol/genesis/GenerateGenesis.g.sol index f1874a56f18..63711cd1bfe 100644 --- a/packages/protocol/genesis/GenerateGenesis.g.sol +++ b/packages/protocol/genesis/GenerateGenesis.g.sol @@ -23,7 +23,7 @@ contract TestGenerateGenesis is Test, AddressResolver { vm.readFile(string.concat(vm.projectRoot(), "/deployments/genesis_alloc.json")); address private ownerTimelockController = configJSON.readAddress(".ownerTimelockController"); address private ownerSecurityCouncil = configJSON.readAddress(".ownerSecurityCouncil"); - uint256 private ownerChainId = configJSON.readUint(".ownerChainId"); + uint256 private l1ChainId = configJSON.readUint(".l1ChainId"); function testSharedContractsDeployment() public { assertEq(block.chainid, 167); @@ -109,13 +109,14 @@ contract TestGenerateGenesis is Test, AddressResolver { TaikoL2 taikoL2Proxy = TaikoL2(getPredeployedContractAddress("TaikoL2")); assertEq(ownerTimelockController, taikoL2Proxy.owner()); - assertEq(ownerChainId, taikoL2Proxy.ownerChainId()); + assertEq(l1ChainId, taikoL2Proxy.l1ChainId()); vm.startPrank(taikoL2Proxy.GOLDEN_TOUCH_ADDRESS()); for (uint32 i = 0; i < 300; ++i) { vm.roll(block.number + 1); vm.warp(block.number + 12); - vm.fee(taikoL2Proxy.getBasefee(12, i)); + (uint256 basefee,) = taikoL2Proxy.getBasefee(uint64(i + 1), i + 1); + vm.fee(basefee); uint256 gasLeftBefore = gasleft(); diff --git a/packages/protocol/genesis/test_config.js b/packages/protocol/genesis/test_config.js index aa65ccec1ce..ccbee737f28 100644 --- a/packages/protocol/genesis/test_config.js +++ b/packages/protocol/genesis/test_config.js @@ -4,7 +4,7 @@ const ADDRESS_LENGTH = 40; module.exports = { ownerTimelockController: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", ownerSecurityCouncil: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8", - ownerChainId: 1, + l1ChainId: 1, chainId: 167, seedAccounts: [ { diff --git a/packages/protocol/package.json b/packages/protocol/package.json index 5234731948c..a51f172ef9c 100644 --- a/packages/protocol/package.json +++ b/packages/protocol/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@types/node": "^20.11.20", - "@typescript-eslint/eslint-plugin": "^7.3.1", + "@typescript-eslint/eslint-plugin": "^7.1.0", "@typescript-eslint/parser": "^7.0.2", "eslint": "^8.51.0", "eslint-config-prettier": "^9.1.0", @@ -29,10 +29,10 @@ "eslint-plugin-prettier": "^5.0.1", "eslint-plugin-promise": "^6.1.1", "ethers": "^5.7.2", - "solc": "0.8.25", - "solhint": "^4.1.1", + "solc": "0.7.3", + "solhint": "^4.5.2", "ts-node": "^10.9.2", - "typescript": "^5.4.3" + "typescript": "^5.2.2" }, "dependencies": { "@openzeppelin/contracts": "4.9.6", diff --git a/packages/protocol/test/L1/TaikoL1.t.sol b/packages/protocol/test/L1/TaikoL1.t.sol index 0ffd8669010..4e8b2b1474f 100644 --- a/packages/protocol/test/L1/TaikoL1.t.sol +++ b/packages/protocol/test/L1/TaikoL1.t.sol @@ -92,13 +92,6 @@ contract TaikoL1Test is TaikoL1TestBase { vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); verifyBlock(Alice, 2); - - (TaikoData.Block memory blk, TaikoData.TransitionState memory ts) = L1.getBlock(meta.id); - assertEq(meta.id, blk.blockId); - - ts = L1.getTransition(meta.id, parentHash); - assertEq(ts.prover, Bob); - parentHash = blockHash; } printVariables(""); @@ -215,10 +208,6 @@ contract TaikoL1Test is TaikoL1TestBase { giveEthAndTko(Henry, 0, maxAmount + 1 ether); // So after this point we have 8 deposits - - vm.prank(Alice, Alice); - bool canAliceDeposit = L1.canDepositEthToL2(1 ether); - assertEq(true, canAliceDeposit); vm.prank(Alice, Alice); L1.depositEtherToL2{ value: 1 ether }(address(0)); vm.prank(Bob, Bob); @@ -257,96 +246,4 @@ contract TaikoL1Test is TaikoL1TestBase { 0x3b61cf81fd007398a8efd07a055ac8fb542bcfa62d76cf6dc28a889371afb21e ); } - - function test_pauseProving() external { - L1.pauseProving(true); - - TaikoData.BlockMetadata memory meta; - - giveEthAndTko(Alice, 1000 ether, 1000 ether); - giveEthAndTko(Bob, 1e8 ether, 100 ether); - - // Proposing is still possible - (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, - bytes32("01"), - bytes32("02"), - meta.minTier, - TaikoErrors.L1_PROVING_PAUSED.selector - ); - } - - function test_unpause() external { - L1.pause(); - - giveEthAndTko(Alice, 1000 ether, 1000 ether); - giveEthAndTko(Bob, 1e8 ether, 100 ether); - - // Proposing is also not possible - proposeButRevert(Alice, Bob, 1024, EssentialContract.INVALID_PAUSE_STATUS.selector); - - // unpause - L1.unpause(); - - // Proposing is possible again - proposeBlock(Alice, Bob, 1_000_000, 1024); - } - - function test_burn() external { - uint256 balanceBeforeBurn = tko.balanceOf(address(this)); - vm.prank(tko.owner(), tko.owner()); - tko.burn(address(this), 1 ether); - uint256 balanceAfterBurn = tko.balanceOf(address(this)); - - assertEq(balanceBeforeBurn - 1 ether, balanceAfterBurn); - } - - function test_snapshot() external { - vm.prank(tko.owner(), tko.owner()); - tko.snapshot(); - - uint256 totalSupplyAtSnapshot = tko.totalSupplyAt(1); - - vm.prank(tko.owner(), tko.owner()); - tko.burn(address(this), 1 ether); - - // At snapshot date vs. now, the total supply differs - assertEq(totalSupplyAtSnapshot, tko.totalSupply() + 1 ether); - } - - function test_getTierIds() external { - uint16[] memory tiers = cp.getTierIds(); - assertEq(tiers[0], LibTiers.TIER_OPTIMISTIC); - assertEq(tiers[1], LibTiers.TIER_SGX); - assertEq(tiers[2], LibTiers.TIER_GUARDIAN); - - vm.expectRevert(); - cp.getTier(123); - } - - function proposeButRevert( - address proposer, - address prover, - uint24 txListSize, - bytes4 revertReason - ) - internal - { - uint256 msgValue = 2 ether; - AssignmentHook.ProverAssignment memory assignment; - TaikoData.HookCall[] memory hookcalls = new TaikoData.HookCall[](1); - hookcalls[0] = TaikoData.HookCall(address(assignmentHook), abi.encode(assignment)); - - vm.prank(proposer, proposer); - vm.expectRevert(revertReason); - L1.proposeBlock{ value: msgValue }( - abi.encode(TaikoData.BlockParams(prover, address(0), 0, 0, hookcalls)), - new bytes(txListSize) - ); - } } diff --git a/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol b/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol index d80f9675d53..1d945354559 100644 --- a/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol +++ b/packages/protocol/test/L1/TaikoL1LibProvingWithTiers.t.sol @@ -559,6 +559,18 @@ contract TaikoL1LibProvingWithTiers is TaikoL1TestBase { proveBlock(Bob, Bob, meta, parentHash, stateRoot, stateRoot, meta.minTier, ""); // Prove as guardian + proveBlock( + Carol, + Carol, + meta, + parentHash, + blockHash, + bytes32(uint256(1)), + LibTiers.TIER_GUARDIAN, + "" + ); + + // Prove as guardian again proveBlock( Carol, Carol, meta, parentHash, blockHash, stateRoot, LibTiers.TIER_GUARDIAN, "" ); diff --git a/packages/protocol/test/L1/TaikoL1TestBase.sol b/packages/protocol/test/L1/TaikoL1TestBase.sol index ad3685656ac..768c27f6830 100644 --- a/packages/protocol/test/L1/TaikoL1TestBase.sol +++ b/packages/protocol/test/L1/TaikoL1TestBase.sol @@ -15,7 +15,7 @@ abstract contract TaikoL1TestBase is TaikoTest { SgxVerifier public sv; GuardianVerifier public gv; GuardianProver public gp; - TestnetTierProvider public cp; + TierProviderV1 public cp; Bridge public bridge; bytes32 public GENESIS_BLOCK_HASH = keccak256("GENESIS_BLOCK_HASH"); @@ -83,11 +83,11 @@ abstract contract TaikoL1TestBase is TaikoTest { setupGuardianProverMultisig(); - cp = TestnetTierProvider( + cp = TierProviderV1( deployProxy({ name: "tier_provider", - impl: address(new TestnetTierProvider()), - data: abi.encodeCall(TestnetTierProvider.init, (address(0))) + impl: address(new TierProviderV1()), + data: abi.encodeCall(TierProviderV1.init, (address(0))) }) ); diff --git a/packages/protocol/test/L2/TaikoL2.t.sol b/packages/protocol/test/L2/TaikoL2.t.sol index fe946766aea..ee9af02ca1c 100644 --- a/packages/protocol/test/L2/TaikoL2.t.sol +++ b/packages/protocol/test/L2/TaikoL2.t.sol @@ -63,8 +63,6 @@ contract TestTaikoL2 is TaikoTest { vm.roll(block.number + 1); vm.warp(block.timestamp + 30); - - vm.deal(address(L2), 100 ether); } function test_L2_AnchorTx_with_constant_block_time() external { @@ -139,22 +137,6 @@ contract TestTaikoL2 is TaikoTest { LibL2Signer.signAnchor(digest, uint8(3)); } - function test_L2_withdraw() external { - vm.prank(L2.owner(), L2.owner()); - L2.withdraw(address(0), Alice); - assertEq(address(L2).balance, 0 ether); - assertEq(Alice.balance, 100 ether); - - // Random EOA cannot call withdraw - vm.expectRevert(); - vm.prank(Alice, Alice); - L2.withdraw(address(0), Alice); - } - - function test_L2_getBlockHash() external { - assertEq(L2.getBlockHash(uint64(1000)), 0); - } - function _anchor(uint32 parentGasLimit) private { bytes32 l1Hash = randBytes32(); bytes32 l1StateRoot = randBytes32(); diff --git a/packages/protocol/test/L2/TaikoL2NoFeeCheck.t.sol b/packages/protocol/test/L2/TaikoL2NoFeeCheck.t.sol index a70bfabd597..0caba175d26 100644 --- a/packages/protocol/test/L2/TaikoL2NoFeeCheck.t.sol +++ b/packages/protocol/test/L2/TaikoL2NoFeeCheck.t.sol @@ -150,7 +150,7 @@ contract TestTaikoL2NoFeeCheck is TaikoTest { vm.prank(L2.GOLDEN_TOUCH_ADDRESS()); _anchorSimulation(currentGasUsed, l1Height); - uint256 currentBaseFee = L2.getBasefee(l1Height, currentGasUsed); + (uint256 currentBaseFee,) = L2.getBasefee(l1Height, currentGasUsed); allBaseFee += currentBaseFee; console2.log("Actual gas in L2 block is:", currentGasUsed); console2.log("L2block to baseFee is:", i, ":", currentBaseFee); diff --git a/packages/protocol/test/TaikoTest.sol b/packages/protocol/test/TaikoTest.sol index 972db50860e..3e98afd532c 100644 --- a/packages/protocol/test/TaikoTest.sol +++ b/packages/protocol/test/TaikoTest.sol @@ -24,7 +24,7 @@ import "../contracts/verifiers/libs/LibPublicInput.sol"; import "../contracts/verifiers/SgxVerifier.sol"; import "../contracts/verifiers/RiscZeroVerifier.sol"; import "../contracts/verifiers/GuardianVerifier.sol"; -import "../contracts/L1/tiers/TestnetTierProvider.sol"; +import "../contracts/L1/tiers/TierProviderV1.sol"; import "../contracts/L1/tiers/ITierProvider.sol"; import "../contracts/L1/tiers/ITierProvider.sol"; import "../contracts/L1/hooks/AssignmentHook.sol"; @@ -33,6 +33,7 @@ import "../contracts/L1/provers/GuardianProver.sol"; import "../contracts/L2/Lib1559Math.sol"; import "../contracts/L2/TaikoL2EIP1559Configurable.sol"; import "../contracts/L2/TaikoL2.sol"; +import "../contracts/L2/DelegateOwner.sol"; import "../contracts/team/TimelockTokenPool.sol"; import "../contracts/team/airdrop/ERC20Airdrop.sol"; diff --git a/packages/protocol/test/bridge/Bridge.t.sol b/packages/protocol/test/bridge/Bridge.t.sol index d94717d7319..ba20767a9c7 100644 --- a/packages/protocol/test/bridge/Bridge.t.sol +++ b/packages/protocol/test/bridge/Bridge.t.sol @@ -40,6 +40,9 @@ contract BridgeTest is TaikoTest { SignalService signalService; SkipProofCheckSignal mockProofSignalService; UntrustedSendMessageRelayer untrustedSenderContract; + DelegateOwner delegateOwner; + address mockDAO = randAddress(); //as "real" L1 owner + uint64 destChainId = 19_389; function setUp() public { @@ -85,6 +88,24 @@ contract BridgeTest is TaikoTest { ) ); + // "Deploy" on L2 only + uint64 l1ChainId = uint64(block.chainid); + vm.chainId(destChainId); + + delegateOwner = DelegateOwner( + payable( + deployProxy({ + name: "delegate_owner", + impl: address(new DelegateOwner()), + data: abi.encodeCall( + DelegateOwner.init, (mockDAO, address(addressManager), l1ChainId) + ) + }) + ) + ); + + vm.chainId(l1ChainId); + mockProofSignalService = SkipProofCheckSignal( deployProxy({ name: "signal_service", @@ -115,6 +136,15 @@ contract BridgeTest is TaikoTest { register(address(addressManager), "bridge", address(destChainBridge), destChainId); register(address(addressManager), "taiko", address(uint160(123)), destChainId); + + register(address(addressManager), "bridge_watchdog", address(uint160(123)), destChainId); + + // Otherwise delegateOwner cannot do actions on them, on behalf of the DAO. + destChainBridge.transferOwnership(address(delegateOwner)); + delegateOwner.acceptOwnership(address(destChainBridge)); + mockProofSignalService.transferOwnership(address(delegateOwner)); + delegateOwner.acceptOwnership(address(mockProofSignalService)); + vm.stopPrank(); } @@ -339,6 +369,147 @@ contract BridgeTest is TaikoTest { assertEq(Carol.balance, 500); } + function test_Bridge_banAddress_via_delegate_owner() public { + bytes memory banAddressCall = abi.encodeCall(Bridge.banAddress, (Alice, true)); + + IBridge.Message memory message = getDelegateOwnerMessage( + address(mockDAO), + abi.encodeCall( + DelegateOwner.onMessageInvocation, + abi.encode(0, address(destChainBridge), banAddressCall) + ) + ); + + // Mocking proof - but obviously it needs to be created in prod + // corresponding to the message + bytes memory proof = hex"00"; + + bytes32 msgHash = destChainBridge.hashMessage(message); + + vm.chainId(destChainId); + + vm.prank(Bob, Bob); + destChainBridge.processMessage(message, proof); + + IBridge.Status status = destChainBridge.messageStatus(msgHash); + assertEq(status == IBridge.Status.DONE, true); + } + + function test_Bridge_pause_bridge_via_delegate_owner() public { + bytes memory pauseCall = abi.encodeCall(EssentialContract.pause, ()); + + IBridge.Message memory message = getDelegateOwnerMessage( + address(mockDAO), + abi.encodeCall( + DelegateOwner.onMessageInvocation, + abi.encode(0, address(destChainBridge), pauseCall) + ) + ); + + // Mocking proof - but obviously it needs to be created in prod + // corresponding to the message + bytes memory proof = hex"00"; + + bytes32 msgHash = destChainBridge.hashMessage(message); + + vm.chainId(destChainId); + + vm.prank(Bob, Bob); + destChainBridge.processMessage(message, proof); + + IBridge.Status status = destChainBridge.messageStatus(msgHash); + assertEq(status == IBridge.Status.DONE, true); + + assertEq(destChainBridge.paused(), true); + } + + function test_Bridge_authorize_signal_service_via_delegate_owner() public { + assertEq(mockProofSignalService.isAuthorized(Alice), false); + + bytes memory authorizeCall = abi.encodeCall(SignalService.authorize, (Alice, true)); + + IBridge.Message memory message = getDelegateOwnerMessage( + address(mockDAO), + abi.encodeCall( + DelegateOwner.onMessageInvocation, + abi.encode(0, address(mockProofSignalService), authorizeCall) + ) + ); + + // Mocking proof - but obviously it needs to be created in prod + // corresponding to the message + bytes memory proof = hex"00"; + + bytes32 msgHash = destChainBridge.hashMessage(message); + + vm.chainId(destChainId); + + vm.prank(Bob, Bob); + destChainBridge.processMessage(message, proof); + + //Status is DONE, proper call + IBridge.Status status = destChainBridge.messageStatus(msgHash); + assertEq(status == IBridge.Status.DONE, true); + + assertEq(mockProofSignalService.isAuthorized(Alice), true); + } + + function test_Bridge_upgrade_delegate_owner() public { + // Needs a compatible impl. contract + address newDelegateOwnerImp = address(new DelegateOwner()); + bytes memory upgradeCall = abi.encodeCall(UUPSUpgradeable.upgradeTo, (newDelegateOwnerImp)); + + IBridge.Message memory message = getDelegateOwnerMessage( + address(mockDAO), + abi.encodeCall( + DelegateOwner.onMessageInvocation, + abi.encode(0, address(delegateOwner), upgradeCall) + ) + ); + + // Mocking proof - but obviously it needs to be created in prod + // corresponding to the message + bytes memory proof = hex"00"; + + bytes32 msgHash = destChainBridge.hashMessage(message); + + vm.chainId(destChainId); + + vm.prank(Bob, Bob); + destChainBridge.processMessage(message, proof); + + //Status is DONE,means a proper call + IBridge.Status status = destChainBridge.messageStatus(msgHash); + assertEq(status == IBridge.Status.DONE, true); + } + + function test_Bridge_non_dao_cannot_call_via_delegate_owner() public { + bytes memory banAddressCall = abi.encodeCall(Bridge.banAddress, (Alice, true)); + + IBridge.Message memory message = getDelegateOwnerMessage( + Alice, + abi.encodeCall( + DelegateOwner.onMessageInvocation, + abi.encode(0, address(destChainBridge), banAddressCall) + ) + ); + + // Mocking proof - but obviously it needs to be created in prod + // corresponding to the message + bytes memory proof = hex"00"; + + bytes32 msgHash = destChainBridge.hashMessage(message); + + vm.chainId(destChainId); + + vm.prank(Bob, Bob); + destChainBridge.processMessage(message, proof); + + //Status retriable hence the low level call failed as from is not the DAO! + IBridge.Status status = destChainBridge.messageStatus(msgHash); + assertEq(status == IBridge.Status.RETRIABLE, true); + } + function test_Bridge_send_message_ether_reverts_if_value_doesnt_match_expected() public { // uint256 amount = 1 wei; IBridge.Message memory message = newMessage({ @@ -565,70 +736,6 @@ contract BridgeTest is TaikoTest { assertEq(status == IBridge.Status.DONE, true); } - function test_Bridge_suspend_messages() public { - vm.startPrank(Alice); - (IBridge.Message memory message, bytes memory proof) = - setUpPredefinedSuccessfulProcessMessageCall(); - - bytes32 msgHash = destChainBridge.hashMessage(message); - bytes32[] memory messageHashes = new bytes32[](1); - messageHashes[0] = msgHash; - - vm.stopPrank(); - // Suspend - vm.prank(destChainBridge.owner(), destChainBridge.owner()); - destChainBridge.suspendMessages(messageHashes, true); - - vm.startPrank(Alice); - vm.expectRevert(Bridge.B_INVOCATION_TOO_EARLY.selector); - destChainBridge.processMessage(message, proof); - - vm.stopPrank(); - // Unsuspend - vm.prank(destChainBridge.owner(), destChainBridge.owner()); - destChainBridge.suspendMessages(messageHashes, false); - - vm.startPrank(Alice); - destChainBridge.processMessage(message, proof); - - IBridge.Status status = destChainBridge.messageStatus(msgHash); - - assertEq(status == IBridge.Status.DONE, true); - } - - function test_Bridge_ban_address() public { - vm.startPrank(Alice); - (IBridge.Message memory message, bytes memory proof) = - setUpPredefinedSuccessfulProcessMessageCall(); - - bytes32 msgHash = destChainBridge.hashMessage(message); - bytes32[] memory messageHashes = new bytes32[](1); - messageHashes[0] = msgHash; - - vm.stopPrank(); - // Ban address - vm.prank(destChainBridge.owner(), destChainBridge.owner()); - destChainBridge.banAddress(message.to, true); - - vm.startPrank(Alice); - // processMessage() still marks it DONE but dont call the invokeMessageCall on them - destChainBridge.processMessage(message, proof); - - IBridge.Status status = destChainBridge.messageStatus(msgHash); - - assertEq(status == IBridge.Status.DONE, true); - } - - function test_Bridge_prove_message_received() public { - vm.startPrank(Alice); - (IBridge.Message memory message, bytes memory proof) = - setUpPredefinedSuccessfulProcessMessageCall(); - - bool received = destChainBridge.proveMessageReceived(message, proof); - - assertEq(received, true); - } - // test with a known good merkle proof / message since we cant generate // proofs via rpc // in foundry @@ -760,4 +867,30 @@ contract BridgeTest is TaikoTest { memo: "" }); } + + function getDelegateOwnerMessage( + address from, + bytes memory encodedCall + ) + internal + view + returns (IBridge.Message memory message) + { + message = IBridge.Message({ + id: 0, + from: from, + srcChainId: uint64(block.chainid), + destChainId: destChainId, + srcOwner: Alice, //Does not matter who is the src/dest owner actually - except if we + // want to send ether + destOwner: Alice, + to: address(delegateOwner), + refundTo: Alice, + value: 0, + fee: 0, + gasLimit: 1_000_000, + data: encodedCall, + memo: "" + }); + } } diff --git a/packages/protocol/test/libs/LibAddress.t.sol b/packages/protocol/test/libs/LibAddress.t.sol deleted file mode 100644 index 5dca9d4bbe7..00000000000 --- a/packages/protocol/test/libs/LibAddress.t.sol +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.24; - -import "../TaikoTest.sol"; -import "../../contracts/libs/LibAddress.sol"; - -contract CalldataReceiver { - // Returns success - function returnSuccess() public pure returns (bool) { - return true; - } - - function supportsInterface(bytes4 interfaceId) external pure returns (bool result) { - if (interfaceId == 0x10101010) { - result = true; - } - } - - // Reverts - receive() external payable { - revert(); - } - - fallback() external payable { - revert(); - } -} - -/// @notice The reason we call LibAddress.sendEther this way is - and not directly inside of a -/// test_sendEther() because this way the coverage adds up if you call functions against initiated -/// contracts, so basically it is a workaround making the library test 'count' towards the coverage. -/// @dev The EtherSenderContract in live environment is the Bridge. -contract EtherSenderContract { - function sendEther( - address _to, - uint256 _amount, - uint256 _gasLimit, - bytes memory _calldata - ) - public - returns (bool success_) - { - success_ = LibAddress.sendEther(_to, _amount, _gasLimit, _calldata); - } - - function sendEtherAndVerify(address _to, uint256 _amount, uint256 _gasLimit) public { - LibAddress.sendEtherAndVerify(_to, _amount, _gasLimit); - } - - function sendEtherAndVerify(address _to, uint256 _amount) public { - LibAddress.sendEtherAndVerify(_to, _amount); - } - - function supportsInterface( - address _addr, - bytes4 _interfaceId - ) - public - view - returns (bool result) - { - return LibAddress.supportsInterface(_addr, _interfaceId); - } - - function isValidSignature( - address _addr, - bytes32 _hash, - bytes memory _sig - ) - public - view - returns (bool) - { - return LibAddress.isValidSignature(_addr, _hash, _sig); - } -} - -contract TestLibAddress is TaikoTest { - EtherSenderContract bridge; - CalldataReceiver calledContract; - - function setUp() public virtual { - bridge = new EtherSenderContract(); - vm.deal(address(bridge), 1 ether); - - calledContract = new CalldataReceiver(); - } - - function test_sendEther() public { - uint256 balanceBefore = Alice.balance; - bridge.sendEther((Alice), 0.5 ether, 2300, ""); - assertEq(Alice.balance, balanceBefore + 0.5 ether); - - // Cannot send to address(0) - vm.expectRevert(LibAddress.ETH_TRANSFER_FAILED.selector); - bridge.sendEther(address(0), 0.5 ether, 2300, ""); - } - - function test_sendEther_with_calldata() public { - bytes memory functionCalldata = abi.encodeCall(CalldataReceiver.returnSuccess, ()); - - bool success = bridge.sendEther(address(calledContract), 0, 230_000, functionCalldata); - - assertEq(success, true); - - // No input argument so it will fall to the fallback. - bytes memory wrongfunctionCalldata = - abi.encodeWithSelector(IERC165Upgradeable.supportsInterface.selector, 10); - success = bridge.sendEther(address(calledContract), 0, 230_000, wrongfunctionCalldata); - - assertEq(success, false); - } - - function test_sendEtherAndVerify() public { - uint256 balanceBefore = Alice.balance; - bridge.sendEtherAndVerify(Alice, 0.5 ether, 2300); - assertEq(Alice.balance, balanceBefore + 0.5 ether); - - // Send 0 ether is also possible - bridge.sendEtherAndVerify(Alice, 0, 2300); - - // If sending fails, call reverts - vm.expectRevert(LibAddress.ETH_TRANSFER_FAILED.selector); - bridge.sendEtherAndVerify(address(calledContract), 0.1 ether, 2300); - - //Call sendEtherAndVerify without the gasLimit - bridge.sendEtherAndVerify(Alice, 0.5 ether); - assertEq(Alice.balance, balanceBefore + 1 ether); - } - - function test_supportsInterface() public { - bool doesSupport = bridge.supportsInterface(Alice, 0x10101010); - - assertEq(doesSupport, false); - - doesSupport = bridge.supportsInterface(address(bridge), 0x10101010); - - assertEq(doesSupport, false); - - doesSupport = bridge.supportsInterface(address(calledContract), 0x10101010); - - assertEq(doesSupport, true); - } - - function test_isValidSignature() public { - bytes32 hash = bytes32("DUMMY_SIGNATURE_HASH_DATA"); - - // 0x1 is Alice's private key - (uint8 v, bytes32 r, bytes32 s) = vm.sign(0x1, hash); - bool validSignature = bridge.isValidSignature(Alice, hash, abi.encodePacked(r, s, v)); - - assertEq(validSignature, true); - } -} From 43ac1ea909a212853594692f12043a642df923d2 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:09:06 +0800 Subject: [PATCH 02/14] more --- packages/protocol/README.md | 2 +- packages/protocol/utils/generate_genesis/interface.ts | 2 +- packages/protocol/utils/generate_genesis/taikoL2.ts | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/protocol/README.md b/packages/protocol/README.md index b7425581a41..371e99b6972 100644 --- a/packages/protocol/README.md +++ b/packages/protocol/README.md @@ -38,7 +38,7 @@ module.exports = { { "0x79fcdef22feed20eddacbb2587640e45491b757f": 1024 }, ], // Owner Chain ID, Security Council, and Timelock Controller - ownerChainId: 31337, + l1ChainId: 31337, ownerSecurityCouncil: "0xDf08F82De32B8d460adbE8D72043E3a7e25A3B39", ownerTimelockController: "0xDf08F82De32B8d460adbE8D72043E3a7e25A3B39", // L2 EIP-1559 baseFee calculation related fields. diff --git a/packages/protocol/utils/generate_genesis/interface.ts b/packages/protocol/utils/generate_genesis/interface.ts index 36759d1b462..34b7f337bf1 100644 --- a/packages/protocol/utils/generate_genesis/interface.ts +++ b/packages/protocol/utils/generate_genesis/interface.ts @@ -1,7 +1,7 @@ export interface Config { ownerTimelockController: string; ownerSecurityCouncil: string; - ownerChainId: number; + l1ChainId: number; chainId: number; seedAccounts: Array<{ [key: string]: number; diff --git a/packages/protocol/utils/generate_genesis/taikoL2.ts b/packages/protocol/utils/generate_genesis/taikoL2.ts index e222008e081..72471e9e0ff 100644 --- a/packages/protocol/utils/generate_genesis/taikoL2.ts +++ b/packages/protocol/utils/generate_genesis/taikoL2.ts @@ -17,7 +17,7 @@ export async function deployTaikoL2( const { ownerTimelockController, ownerSecurityCouncil, - ownerChainId, + l1ChainId, chainId, seedAccounts, } = config; @@ -47,7 +47,7 @@ export async function deployTaikoL2( const contractConfigs: any = await generateContractConfigs( ownerTimelockController, ownerSecurityCouncil, - ownerChainId, + l1ChainId, chainId, config.contractAddresses, config.param1559, @@ -119,7 +119,7 @@ export async function deployTaikoL2( async function generateContractConfigs( ownerTimelockController: string, ownerSecurityCouncil: string, - ownerChainId: number, + l1ChainId: number, chainId: number, hardCodedAddresses: any, param1559: any, @@ -498,7 +498,7 @@ async function generateContractConfigs( // Ownable2Upgradeable _owner: ownerTimelockController, addressManager: addressMap.RollupAddressManager, - ownerChainId, + l1ChainId, gasExcess: param1559.gasExcess, // keccak256(abi.encodePacked(block.chainid, basefee, ancestors)) publicInputHash: `${ethers.utils.solidityKeccak256( From 9c153bdd3767390de0c70e12f35f06d3fe9f9cc7 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:17:47 +0800 Subject: [PATCH 03/14] more --- packages/protocol/script/DeployOnL1.s.sol | 10 +- .../script/upgrade/UpgradeTierProvider.s.sol | 4 +- .../utils/generate_genesis/taikoL2.ts | 126 ++++++++++-------- pnpm-lock.yaml | 81 ++++++----- 4 files changed, 126 insertions(+), 95 deletions(-) diff --git a/packages/protocol/script/DeployOnL1.s.sol b/packages/protocol/script/DeployOnL1.s.sol index 1ad38877e29..73600cb3ea9 100644 --- a/packages/protocol/script/DeployOnL1.s.sol +++ b/packages/protocol/script/DeployOnL1.s.sol @@ -7,8 +7,8 @@ import "../contracts/L1/TaikoToken.sol"; import "../contracts/L1/TaikoL1.sol"; import "../contracts/L1/provers/GuardianProver.sol"; import "../contracts/L1/tiers/DevnetTierProvider.sol"; -import "../contracts/L1/tiers/TestnetTierProvider.sol"; -import "../contracts/L1/tiers/MainnetTierProvider.sol"; +import "../contracts/L1/tiers/TierProviderV1.sol"; +import "../contracts/L1/tiers/TierProviderV2.sol"; import "../contracts/L1/hooks/AssignmentHook.sol"; import "../contracts/L1/gov/TaikoTimelockController.sol"; import "../contracts/L1/gov/TaikoGovernor.sol"; @@ -316,7 +316,7 @@ contract DeployOnL1 is DeployCapability { deployProxy({ name: "tier_provider", impl: deployTierProvider(vm.envString("TIER_PROVIDER")), - data: abi.encodeCall(TestnetTierProvider.init, (timelock)), + data: abi.encodeCall(TierProviderV1.init, (timelock)), registerTo: rollupAddressManager }); @@ -366,9 +366,9 @@ contract DeployOnL1 is DeployCapability { if (keccak256(abi.encode(tierProviderName)) == keccak256(abi.encode("devnet"))) { return address(new DevnetTierProvider()); } else if (keccak256(abi.encode(tierProviderName)) == keccak256(abi.encode("testnet"))) { - return address(new TestnetTierProvider()); + return address(new TierProviderV1()); } else if (keccak256(abi.encode(tierProviderName)) == keccak256(abi.encode("mainnet"))) { - return address(new MainnetTierProvider()); + return address(new TierProviderV2()); } else { revert("invalid tier provider"); } diff --git a/packages/protocol/script/upgrade/UpgradeTierProvider.s.sol b/packages/protocol/script/upgrade/UpgradeTierProvider.s.sol index f8429206b4a..2d9082876c8 100644 --- a/packages/protocol/script/upgrade/UpgradeTierProvider.s.sol +++ b/packages/protocol/script/upgrade/UpgradeTierProvider.s.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.24; import "../../test/DeployCapability.sol"; import "../../contracts/L1/gov/TaikoTimelockController.sol"; import "../../contracts/L1/tiers/ITierProvider.sol"; -import "../../contracts/L1/tiers/TestnetTierProvider.sol"; +import "../../contracts/L1/tiers/TierProviderV1.sol"; contract UpgradeTierProvider is DeployCapability { uint256 public privateKey = vm.envUint("PRIVATE_KEY"); @@ -14,7 +14,7 @@ contract UpgradeTierProvider is DeployCapability { function run() external { vm.startBroadcast(privateKey); - ITierProvider newTierProvider = new TestnetTierProvider(); + ITierProvider newTierProvider = new TierProviderV1(); registerByTimelock( addressManagerAddress, "tier_provider", address(newTierProvider), uint64(block.chainid) diff --git a/packages/protocol/utils/generate_genesis/taikoL2.ts b/packages/protocol/utils/generate_genesis/taikoL2.ts index 72471e9e0ff..3d8d9da863c 100644 --- a/packages/protocol/utils/generate_genesis/taikoL2.ts +++ b/packages/protocol/utils/generate_genesis/taikoL2.ts @@ -170,6 +170,10 @@ async function generateContractConfigs( "./AddressManager.sol/AddressManager.json", ), ), + // Libraries + LibNetwork: require( + path.join(ARTIFACTS_PATH, "./LibNetwork.sol/LibNetwork.json"), + ), }; const proxy = require( @@ -217,7 +221,7 @@ async function generateContractConfigs( // Shared Contracts SharedAddressManagerImpl: { address: addressMap.SharedAddressManagerImpl, - deployedBytecode: replaceUUPSImmutableValues( + deployedBytecode: replaceUUPSImmutableVaules( contractArtifacts.SharedAddressManagerImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad( @@ -234,10 +238,13 @@ async function generateContractConfigs( deployedBytecode: contractArtifacts.SharedAddressManager.deployedBytecode.object, variables: { - // initializer + // EssentialContract + __reentry: 1, // _FALSE + __paused: 1, // _FALSE + // EssentialContract => UUPSUpgradeable => Initializable _initialized: 1, _initializing: false, - // Ownable2Upgradeable + // EssentialContract => Ownable2StepUpgradeable _owner: ownerSecurityCouncil, // AddressManager __addresses: { @@ -277,7 +284,7 @@ async function generateContractConfigs( BridgeImpl: { address: addressMap.BridgeImpl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableValues( + replaceUUPSImmutableVaules( contractArtifacts.BridgeImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgeImpl, 32), @@ -292,15 +299,15 @@ async function generateContractConfigs( address: addressMap.Bridge, deployedBytecode: contractArtifacts.Bridge.deployedBytecode.object, variables: { - // initializer - _initialized: 1, - _initializing: false, - // ReentrancyGuardUpgradeable + // EssentialContract __reentry: 1, // _FALSE __paused: 1, // _FALSE - // Ownable2Upgradeable + // EssentialContract => UUPSUpgradeable => Initializable + _initialized: 1, + _initializing: false, + // EssentialContract => Ownable2StepUpgradeable _owner: ownerSecurityCouncil, - // AddressResolver + // EssentialContract => AddressResolver addressManager: addressMap.SharedAddressManager, }, slots: { @@ -311,7 +318,7 @@ async function generateContractConfigs( ERC20VaultImpl: { address: addressMap.ERC20VaultImpl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableValues( + replaceUUPSImmutableVaules( contractArtifacts.ERC20VaultImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.ERC20VaultImpl, 32), @@ -327,15 +334,15 @@ async function generateContractConfigs( deployedBytecode: contractArtifacts.ERC20Vault.deployedBytecode.object, variables: { - // initializer - _initialized: 1, - _initializing: false, - // ReentrancyGuardUpgradeable + // EssentialContract __reentry: 1, // _FALSE __paused: 1, // _FALSE - // Ownable2Upgradeable + // EssentialContract => UUPSUpgradeable => Initializable + _initialized: 1, + _initializing: false, + // EssentialContract => Ownable2StepUpgradeable _owner: ownerSecurityCouncil, - // AddressResolver + // EssentialContract => AddressResolver addressManager: addressMap.SharedAddressManager, }, slots: { @@ -346,7 +353,7 @@ async function generateContractConfigs( ERC721VaultImpl: { address: addressMap.ERC721VaultImpl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableValues( + replaceUUPSImmutableVaules( contractArtifacts.ERC721VaultImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.ERC721VaultImpl, 32), @@ -362,15 +369,15 @@ async function generateContractConfigs( deployedBytecode: contractArtifacts.ERC721Vault.deployedBytecode.object, variables: { - // initializer - _initialized: 1, - _initializing: false, - // ReentrancyGuardUpgradeable + // EssentialContract __reentry: 1, // _FALSE __paused: 1, // _FALSE - // Ownable2Upgradeable + // EssentialContract => UUPSUpgradeable => Initializable + _initialized: 1, + _initializing: false, + // EssentialContract => Ownable2StepUpgradeable _owner: ownerSecurityCouncil, - // AddressResolver + // EssentialContract => AddressResolver addressManager: addressMap.SharedAddressManager, }, slots: { @@ -381,7 +388,7 @@ async function generateContractConfigs( ERC1155VaultImpl: { address: addressMap.ERC1155VaultImpl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableValues( + replaceUUPSImmutableVaules( contractArtifacts.ERC1155VaultImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.ERC1155VaultImpl, 32), @@ -397,15 +404,15 @@ async function generateContractConfigs( deployedBytecode: contractArtifacts.ERC1155Vault.deployedBytecode.object, variables: { - // initializer - _initialized: 1, - _initializing: false, - // ReentrancyGuardUpgradeable + // EssentialContract __reentry: 1, // _FALSE __paused: 1, // _FALSE - // Ownable2Upgradeable + // EssentialContract => UUPSUpgradeable => Initializable + _initialized: 1, + _initializing: false, + // EssentialContract => Ownable2StepUpgradeable _owner: ownerSecurityCouncil, - // AddressResolver + // EssentialContract => AddressResolver addressManager: addressMap.SharedAddressManager, }, slots: { @@ -415,32 +422,32 @@ async function generateContractConfigs( }, BridgedERC20: { address: addressMap.BridgedERC20Impl, - deployedBytecode: replaceUUPSImmutableValues( + deployedBytecode: linkContractLibs(replaceUUPSImmutableVaules( contractArtifacts.BridgedERC20Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgedERC20Impl, 32), - ).deployedBytecode.object, + ), addressMap), }, BridgedERC721: { address: addressMap.BridgedERC721Impl, - deployedBytecode: replaceUUPSImmutableValues( + deployedBytecode: linkContractLibs(replaceUUPSImmutableVaules( contractArtifacts.BridgedERC721Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgedERC721Impl, 32), - ).deployedBytecode.object, + ), addressMap), }, BridgedERC1155: { address: addressMap.BridgedERC1155Impl, - deployedBytecode: replaceUUPSImmutableValues( + deployedBytecode: linkContractLibs(replaceUUPSImmutableVaules( contractArtifacts.BridgedERC1155Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgedERC1155Impl, 32), - ).deployedBytecode.object, + ), addressMap), }, SignalServiceImpl: { address: addressMap.SignalServiceImpl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableValues( + replaceUUPSImmutableVaules( contractArtifacts.SignalServiceImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.SignalServiceImpl, 32), @@ -456,15 +463,15 @@ async function generateContractConfigs( deployedBytecode: contractArtifacts.SignalService.deployedBytecode.object, variables: { - // initializer - _initialized: 1, - _initializing: false, - // ReentrancyGuardUpgradeable + // EssentialContract __reentry: 1, // _FALSE __paused: 1, // _FALSE - // Ownable2Upgradeable + // EssentialContract => UUPSUpgradeable => Initializable + _initialized: 1, + _initializing: false, + // EssentialContract => Ownable2StepUpgradeable _owner: ownerSecurityCouncil, - // AddressResolver + // EssentialContract => AddressResolver addressManager: addressMap.SharedAddressManager, isAuthorized: { [addressMap.TaikoL2]: true, @@ -479,7 +486,7 @@ async function generateContractConfigs( TaikoL2Impl: { address: addressMap.TaikoL2Impl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableValues( + replaceUUPSImmutableVaules( contractArtifacts.TaikoL2Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.TaikoL2Impl, 32), @@ -494,13 +501,20 @@ async function generateContractConfigs( address: addressMap.TaikoL2, deployedBytecode: contractArtifacts.TaikoL2.deployedBytecode.object, variables: { - // TaikoL2 - // Ownable2Upgradeable + // EssentialContract + __reentry: 1, // _FALSE + __paused: 1, // _FALSE + // EssentialContract => UUPSUpgradeable => Initializable + _initialized: 1, + _initializing: false, + // EssentialContract => Ownable2StepUpgradeable _owner: ownerTimelockController, + // EssentialContract => AddressResolver addressManager: addressMap.RollupAddressManager, + // TaikoL2 => CrossChainOwned l1ChainId, + // TaikoL2 gasExcess: param1559.gasExcess, - // keccak256(abi.encodePacked(block.chainid, basefee, ancestors)) publicInputHash: `${ethers.utils.solidityKeccak256( ["bytes32[256]"], [ @@ -522,7 +536,7 @@ async function generateContractConfigs( }, RollupAddressManagerImpl: { address: addressMap.RollupAddressManagerImpl, - deployedBytecode: replaceUUPSImmutableValues( + deployedBytecode: replaceUUPSImmutableVaules( contractArtifacts.RollupAddressManagerImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad( @@ -539,10 +553,13 @@ async function generateContractConfigs( deployedBytecode: contractArtifacts.RollupAddressManager.deployedBytecode.object, variables: { - // initializer + // EssentialContract + __reentry: 1, // _FALSE + __paused: 1, // _FALSE + // EssentialContract => UUPSUpgradeable => Initializable _initialized: 1, _initializing: false, - // Ownable2Upgradeable + // EssentialContract => Ownable2StepUpgradeable _owner: ownerSecurityCouncil, // AddressManager __addresses: { @@ -564,6 +581,11 @@ async function generateContractConfigs( }, isProxy: true, }, + // Libraries + LibNetwork: { + address: addressMap.LibNetwork, + deployedBytecode: contractArtifacts.LibNetwork.deployedBytecode.object, + }, }; } @@ -631,7 +653,7 @@ function getUUPSImmutableReferences() { return references; } -function replaceUUPSImmutableValues( +function replaceUUPSImmutableVaules( artifact: any, references: any, value: string, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c72f5e8958b..76b23c6d816 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -295,7 +295,7 @@ importers: specifier: ^20.11.20 version: 20.11.20 '@typescript-eslint/eslint-plugin': - specifier: ^7.3.1 + specifier: ^7.1.0 version: 7.3.1(@typescript-eslint/parser@7.0.2)(eslint@8.55.0)(typescript@5.4.3) '@typescript-eslint/parser': specifier: ^7.0.2 @@ -325,16 +325,16 @@ importers: specifier: ^5.7.2 version: 5.7.2 solc: - specifier: 0.8.25 - version: 0.8.25 + specifier: 0.7.3 + version: 0.7.3 solhint: - specifier: ^4.1.1 - version: 4.1.1(typescript@5.4.3) + specifier: ^4.5.2 + version: 4.5.2(typescript@5.4.3) ts-node: specifier: ^10.9.2 version: 10.9.2(@types/node@20.11.20)(typescript@5.4.3) typescript: - specifier: ^5.4.3 + specifier: ^5.2.2 version: 5.4.3 packages/relayer: {} @@ -4045,10 +4045,8 @@ packages: resolution: {integrity: sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==} dev: false - /@solidity-parser/parser@0.16.2: - resolution: {integrity: sha512-PI9NfoA3P8XK2VBkK5oIfRgKDsicwDZfkVq9ZTBCQYGOP1N2owgY2dyLGyU5/J/hQs8KRk55kdmvTLjy3Mu3vg==} - dependencies: - antlr4ts: 0.5.0-alpha.4 + /@solidity-parser/parser@0.18.0: + resolution: {integrity: sha512-yfORGUIPgLck41qyN7nbwJRAx17/jAIXCTanHOJZhB6PJ1iAk/84b/xlsVKFSyNyLXIj0dhppoE0+CRws7wlzA==} dev: true /@stablelib/aead@1.0.1: @@ -5903,10 +5901,6 @@ packages: engines: {node: '>=16'} dev: true - /antlr4ts@0.5.0-alpha.4: - resolution: {integrity: sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==} - dev: true - /any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} dev: true @@ -6656,16 +6650,15 @@ packages: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} dev: false + /commander@3.0.2: + resolution: {integrity: sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==} + dev: true + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} dev: true - /commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - dev: true - /commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -8310,6 +8303,16 @@ packages: engines: {node: '>= 0.6'} dev: false + /fs-extra@0.30.0: + resolution: {integrity: sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 2.4.0 + klaw: 1.3.1 + path-is-absolute: 1.0.1 + rimraf: 2.7.1 + dev: true + /fs-extra@11.2.0: resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} engines: {node: '>=14.14'} @@ -9475,6 +9478,12 @@ packages: /jsonc-parser@3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} + /jsonfile@2.4.0: + resolution: {integrity: sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==} + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + /jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} optionalDependencies: @@ -9514,6 +9523,12 @@ packages: engines: {node: '>=0.10.0'} dev: false + /klaw@1.3.1: + resolution: {integrity: sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==} + optionalDependencies: + graceful-fs: 4.2.11 + dev: true + /kleur@3.0.3: resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} engines: {node: '>=6'} @@ -11866,14 +11881,6 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - /semver@7.5.4: - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} - engines: {node: '>=10'} - hasBin: true - dependencies: - lru-cache: 6.0.0 - dev: true - /semver@7.6.0: resolution: {integrity: sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==} engines: {node: '>=10'} @@ -12078,27 +12085,29 @@ packages: - supports-color dev: false - /solc@0.8.25: - resolution: {integrity: sha512-7P0TF8gPeudl1Ko3RGkyY6XVCxe2SdD/qQhtns1vl3yAbK/PDifKDLHGtx1t7mX3LgR7ojV7Fg/Kc6Q9D2T8UQ==} - engines: {node: '>=10.0.0'} + /solc@0.7.3: + resolution: {integrity: sha512-GAsWNAjGzIDg7VxzP6mPjdurby3IkGCjQcM8GFYZT6RyaoUZKmMU6Y7YwG+tFGhv7dwZ8rmR4iwFDrrD99JwqA==} + engines: {node: '>=8.0.0'} hasBin: true dependencies: command-exists: 1.2.9 - commander: 8.3.0 + commander: 3.0.2 follow-redirects: 1.15.5(debug@4.3.4) + fs-extra: 0.30.0 js-sha3: 0.8.0 memorystream: 0.3.1 + require-from-string: 2.0.2 semver: 5.7.2 tmp: 0.0.33 transitivePeerDependencies: - debug dev: true - /solhint@4.1.1(typescript@5.4.3): - resolution: {integrity: sha512-7G4iF8H5hKHc0tR+/uyZesSKtfppFIMvPSW+Ku6MSL25oVRuyFeqNhOsXHfkex64wYJyXs4fe+pvhB069I19Tw==} + /solhint@4.5.2(typescript@5.4.3): + resolution: {integrity: sha512-o7MNYS5QPgE6l+PTGOTAUtCzo0ZLnffQsv586hntSHBe2JbSDfkoxfhAOcjZjN4OesTgaX4UEEjCjH9y/4BP5w==} hasBin: true dependencies: - '@solidity-parser/parser': 0.16.2 + '@solidity-parser/parser': 0.18.0 ajv: 6.12.6 antlr4: 4.13.1 ast-parents: 0.0.1 @@ -12107,12 +12116,12 @@ packages: cosmiconfig: 8.3.6(typescript@5.4.3) fast-diff: 1.3.0 glob: 8.1.0 - ignore: 5.3.0 + ignore: 5.3.1 js-yaml: 4.1.0 latest-version: 7.0.0 lodash: 4.17.21 pluralize: 8.0.0 - semver: 7.5.4 + semver: 7.6.0 strip-ansi: 6.0.1 table: 6.8.1 text-table: 0.2.0 From 952ea3a773512708a9600905ce2b21650e9b3f8e Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:19:01 +0800 Subject: [PATCH 04/14] more --- packages/protocol/docs/contestable_validity_rollup.md | 2 +- packages/protocol/docs/native_token_support.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/protocol/docs/contestable_validity_rollup.md b/packages/protocol/docs/contestable_validity_rollup.md index 11355ff6440..f48e89d8c89 100644 --- a/packages/protocol/docs/contestable_validity_rollup.md +++ b/packages/protocol/docs/contestable_validity_rollup.md @@ -16,7 +16,7 @@ For a given block, a transition can be uniquely identified by its _parent hash_. The _first transition_ of a block is reserved for the block's assigned prover. However, this exclusivity is contingent upon the assigned prover successfully proving the block within the stipulated proving window of the tier. If the assigned prover fails to meet this deadline, the transition is considered _open_. Upon its opening, the assigned prover is no longer allowed to prove the first transition. -For all other transitions, the proving window doesn't apply. Here, the principle is straightforward: the quickest prover takes the lead. Importantly, the assigned prover is not allowed to prove transitions other than the first one. +For all other transitions, the proving window doesn't apply. Here, the principle is straightforward: the quickest prover takes the lead. Importantly, the assigned prover is not allowed to prove transtions other than the first one. ## Proof Tier Selection diff --git a/packages/protocol/docs/native_token_support.md b/packages/protocol/docs/native_token_support.md index 0c58f5a7d14..0949b51d335 100644 --- a/packages/protocol/docs/native_token_support.md +++ b/packages/protocol/docs/native_token_support.md @@ -2,11 +2,11 @@ ![Wrapped_vs_Native](./images/native_support.png "Wrapped vs. Native bridging") -Taiko's bridging concept is a lock-and-mint type. It simply means (the red path above) on the canonical chain we take custody of the assets and on the destination chain we mint the wrapped counterpart. When someone wants to bridge back (from destination to canonical) it will first burn the tokens, then release the funds on the canonical chain. +Taiko's briding concept is a lock-and-mint type. It simply means (the red path above) on the canonical chain we take custody of the assets and on the destination chain we mint the wrapped counterpart. When someone wants to bridge back (from destination to canonical) it will first burn the tokens, then release the funds on the canonical chain. But there might be some incentives (e.g.: adoption, liquidity fragmentation, etc.) when deploying a native token on the destination chain is beneficial. For this reason Taiko introduced the possibility of deploying the canonical assets (together with all their sub/parent/proxy contracts) and plug it into our ERC20Vault via adapters (green path). -Important to note that while wrapped asset bridging is 'automatically', the native one requires the willingness and efforts from Taiko side (and maybe also original token issuer green light to recognise officially as "native"), to support that type of asset-transfer. +Important to note that while wrapped asset briding is 'automatical', the native one requires the willingness and efforts from Taiko side (and maybe also original token issuer green light to recognise officially as "native"), to support that type of asset-transfer. ## Howto @@ -14,7 +14,7 @@ There are some steps to do in order to facilitate native token bridging. In the 1. Deploy the same (bytecode equivalent) ERC-20 token on L2. An example of the contracts + deployments can be found in our [USDC repo](https://github.com/taikoxyz/USDC). 2. Deploy adapter (e.g.: [USDC adapter](../contracts/tokenvault/adapters/USDCAdapter.sol)). As this will serve as the plug-in to our `ERC20Vault` for custom (native) tokens. This adapter serves multiple purposes. It is also a wrapper around the native token in a way - that it matches our conform `ERC20Vault` interfaces so that we can be sure any kind of native ERC-20 can be supported on L2. Also can handle custom logic required by the native asset (roles handling, specific logic, etc.). -3. Transfer the ownership (if not already owned by) to `ERC20Vault` owner since those 2 have to be owned by the same address. (!IMPORTANT! Not the token owned by the same owner, but the token adapter! USDC owner will still be Circle on L2.) +3. Transfer the ownership (if not already owned by) to `ERC20Vault` owner since those 2 have to be owned by the same address. (!IMPORTANT! Not the token owned by the same owner, but the token adpter! USDC owner will still be Circle on L2.) 4. Since our bridge is permissionless, there might have been some USDC bridge operations in the past. It would mean, there is already an existing `BridgedUSDC` on our L2. To overcome liquidity fragmentation, we (Taiko) need to call `ERC20Vault` `changeBridgedToken()` function with the appropriate parameters. This way the "old" `BridgedUSDC` can be migrated to this new native token and the bridging operation will mint into the new token frm that point on. The above steps (2. - 4.) is incorporated into the script [DeployUSDCAdapter.s.sol](../script/DeployUSDCAdapter.s.sol). From ac311a118f6bcbf3e4616ce3ed40f16fac625c51 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:24:27 +0800 Subject: [PATCH 05/14] more --- packages/protocol/CHANGELOG.md | 2 +- .../protocol/contracts/L1/libs/LibProving.sol | 2 +- .../protocol/docs/native_token_support.md | 4 ++-- .../utils/generate_genesis/taikoL2.ts | 24 +++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/protocol/CHANGELOG.md b/packages/protocol/CHANGELOG.md index 31c4a1f399d..93a850e2659 100644 --- a/packages/protocol/CHANGELOG.md +++ b/packages/protocol/CHANGELOG.md @@ -22,7 +22,7 @@ * **protocol:** add ERC20Airdrop test and deployment script ([#15752](https://github.com/taikoxyz/taiko-mono/issues/15752)) ([e60588c](https://github.com/taikoxyz/taiko-mono/commit/e60588cd5d455d0237ba7f7860d575a727f52103)) * **protocol:** add GuardianApproval event to GuardianProver ([#15817](https://github.com/taikoxyz/taiko-mono/issues/15817)) ([78f0481](https://github.com/taikoxyz/taiko-mono/commit/78f04812de1bcb22ed40c9ae9b16e42d3d3783c2)) * **protocol:** add message owner parameter to vault operations ([#15770](https://github.com/taikoxyz/taiko-mono/issues/15770)) ([136bdb7](https://github.com/taikoxyz/taiko-mono/commit/136bdb7395f4a30a76884c70310c02645ebaead2)) -* **protocol:** add one missing `replaceUUPSImmutableVaules` in genesis generation script ([#15479](https://github.com/taikoxyz/taiko-mono/issues/15479)) ([24d73e7](https://github.com/taikoxyz/taiko-mono/commit/24d73e7e8a2bc324068f296cdcaadd0d87441586)) +* **protocol:** add one missing `replaceUUPSImmutableValues` in genesis generation script ([#15479](https://github.com/taikoxyz/taiko-mono/issues/15479)) ([24d73e7](https://github.com/taikoxyz/taiko-mono/commit/24d73e7e8a2bc324068f296cdcaadd0d87441586)) * **protocol:** Add parent's metaHash to assignment ([#15498](https://github.com/taikoxyz/taiko-mono/issues/15498)) ([267e9a0](https://github.com/taikoxyz/taiko-mono/commit/267e9a083033d19adc7a78af1a191cbfa16937b6)) * **protocol:** add QuillAudits report ([#16186](https://github.com/taikoxyz/taiko-mono/issues/16186)) ([b0ce62e](https://github.com/taikoxyz/taiko-mono/commit/b0ce62ed8c55acce04660b39ae1eb677858aabf1)) * **protocol:** Add TaikoGovernor ([#15228](https://github.com/taikoxyz/taiko-mono/issues/15228)) ([f4a007b](https://github.com/taikoxyz/taiko-mono/commit/f4a007b024e5a868a59e9c97125dd9b9d884b45f)) diff --git a/packages/protocol/contracts/L1/libs/LibProving.sol b/packages/protocol/contracts/L1/libs/LibProving.sol index 4ed79bbace7..9d35e0b971e 100644 --- a/packages/protocol/contracts/L1/libs/LibProving.sol +++ b/packages/protocol/contracts/L1/libs/LibProving.sol @@ -384,7 +384,7 @@ library LibProving { // The contested transition is proven to be valid, contester loses the game reward = _rewardAfterFriction(_ts.contestBond); - // We return the validity bond back, but the origianl prover doesn't get any reward. + // We return the validity bond back, but the original prover doesn't get any reward. _tko.safeTransfer(_ts.prover, _ts.validityBond); } else { // The contested transition is proven to be invalid, contester wins the game. diff --git a/packages/protocol/docs/native_token_support.md b/packages/protocol/docs/native_token_support.md index 0949b51d335..0d01ff616e8 100644 --- a/packages/protocol/docs/native_token_support.md +++ b/packages/protocol/docs/native_token_support.md @@ -6,7 +6,7 @@ Taiko's briding concept is a lock-and-mint type. It simply means (the red path a But there might be some incentives (e.g.: adoption, liquidity fragmentation, etc.) when deploying a native token on the destination chain is beneficial. For this reason Taiko introduced the possibility of deploying the canonical assets (together with all their sub/parent/proxy contracts) and plug it into our ERC20Vault via adapters (green path). -Important to note that while wrapped asset briding is 'automatical', the native one requires the willingness and efforts from Taiko side (and maybe also original token issuer green light to recognise officially as "native"), to support that type of asset-transfer. +Important to note that while wrapped asset briding is 'automatic', the native one requires the willingness and efforts from Taiko side (and maybe also original token issuer green light to recognise officially as "native"), to support that type of asset-transfer. ## Howto @@ -14,7 +14,7 @@ There are some steps to do in order to facilitate native token bridging. In the 1. Deploy the same (bytecode equivalent) ERC-20 token on L2. An example of the contracts + deployments can be found in our [USDC repo](https://github.com/taikoxyz/USDC). 2. Deploy adapter (e.g.: [USDC adapter](../contracts/tokenvault/adapters/USDCAdapter.sol)). As this will serve as the plug-in to our `ERC20Vault` for custom (native) tokens. This adapter serves multiple purposes. It is also a wrapper around the native token in a way - that it matches our conform `ERC20Vault` interfaces so that we can be sure any kind of native ERC-20 can be supported on L2. Also can handle custom logic required by the native asset (roles handling, specific logic, etc.). -3. Transfer the ownership (if not already owned by) to `ERC20Vault` owner since those 2 have to be owned by the same address. (!IMPORTANT! Not the token owned by the same owner, but the token adpter! USDC owner will still be Circle on L2.) +3. Transfer the ownership (if not already owned by) to `ERC20Vault` owner since those 2 have to be owned by the same address. (!IMPORTANT! Not the token owned by the same owner, but the token adapter! USDC owner will still be Circle on L2.) 4. Since our bridge is permissionless, there might have been some USDC bridge operations in the past. It would mean, there is already an existing `BridgedUSDC` on our L2. To overcome liquidity fragmentation, we (Taiko) need to call `ERC20Vault` `changeBridgedToken()` function with the appropriate parameters. This way the "old" `BridgedUSDC` can be migrated to this new native token and the bridging operation will mint into the new token frm that point on. The above steps (2. - 4.) is incorporated into the script [DeployUSDCAdapter.s.sol](../script/DeployUSDCAdapter.s.sol). diff --git a/packages/protocol/utils/generate_genesis/taikoL2.ts b/packages/protocol/utils/generate_genesis/taikoL2.ts index 3d8d9da863c..4fd7e5aac55 100644 --- a/packages/protocol/utils/generate_genesis/taikoL2.ts +++ b/packages/protocol/utils/generate_genesis/taikoL2.ts @@ -221,7 +221,7 @@ async function generateContractConfigs( // Shared Contracts SharedAddressManagerImpl: { address: addressMap.SharedAddressManagerImpl, - deployedBytecode: replaceUUPSImmutableVaules( + deployedBytecode: replaceUUPSImmutableValues( contractArtifacts.SharedAddressManagerImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad( @@ -284,7 +284,7 @@ async function generateContractConfigs( BridgeImpl: { address: addressMap.BridgeImpl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableVaules( + replaceUUPSImmutableValues( contractArtifacts.BridgeImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgeImpl, 32), @@ -318,7 +318,7 @@ async function generateContractConfigs( ERC20VaultImpl: { address: addressMap.ERC20VaultImpl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableVaules( + replaceUUPSImmutableValues( contractArtifacts.ERC20VaultImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.ERC20VaultImpl, 32), @@ -353,7 +353,7 @@ async function generateContractConfigs( ERC721VaultImpl: { address: addressMap.ERC721VaultImpl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableVaules( + replaceUUPSImmutableValues( contractArtifacts.ERC721VaultImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.ERC721VaultImpl, 32), @@ -388,7 +388,7 @@ async function generateContractConfigs( ERC1155VaultImpl: { address: addressMap.ERC1155VaultImpl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableVaules( + replaceUUPSImmutableValues( contractArtifacts.ERC1155VaultImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.ERC1155VaultImpl, 32), @@ -422,7 +422,7 @@ async function generateContractConfigs( }, BridgedERC20: { address: addressMap.BridgedERC20Impl, - deployedBytecode: linkContractLibs(replaceUUPSImmutableVaules( + deployedBytecode: linkContractLibs(replaceUUPSImmutableValues( contractArtifacts.BridgedERC20Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgedERC20Impl, 32), @@ -430,7 +430,7 @@ async function generateContractConfigs( }, BridgedERC721: { address: addressMap.BridgedERC721Impl, - deployedBytecode: linkContractLibs(replaceUUPSImmutableVaules( + deployedBytecode: linkContractLibs(replaceUUPSImmutableValues( contractArtifacts.BridgedERC721Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgedERC721Impl, 32), @@ -438,7 +438,7 @@ async function generateContractConfigs( }, BridgedERC1155: { address: addressMap.BridgedERC1155Impl, - deployedBytecode: linkContractLibs(replaceUUPSImmutableVaules( + deployedBytecode: linkContractLibs(replaceUUPSImmutableValues( contractArtifacts.BridgedERC1155Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgedERC1155Impl, 32), @@ -447,7 +447,7 @@ async function generateContractConfigs( SignalServiceImpl: { address: addressMap.SignalServiceImpl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableVaules( + replaceUUPSImmutableValues( contractArtifacts.SignalServiceImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.SignalServiceImpl, 32), @@ -486,7 +486,7 @@ async function generateContractConfigs( TaikoL2Impl: { address: addressMap.TaikoL2Impl, deployedBytecode: linkContractLibs( - replaceUUPSImmutableVaules( + replaceUUPSImmutableValues( contractArtifacts.TaikoL2Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.TaikoL2Impl, 32), @@ -536,7 +536,7 @@ async function generateContractConfigs( }, RollupAddressManagerImpl: { address: addressMap.RollupAddressManagerImpl, - deployedBytecode: replaceUUPSImmutableVaules( + deployedBytecode: replaceUUPSImmutableValues( contractArtifacts.RollupAddressManagerImpl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad( @@ -653,7 +653,7 @@ function getUUPSImmutableReferences() { return references; } -function replaceUUPSImmutableVaules( +function replaceUUPSImmutableValues( artifact: any, references: any, value: string, From c2845fe8077a417913dfd21e90abed279bbcccec Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:25:36 +0800 Subject: [PATCH 06/14] more --- packages/protocol/docs/contestable_validity_rollup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/protocol/docs/contestable_validity_rollup.md b/packages/protocol/docs/contestable_validity_rollup.md index f48e89d8c89..11355ff6440 100644 --- a/packages/protocol/docs/contestable_validity_rollup.md +++ b/packages/protocol/docs/contestable_validity_rollup.md @@ -16,7 +16,7 @@ For a given block, a transition can be uniquely identified by its _parent hash_. The _first transition_ of a block is reserved for the block's assigned prover. However, this exclusivity is contingent upon the assigned prover successfully proving the block within the stipulated proving window of the tier. If the assigned prover fails to meet this deadline, the transition is considered _open_. Upon its opening, the assigned prover is no longer allowed to prove the first transition. -For all other transitions, the proving window doesn't apply. Here, the principle is straightforward: the quickest prover takes the lead. Importantly, the assigned prover is not allowed to prove transtions other than the first one. +For all other transitions, the proving window doesn't apply. Here, the principle is straightforward: the quickest prover takes the lead. Importantly, the assigned prover is not allowed to prove transitions other than the first one. ## Proof Tier Selection From 27b6f44470a4732d3f340afffea8186406878978 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:47:40 +0800 Subject: [PATCH 07/14] fixes --- packages/protocol/docs/native_token_support.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/protocol/docs/native_token_support.md b/packages/protocol/docs/native_token_support.md index 0d01ff616e8..4fce256435e 100644 --- a/packages/protocol/docs/native_token_support.md +++ b/packages/protocol/docs/native_token_support.md @@ -2,11 +2,11 @@ ![Wrapped_vs_Native](./images/native_support.png "Wrapped vs. Native bridging") -Taiko's briding concept is a lock-and-mint type. It simply means (the red path above) on the canonical chain we take custody of the assets and on the destination chain we mint the wrapped counterpart. When someone wants to bridge back (from destination to canonical) it will first burn the tokens, then release the funds on the canonical chain. +Taiko's bridging concept is a lock-and-mint type. It simply means (the red path above) on the canonical chain we take custody of the assets and on the destination chain we mint the wrapped counterpart. When someone wants to bridge back (from destination to canonical) it will first burn the tokens, then release the funds on the canonical chain. But there might be some incentives (e.g.: adoption, liquidity fragmentation, etc.) when deploying a native token on the destination chain is beneficial. For this reason Taiko introduced the possibility of deploying the canonical assets (together with all their sub/parent/proxy contracts) and plug it into our ERC20Vault via adapters (green path). -Important to note that while wrapped asset briding is 'automatic', the native one requires the willingness and efforts from Taiko side (and maybe also original token issuer green light to recognise officially as "native"), to support that type of asset-transfer. +Important to note that while wrapped asset bridging is 'automatic', the native one requires the willingness and efforts from Taiko side (and maybe also original token issuer green light to recognise officially as "native"), to support that type of asset-transfer. ## Howto From 9a6ff9b66351edce788446d33362d205f931b79d Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:49:50 +0800 Subject: [PATCH 08/14] Update TaikoL1.t.sol --- packages/protocol/test/L1/TaikoL1.t.sol | 103 ++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/packages/protocol/test/L1/TaikoL1.t.sol b/packages/protocol/test/L1/TaikoL1.t.sol index 4e8b2b1474f..0ffd8669010 100644 --- a/packages/protocol/test/L1/TaikoL1.t.sol +++ b/packages/protocol/test/L1/TaikoL1.t.sol @@ -92,6 +92,13 @@ contract TaikoL1Test is TaikoL1TestBase { vm.warp(block.timestamp + tierProvider().getTier(minTier).cooldownWindow * 60 + 1); verifyBlock(Alice, 2); + + (TaikoData.Block memory blk, TaikoData.TransitionState memory ts) = L1.getBlock(meta.id); + assertEq(meta.id, blk.blockId); + + ts = L1.getTransition(meta.id, parentHash); + assertEq(ts.prover, Bob); + parentHash = blockHash; } printVariables(""); @@ -208,6 +215,10 @@ contract TaikoL1Test is TaikoL1TestBase { giveEthAndTko(Henry, 0, maxAmount + 1 ether); // So after this point we have 8 deposits + + vm.prank(Alice, Alice); + bool canAliceDeposit = L1.canDepositEthToL2(1 ether); + assertEq(true, canAliceDeposit); vm.prank(Alice, Alice); L1.depositEtherToL2{ value: 1 ether }(address(0)); vm.prank(Bob, Bob); @@ -246,4 +257,96 @@ contract TaikoL1Test is TaikoL1TestBase { 0x3b61cf81fd007398a8efd07a055ac8fb542bcfa62d76cf6dc28a889371afb21e ); } + + function test_pauseProving() external { + L1.pauseProving(true); + + TaikoData.BlockMetadata memory meta; + + giveEthAndTko(Alice, 1000 ether, 1000 ether); + giveEthAndTko(Bob, 1e8 ether, 100 ether); + + // Proposing is still possible + (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, + bytes32("01"), + bytes32("02"), + meta.minTier, + TaikoErrors.L1_PROVING_PAUSED.selector + ); + } + + function test_unpause() external { + L1.pause(); + + giveEthAndTko(Alice, 1000 ether, 1000 ether); + giveEthAndTko(Bob, 1e8 ether, 100 ether); + + // Proposing is also not possible + proposeButRevert(Alice, Bob, 1024, EssentialContract.INVALID_PAUSE_STATUS.selector); + + // unpause + L1.unpause(); + + // Proposing is possible again + proposeBlock(Alice, Bob, 1_000_000, 1024); + } + + function test_burn() external { + uint256 balanceBeforeBurn = tko.balanceOf(address(this)); + vm.prank(tko.owner(), tko.owner()); + tko.burn(address(this), 1 ether); + uint256 balanceAfterBurn = tko.balanceOf(address(this)); + + assertEq(balanceBeforeBurn - 1 ether, balanceAfterBurn); + } + + function test_snapshot() external { + vm.prank(tko.owner(), tko.owner()); + tko.snapshot(); + + uint256 totalSupplyAtSnapshot = tko.totalSupplyAt(1); + + vm.prank(tko.owner(), tko.owner()); + tko.burn(address(this), 1 ether); + + // At snapshot date vs. now, the total supply differs + assertEq(totalSupplyAtSnapshot, tko.totalSupply() + 1 ether); + } + + function test_getTierIds() external { + uint16[] memory tiers = cp.getTierIds(); + assertEq(tiers[0], LibTiers.TIER_OPTIMISTIC); + assertEq(tiers[1], LibTiers.TIER_SGX); + assertEq(tiers[2], LibTiers.TIER_GUARDIAN); + + vm.expectRevert(); + cp.getTier(123); + } + + function proposeButRevert( + address proposer, + address prover, + uint24 txListSize, + bytes4 revertReason + ) + internal + { + uint256 msgValue = 2 ether; + AssignmentHook.ProverAssignment memory assignment; + TaikoData.HookCall[] memory hookcalls = new TaikoData.HookCall[](1); + hookcalls[0] = TaikoData.HookCall(address(assignmentHook), abi.encode(assignment)); + + vm.prank(proposer, proposer); + vm.expectRevert(revertReason); + L1.proposeBlock{ value: msgValue }( + abi.encode(TaikoData.BlockParams(prover, address(0), 0, 0, hookcalls)), + new bytes(txListSize) + ); + } } From 971fb4156725a03eab3dfc98e0b5a85cf9a63ba9 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:50:42 +0800 Subject: [PATCH 09/14] Update TaikoL2.t.sol --- packages/protocol/test/L2/TaikoL2.t.sol | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/protocol/test/L2/TaikoL2.t.sol b/packages/protocol/test/L2/TaikoL2.t.sol index ee9af02ca1c..fe946766aea 100644 --- a/packages/protocol/test/L2/TaikoL2.t.sol +++ b/packages/protocol/test/L2/TaikoL2.t.sol @@ -63,6 +63,8 @@ contract TestTaikoL2 is TaikoTest { vm.roll(block.number + 1); vm.warp(block.timestamp + 30); + + vm.deal(address(L2), 100 ether); } function test_L2_AnchorTx_with_constant_block_time() external { @@ -137,6 +139,22 @@ contract TestTaikoL2 is TaikoTest { LibL2Signer.signAnchor(digest, uint8(3)); } + function test_L2_withdraw() external { + vm.prank(L2.owner(), L2.owner()); + L2.withdraw(address(0), Alice); + assertEq(address(L2).balance, 0 ether); + assertEq(Alice.balance, 100 ether); + + // Random EOA cannot call withdraw + vm.expectRevert(); + vm.prank(Alice, Alice); + L2.withdraw(address(0), Alice); + } + + function test_L2_getBlockHash() external { + assertEq(L2.getBlockHash(uint64(1000)), 0); + } + function _anchor(uint32 parentGasLimit) private { bytes32 l1Hash = randBytes32(); bytes32 l1StateRoot = randBytes32(); From f32f8bab781597f6ef249fb9ec8f01751be879bf Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:53:03 +0800 Subject: [PATCH 10/14] Update Bridge.t.sol --- packages/protocol/test/bridge/Bridge.t.sol | 64 ++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/packages/protocol/test/bridge/Bridge.t.sol b/packages/protocol/test/bridge/Bridge.t.sol index ba20767a9c7..8b89663fd57 100644 --- a/packages/protocol/test/bridge/Bridge.t.sol +++ b/packages/protocol/test/bridge/Bridge.t.sol @@ -716,6 +716,70 @@ contract BridgeTest is TaikoTest { bridge.sendMessage{ value: amount }(message); } + function test_Bridge_suspend_messages() public { + vm.startPrank(Alice); + (IBridge.Message memory message, bytes memory proof) = + setUpPredefinedSuccessfulProcessMessageCall(); + + bytes32 msgHash = destChainBridge.hashMessage(message); + bytes32[] memory messageHashes = new bytes32[](1); + messageHashes[0] = msgHash; + + vm.stopPrank(); + // Suspend + vm.prank(destChainBridge.owner(), destChainBridge.owner()); + destChainBridge.suspendMessages(messageHashes, true); + + vm.startPrank(Alice); + vm.expectRevert(Bridge.B_INVOCATION_TOO_EARLY.selector); + destChainBridge.processMessage(message, proof); + + vm.stopPrank(); + // Unsuspend + vm.prank(destChainBridge.owner(), destChainBridge.owner()); + destChainBridge.suspendMessages(messageHashes, false); + + vm.startPrank(Alice); + destChainBridge.processMessage(message, proof); + + IBridge.Status status = destChainBridge.messageStatus(msgHash); + + assertEq(status == IBridge.Status.DONE, true); + } + + function test_Bridge_ban_address() public { + vm.startPrank(Alice); + (IBridge.Message memory message, bytes memory proof) = + setUpPredefinedSuccessfulProcessMessageCall(); + + bytes32 msgHash = destChainBridge.hashMessage(message); + bytes32[] memory messageHashes = new bytes32[](1); + messageHashes[0] = msgHash; + + vm.stopPrank(); + // Ban address + vm.prank(destChainBridge.owner(), destChainBridge.owner()); + destChainBridge.banAddress(message.to, true); + + vm.startPrank(Alice); + // processMessage() still marks it DONE but dont call the invokeMessageCall on them + destChainBridge.processMessage(message, proof); + + IBridge.Status status = destChainBridge.messageStatus(msgHash); + + assertEq(status == IBridge.Status.DONE, true); + } + + function test_Bridge_prove_message_received() public { + vm.startPrank(Alice); + (IBridge.Message memory message, bytes memory proof) = + setUpPredefinedSuccessfulProcessMessageCall(); + + bool received = destChainBridge.proveMessageReceived(message, proof); + + assertEq(received, true); + } + // test with a known good merkle proof / message since we cant generate // proofs via rpc // in foundry From b4435d28c14fda97f7f3aaa9359f5a2f561e53bc Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:53:41 +0800 Subject: [PATCH 11/14] Create LibAddress.t.sol --- packages/protocol/test/libs/LibAddress.t.sol | 154 +++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 packages/protocol/test/libs/LibAddress.t.sol diff --git a/packages/protocol/test/libs/LibAddress.t.sol b/packages/protocol/test/libs/LibAddress.t.sol new file mode 100644 index 00000000000..5dca9d4bbe7 --- /dev/null +++ b/packages/protocol/test/libs/LibAddress.t.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.24; + +import "../TaikoTest.sol"; +import "../../contracts/libs/LibAddress.sol"; + +contract CalldataReceiver { + // Returns success + function returnSuccess() public pure returns (bool) { + return true; + } + + function supportsInterface(bytes4 interfaceId) external pure returns (bool result) { + if (interfaceId == 0x10101010) { + result = true; + } + } + + // Reverts + receive() external payable { + revert(); + } + + fallback() external payable { + revert(); + } +} + +/// @notice The reason we call LibAddress.sendEther this way is - and not directly inside of a +/// test_sendEther() because this way the coverage adds up if you call functions against initiated +/// contracts, so basically it is a workaround making the library test 'count' towards the coverage. +/// @dev The EtherSenderContract in live environment is the Bridge. +contract EtherSenderContract { + function sendEther( + address _to, + uint256 _amount, + uint256 _gasLimit, + bytes memory _calldata + ) + public + returns (bool success_) + { + success_ = LibAddress.sendEther(_to, _amount, _gasLimit, _calldata); + } + + function sendEtherAndVerify(address _to, uint256 _amount, uint256 _gasLimit) public { + LibAddress.sendEtherAndVerify(_to, _amount, _gasLimit); + } + + function sendEtherAndVerify(address _to, uint256 _amount) public { + LibAddress.sendEtherAndVerify(_to, _amount); + } + + function supportsInterface( + address _addr, + bytes4 _interfaceId + ) + public + view + returns (bool result) + { + return LibAddress.supportsInterface(_addr, _interfaceId); + } + + function isValidSignature( + address _addr, + bytes32 _hash, + bytes memory _sig + ) + public + view + returns (bool) + { + return LibAddress.isValidSignature(_addr, _hash, _sig); + } +} + +contract TestLibAddress is TaikoTest { + EtherSenderContract bridge; + CalldataReceiver calledContract; + + function setUp() public virtual { + bridge = new EtherSenderContract(); + vm.deal(address(bridge), 1 ether); + + calledContract = new CalldataReceiver(); + } + + function test_sendEther() public { + uint256 balanceBefore = Alice.balance; + bridge.sendEther((Alice), 0.5 ether, 2300, ""); + assertEq(Alice.balance, balanceBefore + 0.5 ether); + + // Cannot send to address(0) + vm.expectRevert(LibAddress.ETH_TRANSFER_FAILED.selector); + bridge.sendEther(address(0), 0.5 ether, 2300, ""); + } + + function test_sendEther_with_calldata() public { + bytes memory functionCalldata = abi.encodeCall(CalldataReceiver.returnSuccess, ()); + + bool success = bridge.sendEther(address(calledContract), 0, 230_000, functionCalldata); + + assertEq(success, true); + + // No input argument so it will fall to the fallback. + bytes memory wrongfunctionCalldata = + abi.encodeWithSelector(IERC165Upgradeable.supportsInterface.selector, 10); + success = bridge.sendEther(address(calledContract), 0, 230_000, wrongfunctionCalldata); + + assertEq(success, false); + } + + function test_sendEtherAndVerify() public { + uint256 balanceBefore = Alice.balance; + bridge.sendEtherAndVerify(Alice, 0.5 ether, 2300); + assertEq(Alice.balance, balanceBefore + 0.5 ether); + + // Send 0 ether is also possible + bridge.sendEtherAndVerify(Alice, 0, 2300); + + // If sending fails, call reverts + vm.expectRevert(LibAddress.ETH_TRANSFER_FAILED.selector); + bridge.sendEtherAndVerify(address(calledContract), 0.1 ether, 2300); + + //Call sendEtherAndVerify without the gasLimit + bridge.sendEtherAndVerify(Alice, 0.5 ether); + assertEq(Alice.balance, balanceBefore + 1 ether); + } + + function test_supportsInterface() public { + bool doesSupport = bridge.supportsInterface(Alice, 0x10101010); + + assertEq(doesSupport, false); + + doesSupport = bridge.supportsInterface(address(bridge), 0x10101010); + + assertEq(doesSupport, false); + + doesSupport = bridge.supportsInterface(address(calledContract), 0x10101010); + + assertEq(doesSupport, true); + } + + function test_isValidSignature() public { + bytes32 hash = bytes32("DUMMY_SIGNATURE_HASH_DATA"); + + // 0x1 is Alice's private key + (uint8 v, bytes32 r, bytes32 s) = vm.sign(0x1, hash); + bool validSignature = bridge.isValidSignature(Alice, hash, abi.encodePacked(r, s, v)); + + assertEq(validSignature, true); + } +} From adeae9188d43d43ad9264af293ad54d5469e0640 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:54:48 +0800 Subject: [PATCH 12/14] Update Bridge.t.sol --- packages/protocol/test/bridge/Bridge.t.sol | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/protocol/test/bridge/Bridge.t.sol b/packages/protocol/test/bridge/Bridge.t.sol index 8b89663fd57..3a7eb32809b 100644 --- a/packages/protocol/test/bridge/Bridge.t.sol +++ b/packages/protocol/test/bridge/Bridge.t.sol @@ -716,6 +716,26 @@ contract BridgeTest is TaikoTest { bridge.sendMessage{ value: amount }(message); } + // test with a known good merkle proof / message since we cant generate + // proofs via rpc + // in foundry + function test_Bridge_process_message() public { + // This predefined successful process message call fails now + // since we modified the iBridge.Message struct and cut out + // depositValue + vm.startPrank(Alice); + (IBridge.Message memory message, bytes memory proof) = + setUpPredefinedSuccessfulProcessMessageCall(); + + bytes32 msgHash = destChainBridge.hashMessage(message); + + destChainBridge.processMessage(message, proof); + + IBridge.Status status = destChainBridge.messageStatus(msgHash); + + assertEq(status == IBridge.Status.DONE, true); + } + function test_Bridge_suspend_messages() public { vm.startPrank(Alice); (IBridge.Message memory message, bytes memory proof) = From 110ac9620fe75caccbde33cd4abcbb826f379593 Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 13:55:15 +0800 Subject: [PATCH 13/14] Update Bridge.t.sol --- packages/protocol/test/bridge/Bridge.t.sol | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/packages/protocol/test/bridge/Bridge.t.sol b/packages/protocol/test/bridge/Bridge.t.sol index 3a7eb32809b..ce5a9b485f1 100644 --- a/packages/protocol/test/bridge/Bridge.t.sol +++ b/packages/protocol/test/bridge/Bridge.t.sol @@ -800,26 +800,6 @@ contract BridgeTest is TaikoTest { assertEq(received, true); } - // test with a known good merkle proof / message since we cant generate - // proofs via rpc - // in foundry - function test_Bridge_process_message() public { - // This predefined successful process message call fails now - // since we modified the iBridge.Message struct and cut out - // depositValue - vm.startPrank(Alice); - (IBridge.Message memory message, bytes memory proof) = - setUpPredefinedSuccessfulProcessMessageCall(); - - bytes32 msgHash = destChainBridge.hashMessage(message); - - destChainBridge.processMessage(message, proof); - - IBridge.Status status = destChainBridge.messageStatus(msgHash); - - assertEq(status == IBridge.Status.DONE, true); - } - // test with a known good merkle proof / message since we cant generate // proofs via rpc // in foundry From c7b91be76f9395170a7ee8e75b20d86c84044f8d Mon Sep 17 00:00:00 2001 From: Daniel Wang Date: Thu, 28 Mar 2024 15:04:41 +0800 Subject: [PATCH 14/14] improve _authorizePause for Bridge --- packages/protocol/contracts/L1/TaikoL1.sol | 8 ++++++-- .../protocol/contracts/L2/DelegateOwner.sol | 2 +- packages/protocol/contracts/bridge/Bridge.sol | 20 +++++++++++-------- .../contracts/common/AddressManager.sol | 2 +- .../contracts/common/EssentialContract.sol | 6 +++--- .../contracts/signal/SignalService.sol | 2 +- 6 files changed, 24 insertions(+), 16 deletions(-) diff --git a/packages/protocol/contracts/L1/TaikoL1.sol b/packages/protocol/contracts/L1/TaikoL1.sol index 14c12095203..2e1de32650d 100644 --- a/packages/protocol/contracts/L1/TaikoL1.sol +++ b/packages/protocol/contracts/L1/TaikoL1.sol @@ -117,7 +117,7 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors { /// @notice Pause block proving. /// @param _pause True if paused. function pauseProving(bool _pause) external { - _authorizePause(msg.sender); + _authorizePause(msg.sender, _pause); LibProving.pauseProving(state, _pause); } @@ -216,7 +216,11 @@ contract TaikoL1 is EssentialContract, ITaikoL1, TaikoEvents, TaikoErrors { }); } - function _authorizePause(address) + /// @dev chain_pauser is supposed to be a cold wallet. + function _authorizePause( + address, + bool + ) internal view virtual diff --git a/packages/protocol/contracts/L2/DelegateOwner.sol b/packages/protocol/contracts/L2/DelegateOwner.sol index ba937aa7dc7..6a051e5d4bb 100644 --- a/packages/protocol/contracts/L2/DelegateOwner.sol +++ b/packages/protocol/contracts/L2/DelegateOwner.sol @@ -88,7 +88,7 @@ contract DelegateOwner is EssentialContract, IMessageInvocable { emit OwnershipAccepted(target); } - function _authorizePause(address) internal pure override { + function _authorizePause(address, bool) internal pure override { revert DO_UNSUPPORTED(); } } diff --git a/packages/protocol/contracts/bridge/Bridge.sol b/packages/protocol/contracts/bridge/Bridge.sol index da7b4a69694..7f8269f9cf1 100644 --- a/packages/protocol/contracts/bridge/Bridge.sol +++ b/packages/protocol/contracts/bridge/Bridge.sol @@ -442,14 +442,18 @@ contract Bridge is EssentialContract, IBridge { return _msgHash ^ bytes32(uint256(Status.FAILED)); } - /// @notice Checks if the given address can pause and unpause the bridge. - function _authorizePause(address) - internal - view - virtual - override - onlyFromOwnerOrNamed("bridge_pauser") - { } + /// @notice Checks if the given address can pause and/or unpause the bridge. + /// @dev Considering that the watchdog is a hot wallet, in case its private key is leaked, we + /// only allow watchdog to pause the bridge, but does not allow it to unpause the bridge. + function _authorizePause(address addr, bool toPause) internal view virtual override { + // Owenr and chain_pauser can pause/unpause the bridge. + if (addr == owner() || addr == resolve("chain_pauser", true)) return; + + // bridge_watchdog can pause the bridge, but cannot unpause it. + if (toPause && addr == resolve("bridge_watchdog", true)) return; + + revert RESOLVER_DENIED(); + } /// @notice Invokes a call message on the Bridge. /// @param _message The call message to be invoked. diff --git a/packages/protocol/contracts/common/AddressManager.sol b/packages/protocol/contracts/common/AddressManager.sol index dae2e26cad2..16cb4267900 100644 --- a/packages/protocol/contracts/common/AddressManager.sol +++ b/packages/protocol/contracts/common/AddressManager.sol @@ -55,7 +55,7 @@ contract AddressManager is EssentialContract, IAddressManager { return __addresses[_chainId][_name]; } - function _authorizePause(address) internal pure override { + function _authorizePause(address, bool) internal pure override { revert AM_UNSUPPORTED(); } } diff --git a/packages/protocol/contracts/common/EssentialContract.sol b/packages/protocol/contracts/common/EssentialContract.sol index 20ba84e0fe4..f0467bee5be 100644 --- a/packages/protocol/contracts/common/EssentialContract.sol +++ b/packages/protocol/contracts/common/EssentialContract.sol @@ -72,7 +72,7 @@ abstract contract EssentialContract is UUPSUpgradeable, Ownable2StepUpgradeable, emit Paused(msg.sender); // We call the authorize function here to avoid: // Warning (5740): Unreachable code. - _authorizePause(msg.sender); + _authorizePause(msg.sender, true); } /// @notice Unpauses the contract. @@ -81,7 +81,7 @@ abstract contract EssentialContract is UUPSUpgradeable, Ownable2StepUpgradeable, emit Unpaused(msg.sender); // We call the authorize function here to avoid: // Warning (5740): Unreachable code. - _authorizePause(msg.sender); + _authorizePause(msg.sender, false); } /// @notice Returns true if the contract is paused, and false otherwise. @@ -114,7 +114,7 @@ abstract contract EssentialContract is UUPSUpgradeable, Ownable2StepUpgradeable, function _authorizeUpgrade(address) internal virtual override onlyOwner { } - function _authorizePause(address) internal virtual onlyOwner { } + function _authorizePause(address, bool) internal virtual onlyOwner { } // Stores the reentry lock function _storeReentryLock(uint8 _reentry) internal virtual { diff --git a/packages/protocol/contracts/signal/SignalService.sol b/packages/protocol/contracts/signal/SignalService.sol index b8c6564664e..4bb26d7c8dd 100644 --- a/packages/protocol/contracts/signal/SignalService.sol +++ b/packages/protocol/contracts/signal/SignalService.sol @@ -227,7 +227,7 @@ contract SignalService is EssentialContract, ISignalService { ); } - function _authorizePause(address) internal pure override { + function _authorizePause(address, bool) internal pure override { revert SS_UNSUPPORTED(); }