VirtuSwap Stealing funds using virtual pools with multicall BugFix review

Demyd Korotkykh
Security analyst at HackenProof
3 Minutes Read

Intro

VirtuSwap uses a novel pool architecture, based on reserve-powered virtual liquidity pools, which solve the problem of triangular trading by making every trade direct — even in the absence of a corresponding pool, and generates up to 50% savings.

VirtuSwap is an innovative DEX addressing a large and neglected market segment: trading in smaller-cap assets outside the top 5.

Summary

One of the bug bounty hunters discovered that, the attacker was able to steal funds through manipulation with virtual pools transactions. The attack leverages the limited update frequency of the _update function in vPair.sol, enabling the attacker to execute multiple swaps within a single block using multicall, selectively freezing token balances in pairs AB while updating balances correctly in pairs BC, thereby manipulating swap rates and accumulating more tokens in a single transaction, ultimately resulting in fund theft from the DEX.

Vulnerability Analysis

The _update function within vPair.sol is designed to set _lastPairBalance0 and _lastPairBalance1 once per block. However, it doesn’t account for scenarios where multiple swaps can occur within a single block, especially with the convenience of multicall. Consequently, when these balances are utilized in the getLastBalances() method in vPair, which is further invoked by getVirtualPoolBase in vSwapLibrary.sol, inaccuracies arise in the resulting vPool balances. This discrepancy ultimately leads to incorrect swap rates being calculated.

Potential losses in this scenario could have amounted to as much as two-thirds of the company’s assets. The attacker had the capability to increase their initial amount by a factor of up to 50x.

Proof of Concept

Exploiting this vulnerability, an attacker can repeatedly call swapReserveExactOutput in vRouter.sol within a single transaction. During the swapping process, involving exemplary tokens A, B, and C across pairs AB and BC, the issue becomes evident:

“ -> Swap B -> A (lastBalance will not change anymore in the AB-pair)
  -> Swap C -> B (last Balance will not change anymore in the BC-pair)
  -> Perform as often as possible (A->C using B as a common token, A->B using C as a common token) “

Here’s how the attack unfolds: utilizing multicall, the attacker executes a sequence of swaps within a single transaction. They start by swapping B for A, ensuring that the last balance in pair AB remains static. Then, they proceed to swap C for B, similarly freezing the last balance in pair BC. This process repeats as frequently as possible, alternating between A->C using B as an intermediary token and
A->B using C as the intermediary.

Upon completion of the transaction, the attacker ends up with more tokens than before, effectively siphoning funds from the decentralized exchange unnoticed.

Remediation

https://github.com/Virtuswap/v1-core/commit/ae10f39f7805223f714cd91602785898a0bc5c19

As you can see on the graph above and in commit, this is the corrected vPair.sol with fixed balance update functionality —  a result of cooperation with the VirtuSwap team and fella white hat hacker. 

The issue was swiftly addressed through the implementation of vPair.getBalances() in place of vPair.getLastBalances(), effectively rectifying the bug.

Acknowledgment

We express our heartfelt gratitude to the security researcher for his diligent efforts and responsible disclosure of findings. This underscores the significance of public bug bounty programs and the invaluable contributions independent researchers make to advancing the industry. We also extend our thanks to the VirtuSwap team for their prompt investigations, proactive engagement, and generous bounty.

Read more on HackenProof Blog