Platform: Code4rena
Start Date: 04/03/2024
Pot Size: $140,000 USDC
Total HM: 19
Participants: 69
Period: 21 days
Judge: 0xean
Total Solo HM: 4
Id: 343
League: ETH
Rank: 25/69
Findings: 1
Award: $423.58
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: kaveyjoe
Also found by: 0xbrett8571, 0xepley, JCK, LinKenji, MrPotatoMagic, Myd, Sathish9098, aariiif, albahaca, cheatc0d3, clara, emerald7017, fouzantanveer, foxb868, hassanshakeel13, hunter_w3b, joaovwfreire, pavankv, popeye, roguereggiant, yongskiws
423.5827 USDC - $423.58
A based rollup -- inspired, secured, and sequenced by Ethereum.
The Taiko protocol is designed as an advanced Layer 2 (L2) solution for Ethereum, aiming to extend Ethereum's scalability, security, and decentralization. Its unique architecture is built upon the principles of Based Contestable Rollup (BCR), providing a novel approach to handling transactions, state transitions, and inter-layer communications. The protocol leverages several core mechanisms to ensure efficient operation, seamless user interaction, and robust security.
Taiko introduces a multi-tiered rollup strategy, operating directly atop Ethereum's Layer 1 (L1) and capable of deploying further Taiko instances as Layer 3 (L3), essentially enabling horizontal scalability. This structure allows each layer to maintain its own state while synchronizing with the L1 state, thus ensuring data integrity and security without compromising scalability.
Central to Taiko's design is the Based Contestable Rollup (BCR) architecture, which employs a unique contestation mechanism to ensure the integrity of state transitions. This is achieved through a process where proposed blocks can be contested by the community, with economic incentives designed to encourage honest participation and penalize incorrect submissions. This process relies heavily on cryptographic proofs and the use of Merkle proofs for efficient and secure cross-chain communication.
<br/>Following is the main functioclity of the protocol ordered by significance and impact.
State Transition Verification with Merkle Proofs: At the heart of Taiko's operational integrity is the state transition verification mechanism. Leveraging Merkle proofs, it ensures that state changes across its layered architecture are accurately and securely validated. This functionality underpins the trust and security model of Taiko, allowing for decentralized verification of state changes without compromising scalability.
Cross-Chain Communication and Signal Service: Essential for interoperability within Taiko's ecosystem, the Signal Service facilitates secure and efficient communication between different layers (L1, L2, L3) and instances of Taiko. This service manages the emission, recording, and verification of signals across chains, crucial for asset bridging and maintaining coherence across the network.
Decentralized Rollup Operation (Based Contestable Rollup - BCR): Taiko introduces a novel rollup mechanism where state transitions can be contested, ensuring the network's integrity through community-driven checks and balances. This approach not only enhances security but also aligns with the decentralized ethos of blockchain technology by allowing participants to challenge and verify state changes.
Token Bridging and Asset Management: Facilitating the seamless transfer and management of assets across Taiko's multi-layered ecosystem, this functionality includes smart contracts and mechanisms for locking, minting, and burning tokens as they move between layers. It's critical for enabling a fluid user experience and asset interoperability within Taiko and with external chains.
Ethereum-Equivalence and EVM Compatibility: By maintaining compatibility with Ethereum's Virtual Machine (EVM), Taiko ensures that developers can easily migrate existing Ethereum dApps to Taiko's network without significant modifications. This feature is pivotal for fostering adoption and facilitating a rich ecosystem of applications on Taiko.
Governance through Decentralization: Implementing a token-based governance model, Taiko empowers TKO token holders to participate in crucial decision-making processes, such as protocol upgrades and parameter adjustments. This functionality is key to ensuring that Taiko remains adaptive, community-driven, and aligned with the interests of its stakeholders.
State Transition Verification within Taiko leverages Merkle Proofs to ensure the integrity and security of transitions between states across its network. This mechanism is a cornerstone of Taiko's architecture, providing a decentralized and efficient method for validating state changes without requiring the full transaction data. By utilizing cryptographic proofs, Taiko can verify the inclusion or absence of a particular transaction in a block, thus ensuring the authenticity and finality of cross-chain communications and state transitions.
Merkle Tree Construction: At the core of the Merkle Proof mechanism is the construction of a Merkle Tree for each block of transactions. Each leaf node of the tree represents a hash of individual transaction data, and each non-leaf node represents a hash of its child nodes. This recursive hashing continues until there is a single hash, the Merkle Root, which represents the entire block.
Merkle Proof Verification: To verify the inclusion of a transaction within a block, Taiko uses a Merkle Proof, which is a sequence of hashes that, when combined with the transaction hash, can recreate the Merkle Root stored in the block header. If the calculated root matches the stored root, the transaction's inclusion is proven.
function verifyMerkleProof( bytes32 _rootHash, address _addr, bytes32 _slot, bytes32 _value, bytes[] memory _accountProof, bytes[] memory _storageProof ) internal pure returns (bytes32 storageRoot_) { if (_accountProof.length != 0) { bytes memory rlpAccount = SecureMerkleTrie.get(abi.encodePacked(_addr), _accountProof, _rootHash); if (rlpAccount.length == 0) revert LTP_INVALID_ACCOUNT_PROOF(); RLPReader.RLPItem[] memory accountState = RLPReader.readList(rlpAccount); storageRoot_ = bytes32(RLPReader.readBytes(accountState[_ACCOUNT_FIELD_INDEX_STORAGE_HASH])); } else { storageRoot_ = _rootHash; } bool verified = SecureMerkleTrie.verifyInclusionProof( bytes.concat(_slot), RLPWriter.writeUint(uint256(_value)), _storageProof, storageRoot_ ); if (!verified) revert LTP_INVALID_INCLUSION_PROOF(); }
Block Header and Merkle Root Storage: Taiko stores the Merkle Root of each block within its smart contracts, enabling the verification of Merkle Proofs without requiring access to the full block data. This approach significantly reduces the data and computational requirements for verification.
Cross-Chain Verification: For cross-chain communication and state transitions, Taiko employs Merkle Proofs to verify the state or transaction on one chain within another chain's smart contracts. This ensures that actions taken on one layer or instance are accurately reflected across the network.
Cross-Chain Communication and the Signal Service in Taiko represent core components designed to enable secure and verified interactions between different blockchain layers or entirely separate blockchain networks. This mechanism is pivotal for maintaining the integrity and security of state transitions and data exchange across Taiko's layered architecture and external blockchains.
At its core, the Signal Service facilitates the broadcasting and verification of signals (or messages) across chains. Signals could encompass state changes, token transfers, or generic messages that need to be communicated securely between chains.
Signal Generation and Storage:
SignalService.sol
plays a pivotal role here. It defines the structure for storing signal-related data and the functionality to emit signals.Cross-Chain Signal Transmission:
Bridge.sol
facilitate the secure transmission of signals across chains. The bridge utilizes the signal data from SignalService.sol
to initiate cross-chain messages.function _sendSignal( address _app, bytes32 _signal, bytes32 _value ) private validSender(_app) nonZeroValue(_signal) nonZeroValue(_value) returns (bytes32 slot_) { slot_ = getSignalSlot(uint64(block.chainid), _app, _signal); assembly { sstore(slot_, _value) } emit SignalSent(_app, _signal, slot_, _value); }
SignalService.sol
on the destination chain are responsible for verifying the signal's authenticity and integrity.<br/>function proveSignalReceived( uint64 _chainId, address _app, bytes32 _signal, bytes calldata _proof ) public virtual validSender(_app) nonZeroValue(_signal) { HopProof[] memory hopProofs = abi.decode(_proof, (HopProof[])); if (hopProofs.length == 0) revert SS_EMPTY_PROOF(); uint64 chainId = _chainId; address app = _app; bytes32 signal = _signal; bytes32 value = _signal; address signalService = resolve(chainId, "signal_service", false); HopProof memory hop; for (uint256 i; i < hopProofs.length; ++i) { hop = hopProofs[i]; bytes32 signalRoot = _verifyHopProof(chainId, app, signal, value, hop, signalService); bool isLastHop = i == hopProofs.length - 1; if (isLastHop) { if (hop.chainId != block.chainid) revert SS_INVALID_LAST_HOP_CHAINID(); signalService = address(this); } else { if (hop.chainId == 0 || hop.chainId == block.chainid) { revert SS_INVALID_MID_HOP_CHAINID(); } signalService = resolve(hop.chainId, "signal_service", false); } bool isFullProof = hop.accountProof.length > 0; _cacheChainData(hop, chainId, hop.blockId, signalRoot, isFullProof, isLastHop); bytes32 kind = isFullProof ? LibSignals.STATE_ROOT : LibSignals.SIGNAL_ROOT; signal = signalForChainData(chainId, kind, hop.blockId); value = hop.rootHash; chainId = hop.chainId; app = signalService; } if (value == 0 || value != _loadSignalValue(address(this), signal)) { revert SS_SIGNAL_NOT_FOUND(); } }
The Taiko protocol introduces a novel concept called Based Contestable Rollup (BCR), designed to enhance the scalability of Ethereum while ensuring the security and decentralization of the network. The BCR mechanism is pivotal to the protocol's architecture, offering a scalable solution that maintains the integrity of state transitions through a process of proposal, contestation, and verification.
Proposal Submission:
Contestation Period:
Challenge Process:
Adjudication:
Resolution:
Finalization:
Token Bridging and Asset Management within Taiko is a fundamental aspect that facilitates the seamless transfer and management of assets across different layers (L1, L2, L3) and potentially across different blockchains. This process ensures that users can interact with a unified ecosystem without being restricted by the underlying complexity of cross-chain operations.
The core implementation of Token Bridging and Asset Management within Taiko revolves around a set of smart contracts, including Bridge, BridgedERC20, BridgedERC721, BridgedERC1155, and vault contracts like ERC20Vault, ERC721Vault, and ERC1155Vault.
Bridge Contract:
Bridged Token Contracts (BridgedERC20, BridgedERC721, BridgedERC1155):
Vault Contracts (ERC20Vault, ERC721Vault, and ERC1155Vault):
SignalService and Merkle Proofs:
The bridging process starts with the Bridge contract, where a user initiates a token transfer to another chain. The contract records this operation and emits a cross-chain message containing the asset information and destination details.
function sendMessage(Message calldata _message) external payable override nonReentrant whenNotPaused returns (bytes32 msgHash_, Message memory message_) { // Ensure the message owner is not null. if (_message.srcOwner == address(0) || _message.destOwner == address(0)) { revert B_INVALID_USER(); } // Check if the destination chain is enabled. (bool destChainEnabled,) = isDestChainEnabled(_message.destChainId); // Verify destination chain and to address. if (!destChainEnabled) revert B_INVALID_CHAINID(); if (_message.destChainId == block.chainid) { revert B_INVALID_CHAINID(); } // Ensure the sent value matches the expected amount. uint256 expectedAmount = _message.value + _message.fee; if (expectedAmount != msg.value) revert B_INVALID_VALUE(); message_ = _message; // Configure message details and send signal to indicate message sending. message_.id = nextMessageId++; message_.from = msg.sender; message_.srcChainId = uint64(block.chainid); msgHash_ = hashMessage(message_); ISignalService(resolve("signal_service", false)).sendSignal(msgHash_); emit MessageSent(msgHash_, message_); }
Upon receiving the message on the target chain, the SignalService verifies its authenticity using Merkle proofs. Once verified, the corresponding Vault contract interacts with the BridgedToken contract to mint or release the asset to the recipient.
function onMessageInvocation(bytes calldata _data) external payable nonReentrant whenNotPaused { (CanonicalERC20 memory ctoken, address from, address to, uint256 amount) = abi.decode(_data, (CanonicalERC20, address, address, uint256)); // `onlyFromBridge` checked in checkProcessMessageContext IBridge.Context memory ctx = checkProcessMessageContext(); // Don't allow sending to disallowed addresses. // Don't send the tokens back to `from` because `from` is on the source chain. if (to == address(0) || to == address(this)) revert VAULT_INVALID_TO(); // Transfer the ETH and the tokens to the `to` address address token = _transferTokens(ctoken, to, amount); to.sendEther(msg.value); emit TokenReceived({ msgHash: ctx.msgHash, from: from, to: to, srcChainId: ctx.srcChainId, ctoken: ctoken.addr, token: token, amount: amount }); }
These interactions ensure that tokens can be securely transferred across chains, with the system automatically handling asset locking, minting of bridged tokens, and unlocking of assets to the rightful owners. The use of Merkle proofs for message verification adds an additional layer of security, making the process reliable and tamper-proof.
<br/>Overall, I consider the quality of the Taiko codebase to be of high caliber. The codebase exhibits mature software engineering practices with a strong emphasis on security, modularity, and clear documentation. The smart contracts leverage established standards, which demonstrates adherence to best practices within the Ethereum development community. Details are explained below:
Codebase Quality Categories | Comments |
---|---|
Documentation and Comments | The codebase is well-documented with comprehensive comments that explain the functionality and purpose of each contract and function, facilitating understandability and maintainability. |
Code Structure and Organization | Code is logically organized into directories and files based on functionality, making navigation and understanding of the project's architecture straightforward. |
Consistency and Coding Standards | The project adheres to common Solidity coding standards and naming conventions, ensuring consistency and readability across the codebase. |
Test Coverage and Quality | With a test coverage of 79%, the project demonstrates a strong commitment to testing, though there's room for improvement to cover edge cases and potential security vulnerabilities more comprehensively. |
Security Practices and Considerations | The use of libraries like OpenZeppelin and custom security mechanisms indicates a focus on security. However, continuous security audits and reviews are essential to maintain high security standards. |
Use of Libraries and Dependencies | The project effectively uses reputable libraries (e.g., OpenZeppelin) to leverage pre-built functionalities, reducing the likelihood of bugs in foundational components. |
Upgradability and Maintenance | The project is designed with upgradability in mind, using proxy patterns and carefully managing state to ensure future improvements can be made with minimal disruption. |
Performance and Gas Optimization | The code shows considerations for gas optimization, crucial for scalability and user experience on Ethereum. Continuous profiling and optimization can further enhance performance. |
Error Handling and Data Validation | Solid error handling and data validation are present throughout, using require statements and custom error messages to ensure contract integrity and inform users of issues. |
The most important summary in analyzing the code base is the stacking of codes to be analyzed. In this way, many predictions can be made, including the difficulty levels of the contracts, which one is more important for the auditor, the features they contain that are important for security (payable functions, uses assembly, etc.).
using vscode-counter
filename: This field indicates the language in which smart contracts are written
Language: Language in which the codebase is written.
Code: This field indicates the number of actual lines of code in the smart contract.
Comment: This field indicates the number of lines in the smart contract.
Blank: This field indicates the number of Blank lines in the smart contract.
Total: This field indicates the number of Total lines (code + comment + blank) in the smart contract.
Total : 85 files, 7611 codes, 2757 comments, 1534 blanks, all 11902 lines
Dependency / Import Path | Count |
---|---|
access/Ownable2StepUpgradeable.sol | 1 |
governance/GovernorUpgradeable.sol | 1 |
governance/TimelockControllerUpgradeable.sol | 1 |
governance/compatibility/GovernorCompatibilityBravoUpgradeable.sol | 1 |
governance/extensions/GovernorTimelockControlUpgradeable.sol | 1 |
governance/extensions/GovernorVotesQuorumFractionUpgradeable.sol | 1 |
governance/extensions/GovernorVotesUpgradeable.sol | 1 |
proxy/utils/Initializable.sol | 1 |
token/ERC1155/ERC1155Upgradeable.sol | 1 |
token/ERC1155/extensions/IERC1155MetadataURIUpgradeable.sol | 1 |
token/ERC1155/utils/ERC1155ReceiverUpgradeable.sol | 1 |
token/ERC20/ERC20Upgradeable.sol | 1 |
token/ERC20/extensions/ERC20SnapshotUpgradeable.sol | 2 |
token/ERC20/extensions/ERC20VotesUpgradeable.sol | 2 |
token/ERC20/extensions/IERC20MetadataUpgradeable.sol | 1 |
token/ERC721/ERC721Upgradeable.sol | 1 |
utils/introspection/IERC165Upgradeable.sol | 1 |
utils/IVotes.sol | 1 |
interfaces/IERC1271.sol | 1 |
ERC1967/ERC1967Proxy.sol | 1 |
utils/UUPSUpgradeable.sol | 1 |
ERC1155/IERC1155.sol | 1 |
ERC20/IERC20.sol | 10 |
ERC20/extensions/IERC20Metadata.sol | 1 |
ERC20/utils/SafeERC20.sol | 4 |
ERC721/IERC721.sol | 2 |
ERC721/IERC721Receiver.sol | 1 |
utils/Address.sol | 2 |
utils/Strings.sol | 4 |
utils/cryptography/ECDSA.sol | 3 |
utils/cryptography/MerkleProof.sol | 1 |
utils/introspection/IERC165.sol | 1 |
utils/math/SafeCast.sol | 1 |
solady/src/utils/Base64.sol | 2 |
solady/src/utils/LibString.sol | 2 |
On average there are 2.59 code lines per comment (lower=better).
Setup
Clone using:
git clone https://github.com/code-423n4/2024-03-taiko
Getting into the directory
cd 2024-03-taiko/packages/protocol
I ran this command to install dependencies:
pnpm install
Then to compile the testcases I ran this:
pnpm compile
Then for testcases I ran this:
pnpm test
When conducting an audit for a complex blockchain project like Taiko, my approach combines rigorous technical analysis with strategic insights gleaned from the project's documentation and any previous audits. This multifaceted strategy ensures a comprehensive understanding and thorough examination of the project's codebase and operational mechanisms.
Understanding the Theoretical Framework: Firstly, I immerse myself in the project's whitepaper and any available technical documentation. This deep dive is critical for understanding the theoretical underpinnings of the project, including its unique features and the specific challenges it aims to address. For Taiko, which introduces novel concepts such as Cross-Chain Communication and Based Contestable Rollup (BCR), gaining a clear understanding of these mechanisms at a theoretical level is crucial for effectively auditing their implementation.
Reviewing Previous Audits: Next, I review any previous audit reports available for the project. This step is invaluable for several reasons. It allows me to identify any recurring issues or vulnerabilities that have been previously highlighted, assess how the development team has addressed these past concerns, and understand the evolution of the project's security practices over time. This historical context helps in focusing the audit efforts on newly developed features or areas that have posed challenges in the past.
In-depth Code Analysis: With a solid understanding of Taiko's theoretical framework and historical security context, I proceed to a detailed analysis of the codebase. My focus is on the critical components that are essential to the protocol's core functionality and security. This includes, but is not limited to, smart contracts responsible for Merkle proof-based state transition verification, cross-chain signal service mechanisms, and the intricacies of the BCR model. During this stage, I meticulously examine the code for common vulnerabilities, such as reentrancy, overflow/underflow, and improper access controls. I also assess the project's unique features for potential security risks specific to its architecture.
Testing and Validation: An important part of the audit involves simulating various operational scenarios to test the system's resilience and behavior under different conditions. I ran the test cases to see how they are performing.
For each component of the system, I evaluated the coding standards and documentation quality. Well-documented code and adherence to established coding conventions are crucial for maintaining code quality, facilitating future updates, and ensuring that new developers can easily understand and contribute to the project.
Previous Audits There are 2 previous audits(mentioned on c4 website) Sigma Prime Quill Audits
Known issues and risks: 3naly3er Report
Systemic risks within the Taiko project include:
Cross-Chain Communication Security: The Signal Service, pivotal for secure cross-chain message passing, could be compromised if vulnerabilities in the verification process or external dependencies are exploited, leading to potential breaches in data integrity or asset misappropriation.
Economic Attack Vectors on BCR: The Based Contestable Rollup relies on economic incentives for its security model. Sophisticated attackers could potentially manipulate or exploit these economic models, leading to issues like false contestation or block verification delays.
Dependency on External Systems: Taiko's functionality and security rely on external systems, such as Ethereum L1 and trusted execution environments (TEEs) like Intel SGX. Vulnerabilities or failures in these systems could cascade into Taiko, impacting its operational integrity.
Governance Manipulation: While decentralized governance aims to democratize decision-making, there's a risk of governance attacks where entities acquire disproportionate control or influence, leading to decisions that could compromise the system's security or deviate from its intended path.
Understanding and mitigating these systemic risks are crucial for ensuring the long-term resilience and success of the Taiko project.
Centralization risks in the Taiko project primarily stem from points of control that could potentially be exploited or mismanaged, leading to centralized decision-making or vulnerabilities. Here are a few examples:
Ownership Control in Smart Contracts: Contracts like SignalService
and Bridge
have functions that are only callable by the owner or designated addresses, which could centralize control over critical functionalities.
function authorize(address _addr, bool _authorize) external onlyOwner { isAuthorized[_addr] = _authorize; emit Authorized(_addr, _authorize); }
The authorize
function allows the contract owner to control which addresses are authorized to perform certain actions, like syncing chain data. This centralizes control over an aspect of cross-chain communication.
Guardian Roles in Emergency Decisions: Certain contracts include roles or mechanisms for emergency intervention, which, while designed for protection, concentrate power in the hands of a few.
function pauseProving(bool _pause) external { _authorizePause(msg.sender); LibProving.pauseProving(state, _pause); }
Token Management and Economic Incentives: Contracts managing tokens or economic incentives have functions that could be exploited if not properly decentralized.
function grant(address _recipient, Grant memory _grant) external onlyOwner { ... }
The grant
function allows only the contract owner to allocate tokens, centralizing the decision-making process regarding token distribution.
Addressing centralization risks involves ensuring a transparent and distributed control mechanism, improving smart contract security practices, and incorporating community governance where feasible to mitigate the potential for misuse or targeted attacks.
Smart Contract Vulnerabilities: Bugs or logical errors in the smart contracts can lead to loss of funds, unauthorized access, or unintended behavior.
Scalability Concerns: As transaction volumes grow, the platform must scale without compromising performance or security.
Reflecting on the Taiko project audit, my insights and learnings are encapsulated as follows:
Modular and Layered Architecture: The architecture demonstrated a scalable solution for blockchain scalability challenges, showcasing how modular design can facilitate scalability without compromising on security or decentralization.
Advanced State Transition Verification: The application of Merkle proofs for secure and efficient state verification across chains has deepened my understanding of cryptographic proofs in ensuring data integrity within decentralized systems.
Economic Incentives in Rollup Operations: The Based Contestable Rollup (BCR) approach provided a novel perspective on maintaining network integrity through economic incentives and contestation mechanisms, emphasizing the role of game theory in blockchain security.
Cross-Chain Communication: The Signal Service mechanism underscored the complexity of secure message passing between chains, offering valuable insights into the design of robust cross-chain protocols.
Token Bridging and Asset Management: The mechanisms for token bridging and asset management, including the mathematical logic behind token locking, minting, and burning, illustrated the intricacies of managing digital assets in a multi-chain environment.
These insights not only enhance my understanding of blockchain technology's evolving landscape but also equip me with valuable perspectives for future audits and blockchain development projects.
NOTE: I don't track time while auditing or writing report, so what the time I specified is just a number
5 hours
#0 - c4-judge
2024-04-10T10:59:09Z
0xean marked the issue as grade-a