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 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.

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

Dedaub Security Suite (former Watchdog) is not just a tool; it’s 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

In the ever-changing field of Smart Contract security, proactivity is key. 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; 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: 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.

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.

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

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

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!

Latent Bugs in Billion-plus Dollar Code

You are probably safe, but be aware…

Daniel Von Fange pinged me last week:

Hey, I just realized that the xSushi reward distribution contract that’s commonly cloned around would be vulnerable to complete theft if the deposit token used was an ERC777 style that allowed rentrancy.

The message set in motion the close examination of just ~15 lines of code, handling funds in the billions.

We found not one, but two latent bugs. Both have pretty specific conditions for becoming vulnerabilities. We did our best to ascertain that current deployments are not at risk. (There was a time when an attacker could steal $60M, though.) However, this doesn’t mean there’s no risk: there may be several tokens that if you intend to stake in, one can attack you, right now, let alone what can happen with future deployments.

Take-home message:

  • be extra careful with the initial stakes of xSushi-like reward contracts
  • never deploy such contracts with ERC777 underlying tokens.

and generally:

  • be aware of reentrancy threats when interacting with ERC777 tokens.

The Code

Here is an instance of the code in question, a common snippet of a staking token contract. Such code was originally used in the xSushi staking contract and has since been extensively cloned.

contract VulnerableXToken {
    // ..

    // Pay some tokens and earn some shares.
    function enter(uint256 _amount) public {
        uint256 totalToken = token.balanceOf(address(this));
        uint256 totalShares = totalSupply();
        if (totalShares == 0 || totalToken == 0) {
            _mint(msg.sender, _amount);
        } else {
            uint256 what = _amount.mul(totalShares).div(totalToken);
            _mint(msg.sender, what);
        }
        token.transferFrom(msg.sender, address(this), _amount);
    }

    // Claim back your tokens.
    function leave(uint256 _share) public {
        uint256 totalShares = totalSupply();
        uint256 what = _share.mul(token.balanceOf(address(this))).div(totalShares);
        _burn(msg.sender, _share);
        token.transfer(msg.sender, what);
    }
    
    // ..
}

The enter function just accepts an investment in an underlying token (token, in the above) and issues shares by minting staking tokens (VulnerableXToken). The staking tokens accrue rewards, and upon leave the investor can claim back their rightful proportion of the underlying token.

Bug #1

The code looks reentrancy-safe at first glance. The external call (transferFrom) happens after all state updates (_mint). So, it seems that nothing can go wrong.

Back on Feb. 24, well-known Ethereum security engineer t11s had tweeted a warning about ERC777 tokens.

killer feature of ERC777 is its receive hooks, allowing contracts to react when receiving tokens. What is not mentioned often is the respective hooks called on the senders of the funds, which MUST be called before updating the state.

Implementation Requirement:
The token contract MUST call the 
tokensToSend hook before updating the state.
The token contract MUST call the 
tokensReceived hook after updating the state.

This means that any ERC777 token is a reentrancy death trap! The token itself violates the “effects before external calls” rule: it calls out to the sender before it commits the effects of a transfer. Any caller into the token may be maintaining the effects-before-calls rule, but it may not matter, if the token itself does not. The caller should either be agnostic to the token’s effects (i.e., never read balances) or should use reentrancy locks.

What Daniel had realized regarding xSushi-like code is that the PRE-transfer hook in an ERC777 token’s transferFrom would allow an attacker to reenter (literally: re-enter, in the above code) before any funds had been transferred to the staking contract, taking advantage of any state changes made before the call. In our case, upon reentering, the VulnerableXToken balance has changed (by the internal _mint call) but the underlying token’s balance has not: there are more shares but the same funds, so, when re-entering, shares appear to be cheaper!

Of course, the underlying token does not necessarily need to be an ERC777, as this exploit could be possible for any token that implements similar callback mechanisms to the ones described.

To summarize:

  • IF the underlying token (token, in the above code) of an xSushi-like rewards contract calls back a hook (e.g., is an ERC777, which calls tokensToSend on the sender)
  • AND the underlying token does this callback before adjusting the balance,
  • THEN one can reenter and get shares cheaper, all the way to full depletion of everyone else’s funds.

