Skip to main content
zeroShadow

Hacks

Case Studies

News

Learning & Information

Dolomite Legacy Smart Contract Vulnerability: Post Mortem Analysis

May 30, 2024 | 6 min read

A 2019 Dolomite smart contract vulnerability led to a $1.8M exploit affecting 187 users. The team swiftly contained the issue, recovered 90% of funds, and reimbursed the rest while adopting modern DeFi security practices.

Concept illustration showing a vault in a mountain landscape, symbolising secure storage.

Summary

On March 20, 2024, we were made aware of a vulnerability in Dolomite’s old product on Ethereum Mainnet that was deployed in 2019 and spun down in 2020. Users who had stale approvals on the old system were exposed to assets being stolen from their wallet if they maintained approvals on the Loopring Trade Delegate smart contract.

The Dolomite team immediately disabled the vulnerable smart contract and suspended the old system. Despite taking swift action and taking control over the situation in less than 1 hour, approximately 1,245,271 USDC, 94,423 DAI, and 165.9 WETH (totaling $1.8M) was taken from 187 victims.

By March 24, 2024 at 3:44:35 PM UTC, we recovered 90% of the assets that were taken by the exploiter. We then chose to use Dolomite’s treasury to make users whole for the remaining 10% of assets. We spent the proceeding days combing through the transactions and assembling a precise list of users whose assets were taken. On March 26, 2024, we sent the stolen funds and our contribution back to each victim in the following transactions:

Primer on Old Dolomite & Loopring

Back in the early days of DeFi, when it was still called Open Finance, Dolomite was a Loopring-based exchange that utilized an order book and offered off-chain order matching with on-chain settlement. The infrastructure was similar to what popular platforms like Vertex are today. Dolomite utilized dYdX’s Solo Margin system to enhance Loopring’s offering to enable margin trading.

To enable trading, users would approve tokens against the Loopring Trade Delegate, then sign typed messages that would compose an order. Each order’s validity was checked on the Ethereum blockchain after Dolomite’s Trade Execution Coordinator had batched matched orders for final settlement. When two orders were sent to the contract for settlement, they would be called a Ring (hence Loopring’s name).

Loopring used a very defensive style of coding/architecture that would attempt to continue processing a transaction under most circumstances. If a ring/order was considered invalid, rather than revert, it would emit an InvalidRing event instead. Nowadays we know this to be an antipattern in Solidity. Current best practices call for transaction execution to cease and revert as soon as any issue is encountered.

Loopring was extremely optimized for gas efficiency. Notably, the protocol used a custom byte compaction algorithm for calldata, skipped checks/validation that was deemed redundant, and used assembly to emit events & create data structures to squeeze every bit of performance out of the EVM.


Details of the Vulnerability

The attacker submitted a series of transactions that contained a number of invalid orders and 2 valid orders. The invalid orders were used to transfer the victims’ funds to DolomiteMarginExchange and create actions to be executed within SoloMargin. The two valid orders were used to exchange all of the victims’ supplied funds for a small amount of LRC.

Walking through the call stack, here is a break down of what happened:

Screenshot of Solidity code showing a trade execution function handling token deposits.
In _generateDydxPerformParams the Call action is created that will eventually invoke submitRings on the Loopring protocol. This function enqueues the malicious margin deposits via ctx.requireTokenTransfer(…). This function assumes the margin deposits are validated in the call to submitRings.


In _generateDydxPerformParams the Call action is created that will eventually invoke submitRings on the Loopring protocol. This function enqueues the malicious margin deposits via ctx.requireTokenTransfer(…). This function assumes the margin deposits are validated in the call to submitRings.

Solidity code snippet illustrating conditional checks for partially filled orders in Loopring.
The check function in Loopring optimized for gas by skipping signature validation (line 176) if an order is partially filled. If the order’s signatures were checked for validity at this point in the call stack, the transaction would have reverted and the exploit would not have been possible.


The check function in Loopring optimized for gas by skipping signature validation (line 176) if an order is partially filled. If the order’s signatures were checked for validity at this point in the call stack, the transaction would have reverted and the exploit would not have been possible.

Learnings

DeFi has certainly come a long way from its humble beginnings when those smart contracts were originally deployed. Thematically there are two key takeaways from the exploit:

  1. When you optimize too much for gas, you end up optimizing for nothing! Meaning, excessively optimizing for gas consumption can lead to critical exploits or strange behavior that enables the manipulation of state with the smart contracts.
  2. Defensive programming with composable smart contracts is an anti-pattern. Nowadays, smart contracts that build on top of other smart contracts expect invalid input or erroneous state to cause reversions, not bubble up errors or try to recover from them!

Fortunately, these are both concepts that Dolomite has left behind. As DeFi evolved, the best practices did too, and we’re happy that Dolomite has stayed in lockstep with them.


Thank you

We’d like to extend a huge thank-you to everyone involved in the recovery effort! Each volunteer swiftly joined the war room and worked closely with the team to aid in the investigation, negotiation, and swift recovery of the victims’ funds. We couldn’t have done it without you!!

Our advisors and collaborators in no specific order:

Original article by:

Corey Caplan

Share this post