zkLend Finance Incident Analysis
On Feb 12, 2025, the zkLend Finance protocol was exploited, resulting in a loss of approximately $9.5 million.
zkLend is a decentralized lending protocol that operates on the Starknet blockchain. It allows users to lend and borrow assets, similar to protocols like Aave or Compound.
The attack targeted the zkLend Market contract (specifically the wstETH market), which is the main contract that enables users to lend and borrow assets.
Key information
Attacker address: https://voyager.online/contract/0x04d7191dc8eac499bac710dd368706e3ce76c9945da52535de770d06ce7d3b26
Vulnerable contract (zkLend Market): https://voyager.online/contract/0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05
Sample attack transaction: https://voyager.online/tx/0x596bb905f74b545ca5a2af39c5724d952e43ef9887af3f6fd603eebfcc9c2a
Exploit analysis
Looking at the attacker address, we can see that there were multiple transactions that occurred during the time of the attack.
To understand the attack, we need to first examine the transaction that calls runwithfirstdep, as it appears to be the most important one.
In the runwithfirstdep transaction, we can see multiple consecutive deposit and withdraw calls to the zkLend Market contract. Let's examine the details of one deposit call first.
It's clear that the amounts in the deposit and withdraw calls are not the same, indicating this is not a simple deposit and withdraw transaction. Here, we see that the attacker only deposited 4.069 wstETH but withdrew 6.103 wstETH. Therefore, we need to examine the code of the deposit and withdraw functions to understand the attack.
Walking through the code of the deposit function, we can see that it takes the amount of input token and mints a corresponding amount of zToken. The same applies to the withdraw function. However, why could the attacker burn more zToken than they should? This leads us to examine the implementation of the burn function in the zToken contract.
From the implementation of the burn function, we can see that the actual amount burned is scaledDownAmount. Additionally, by examining the safe_math::sub() function, we can determine that the formula for calculating scaledDownAmount is amount * SCALE / accumulator.
So, the easiest way to check this value is by checking these values from the attack transaction. And we have the following values:
amount:
6103946859077466029(withdraw call)accumulator:
4069297906051644020000000000000000000000000000SCALE:
1000000000000000000000000000
From these values, we can calculate the scaledDownAmount as follows:
scaledDownAmount = amount * SCALE / accumulator
= 6103946859077466029 * 1000000000000000000000000000 / 4069297906051644020000000000000000000000000000
= 1
So now, it's clear that this value was carefully chosen by the attacker to bypass the check of scaled_down_amount.is_non_zero(). The deposit transaction before the runwithfirstdep transaction was used to manipulate the result of the get_lending_accumulator() function to make this value much larger and effectively make the scaled_down_amount to be 1.
Post-attack analysis
After the attack, the stolen funds were bridged from Starknet to the Ethereum blockchain. The attacker then attempted to launder the funds by depositing them into Railgun privacy pools but was prevented by the protocol's policies. The failed money laundering attempt emphasizes the importance of balancing privacy and transparency in the DeFi space.
Lessons learned
This incident highlights the importance of careful validation of input values in smart contracts, especially when dealing with complex and sensitive financial operations. By learning from incidents like this one, the DeFi community can build more resilient systems and better safeguard user funds.