Bug #2

When I shared the code in the Dedaub internal channels, Konstantinos (one of our senior engineers) immediately commented:
“I see the bug — haven’t we encountered this in an audit before?”

Indeed we had…

… but he wasn’t seeing Daniel’s bug!!!

It was an entirely different bug, based on making the division _amount.mul(totalShares).div(totalToken) round down to zero when another user is depositing. In this way, the depositor would get zero shares, but the old holders of shares would keep the newly deposited funds.

A simple attack scenario with only two depositors (attacker and victim) would go as follows:

  • The attacker is the first person to call enter and deposits amount1 of token, getting back an equal amount of shares.
  • The next depositor comes in and tries to deposit amount2 of token. The attacker front-runs them and directly transfer to the contract any amount of token greater than (amount2–1)*amount1.
  • The attacker gets no shares in return, but they have all the shares to begin with! In this way, amount2*totalShares/totalToken rounds down to zero, leaving the next depositor with nothing, while the attacker can withdraw all the deposited token by calling leave, as they own all the shares.

To see how big an impact this bug can have, consider the first transfer to the xDVF staking token:

This was a transfer for 12.5 million DeversiFi tokens, currently valued at $5 each. An attacker could have front-run that transfer and stolen all $60M worth of tokens!

Checks for Live Vulnerabilities

To determine if there are funds under threat right now, we used the Dedaub Watchdog database to query all currently-deployed contracts on Ethereum, together with their current balances.

  • There are 239 deployed Ethereum contracts with the xSushi-like enter/leave code in publicly posted source.
  • 13 of those have staked funds right now. The highest-value are xShib ($960M), xSushi ($233M), and xDVF ($72M).
  • None of those has an ERC777 as an underlying.
  • The largest value that would have been at risk of a front-running attack during the initial deposit is the $60M in xDVF, as discussed earlier.
  • We also checked Polygon, found only 4 xSushi-like contracts, none with staked funds.

Although the above numbers should be fairly complete, it’s worth noting there may still be threats we are missing. The same code could be deployed in networks other than Ethereum; vulnerable contracts may have no published source, so our search may have missed them; our balances query may be incomplete for tokens that don’t emit the expected events upon transfers; and our Polygon scan was not exhaustive — just considered the last 200K contracts or so.

And, of course, any initial staking in any xSushi-like contract, among the current 200+ deployed or future ones, is vulnerable to front-running attacks.

Conclusion

The code we looked at is very simple and problematic only in very subtle ways, not all under its control (e.g., the ERC777 reentrancy is arguably not the xSushi code’s problem). It is likely to keep getting cloned, or to have independently arisen in other settings. (In the latter case, please let us know!)

Either way, we repeat our message for awareness:

  • be extra careful with the initial stakes of xSushi-like reward contracts
  • never deploy such contracts with ERC777 underlying tokens.

and generally:

  • be aware of reentrancy threats when interacting with ERC777 tokens.

These are attack vectors that the community should know about, lest we see one of them being exploited for heavy damages.

Mass Disclosure of Griefing Vulnerabilities

This week, with the help of @drdr_zz and @wh01s7 of SecuRing, we tackled a backlog of warnings from the Dedaub Watchdog tool, notifying around 100 holders of vulnerable accounts, with some $80M in funds exposed. (@_trvalentine had earlier produced proof-of-concept code to demonstrate that the attack is valid.)

The warnings concern griefing vulnerabilities: cases where an attacker can move the victim’s funds to a contract, but this does not confer the attacker any direct benefit — only makes life harder for the victim, up to possible loss of funds.

Although there’s not much technically novel in the vulnerabilities themselves, we decided to write this report to describe the events and the mass-disclosure method, via etherscan chat messages.

A Word of Introduction

The Dedaub Watchdog tool (built over our public contract-library.com infrastructure) continuously analyzes all deployed contracts on Ethereum. It implements some-80 different analyses and combines warnings over the code with the current state of the chain. (E.g., balances, storage contents, approvals.) It is our main workhorse for discovering vulnerabilities — in the past year, we have disclosed several high-impact vulnerabilities with exposure in the billions and received 9 bug bounties totaling ~$3M. (Bounties by DeFi SaverDinngo/FurucomboPrimitiveArmorVesperBT FinanceHarvestMultichain/AnyswapRari/Tribe DAO.)

