Full Report
DyDx is a trading platform perpetuals, leveraged trading and general trades that runs on Ethereum. DyDx had a smart contract for currency conversion that worked by trading all assets to USDC then to the DyDx exchange in a single transaction. The goal was to make this doable in 0x API calls. Initially, this was achieved by calling taking in user provided input for an exchange address and the call data. This is shown below: (bool success, bytes memory returndata) = exchange.call(data); require(success, string(returndata)); Being able to contract the location of the call and the parameters from within the contract feels bad. However, the design of this was okay because of the design. There are three contracts: the currency converter proxy, the exchange wrapper and the exchange. A user would call approve() for an asset on the currency converter proxy. Then, the funds would be exchanged to the wrapper and eventually the exchange. This call() with the arbitrary data was being made from within the wrapper after some input had already been provided. Since the wrapper didn't have the approvals, there were not funds at risk. The problem comes up with a redesign of the system. Instead of having three contracts for the one call, they made only TWO. Now, the currency converter proxy was the contract with the arbitrary call() within the code. Since this contract had the approvals, this was bad. A malicious user could provide an arbitrary ERC-20 token and arbitrary calldata. Since the contract was approved by users, a malicious user could steal anybody's funds! How did this get through? The code had 100% coverage. It seems the reason this slipped through the cracks was a lack of importance on a small contract and a last minute change for performance reasons. Overall, interesting yet simple vulnerability. The finder of this bug received 500K and all of the funds were whitehat hacked by DyDx.
Analysis Summary
# Incident Report: dYdX Currency Converter Proxy Vulnerability
## Executive Summary
On November 27, 2021, dYdX was notified of a critical vulnerability in its new "Currency Converter" proxy smart contract. A design flaw allowed arbitrary calls to be made from a contract that held user ERC-20 approvals, enabling any attacker to drain funds from users who had granted the contract permissions. dYdX successfully performed a white-hat rescue of ~$2M in funds, though approximately $211,000 was lost to frontrunning bots.
## Incident Details
- **Discovery Date:** November 27, 2021, 05:21 UTC
- **Incident Date:** November 24–27, 2021 (Period of vulnerability)
- **Affected Organization:** dYdX
- **Sector:** Decentralized Finance (DeFi) / Cryptocurrency Exchange
- **Geography:** Global / Distributed
## Timeline of Events
### Initial Access
- **Date/Time:** November 24, 2021
- **Vector:** Deployment of a flawed smart contract update for the deposit flow.
- **Details:** A performance-driven redesign reduced the contract architecture from three components to two, mistakenly placing an arbitrary `call()` function within a contract that held user fund approvals.
### Lateral Movement
- **N/A:** As this was a smart contract vulnerability, "movement" consisted of exploiting the `approve` permissions granted by 730 unique user addresses to the vulnerable contract address.
### Data Exfiltration/Impact
- **Losses:** ~$211,000 USD (including 1.5 WETH) stolen by third-party frontrunner bots.
- **Risk:** $2,000,000 USD across 180 addresses were at immediate risk before the white-hat intervention.
### Detection & Response
- **05:21 UTC:** Report received from a member of a trading firm.
- **06:40 UTC:** Bug confirmed by dYdX; Paradigm security researchers (samczsun, Georgios) engaged.
- **07:33–10:33 UTC:** Proof of Concept (PoC) developed; white-hat escrow contract deployed.
- **12:04 UTC:** First white-hat rescue tx executed via Flashbots to prevent frontrunning.
- **13:16 UTC:** Automated recovery bot deployed to rescue any subsequent deposits.
## Attack Methodology
- **Initial Access:** Smart contract logic flaw (Arbitrary Call).
- **Persistence:** Not applicable; the vulnerability remained as long as the contract was active and approvals were not revoked.
- **Privilege Escalation:** Exploitation of `ERC-20` `approve()` mechanism. The contract had the "right" to move user funds, and the vulnerability allowed an attacker to trigger that right for their own benefit.
- **Defense Evasion:** Use of Flashbots by both the white-hats and some attackers to bypass the public mempool and avoid competitive frontrunning.
- **Impact:** Use of `exchange.call(data)` where `exchange` and `data` were user-provided inputs, allowing for the unauthorized transfer of assets from users to an attacker-controlled address.
## Impact Assessment
- **Financial:** ~$2.5M total at risk; ~$2.1M rescued; ~$211k lost (reimbursed by dYdX). $500k Bug Bounty paid.
- **Data Breach:** None; public blockchain data only.
- **Operational:** Deposit flow was disabled and reverted to the legacy system during the incident.
- **Reputational:** High-profile vulnerability but mitigated by a rapid, transparent response and successful white-hat rescue.
## Indicators of Compromise
- **Contract Address:** `0x53773fE5ff4451c896127Dd2c91b8dE7ac51Ba2C` (Vulnerable Proxy)
- **Escrow Address:** `0xbcc7b2ec3c43edf5652d65460e2b42eaa607ccf6` (White-hat storage)
- **Behavioral:** Unexpected `TransferFrom` events triggered by the dYdX proxy contract to non-dYdX exchange addresses.
## Response Actions
- **Containment:** Disabled `setAllowance` in the frontend to prevent new users from becoming vulnerable.
- **Eradication:** Requested Etherscan remove verified source code to slow down malicious actors; took GitHub repo private.
- **Recovery:** Executed white-hat transactions to move funds to a non-custodial escrow; developed a UI for users to reclaim funds and revoke approvals.
## Lessons Learned
- **Code Coverage vs. Security:** 100% test coverage does not guarantee the absence of logic flaws.
- **Last-Minute Changes:** The vulnerability was introduced during a "last-minute change" for performance reasons, bypassing the rigor of the initial design.
- **Complexity Risk:** Reducing the number of contracts simplified the architecture but inadvertently combined "permissioned" logic with "untrusted" input handling.
## Recommendations
- **Rigorous Peer Review:** Mandate fresh internal/external audits for any "performance optimizations" made after initial audits.
- **Separation of Concerns:** Ensure contracts that hold user approvals (allowances) never contain logic that executes arbitrary external calls.
- **Incident Readiness:** Maintain relationships with "war room" experts (like Paradigm) and flashbots infrastructure for rapid asset recovery.