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/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/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..3b380768170 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 original 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,17 +435,23 @@ 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; + bool isAssignedProver = msg.sender == _blk.assignedProver; // The assigned prover can only submit the very first transition. - if (_tid == 1 && _ts.tier == 0 && inProvingWindow) { - if (!isAssignedPover) revert L1_NOT_ASSIGNED_PROVER(); + if ( + _tid == 1 && _ts.tier == 0 + && !LibUtils.isPostDeadline(_ts.timestamp, _lastUnpausedAt, _tier.provingWindow) + ) { + if (!isAssignedProver) revert L1_NOT_ASSIGNED_PROVER(); } else { // Disallow the same address to prove the block so that we can detect that the // assigned prover should not receive his liveness bond back - if (isAssignedPover) revert L1_ASSIGNED_PROVER_NOT_ALLOWED(); + if (isAssignedProver) revert L1_ASSIGNED_PROVER_NOT_ALLOWED(); } } + + /// @dev Returns the reward after applying 12.5% friction. + function _rewardAfterFriction(uint256 _amount) private pure returns (uint256) { + return (_amount * 7) >> 3; + } } 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/docs/native_token_support.md b/packages/protocol/docs/native_token_support.md index 0c58f5a7d14..4fce256435e 100644 --- a/packages/protocol/docs/native_token_support.md +++ b/packages/protocol/docs/native_token_support.md @@ -6,7 +6,7 @@ Taiko's bridging concept is a lock-and-mint type. It simply means (the red path 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 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 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/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/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/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..ce5a9b485f1 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({ @@ -760,4 +931,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/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..4fd7e5aac55 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, @@ -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( @@ -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: { @@ -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: { @@ -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: { @@ -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: { @@ -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,27 +422,27 @@ async function generateContractConfigs( }, BridgedERC20: { address: addressMap.BridgedERC20Impl, - deployedBytecode: replaceUUPSImmutableValues( + deployedBytecode: linkContractLibs(replaceUUPSImmutableValues( contractArtifacts.BridgedERC20Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgedERC20Impl, 32), - ).deployedBytecode.object, + ), addressMap), }, BridgedERC721: { address: addressMap.BridgedERC721Impl, - deployedBytecode: replaceUUPSImmutableValues( + deployedBytecode: linkContractLibs(replaceUUPSImmutableValues( contractArtifacts.BridgedERC721Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgedERC721Impl, 32), - ).deployedBytecode.object, + ), addressMap), }, BridgedERC1155: { address: addressMap.BridgedERC1155Impl, - deployedBytecode: replaceUUPSImmutableValues( + deployedBytecode: linkContractLibs(replaceUUPSImmutableValues( contractArtifacts.BridgedERC1155Impl, uupsImmutableReferencesMap, ethers.utils.hexZeroPad(addressMap.BridgedERC1155Impl, 32), - ).deployedBytecode.object, + ), addressMap), }, SignalServiceImpl: { address: addressMap.SignalServiceImpl, @@ -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, @@ -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, - ownerChainId, + // TaikoL2 => CrossChainOwned + l1ChainId, + // TaikoL2 gasExcess: param1559.gasExcess, - // keccak256(abi.encodePacked(block.chainid, basefee, ancestors)) publicInputHash: `${ethers.utils.solidityKeccak256( ["bytes32[256]"], [ @@ -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, + }, }; } 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