The griefing vulnerabilities of this report are a bit below this bar: they typically represent simple, direct oversight, which could be lethal, but thankfully the attacker can’t gain much by exploiting it. It’s the kind of vulnerability that we might typically silently report and forget about it.

In this case, however, the number of potential victim accounts started mounting. We had a list of 18 vulnerable contracts that exposed atransferFrom(from any victim) to untrusted callers, while holding approvals for high-value tokens from many hundreds of victims. When we actually ran the query to cross-reference vulnerable contracts with the victims that had approved them AND had balances in the exposed token, we were stunned to find 564 threatened accounts, with a total amount of funds at risk at $80M, including one account with $76M exposed!

Griefing Vulnerability

There were 2–3 different vulnerable code patterns, but the main one is quite simple. It allows transferring the victims’s funds to a bridge contract using a function callable by anyone.

Griefing Vulnerabilities

How serious this is depends on the bridge protocol’s specifics. In the best case, it is just messy: a human needs to be involved, verify that the victim’s funds were indeed transferred inadvertently and are still at the bridge, and authorize a transaction to return the funds to the victim. In the worst case, the funds remain stuck forever. Also, such vulnerability greatly increases the griefing attack surface: the bridge contract may itself be vulnerable.

Responsible Disclosure

We contacted the main bridge protocol with vulnerable contracts, but the contract was in active use. The right course of action would be to also contact potential victims directly, especially those with current exposure (i.e., combination of balance and approval over the same token).

We had a list of 564 addresses in front of us. But we did not know the identity of the account owners.

The best-practices playbook in this case seems to be:

  • Check if the address is the owner of an ENS domain and if there is a contact there.
  • In the case of addresses with significant funds, direct contact with projects (i.e., the protocol with the vulnerable contract) which very often know the identity of their largest clients/holders.
  • Use the etherscan chat feature.

Another standard concern when notifying victims is that each victim, after securing their own funds, could become a potential attacker. After discussion, we considered this risk to be small. We lowered it to a minimum by starting with the addresses with the most funds (thus reducing the potential reward / satisfaction of the attacker).

The plan was as follows:

  1. Manually verify and try to establish the identity of the addresses with the most funds through the ENS or through the official channel of communication with project teams in which they have a large share.
  2. Automated notification of holders at risk using the etherscan chat.

Manual verification turned out to be relatively simple. The address with $76M threatened was the 3rd largest holder in the XCAD Network project. @drdr_zz spoke directly to the CEO via the official channel and after only 45 minutes, the funds were safu again.

However, there were still a lot of addresses, the identities of which were unknown. Even with the etherscan chat feature, it would be very time-consuming to write to each holder separately. As far as we know, etherscan chat does not currently offer a simple API that we can hook to. However, after a little research, we determined that it works over a websockets connection.

The mechanism is as follows:

  1. A wallet owner connects her wallet (e.g., via Metamask) to the Chat application.
  2. The application assigns a valid session cookie to the user and a temporal token to authenticate the WebSocket connection.
  3. A WebSocket connection is opened using the cookie and the token is sent to the server.
  4. The user can now send chat messages via WebSocket.

We wrote a script to automate the whole process and 98 messages were sent to inform users that had over $1k exposed:

The message sent was:

Hello,

I’m a security researcher (from SecuRing, collaborating with Dedaub) and I’m writing to let you know that your account has exposed funds to contract ADDRESS, for token “TOKEN”.

Any attacker can call a function on this contract that will cause it to transferFrom your funds to the contract. The attacker does not stand to gain from this, so the risk is perhaps not critical.

However, it is a threat, and you may have significant trouble getting your funds back if it happens.

We strongly recommend removing approvals to contract ADDRESS for the “TOKEN” token. You can use the etherscan tool to do so, or any other trusted approval remover.

(This threat was automatically identified using Dedaub’s Watchdog analysis, but the threat was confirmed manually with a proof-of-concept implementation.)

Thank you.

