VEE finance has released a report about the recent security incident. In the report, they state that the main cause is “Manipulating the price of the Vee Finance oracle machine and the acquisition of the oracle machine price were not processed for decimals, resulting in the expected slippage check before the swap did not work.”
“The main cause of the accident was that in the process of creating an order for leveraged trading, only the price of the Pangolin pool was used by the oracle as the source of price feed, and the pool price fluctuated more than 3%. The oracle refreshed the price, causing the attacker to manipulate the price of the Pangolin pool. Manipulating the price of the Vee Finance oracle machine and the acquisition of the oracle machine price were not processed for decimals, resulting in the expected slippage check before the swap did not work.” — quoted from the report released by VEE finance.
However, after our careful investigation, we find this may NOT be the case. The real reason is that the attacker creates a FAKE ctokenB
and passes to the contract. Since the ctokenB
is controlled by the attacker, it can return an arbitrary underlying token that is used for calculating the token price. This is the real root cause of the attack.
The whole process
There is an external function called createOrderERC20ToERC20
that can be invoked by any users to create a token swap, using the ctoken
obtained through putting the collateral into the contract. The ctoken
has an underlying token that represents the real token.
In a normal case, when invoking thecreateOrderERC20ToERC20
to trade a token pair using leverage, the dapp needs to ensure that the trade cannot make any loss (otherwise, the dapp will lose money). This is enforced by invoking the getAmountOutMin
function.
Say we put 0.95
ETH into the dapp and get a ctoken
that worths 0.5 ETH (Overcollateralization). Then we can use use the 3x leverage to ask the dapp to trade 1.5 ETH for another token (say tokenX), on behalf of the user. In this case, to prevent the loss, the dapp needs to ensure that the value of the traded tokenX cannot be under 0.95* 1.5ETH (otherwise, the dapp has the risk to lose money). The dapp leverages an external price oracle to calculate the value of ETH and tokenX.
However, the function createOrderERC20ToERC20
does not validate the passedctokenB
. Thus, the attacker could implement his/her own contract as a ctokenB
and feed it to the function createOrderERC20ToERC20
. As a result, this fake ctokenB
can return the underlying token as LINK
(0x5947bb275c521040051d82396192181b413227a3) when being invoked in createOrderERC20ToERC20
and getAmountOutMin
, but return the underlying token as BTC
when being invoked in IPriceOracle(oracle).getUnderlyingPrice(createParams.ctokenB)
. Thus it can bypass the check isRightPrice — since the token used to calculate the price is BTC (instead of LINK)
. This can be confirmed by the returned value of the priceB — 0x15e1549d1216fe9fc032e7c00000
(443783124870000000000000000000000
). This is the price of BTC.
Conclusion
In summary, the attacker exploits the missing check of the ctokenB. Since the ctokenB
is not a trusted one, then the underlying token returned by ctokenB
cannot be trusted. However, the price oracle has NOT been compromised in this attack.
Credits: Yufeng Hu, Hailin Wang, Lei Wu, Yajin Zhou