Blog

  • Smart Contract Audits Guide

    Smart Contract Audits Guide

    Smart Contract Audit Essentials: Navigating the Web 3 Landscape with Expertise and Security

    With blockchain platforms, Smart Contract Audits play a critical role in ensuring the security and reliability of decentralized applications. These audits are routine checks and an indispensable part of the development process, safeguarding all transactions and agreements that define the blockchain ecosystem.

    Smart Contracts, with their immutable and autonomous nature, demand absolute precision in their code. Any oversight or vulnerability can lead to significant financial losses or erode trust in the technology.

    Smart Contract Audits Guide

    At Dedaub, we blend academic thoroughness with a hacker’s practical acumen to delve deep into Smart Contract code. The main goal of a Smart Contract Audit is to eliminate faults. Our approach is to understand the intricacies of each contract and its potential pitfalls, to provide solutions that fortify its foundation.

    To date, we have conducted over 200 rigorous audits for leading blockchain protocols and safeguarded billions in Total Value Locked (TVL).

    Leading blockchain clients such as the Ethereum Foundation, Chainlink, and Coinbase have placed their trust in us, not just for our ability to spot vulnerabilities but for our commitment to elevating the standards of blockchain security.

    The Critical Role of Audits in Blockchain Security

    At its core, a Smart Contract Audit is a meticulous process where experts scrutinize the code of a blockchain Smart Contract (SC) to identify vulnerabilities, inefficiencies, and potential exploits.

    The systematic examination of Smart Contract Audits is crucial in the blockchain domain, where SCs play a pivotal role in automating, verifying, and enforcing the terms of a digital contract. This is essential when using blockchain technology because transactions are irreversible, making the accuracy and security of SCs essential.

    Smart Contract Audits combine automated tools with expert reviews. The process starts with thoroughly analyzing the contract’s design and architecture. It continues with a detailed line-by-line code examination to uncover hidden issues.

    Auditors look for common vulnerabilities like reentrancy attacks, overflow/underflow issues, gas limit problems, and more nuanced logic errors that could compromise the contract’s functionality.

    Dedaub is a reliable partner with expertise and dedication to excellence. We specialize in ensuring that Smart Contracts adhere to the highest security and reliability standards, regardless of the protocol used.

    The Dedaub Audit Methodology

    At Dedaub, each of our Smart Contract Audits is a meticulously crafted process. Each one uniquely combines academic precision with practical hacking insights. This comprehensive approach is structured into five stages, ensuring a thorough and effective audit tailored to each project’s needs.

    Stage 1: Cost and Schedule Proposal

    Our process begins with carefully assessing the Smart Contract’s codebase, considering its scope and complexity. We formulate a cost-effective proposal and a realistic timeline that aligns with your project’s deadlines and budget constraints. This initial stage sets the groundwork for a well-organized audit process.

    Stage 2: Audit Commencement

    In the second stage, our experts dedicate the agreed time to analyze your Smart Contract thoroughly. This phase includes ongoing interaction with your development team. This fosters a collaborative and efficient audit, where we examine every aspect of the Smart Contracts to identify potential vulnerabilities.

    Stage 3: Preliminary Findings Delivery

    We then categorize and detail the findings in a preliminary report, classifying them by risk level: Critical, High, Medium, Low, or Advisory. A discussion session with your team is held at this stage to clarify any issues and set the groundwork for the next improvement steps.

    Stage 4: Issue Resolution Process

    At this stage, your developers work to address the identified issues, guided by our tailored advice provided in the initial report. This collaborative approach ensures the effective implementation of solutions to enhance the contract’s security and functionality.

    Stage 5: Final Review and Report

    In the final stage, we conduct a comprehensive post-mitigation review to confirm the resolution of all issues. The process culminates with a detailed final report documenting the entire audit process and its outcomes. This results in a clear roadmap for ongoing Smart Contract security.

    Dedaub’s audit methodology is designed to ensure precise and practical auditing of Smart Contracts. Our approach helps to enhance the security issues of blockchain projects by effectively identifying and addressing potential vulnerabilities

    Case Studies

    We work for the Ethereum Foundation on complex studies such as Ethereum Improvement Proposals (EIPs) EIP-4878EIP 6404, EIP 6466EIP 4758 and EIP 6780.

    The EIP 6404 and EIP 6466 is a study to assess the potential impact of Ethereum Improvement Proposals (EIPs) 6404 and 6466. In a project commissioned by the Ethereum Foundation, Dedaub undertook an extensive study to assess the potential impact of Ethereum Improvement Proposals (EIPs) 6404 and 6466.

    These EIPs proposed significant modifications to the Ethereum network, particularly in the serialization algorithm for transactions and receipts. This shift involved moving from the Recursive Length Prefix (RLP) format to the Simple Serialize (SSZ) format.

    This change directly impacted the Receipts Root and Transactions Root fields in the execution layer headers, presenting a complex challenge for existing Smart Contracts on the Ethereum mainnet.

    The Challenge

    The primary concern was the potential disruption to contracts relying on RLP for proofs, especially those critical to decentralized bridges. These bridges play a crucial role in creating proofs about historical transaction logs.

    Our objective was to quantify and qualify the extent of potential disruption and identify specific on-chain patterns verifying commitments in this new manner. This required a detailed, semi-automated examination of all Smart Contracts on the Ethereum network, analyzing their recent behavior to gauge the impact of these changes.

    Our Approach

    We analyzed various Smart Contracts, identifying those critical to projects and assessing possible mitigating actions. Our team concentrated on evaluating the impact of these changes, especially on projects involving cross-chain bridges, and considered both on-chain solutions like upgrades and off-chain strategies like modifying oracles.

    Findings and Impact

    Our study revealed that the changes proposed in the EIPs notably affected a handful of projects, predominantly cross-chain bridges. Some of the key projects impacted included:

    Interestingly, our findings showed that out of the two proposed EIPs, only EIP-6466 (Receipts Root EIP) significantly impacted the inspected protocols. This was due to its effect on log-inclusion proofs, a common method for conducting cross-chain message passing.

    Why Choose Dedaub for Smart Contract Audits?

    If you’re looking to get a Smart Contract audit for your blockchain project, choosing the right partner is important. Dedaub is a reliable and trustworthy choice in this regard, not just because of our technical expertise but also because of the values we stand for – integrity, innovation, and the empowerment of blockchain talent. Our approach is rooted in these core values, directly translating into our high-quality audits.

    Integrity in Every Audit

    At Dedaub, integrity is at the forefront of everything we do. This means conducting audits with the utmost honesty, thoroughness, and transparency. Our clients’ trust in us is integral to their success.

    Our commitment to integrity ensures that every audit is conducted with meticulous attention to detail, offering our clients a true and complete assessment of their Smart Contract’s security.

    Pioneering Innovation

    Innovation is key in the rapidly evolving blockchain landscape. Our team constantly explores the latest advancements in blockchain technology and Smart Contract development. This pursuit of innovation enables us to provide cutting-edge solutions to our clients, ensuring their Smart Contracts are resilient against current and future security threats.

    Empowering Blockchain Talent

    We believe in empowering the next generation of blockchain professionals. Through our Smart Contract Audits, we secure our clients’ projects and share knowledge and insights that contribute to the overall growth of the blockchain community.

    By educating and nurturing talent, we’re helping to build a more secure and robust blockchain ecosystem.

    These core values of Dedaub translate into a thorough and forward-thinking audit service that contributes positively to the broader blockchain community. Choosing Dedaub means partnering with a team that is deeply invested in the success and security of your project, as well as the advancement of the entire blockchain industry.

    The Future of Smart Contract Audits, Embracing ZK Audits and Beyond

    The landscape of Smart Contract Auditing is constantly evolving and is being influenced by groundbreaking trends and innovations. One of these trends is the emergence of Zero-Knowledge (ZK) proofs, a pivotal technology that is reshaping how audits are conducted. At Dedaub, we are always at the forefront of these advancements and are integrating them to offer more robust and sophisticated audit services.

    Our team has a combination of cryptography expertise and hands-on knowledge of ZK-proof systems and technologies. Our auditors invest significant time in continuous education on foundational knowledge and applied knowledge, with a recent emphasis on the domain of zero-knowledge proofs.

    Conclusion

    The significance of Smart Contract Audits in fortifying the Web3 ecosystem cannot be overstated. As the digital landscape evolves, these audits form the backbone of trust and security, ensuring blockchain technologies function as intended and uphold the highest standards of reliability and integrity.

    Dedaub, with our unique blend of academic rigor and practical expertise, stands as a vanguard in this field. We offer comprehensive audits that safeguard against vulnerabilities and fortify the foundations of decentralized applications.

    We invite you to leverage our extensive experience and expertise. Contact us at Dedaub to discuss how we can elevate the security and performance of your Smart Contracts, paving the way for a safer, more robust Web3 future. More info.

  • Smart Contract Security Tools | A Guide to Dedaub Security Suite, Step-by-step Tutorial

    Smart Contract Security Tools | A Guide to Dedaub Security Suite, Step-by-step Tutorial

    Dedaub Security Suite (former Watchdog) is a comprehensive security system designed for Smart Contract analysis and transaction monitoring. To make the most of Dedaub Security Suite’s offers, we’ve released a detailed step-by-step tutorial to guide you through its various capabilities.

    Let’s delve into how this tutorial empowers you to harness the full potential of Watchdog.

    Smart Contract Security Tools | Static Analysis

    Smart Contract security is always evolving, and staying ahead of threats is crucial. Dedaub Security Suite‘s Static Analysis serves as your first line of defense, rigorously examining contract bytecode to flag potential vulnerabilities before they manifest into real threats. Our tutorial shows you how to navigate this preemptive feature for a stronger, more resilient codebase.

    • Deep-dive into contract bytecode to identify looming vulnerabilities with Watchdog’s state-of-the-art static analysis engine.
    • Benefit from various warning types, alerting you to diverse potential issues.
    • Harness the power of extensive warning categorization and tagging, including tens of warning categories, such as reentrancy, signature malleability, and untrusted transfers.
    • Craft your own code queries to scrutinize specific vulnerabilities, behaviors, or attributes in contracts (such as balances, allowances, or recent transactions).

    Smart Contract Security Tools | Transaction Monitoring

    Blockchain is a fast-paced world, and reactive strategies don’t work too well. Dedaub Security Suite‘s Transaction Monitoring empowers you to respond, anticipate, and preempt security threats with real-time blockchain surveillance. Learn to set up intricate filters and monitoring systems via our in-depth tutorial.

    • Conduct deep transaction analysis for nuanced insights into contract interactions, down to minor details.
    • Use advanced filters to focus on the events most critical to your project’s security.
    • Leverage macros to calculate and extract specific data values for even deeper transaction scrutiny.
    • Access detailed transaction logs, replete with decoded function calls, emitted events, and vital status information.
    • Tailor your monitoring scope by setting transaction amount or frequency conditions, sharpening your project’s risk management.

    Smart Contract Security Tools | Reports

    Regular updates on your project’s security posture are not a luxury but a necessity. Dedaub Security Suite‘s Reports feature goes beyond mere data compilation, offering actionable insights to inform your strategic decision-making. Master the generation and interpretation of these comprehensive reports through our tutorial.

    • Receive meticulous, in-depth reports to dissect and understand contract vulnerabilities in detail.
    • Expect rigorously compiled quarterly reviews to gauge your project’s security landscape consistently.
    • Benefit from an added layer of human scrutiny, focusing on high-severity vulnerabilities that automated systems might overlook.

    Development Support: Safety Before Deployment

    Deploying a Smart Contract is irreversible and any vulnerabilities can become permanent liabilities. Our tutorial allows you to utilize Watchdog’s Development Support feature for critical pre-deployment assessments. Learn how to upload project snapshots and scrutinize them against potential security flaws.

    • Seamlessly upload snapshots of your projects that are still in the development phase.
    • Utilize support for popular development frameworks such as Foundry and Hardhat.
    • Engage pre-deployment checks to catch vulnerabilities before they become part of the blockchain.
    • Use the project snapshot feature for an additional layer of pre-deployment scrutiny.

    Stay Ahead with Dedaub Security Suite

    Smart Contract Security Tools

    Unleash Dedaub Security Suite”s full capabilities, gaining the right expertise. Learn the nitty-gritty details to take full control of your Smart Contract security. Watch our comprehensive step-by-step tutorial now!

  • Platypus Finance Hack

    Platypus Finance Hack


    Platypus Finance Hack: The platform was targeted by a flashloan attack, resulting in an approximate $2 million loss. This sophisticated attack utilized flashloans alongside tactics to alter slippage calculations in various swaps, thereby manipulating the price of the swapped assets to benefit the attacker.

    Platypus Finance Hack | The Attack Summary

    At 12th Oct 2023, 06:32 UTC, an attacker on Avalanche C-Chain (addresses: 0x0cd4fd0eecd2c5ad24de7f17ae35f9db6ac51ee7 & 0x464073F659591507d9255B833D163ef1Af5ccc2C), performed multiple on-chain transactions via smart contracts deployed within the same transaction itself. We shall concentrate on a single instance on this attack, where the attacker profits around $570k. The operations performed are as follows:

    Flash loan on AAVE
    Deposit 1050k WAVAX
    Deposit 316k sAVAX
    Swap 600k sAVAX to 659k WAVAX
    Withdraw 728k WAVAX

    Swap 1200k WAVAX to 1250 sAVAX
    Withdraw 34k WAVAX
    Swap 600k sAVAX to 840k WAVAX
    Withdraw 316k sAVAX
    Repay AAVE Flash Loan

    Note that the deposits and swaps are performed on a relatively novel “Stable Swap” AMM (Platypus).

    Judging by the events that took place and by looking at calculations performed throughout the attack, we are fairly sure that the root cause of this attack occurs due to a manipulated slippage calculation. Moreover, the mechanism employed in calculating the slippage (and thus the price at which a swap takes place) is flawed, in cases where liability balance and cash balances are manipulated differently. When such a condition arises, the slippage manipulation can be in the attacker’s favor in both directions of the swap, thus breaking the invariant that the slippage is symmetric.

    In order to understand the intimate mechanics of the protocol, let’s first back up and look the key features of the Platypus AMM and lending protocol:

    1. Unilateral Liquidity Provision: Platypus allows users to provide liquidity to just one side of a trading pair, rather than requiring liquidity for both tokens in a pair.
    2. Account-based Design: Instead of using pools for each token pair, the protocol uses accounts to record assets and liabilities, allowing for a more flexible and capital-efficient system.
    3. Coverage Ratio: Platypus uses a “coverage ratio” as an input parameter instead of simply focusing on liquidity. The coverage ratio is defined as the assets (A) divided by the liabilities (L) for a given token account. A higher ratio indicates lower default risk. This is a departure from Curve’s stableswap invariant and allows the token pool to grow based on organic demand and supply.
    4. Open Liquidity Pool: The protocol is designed to be extensible, allowing new tokens to be added to existing pools easily. For example, starting with a base of USDT, USDC, and DAI, more tokens like TUSD and FRAX can be added later.
    5. Price Oracle: Platypus uses external price oracles like Chainlink to track the exchange rate of each token in terms of USD. This is important for maintaining pegs and calculating exchange rates for swaps.
    6. Solvency Risk: The protocol aims to keep the coverage ratio above a certain level to mitigate the risk of default. If a withdrawal request exceeds the assets available in a specific token account, that could trigger a default.

    Platypus Finance Hack | DETAILED DESCRIPTION

    Now that we got a glimpse of the features, in this section we will discuss how prices are calculated.

    Platypus Finance Hack

    Platypus uses an Oracle to calculate the ideal prices between assets. When the assets are of the same variety (e.g., wrapped vs. staked versions), such a price Oracle is easily implemented. However, what the makes a big impact to the price in this exploit is the slippage calculation, which can benefit the attacker. The goal of the attacker was to amplify the slippage in their favor, by using a clever trick.

    Normally, in this protocol, depositing and withdrawing increases or decreases, respectively, both assets (called cash) and liability in tandem. However, when a withdrawal takes place but there is not enough cash remaining to satisfy the withdrawal, the full liability amount is decreased, despite the asset amount partially decreasing. When this happens, the slippage amount is seemingly manipulated towards the attacker in both directions of a swap.

    /**
         * @notice Yellow Paper Def. 2.4 (Asset Slippage)
         * @dev Calculates -Si or -Sj (slippage from and slippage to)
         * @param k K slippage parameter in WAD
         * @param n N slippage parameter
         * @param c1 C1 slippage parameter in WAD
         * @param xThreshold xThreshold slippage parameter in WAD
         * @param cash cash position of asset in WAD
         * @param cashChange cashChange of asset in WAD
         * @param addCash true if we are adding cash, false otherwise
         * @return The result of one-sided asset slippage
         */
        function _slippage(
            uint256 k,
            uint256 n,
            uint256 c1,
            uint256 xThreshold,
            uint256 cash,
            uint256 liability,
            uint256 cashChange,
            bool addCash
        ) internal pure returns (uint256) {
            uint256 covBefore = cash.wdiv(liability);
            uint256 covAfter;
            if (addCash) {
                covAfter = (cash + cashChange).wdiv(liability);
            } else {
                covAfter = (cash - cashChange).wdiv(liability);
            }
    
            // if cov stays unchanged, slippage is 0
            if (covBefore == covAfter) {
                return 0;
            }
    
            uint256 slippageBefore = _slippageFunc(k, n, c1, xThreshold, covBefore);
            uint256 slippageAfter = _slippageFunc(k, n, c1, xThreshold, covAfter);
    
            if (covBefore > covAfter) {
                return (slippageAfter - slippageBefore).wdiv(covBefore - covAfter);
            } else {
                return (slippageBefore - slippageAfter).wdiv(covAfter - covBefore);
            }
        }
    
        /**
         * @notice Yellow Paper Def. 2.5 (Swapping Slippage). Calculates 1 - (Si - Sj).
         * Uses the formula 1 + (-Si) - (-Sj), with the -Si, -Sj returned from _slippage
         * @dev Adjusted to prevent dealing with underflow of uint256
         * @param si -si slippage parameter in WAD
         * @param sj -sj slippage parameter
         * @return The result of swapping slippage (1 - Si->j)
         */
        function _swappingSlippage(uint256 si, uint256 sj) internal pure returns (uint256) {
            return WAD + si - sj;
        }

    Decreasing Liability but Not Asset balance manipulates slippage

    LESSONS LEARNED

    The more complex a protocol’s financial algorithms are, the more difficult it is to protect from design deficiencies being exploited. Platypus emphasized the ability to maintain a very low slippage and one-sided deposits, which are very hard to implement in a decentralized manner. The Platypus Finance Hack was a difficult one to follow, but there are still ways to improve the security of protocols like these.

    One way we could help with similar protocols is by increasing the security posture, in multiple ways. Note that this attacker was relatively smart, and bypassed the mempool when exploiting the protocol, which renders simple mempool scanning techniques ineffective. At the same time the attacks on different pools happened in different transaction. Some vaults could have been paused more quickly had a sophisticated monitoring solution like Watchdog been employed. Finally the protocol’s financial design & calculations are to blame here, employing the services of specialist security firms like ours to conduct financial design audits could have prevented this attack.

    The attacker themselves also made a mistake in the attack, and the Platypus team rescued $575 (such funds were transferred to 0x068e297e8ff74115c9e1c4b5b83b700fda5afdeb).

    We wish the Platypus team good luck in getting the protocol up and running again, and recovering from this serious incident.

  • Ethereum improvement proposal 4788 | EIP-4877 Summary

    Ethereum improvement proposal 4788 | EIP-4877 Summary

    Dedaub was commissioned by the Ethereum Foundation to perform a security audit of the bytecode of a smart contract that was introduced to the EIP-4877 in a recent change, enabling the on-chain storing and accessing of the beacon block roots of recent blocks.

    In this blog post, titled “EIP-4877 summary,” we highlight key insights from the audit. You can access the complete report here.

    The audited contract uses the block’s timestamp as a key for their parent beacon blocks’ roots. To bind the contract’s storage footprint while retaining accurate information, a set of two ring buffers are used (using a HISTORY_BUFFER_LENGTH with a value of 98304):

    1. The first one stores the timestamp (i.e. the key) and is used to ensure that the result for the provided timestamp is the one that is currently stored on-chain. Its value will be stored at storage location timestamp % HISTORY_BUFFER_LENGTH.
    2. The second one is used to store the beacon root chain value for the timestamp. Ιts value will be stored at storage location HISTORY_BUFFER_LENGTH + timestamp % HISTORY_BUFFER_LENGTH.

    The audited contract implements two methods, set() and get(). As the contract does not adhere to the contract ABI specification, the method to be executed is chosen based on the contract’s caller; if called by the special address 0xfffffffffffffffffffffffffffffffffffffffe, the set() function is called, while every other address the calls the get() function. Both methods accept the first 32 bytes of the call’s calldata as their arguments.

    The highest severity vulnerability found during this audit is one where calling the get() function. More specifically, if this is called with a value of zero it will not fail and return the zero value back.

    // get()
    require(msg.data.length == 32);
    require(calldata0_32 == STORAGE[calldata0_32 % 0x18000]);
    return STORAGE[0x18000 + calldata0_32 % 0x18000];

    EIP-4877 Summary | Main body of the get() function

    Although this does not affect the contract’s functionality for valid timestamps it can potentially lead to misuse, and funds stolen in projects that rely on this root to exist and valid. Therefore we suggested adding a special case for the zero value, in the get() function or invalidating it by storing a value in the 0th storage slot during the contract’s construction.

    You can read the full audit report here.

    EIP-4877 Summary
  • Ethereum Study – Rlp to Ssz Mpt Commitment Migration

    Ethereum Study – Rlp to Ssz Mpt Commitment Migration

    The Ethereum Foundation commissioned our team to examine the potential impact of Ethereum Improvement Proposals (EIPs) 6404 and 6466. These EIPs propose the modification of Merkle-Patricia Trie (MPT) commitments for transactions and receipts, respectively. Importantly, this entails a change in the serialization algorithm, from Recursive Length Prefix (RLP) format to the Simple Serialize (SSZ) format for the Receipts and Transactions containers. In turn, this changes the Receipts Root and Transactions Root fields in the execution layer headers.

    A primary concern is that this transition could disrupt contracts that rely on RLP for proofs on data committed to the Ethereum mainnet. These contracts may include critical parts of decentralized bridges, which generate proofs about some log that was emitted in historical transactions.

    EIPs 6404 and 6466 | This research seeks to quantify and qualify the extent of potential disruption caused by these changes. Identifying the specific on-chain patterns that verify commitments in this manner represents a significant challenge, necessitating a semi-automated examination of all smart contracts deployed on the Ethereum network, together with their recent behavior. The study also attempts to identify which projects these contracts are part of, and whether actions can be taken, on-chain (such as upgrading) or off-chain (such as modifying their respective oracles) to limit the impact of these changes.

    For the proposed EIPs, we were able to measure the extent of the impact of these changes. The effects are observed on a handful of known projects, all of which are cross-chain bridges.

    Notably, many other protocols that do employ RLP functionality are not affected. For instance the Optimism and Polygon bridges use RLP operations for inclusion proofs when bridging from L2 networks back to Ethereum, and, thus, are not affected by the Ethereum encoding of transactions.

    Project NameWebsiteEstimated Impact
    zkBridgehttps://zkbridge.comModerate
    LayerZerohttps://layerzero.network/Moderate
    Telepathyhttps://docs.telepathy.xyz/Moderate

    Finally, an interesting result of our study is that out of the two proposed EIPs, only EIP-6466 (Receipts Root EIP) was observed to have an impact on the inspected protocols. This makes sense as log-inclusion proofs are probably the most common way to conduct cross-chain message passing.

    EIPs 6404 and 6466


    Read the rest of the study here.

  • Preparing for Your First Web3 Audit

    Preparing for Your First Web3 Audit

    Your project is at an advanced state of engineering and you have decided to hire an auditor to maximize security and legitimacy. Great decision! However, not all audit experiences are equal, so follow this guide to maximize your mileage.

    During auditing, you are employing security consultants to go over your code. For auditors, studying your code and issuing an Web3 audit report is a complex balance. Auditors need to have some level of confidence to sign off on your project, yet they will not spend infinite time to gain this confidence. The time allotted has typically been scoped based on your code size and apparent complexity, using extensive past experience.

    Unpleasant Truth #1: At the end of an Web3 audit, no professional auditor will ever claim 100% confidence. Sometimes the auditor will explicitly assess that they do not have as high confidence as they would like, but the time they can allocate is simply over!

    To enhance the auditor’s confidence and facilitate their work:

    1. Provide Succinct, But Sufficient, Documentation: Before auditors start their work, provide them with clear and comprehensive documentation that explains the intent and design of your project. This is not merely about code specifics, but should also provide an understanding of the project at a high level. Of course, when one understands both levels, it should match the specifics.
    2. Use Consistent Naming and Comments: Try to use consistent variable and function names to explicitly document the intent of the code. Use comments to document complex parts of the code but also make sure these are consistent with the code. If you can write invariants or explain the intent of complex parts, this can go a long way.
    3. Set Up a Communication Channel: Establish a synchronous communication channel between your team and the auditors to facilitate information sharing. If requested, walk the auditors through your code before they start the Web3 audit or when they prefer to (e.g., after they have read the documentation, or after one pass over the code). Be responsive during the audit period.
    4. Ensure the Project is Fully Tested and Compiles: By the time an audit begins, make sure the project compiles without errors and is fully tested. This will allow the auditors to focus on the difficult parts of the code, rather than discovering that some functions are uncallable or do not do what they are expected to do under straightforward inputs. Deploy your code on a testnet and exercise it to its limits, focusing on unexpected, corner-case, and possibly adversarial behavior.
    5. Understand the Limitations of an Audit: An Web3 audit is not a testing service or a magical way to find all bugs. Auditors will not know your specification (i.e., your mathematical formulas, your desired behavior) if you do not clearly communicate it. Auditing will likely miss violations of functional correctness when the definition of correct calculations is not given.
    6. Focus on Adversarial Environmental Conditions: Auditors’ time is best spent thinking about adversarial environmental conditions and not simply uncommon inputs. The latter is a functional correctness issue, best uncovered via testing.

    Unpleasant Truth #2: Auditing sometimes misses issues that the programmers themselves find. This is not surprising. The auditor is not the owner of your code. You are!

    Auditors will think about all issues to the best of their ability, but, in the end, they are only “involved”, not “committed” to your project. Therefore, you will benefit the most from an audit if you have the right frame of mind. You are the owner. Your project will face an adversarial world. You have the final word, the final command. Thankfully, good people are here to help you. Help them help you. And magnify their help. If an issue is found, ask yourself: how did I miss it? How does it generalize? What other analogous issues may I have missed?

    Web3 audit

    We are looking forward to working with you to best secure your project! Request an audit.

  • EIP-4758 and EIP-6780 | Removal of Selfdestruct

    EIP-4758 and EIP-6780 | Removal of Selfdestruct

    Dedaub was commissioned by the Ethereum Foundation to perform an impact study of Ethereum Improvement Proposals (EIPs) 4758 and 6780 on existing contracts. EIP-4758 proposes to deactivate SELFDESTRUCT by changing it to SENDALL, which recovers all funds (in ETH) to the beneficiary without deleting any code or storage. On the other hand, EIP-6780 modifies SELFDESTRUCT to work only in the same transaction in which the contract was created, while in all other cases it recovers all funds but does not delete any other account data.

    The aim of this study is (i) to help the Ethereum community decide whether to implement, based on the impact of these changes to the ecosystem, EIP-4758 or EIP-6780. In either case we also aimed to (ii) find out which projects are affected and by how much. To evaluate the impact of these proposed changes, we performed comprehensive queries over past on-chain behaviors of smart contacts and queries on code and bytecode of deployed contracts; inspected code manually; checked balances, approvals, and contract proxying state; and informally interviewed developers.

    The study found that a small number of known projects and many smart contracts, mainly involved in Miner Extractable Value (MEV) bot networks, would be affected. Quantitatively, over 98% of SELFDESTRUCT-CREATE2 pairs in known contracts would remain unaffected if EIP-6780 is implemented, while the impact of EIP-4758 is less certain. Metamorphic contracts used for upgrades were found to be rare. If implemented today, EIP-4758 could affect some functionalities of certain projects, including AxelarNetwork, Pine Finance, Revest, and JPEGd (with high impact), and Sorbet Finance, Celer, Gelato, Ricmoo’s Wisps, Chainhop Protocol (with low impact), and Thousand Ether Homepage (with moderate impact). However, most of these projects could be upgraded in time for a deployment of the proposed EIPs.

    Based on this, we judged the impact of EIP-4758 and EIP-6780 to be manageable and could be a net positive due to the simplification of Ethereum Clients’ implementations, especially if EIP-6780 is selected.

    EIP-4758 and EIP-6780 | Removal of Selfdestruct

    The study used dynamic analysis and static program analysis, along with smart contract inspection and transaction debugging.


    Read more

  • I See Dead Code

    I See Dead Code

    What if I told you that over one-third of recently-deployed Ethereum smart contracts consist mostly of unusable junk?

    Dead Code

    We recently identified a bug in the solidity compiler, resulting in the inclusion of dead code in the deployed bytecode of contracts.

    What we did not know (and did not expect!) was how pervasive the bug was, affecting (almost certainly) many tens of thousands of contracts and the majority of their deployed bytecode.

    We discovered the bug when evaluating Gigahorse, our open source binary-lifter underpinning the decompiler and analyzer at https://library.dedaub.com/, οn recently deployed contracts.
    We originally reported this bug back in November. Following the Solidity team’s acknowledgement of the issue, we performed experiments at scale to investigate the impact.

    We had no idea…

    Issue description

    The bug manifests itself when library methods are only called by a contract’s constructor. The EVM bytecode produced by these methods should only be part of the contract’s creation bytecode (initcode). However, the low-level code implementing these “dead” library methods makes it to the contract’s runtime bytecode, increasing its size for no benefit.

    Simple real world example

    Looking at an EIP1967 proxy contract deployed at Ethereum address 0x5bbb007b32828f4550cd2a3d16c8dbe3c555ef90. The decompilation output of Dedaub’s contract library shows what the on chain code really does:

    // Decompiled by library.dedaub.com
    // 2023.01.16 02:06 UTC
    
    // Data structures and variables inferred from the use of storage instructions
    address _fallback; // STORAGE[0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc] bytes 0 to 19
    
    
    function () public payable { 
        v0 = _fallback.delegatecall(MEM[0 len msg.data.length], MEM[0 len 0]).gas(msg.gas);
        require(v0); // checks call status, propagates error data on error
        return MEM[0 len (RETURNDATASIZE())];
    }
    
    function implementation() public nonPayable { 
        return _fallback;
    }
    
    // Note: The function selector is not present in the original solidity code.
    // However, we display it for the sake of completeness.
    
    function __function_selector__(bytes4 function_selector) public payable { 
        MEM[64] = 128;
        if (msg.data.length < 4) {
            if (!msg.data.length) {
                ();
            }
        } else if (0x5c60da1b == function_selector >> 224) {
            implementation();
        }
        v0 = _fallback.delegatecall(MEM[0 len msg.data.length], MEM[0 len 0]).gas(msg.gas);
        require(v0); // checks call status, propagates error data on error
        return MEM[0 len (RETURNDATASIZE())];
    }

    Our lifter reports that 30 of the 46 low-level code blocks can never be executed when transacting with the contract. By closely inspecting the results we realized that the call to Address.functionDelegateCall() at line 260 (of the source code) results in the code of library methods declared in lines 170, 180, 195. 36, 231 being part of the deployed bytecode although they are never used in the logic of the runtime code.
    We can confirm the issue by hand-tweaking the source code, declaring the library methods inside the compiled contract instead of a library. As a result the deployed bytecode size drops from 808 to 298 bytes.

    Impact on deployed contracts

    We assembled a total of 10,000 unique bytecodes of contracts compiled using solc 0.8.17 deployed on the Ethereum mainnet. Analyzing them using gigahorse we can tell that at least 35% of the above have some dead code and 33% of them have dead code taking up the majority of their runtime bytecode! These results are dominated by manifold NFT proxies but other proxy contracts also face the same issue.

    The most extreme example we found was a contract using the diamond proxy pattern. Its deployed bytecode is 4908 bytes, but removing the dead libraries results in a bytecode of just 302 bytes!

    On large contracts, thankfully, the impact of this issue is usually negligible. But most deployed contracts by number are small contracts, and they are heavily burdened by dead code.

    And this was a surprise, to say the least.

  • Poly Network Hack Postmortem

    Poly Network Hack Postmortem

    On July 2nd, 2023 06:47:20 PM UTC Poly Network suffered what was initially reported to be a notional $34b hack (the actual realized amounts were far less, due to most of the tokens being illiquid). The Poly team paused their smart contracts EthCrossChainManager on several chains, most notably on Metis, BSC and Ethereum. After our team reconstructed the attack, we concluded that the root cause was not a logical bug on the smart contract, but, most likely, stolen (or misused) private keys of 3 out of 4 of Poly network’s keepers (off-chain systems controlled by the team). In order to understand how the attack took place, we need to understand the architecture of Poly’s cross-chain managers.

    Poly runs a network of cross-chain management contracts, allowing tokens to be “transferred” from an origin chain to a destination chain. These contracts accept proofs of token transfer changes on the origin chain, together with encoded arguments for a transaction that withdraws these tokens on the current chain.

    function verifyHeaderAndExecuteTx(bytes memory proof, bytes memory rawHeader, bytes memory headerProof, bytes memory curRawHeader,bytes memory headerSig) whenNotPaused public returns (bool)
    // proof = Poly chain tx merkle proof
    // rawHeader = The header containing crossStateRoot to verify the above tx merkle proof
    // headerSig = The converted signature variable for solidity derived from Poly chain's keepers

    Main entry point allowing users to “unlock” tokens on the “destination” chain that were “locked” on the origin chain

    In Poly, the operation to transfer tokens from the origin chain is referred to as “lock” and the function to retrieve the tokens is referred to “unlock”. Poly employs a system of so-called “consensus nodes” that are essentially EOAs that sign off on the “unlock” event on the destination chain, by including relevant entropy from the origin chain confirming the lock event. This entropy consists of a state root reflecting the locked tokens on the origin chain. Here’s the relevant code which checks that the “header” structure was correctly signed by the “consensus nodes”. The header contains the state root of a Merkle tree. Since the entire header is signed, so is the state root, and, by extension the entire state as witnessed by the Merkle tree.

    function verifySig(bytes memory _rawHeader, bytes memory _sigList, address[] memory _keepers, uint _m) internal pure returns (bool){
            // (Dedaub comment)        
            //_rawHeader = 0x0000000000000000000000001e8bb7336ce3a75ea668e10854c6b6c9530dab7...
            //_sigList = // List of 3 signatures from 0x3dFcCB7b8A6972CDE3B695d3C0c032514B0f3825,0x4c46e1f946362547546677Bfa719598385ce56f2,0x51b7529137D34002c4ebd81A2244F0ee7e95B2C0
            //_keepers = ["0x3dFcCB7b8A6972CDE3B695d3C0c032514B0f3825","0x4c46e1f946362547546677Bfa719598385ce56f2","0xF81F676832F6dFEC4A5d0671BD27156425fCEF98","0x51b7529137D34002c4ebd81A2244F0ee7e95B2C0"]
            //_m = 3
            
            bytes32 hash = getHeaderHash(_rawHeader);
    
            uint sigCount = _sigList.length.div(POLYCHAIN_SIGNATURE_LEN);
            address[] memory signers = new address[](sigCount);
    
            // (Dedaub comment)
            //   signers = [
            //     0x4c46e1f946362547546677Bfa719598385ce56f2,
            //     0x3dFcCB7b8A6972CDE3B695d3C0c032514B0f3825,
            //     0x51b7529137D34002c4ebd81A2244F0ee7e95B2C0
            // ]
            bytes32 r;
            bytes32 s;
            uint8 v;
            for(uint j = 0; j  < sigCount; j++){
                r = Utils.bytesToBytes32(Utils.slice(_sigList, j*POLYCHAIN_SIGNATURE_LEN, 32));
                s =  Utils.bytesToBytes32(Utils.slice(_sigList, j*POLYCHAIN_SIGNATURE_LEN + 32, 32));
                v =  uint8(_sigList[j*POLYCHAIN_SIGNATURE_LEN + 64]) + 27;
                signers[j] =  ecrecover(sha256(abi.encodePacked(hash)), v, r, s);
                if (signers[j] == address(0)) return false;
            }
            return Utils.containMAddresses(_keepers, signers, _m);
        }

    Function to verify signed header, which contains the very-important state root. Comments added by Dedaub.

    Our team verified that the code above was correctly invoked and that the header was indeed signed by 3 of the centralized keepers, satisfying the (k-1) out of k keeper signature scheme. We also checked that the list of keepers was not modified prior to the attack. Indeed, over the span of 2 years, the list of keepers remains unchanged and consists of 4 EOAs. It is common for decentralized protocols to employ “keepers”, i.e., external systems controlled by the development team, that feed vital information into the smart contracts. This is sometimes necessary since smart contracts cannot operate autonomously, and need to be invoked externally. What’s less common, however, is to rely on 3 keepers for the end-to-end security in a high TVL cross-chain bridge.

    Continuing with our investigation, assuming the attacker did not have control over 3 of the EOAs, the Merkle prover would have been the likely cause of a logical bug in the smart contracts. We therefore looked into this next.

    /* @notice                  Verify Poly chain transaction whether exist or not
        *  @param _auditPath        Poly chain merkle proof
        *  @param _root             Poly chain root
        *  @return                  The verified value included in _auditPath
        */
        function merkleProve(bytes memory _auditPath, bytes32 _root) internal pure returns (bytes memory) {
            uint256 off = 0;
            bytes memory value;
            //_auditPath = 0xef20a106246297a2d44f97e78f3f402804011ce360c224ac33b87fe8b6d7b7e618c306000000000000002000000000000000000000000000000000000000000000000000000000000382fc20114c912bcc8ae04b5f5bd386a4bddd8770ae2c3111b7537327c3a369d07179d6142f7ac9436ba4b548f9582af91ca1ef02cd2f1f03020000000000000014250e76987d838a75310c34bf422ea9f1ac4cc90606756e6c6f636b4a14cd1faff6e578fa5cac469d2418c95671ba1a62fe14e0afadad1d93704761c8550f21a53de3468ba5990008f882cc883fe55c3d18000000000000000000000000000000000000000000
            (value, off)  = ZeroCopySource.NextVarBytes(_auditPath, off);
    
            bytes32 hash = Utils.hashLeaf(value);
            uint size = _auditPath.length.sub(off).div(33);
            bytes32 nodeHash;
            byte pos;
            for (uint i = 0; i < size; i++) {
                (pos, off) = ZeroCopySource.NextByte(_auditPath, off);
                (nodeHash, off) = ZeroCopySource.NextHash(_auditPath, off);
                if (pos == 0x00) {
                    hash = Utils.hashChildren(nodeHash, hash);
                } else if (pos == 0x01) {
                    hash = Utils.hashChildren(hash, nodeHash);
                } else {
                    revert("merkleProve, NextByte for position info failed");
                }
            }
            require(hash == _root, "merkleProve, expect root is not equal actual root");
            return value;
        }

    Poly Network Hack | Merkle prover of the Poly chain

    The Merkle prover above takes as input a byte sequence (_auditPath) containing the leaf node followed by a path through the Merkle tree that proves the existence of the leaf node, given the state root (_root). Remember, this state root has already been signed by the keepers. In case you’re unfamiliar how Merkle trees work, the picture below depicts a Merkle tree, which is a cryptographically-secure data structure. The key of the algorithm rests on the fact that the root of the Merkle tree contains (transitively) entropy from all the leaf nodes in the tree. Therefore a proof (often called “witness”) can be easily constructed, and cheaply verified. We only need to trust the root of the tree, and if it’s trusted, so is anything else verified by a Merkle tree witness.

    Poly Network Hack

    The kicker here is that in order to simplify the exploitation scenario, the attacker made full use of the flexibility afforded by the verifier’s implementation. It turns out that the verifier allows for zero-length witnesses. Essentially, the attacker passed in the leaf node, which is exactly 240 bytes in this case, and an empty path as a proof. As it turns out in this case, the hash of the leaf node needs to correspond to the state root (hash) in order for this proof to succeed. This further adds merit to the hypothesis that the Poly chain keepers were likely compromised and signed a state root that turned out to be artificially constructed. The only information within it contained an unlock command that sends tokens to the attacker.

    It is unfortunate to note that Poly network had been previously attacked by a greyhat hacker almost two years ago.

    Finally, it took Poly network 7 hours to react to today’s attack, and in the meantime the attacker had orchestrated several transactions on multiple chains to exploit this.

    Despite the narrative above, there is no definitive proof so far that the keys were stolen. It could have been a rugpull, or it could have been compromised off-chain software running on 3 out of 4 of the keepers. The effect is the same, as far as we can observe. What appears to be unequivocal in the Poly network hack is the fact that a logical bug was not exploited in the smart contracts carrying out the token transfers and that the keepers signed a maliciously-crafted proof. If indeed the Poly network developers confirm the attack has to do with compromised signature keys, as is likely the case, this brings to question the suitability of centralized bridges controlling so much funds. The attack also suggests less-than-perfect monitoring by the Poly network team of the underlying bridge. Had the protocol been set up with a fast monitoring solution, such as Dedaub Watchdog, this would have significantly reduced the reaction time and possibly saved some funds.

  • Uniswap Reentrancy Vulnerability Disclosure

    Uniswap Reentrancy Vulnerability Disclosure

    By the Dedaub team

    Uniswap Reentrancy Vulnerability Disclosure

    Uniswap Reentrancy | Uniswap Labs recently advertised a boosted $3M bounty program for bug reports over their smart contracts, and especially the new UniversalRouter and Permit2 functionality. We submitted a bug report and received a bounty — thank you! To our knowledge, ours was the only bug report that Uniswap acted upon (i.e., the only one to have apparently resulted in a commit-fix to smart contract code and a new deployment of the UniversalRouter).

    The explanation of the issue is fairly straightforward, so we begin with the verbatim text of our bug report.

    Clearly, the UniversalRouter should not hold any balances between transactions, or these can be emptied by anyone (e.g., by calling dispatch with a Commands.TRANSFER, or Commands.SWEEP).

    This property is dangerous when combined with any possibility of untrusted parties gaining control in the middle of a user transaction. This can happen, e.g., with tainted ERC20 tokens in Uniswap pools, or with callbacks from token transfers (e.g., 721s). E.g., simple attack scenario: a user sends to the UniversalRouter funds and calls it with 3 commands:

    1) Commands.V3_SWAP_EXACT_IN, swap to a specific token
    2) Commands.LOOKS_RARE_721 (or any of a large number of commands that trigger recipient callbacks), purchase NFT, send it to recipientX
    3) sweep amount remaining after purchase to original caller.

    In this case, recipientX can easily reenter the contract (by calling transfer or sweep inside its onERC721Received handler) and drain the entire amount, i.e., also the amount of step 3.

    In essence, the UniversalRouter is a scripting language for all sorts of token transfers, swaps, and NFT purchases. One can perform several actions in a row, by supplying the right sequence of commands. These commands could include transfers to untrusted recipients. In this case, it is natural to expect that the transfer should send to the recipient only what the call parameters specify, and nothing more.

    However, this is not always what would happen. If untrusted code is invoked at any point in the transfer, the code can re-enter the UniversalRouter and claim any tokens already in the UniversalRouter contract. Such tokens can, for instance, exist because the user intends to later buy an NFT, or transfer tokens to a second recipient, or because the user swaps a larger amount than needed and intends to “sweep” the remainder to themselves at the end of the UniversalRouter call. And there is no shortage of scenarios in which an untrusted recipient may be called: WETH unwrapping triggers a callback, there are tokens that perform callbacks, and tokens could be themselves untrusted, executing arbitrary code when their functions get called.

    Our proof-of-concept demonstrated an easy scenario:

    Attached are two files. One is a replacement (slight addition) to your foundry test file, universal-router/test/foundry-tests/UniversalRouter.t.sol. The other should be dropped into the “mock” subdirectory. If you then run your standard `forge test -vvv` you will see for the relevant test the output:
    ======

    [PASS] testSweepERC1155Attack() (gas: 126514)
    Logs:
    1000000000000000000

    ======

    The last line is a console.log from the attacker showing that she got a balance in an erc20 token, although she was only sent an ERC1155 token.

    The test case is simply:

    function testSweepERC1155Attack() public {
     bytes memory commands = abi.encodePacked(bytes1(uint8(Commands.SWEEP_ERC1155)));
     bytes[] memory inputs = new bytes[](1);
     uint256 id = 0;
     inputs[0] = abi.encode(address(erc1155), attacker, id, 0);
     
     erc1155.mint(address(router), id, AMOUNT);
     erc20.mint(address(router), AMOUNT);
     
     router.execute(commands, inputs);
    }

    That is, the attacker is being sent an erc1155 token amount, but the router also has an erc20 token amount. (In reality, this would probably be due to other transfers, to happen later.) The attacker manages to steal the erc20 amount as well.

    The remedy we suggested was easy:

    … add a Uniswap reentrancy lock, although inelegant: no dispatching commands while in the middle of dispatching commands.

    We got immediate confirmation that the issue is being examined and will be assessed for the bounty program. A couple of weeks later, we received the bounty assessment:

    we would like to award you a one-time bounty of $40,000 (payable in USDC) for your contribution. This amount includes a $30,000 bounty for the report, as well as a 33% bonus for reporting the issue during our current bonus period. We have classified the issue as medium severity and appreciate your assistance in helping us improve the safety of our users and the web3 ecosystem as a whole.

    Further communication clarified that the bug was assessed to have High impact and Low likelihood: the possibility of a user sending NFTs to an untrusted recipient directly (although present in the UniversalRouter unit tests) is considered “user error”. Therefore, only much more complex (and less likely) scenarios were considered valid for Uniswap reentrancy, resulting in the “low likelihood” rating.

    We want to thank Uniswap Labs for the bounty and are happy to have helped!