After half a day, we started getting back “thank you” messages.

Lessons learned

  • The etherscan chat feature is a great tool for direct contact with unknown account holders. But the response time may be unsatisfactory since the holder needs to poll their account page on etherscan, and the chat feature is not too widely known and used.
  • Token approvals of this form are arguably more of a UI problem than a smart contract problem, which is why it is important to be aware of standing approvals as an account holder (just as it is to verify transactions before signing them).

It was a pleasure to have this collaboration between Dedaub and SecuRing, and hopefully we helped improve Ethereum security a bit.

Rari Capital Vulnerability

Security researchers actively participating in Tribe DAO’s Discord security channel, raised concerns about a security issue relating to Fuse pools. The Rari Capital team executed our pre-established emergency response plan and immediately fixed the vulnerability. Because of the identification of the vulnerability, and the quick actions in response, no funds were lost. This article will address the nature and identification of the vulnerability as well as the remediation steps executed by the Rari Capital team.

While security has always been a priority in Rari Capital’s projects, Rari Capital’s capabilities have come a long way since its inception. Rari started as a fair launch project with next to no resources and a team of scrappy yet talented engineers. Today, as a highly experienced group of contributors and a part of the Tribe DAO, the Rari Capital team has exceptional resources enabling it to implement the most extensive security measures. Rari now has contributors who are some of DeFi’s top smart contract engineers, a robust network of security auditing professionals and organizations, and a thriving relationship with the white hat community. However, some of the initial Fuse contracts were written prior to having access to these resources and were not scrutinized like contracts are today.

The Vulnerability

At around 4:30 PM PT on March 3rd security researchers including @samczsun, @hritzdorf, and @YSmaragdakis (of @Dedaub) identified a vulnerability across multiple Fuse pools. Pools 0 to 32 with the exception of Pool 6 were at risk. The Rari Capital team was informed, and the admin multisig immediately paused borrowing across all Fuse pools.

The vulnerability was on an old version of the cToken and comptroller implementations. Specifically, the vulnerability was on the cEther contract which used .call.value to transfer ETH instead of .transfer like Compound. This is one of a handful of audited changes from the Compound codebase. Unfortunately audits aren’t a silver bullet and this vulnerability slipped through the cracks. It allowed for a cross-asset reentrancy upon cEther redemption where all assets in vulnerable pools could be borrowed for free. This was because the cEther state hadn’t fully been updated with the effects of the redemption before the ETH transfer. As a result all borrowable assets could have been stolen from those pools.

In late 2021, the Fuse contracts were upgraded to a version which contained a pool-wide check for reentrancy. All pools deployed after pool 32 used the upgraded contracts. Pool admins who deployed prior to the update had an option to upgrade to the latest Fuse contracts from the UI. Of the 32 pools that were deployed with this version of Fuse, only pool 6 was upgraded by its admin.

The Rari Capital Response

Upon being contacted by the researchers about the possible vulnerability in the platform, the Rari Capital team initiated processes to identify, confirm, and validate the issue. Per the incident response playbook, it was determined from a set of available actions to immediately pause borrowing globally. An extensive review of the vulnerability took place, which included a PoC, and review of available remediations options. Once the solution was developed, validated, and tested, the team acted fast to expedite the implementation of the fix.

Each Fuse at risk pool was upgraded to the latest cToken and Comptroller implementations, which prevents this or similar reentrancy vulnerabilities from being exploited. All pools were re-tested to confirm the vulnerability was remediated and borrowing was re-enabled the next morning. All of this occurred within 16 hours of identifying the vulnerability.

Future Security Measures

In the past the Rari Team has ensured that all code in production is scrutinized and goes through an extensive auditing process. In response to the identified vulnerability Rari will be taking a series of enhanced security measures. First, Rari Capital engineers are currently conducting extensive internal reviews of the Fuse codebase. Rari Capital and Fei Protocol have also merged their respective Immunefi bug bounties into one joint Tribe DAO bug bounty.

Rari greatly appreciates the relationship and collaboration with the white hat community and with many of DeFi’s top security engineers. Thank you to all who assisted in identifying and nullifying this vulnerability and to the community who continues to contribute and support as we move forward stronger than ever.